e43b5ce5e5
This is possible now that we no longer support go1.12 and brings rclone into line with standard practices in the Go world. This also removes errors.New and errors.Errorf from lib/errors and prefers the stdlib errors package over lib/errors.
110 lines
2.8 KiB
Go
110 lines
2.8 KiB
Go
//go:build windows
|
|
// +build windows
|
|
|
|
package file
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
var (
|
|
ntdll = windows.NewLazySystemDLL("ntdll.dll")
|
|
ntQueryVolumeInformationFile = ntdll.NewProc("NtQueryVolumeInformationFile")
|
|
ntSetInformationFile = ntdll.NewProc("NtSetInformationFile")
|
|
preAllocateMu sync.Mutex
|
|
)
|
|
|
|
type fileAllocationInformation struct {
|
|
AllocationSize uint64
|
|
}
|
|
|
|
type fileFsSizeInformation struct {
|
|
TotalAllocationUnits uint64
|
|
AvailableAllocationUnits uint64
|
|
SectorsPerAllocationUnit uint32
|
|
BytesPerSector uint32
|
|
}
|
|
|
|
type ioStatusBlock struct {
|
|
Status, Information uintptr
|
|
}
|
|
|
|
// PreallocateImplemented is a constant indicating whether the
|
|
// implementation of Preallocate actually does anything.
|
|
const PreallocateImplemented = true
|
|
|
|
// PreAllocate the file for performance reasons
|
|
func PreAllocate(size int64, out *os.File) error {
|
|
if size <= 0 {
|
|
return nil
|
|
}
|
|
|
|
preAllocateMu.Lock()
|
|
defer preAllocateMu.Unlock()
|
|
|
|
var (
|
|
iosb ioStatusBlock
|
|
fsSizeInfo fileFsSizeInformation
|
|
allocInfo fileAllocationInformation
|
|
)
|
|
|
|
// Query info about the block sizes on the file system
|
|
_, _, e1 := ntQueryVolumeInformationFile.Call(
|
|
uintptr(out.Fd()),
|
|
uintptr(unsafe.Pointer(&iosb)),
|
|
uintptr(unsafe.Pointer(&fsSizeInfo)),
|
|
uintptr(unsafe.Sizeof(fsSizeInfo)),
|
|
uintptr(3), // FileFsSizeInformation
|
|
)
|
|
if e1 != nil && e1 != syscall.Errno(0) {
|
|
return fmt.Errorf("preAllocate NtQueryVolumeInformationFile failed: %w", e1)
|
|
}
|
|
|
|
// Calculate the allocation size
|
|
clusterSize := uint64(fsSizeInfo.BytesPerSector) * uint64(fsSizeInfo.SectorsPerAllocationUnit)
|
|
if clusterSize <= 0 {
|
|
return fmt.Errorf("preAllocate clusterSize %d <= 0", clusterSize)
|
|
}
|
|
allocInfo.AllocationSize = (1 + uint64(size-1)/clusterSize) * clusterSize
|
|
|
|
// Ask for the allocation
|
|
_, _, e1 = ntSetInformationFile.Call(
|
|
uintptr(out.Fd()),
|
|
uintptr(unsafe.Pointer(&iosb)),
|
|
uintptr(unsafe.Pointer(&allocInfo)),
|
|
uintptr(unsafe.Sizeof(allocInfo)),
|
|
uintptr(19), // FileAllocationInformation
|
|
)
|
|
if e1 != nil && e1 != syscall.Errno(0) {
|
|
if e1 == syscall.Errno(windows.ERROR_DISK_FULL) || e1 == syscall.Errno(windows.ERROR_HANDLE_DISK_FULL) {
|
|
return ErrDiskFull
|
|
}
|
|
return fmt.Errorf("preAllocate NtSetInformationFile failed: %w", e1)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
const (
|
|
FSCTL_SET_SPARSE = 0x000900c4
|
|
)
|
|
|
|
// SetSparseImplemented is a constant indicating whether the
|
|
// implementation of SetSparse actually does anything.
|
|
const SetSparseImplemented = true
|
|
|
|
// SetSparse makes the file be a sparse file
|
|
func SetSparse(out *os.File) error {
|
|
var bytesReturned uint32
|
|
err := syscall.DeviceIoControl(syscall.Handle(out.Fd()), FSCTL_SET_SPARSE, nil, 0, nil, 0, &bytesReturned, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("DeviceIoControl FSCTL_SET_SPARSE: %w", err)
|
|
}
|
|
return nil
|
|
}
|