mirror of
https://github.com/rclone/rclone.git
synced 2024-11-23 00:43:49 +01:00
c4110780bf
Before this change we passed both lpOverlapped and lpBytesReturned as NULL. > If lpOverlapped is NULL, lpBytesReturned cannot be NULL. Even when > an operation produces no output data, and lpOutBuffer can be NULL, > the DeviceIoControl function makes use of the variable pointed to by > lpBytesReturned. After such an operation, the value of the variable > is without meaning. After this change we set lpBytesReturned to a valid pointer. See: https://forum.rclone.org/t/errors-when-downloading-any-file-over-250mb-from-google-drive-windows-sparse-files/16889
102 lines
2.6 KiB
Go
102 lines
2.6 KiB
Go
//+build windows
|
|
|
|
package file
|
|
|
|
import (
|
|
"os"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"github.com/pkg/errors"
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
var (
|
|
ntdll = windows.NewLazySystemDLL("ntdll.dll")
|
|
ntQueryVolumeInformationFile = ntdll.NewProc("NtQueryVolumeInformationFile")
|
|
ntSetInformationFile = ntdll.NewProc("NtSetInformationFile")
|
|
)
|
|
|
|
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
|
|
}
|
|
|
|
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 errors.Wrap(e1, "preAllocate NtQueryVolumeInformationFile failed")
|
|
}
|
|
|
|
// Calculate the allocation size
|
|
clusterSize := uint64(fsSizeInfo.BytesPerSector) * uint64(fsSizeInfo.SectorsPerAllocationUnit)
|
|
if clusterSize <= 0 {
|
|
return errors.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) {
|
|
return errors.Wrap(e1, "preAllocate NtSetInformationFile failed")
|
|
}
|
|
|
|
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 errors.Wrap(err, "DeviceIoControl FSCTL_SET_SPARSE")
|
|
}
|
|
return nil
|
|
}
|