github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/lib/file/preallocate_windows.go (about)

     1  //+build windows
     2  
     3  package file
     4  
     5  import (
     6  	"os"
     7  	"syscall"
     8  	"unsafe"
     9  
    10  	"github.com/pkg/errors"
    11  	"golang.org/x/sys/windows"
    12  )
    13  
    14  var (
    15  	ntdll                        = windows.NewLazySystemDLL("ntdll.dll")
    16  	ntQueryVolumeInformationFile = ntdll.NewProc("NtQueryVolumeInformationFile")
    17  	ntSetInformationFile         = ntdll.NewProc("NtSetInformationFile")
    18  )
    19  
    20  type fileAllocationInformation struct {
    21  	AllocationSize uint64
    22  }
    23  
    24  type fileFsSizeInformation struct {
    25  	TotalAllocationUnits     uint64
    26  	AvailableAllocationUnits uint64
    27  	SectorsPerAllocationUnit uint32
    28  	BytesPerSector           uint32
    29  }
    30  
    31  type ioStatusBlock struct {
    32  	Status, Information uintptr
    33  }
    34  
    35  // PreallocateImplemented is a constant indicating whether the
    36  // implementation of Preallocate actually does anything.
    37  const PreallocateImplemented = true
    38  
    39  // PreAllocate the file for performance reasons
    40  func PreAllocate(size int64, out *os.File) error {
    41  	if size <= 0 {
    42  		return nil
    43  	}
    44  
    45  	var (
    46  		iosb       ioStatusBlock
    47  		fsSizeInfo fileFsSizeInformation
    48  		allocInfo  fileAllocationInformation
    49  	)
    50  
    51  	// Query info about the block sizes on the file system
    52  	_, _, e1 := ntQueryVolumeInformationFile.Call(
    53  		uintptr(out.Fd()),
    54  		uintptr(unsafe.Pointer(&iosb)),
    55  		uintptr(unsafe.Pointer(&fsSizeInfo)),
    56  		uintptr(unsafe.Sizeof(fsSizeInfo)),
    57  		uintptr(3), // FileFsSizeInformation
    58  	)
    59  	if e1 != nil && e1 != syscall.Errno(0) {
    60  		return errors.Wrap(e1, "preAllocate NtQueryVolumeInformationFile failed")
    61  	}
    62  
    63  	// Calculate the allocation size
    64  	clusterSize := uint64(fsSizeInfo.BytesPerSector) * uint64(fsSizeInfo.SectorsPerAllocationUnit)
    65  	if clusterSize <= 0 {
    66  		return errors.Errorf("preAllocate clusterSize %d <= 0", clusterSize)
    67  	}
    68  	allocInfo.AllocationSize = (1 + uint64(size-1)/clusterSize) * clusterSize
    69  
    70  	// Ask for the allocation
    71  	_, _, e1 = ntSetInformationFile.Call(
    72  		uintptr(out.Fd()),
    73  		uintptr(unsafe.Pointer(&iosb)),
    74  		uintptr(unsafe.Pointer(&allocInfo)),
    75  		uintptr(unsafe.Sizeof(allocInfo)),
    76  		uintptr(19), // FileAllocationInformation
    77  	)
    78  	if e1 != nil && e1 != syscall.Errno(0) {
    79  		return errors.Wrap(e1, "preAllocate NtSetInformationFile failed")
    80  	}
    81  
    82  	return nil
    83  }
    84  
    85  const (
    86  	FSCTL_SET_SPARSE = 0x000900c4
    87  )
    88  
    89  // SetSparseImplemented is a constant indicating whether the
    90  // implementation of SetSparse actually does anything.
    91  const SetSparseImplemented = true
    92  
    93  // SetSparse makes the file be a sparse file
    94  func SetSparse(out *os.File) error {
    95  	var bytesReturned uint32
    96  	err := syscall.DeviceIoControl(syscall.Handle(out.Fd()), FSCTL_SET_SPARSE, nil, 0, nil, 0, &bytesReturned, nil)
    97  	if err != nil {
    98  		return errors.Wrap(err, "DeviceIoControl FSCTL_SET_SPARSE")
    99  	}
   100  	return nil
   101  }