| // Copyright 2017 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // +build windows |
| |
| package tar |
| |
| import ( |
| "os" |
| "syscall" |
| "unsafe" |
| ) |
| |
| var errInvalidFunc = syscall.Errno(1) // ERROR_INVALID_FUNCTION from WinError.h |
| |
| func init() { |
| sysSparseDetect = sparseDetectWindows |
| sysSparsePunch = sparsePunchWindows |
| } |
| |
| func sparseDetectWindows(f *os.File) (sph sparseHoles, err error) { |
| const queryAllocRanges = 0x000940CF // FSCTL_QUERY_ALLOCATED_RANGES from WinIoCtl.h |
| type allocRangeBuffer struct{ offset, length int64 } // FILE_ALLOCATED_RANGE_BUFFER from WinIoCtl.h |
| |
| s, err := f.Stat() |
| if err != nil { |
| return nil, err |
| } |
| |
| queryRange := allocRangeBuffer{0, s.Size()} |
| allocRanges := make([]allocRangeBuffer, 64) |
| |
| // Repeatedly query for ranges until the input buffer is large enough. |
| var bytesReturned uint32 |
| for { |
| err := syscall.DeviceIoControl( |
| syscall.Handle(f.Fd()), queryAllocRanges, |
| (*byte)(unsafe.Pointer(&queryRange)), uint32(unsafe.Sizeof(queryRange)), |
| (*byte)(unsafe.Pointer(&allocRanges[0])), uint32(len(allocRanges)*int(unsafe.Sizeof(allocRanges[0]))), |
| &bytesReturned, nil, |
| ) |
| if err == syscall.ERROR_MORE_DATA { |
| allocRanges = make([]allocRangeBuffer, 2*len(allocRanges)) |
| continue |
| } |
| if err == errInvalidFunc { |
| return nil, nil // Sparse file not supported on this FS |
| } |
| if err != nil { |
| return nil, err |
| } |
| break |
| } |
| n := bytesReturned / uint32(unsafe.Sizeof(allocRanges[0])) |
| allocRanges = append(allocRanges[:n], allocRangeBuffer{s.Size(), 0}) |
| |
| // Invert the data fragments into hole fragments. |
| var pos int64 |
| for _, r := range allocRanges { |
| if r.offset > pos { |
| sph = append(sph, SparseEntry{pos, r.offset - pos}) |
| } |
| pos = r.offset + r.length |
| } |
| return sph, nil |
| } |
| |
| func sparsePunchWindows(f *os.File, sph sparseHoles) error { |
| const setSparse = 0x000900C4 // FSCTL_SET_SPARSE from WinIoCtl.h |
| const setZeroData = 0x000980C8 // FSCTL_SET_ZERO_DATA from WinIoCtl.h |
| type zeroDataInfo struct{ start, end int64 } // FILE_ZERO_DATA_INFORMATION from WinIoCtl.h |
| |
| // Set the file as being sparse. |
| var bytesReturned uint32 |
| devErr := syscall.DeviceIoControl( |
| syscall.Handle(f.Fd()), setSparse, |
| nil, 0, nil, 0, |
| &bytesReturned, nil, |
| ) |
| if devErr != nil && devErr != errInvalidFunc { |
| return devErr |
| } |
| |
| // Set the file to the right size. |
| var size int64 |
| if len(sph) > 0 { |
| size = sph[len(sph)-1].endOffset() |
| } |
| if err := f.Truncate(size); err != nil { |
| return err |
| } |
| if devErr == errInvalidFunc { |
| // Sparse file not supported on this FS. |
| // Call sparsePunchManual since SetEndOfFile does not guarantee that |
| // the extended space is filled with zeros. |
| return sparsePunchManual(f, sph) |
| } |
| |
| // Punch holes for all relevant fragments. |
| for _, s := range sph { |
| zdi := zeroDataInfo{s.Offset, s.endOffset()} |
| err := syscall.DeviceIoControl( |
| syscall.Handle(f.Fd()), setZeroData, |
| (*byte)(unsafe.Pointer(&zdi)), uint32(unsafe.Sizeof(zdi)), |
| nil, 0, |
| &bytesReturned, nil, |
| ) |
| if err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| // sparsePunchManual writes zeros into each hole. |
| func sparsePunchManual(f *os.File, sph sparseHoles) error { |
| const chunkSize = 32 << 10 |
| zbuf := make([]byte, chunkSize) |
| for _, s := range sph { |
| for pos := s.Offset; pos < s.endOffset(); pos += chunkSize { |
| n := min(chunkSize, s.endOffset()-pos) |
| if _, err := f.WriteAt(zbuf[:n], pos); err != nil { |
| return err |
| } |
| } |
| } |
| return nil |
| } |