github.com/mattn/go@v0.0.0-20171011075504-07f7db3ea99f/src/archive/tar/sparse_windows.go (about) 1 // Copyright 2017 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // +build windows 6 7 package tar 8 9 import ( 10 "os" 11 "syscall" 12 "unsafe" 13 ) 14 15 var errInvalidFunc = syscall.Errno(1) // ERROR_INVALID_FUNCTION from WinError.h 16 17 func init() { 18 sysSparseDetect = sparseDetectWindows 19 sysSparsePunch = sparsePunchWindows 20 } 21 22 func sparseDetectWindows(f *os.File) (sph sparseHoles, err error) { 23 const queryAllocRanges = 0x000940CF // FSCTL_QUERY_ALLOCATED_RANGES from WinIoCtl.h 24 type allocRangeBuffer struct{ offset, length int64 } // FILE_ALLOCATED_RANGE_BUFFER from WinIoCtl.h 25 26 s, err := f.Stat() 27 if err != nil { 28 return nil, err 29 } 30 31 queryRange := allocRangeBuffer{0, s.Size()} 32 allocRanges := make([]allocRangeBuffer, 64) 33 34 // Repeatedly query for ranges until the input buffer is large enough. 35 var bytesReturned uint32 36 for { 37 err := syscall.DeviceIoControl( 38 syscall.Handle(f.Fd()), queryAllocRanges, 39 (*byte)(unsafe.Pointer(&queryRange)), uint32(unsafe.Sizeof(queryRange)), 40 (*byte)(unsafe.Pointer(&allocRanges[0])), uint32(len(allocRanges)*int(unsafe.Sizeof(allocRanges[0]))), 41 &bytesReturned, nil, 42 ) 43 if err == syscall.ERROR_MORE_DATA { 44 allocRanges = make([]allocRangeBuffer, 2*len(allocRanges)) 45 continue 46 } 47 if err == errInvalidFunc { 48 return nil, nil // Sparse file not supported on this FS 49 } 50 if err != nil { 51 return nil, err 52 } 53 break 54 } 55 n := bytesReturned / uint32(unsafe.Sizeof(allocRanges[0])) 56 allocRanges = append(allocRanges[:n], allocRangeBuffer{s.Size(), 0}) 57 58 // Invert the data fragments into hole fragments. 59 var pos int64 60 for _, r := range allocRanges { 61 if r.offset > pos { 62 sph = append(sph, SparseEntry{pos, r.offset - pos}) 63 } 64 pos = r.offset + r.length 65 } 66 return sph, nil 67 } 68 69 func sparsePunchWindows(f *os.File, sph sparseHoles) error { 70 const setSparse = 0x000900C4 // FSCTL_SET_SPARSE from WinIoCtl.h 71 const setZeroData = 0x000980C8 // FSCTL_SET_ZERO_DATA from WinIoCtl.h 72 type zeroDataInfo struct{ start, end int64 } // FILE_ZERO_DATA_INFORMATION from WinIoCtl.h 73 74 // Set the file as being sparse. 75 var bytesReturned uint32 76 devErr := syscall.DeviceIoControl( 77 syscall.Handle(f.Fd()), setSparse, 78 nil, 0, nil, 0, 79 &bytesReturned, nil, 80 ) 81 if devErr != nil && devErr != errInvalidFunc { 82 return devErr 83 } 84 85 // Set the file to the right size. 86 var size int64 87 if len(sph) > 0 { 88 size = sph[len(sph)-1].endOffset() 89 } 90 if err := f.Truncate(size); err != nil { 91 return err 92 } 93 if devErr == errInvalidFunc { 94 // Sparse file not supported on this FS. 95 // Call sparsePunchManual since SetEndOfFile does not guarantee that 96 // the extended space is filled with zeros. 97 return sparsePunchManual(f, sph) 98 } 99 100 // Punch holes for all relevant fragments. 101 for _, s := range sph { 102 zdi := zeroDataInfo{s.Offset, s.endOffset()} 103 err := syscall.DeviceIoControl( 104 syscall.Handle(f.Fd()), setZeroData, 105 (*byte)(unsafe.Pointer(&zdi)), uint32(unsafe.Sizeof(zdi)), 106 nil, 0, 107 &bytesReturned, nil, 108 ) 109 if err != nil { 110 return err 111 } 112 } 113 return nil 114 } 115 116 // sparsePunchManual writes zeros into each hole. 117 func sparsePunchManual(f *os.File, sph sparseHoles) error { 118 const chunkSize = 32 << 10 119 zbuf := make([]byte, chunkSize) 120 for _, s := range sph { 121 for pos := s.Offset; pos < s.endOffset(); pos += chunkSize { 122 n := min(chunkSize, s.endOffset()-pos) 123 if _, err := f.WriteAt(zbuf[:n], pos); err != nil { 124 return err 125 } 126 } 127 } 128 return nil 129 }