mirror of
https://github.com/rclone/rclone.git
synced 2024-11-07 17:14:44 +01:00
110 lines
2.9 KiB
Go
110 lines
2.9 KiB
Go
//go: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 // Control code to set or clears the FILE_ATTRIBUTE_SPARSE_FILE attribute of a file.
|
|
)
|
|
|
|
// 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
|
|
}
|