130 lines
3.5 KiB
Go
130 lines
3.5 KiB
Go
// 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
|
|
}
|