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  }