diff --git a/backend/local/local.go b/backend/local/local.go index 71b52b723..8c6f30d6a 100644 --- a/backend/local/local.go +++ b/backend/local/local.go @@ -817,6 +817,12 @@ func (o *Object) Update(in io.Reader, src fs.ObjectInfo, options ...fs.OpenOptio return err } + // Pre-allocate the file for performance reasons + err = preAllocate(src.Size(), out) + if err != nil { + fs.Debugf(o, "Failed to pre-allocate: %v", err) + } + // Calculate the hash of the object we are reading as we go along hash, err := hash.NewMultiHasherTypes(hashes) if err != nil { diff --git a/backend/local/preallocate_other.go b/backend/local/preallocate_other.go new file mode 100644 index 000000000..7afff5e29 --- /dev/null +++ b/backend/local/preallocate_other.go @@ -0,0 +1,10 @@ +//+build !windows + +package local + +import "os" + +// preAllocate the file for performance reasons +func preAllocate(size int64, out *os.File) error { + return nil +} diff --git a/backend/local/preallocate_windows.go b/backend/local/preallocate_windows.go new file mode 100644 index 000000000..dfd88369d --- /dev/null +++ b/backend/local/preallocate_windows.go @@ -0,0 +1,79 @@ +//+build windows + +package local + +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 +} + +// 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 +}