mirror of
https://github.com/octoleo/syncthing.git
synced 2024-12-22 10:58:57 +00:00
lib/fs: Add support for Windows duplicate extents (#6764)
This commit is contained in:
parent
22f0077262
commit
deaccc7f8d
128
lib/fs/basicfs_copy_range_duplicateextents.go
Normal file
128
lib/fs/basicfs_copy_range_duplicateextents.go
Normal file
@ -0,0 +1,128 @@
|
||||
// Copyright (C) 2020 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build windows
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerCopyRangeImplementation(CopyRangeMethodDuplicateExtents, copyRangeImplementationForBasicFile(copyRangeDuplicateExtents))
|
||||
}
|
||||
|
||||
// Inspired by https://github.com/git-lfs/git-lfs/blob/master/tools/util_windows.go
|
||||
|
||||
var (
|
||||
availableClusterSize = []int64{64 * 1024, 4 * 1024} // ReFS only supports 64KiB and 4KiB cluster.
|
||||
GiB = int64(1024 * 1024 * 1024)
|
||||
)
|
||||
|
||||
// fsctlDuplicateExtentsToFile = FSCTL_DUPLICATE_EXTENTS_TO_FILE IOCTL
|
||||
// Instructs the file system to copy a range of file bytes on behalf of an application.
|
||||
//
|
||||
// https://docs.microsoft.com/windows/win32/api/winioctl/ni-winioctl-fsctl_duplicate_extents_to_file
|
||||
const fsctlDuplicateExtentsToFile = 623428
|
||||
|
||||
// duplicateExtentsData = DUPLICATE_EXTENTS_DATA structure
|
||||
// Contains parameters for the FSCTL_DUPLICATE_EXTENTS control code that performs the Block Cloning operation.
|
||||
//
|
||||
// https://docs.microsoft.com/windows/win32/api/winioctl/ns-winioctl-duplicate_extents_data
|
||||
type duplicateExtentsData struct {
|
||||
FileHandle windows.Handle
|
||||
SourceFileOffset int64
|
||||
TargetFileOffset int64
|
||||
ByteCount int64
|
||||
}
|
||||
|
||||
func copyRangeDuplicateExtents(src, dst basicFile, srcOffset, dstOffset, size int64) error {
|
||||
var err error
|
||||
// Check that the destination file has sufficient space
|
||||
if fi, err := dst.Stat(); err != nil {
|
||||
return err
|
||||
} else if fi.Size() < dstOffset+size {
|
||||
// set file size. There is a requirements "The destination region must not extend past the end of file."
|
||||
if err = dst.Truncate(dstOffset + size); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Requirement
|
||||
// * The source and destination regions must begin and end at a cluster boundary. (4KiB or 64KiB)
|
||||
// * cloneRegionSize less than 4GiB.
|
||||
// see https://docs.microsoft.com/windows/win32/fileio/block-cloning
|
||||
|
||||
// Clone first xGiB region.
|
||||
for size > GiB {
|
||||
err = callDuplicateExtentsToFile(src.Fd(), dst.Fd(), srcOffset, dstOffset, GiB)
|
||||
if err != nil {
|
||||
return wrapError(err)
|
||||
}
|
||||
size -= GiB
|
||||
srcOffset += GiB
|
||||
dstOffset += GiB
|
||||
}
|
||||
|
||||
// Clone tail. First try with 64KiB round up, then fallback to 4KiB.
|
||||
for _, cloneRegionSize := range availableClusterSize {
|
||||
err = callDuplicateExtentsToFile(src.Fd(), dst.Fd(), srcOffset, dstOffset, roundUp(size, cloneRegionSize))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
return wrapError(err)
|
||||
}
|
||||
|
||||
func wrapError(err error) error {
|
||||
if err == windows.SEVERITY_ERROR {
|
||||
return syscall.ENOTSUP
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// call FSCTL_DUPLICATE_EXTENTS_TO_FILE IOCTL
|
||||
// see https://docs.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_duplicate_extents_to_file
|
||||
//
|
||||
// memo: Overflow (cloneRegionSize is greater than file ends) is safe and just ignored by windows.
|
||||
func callDuplicateExtentsToFile(src, dst uintptr, srcOffset, dstOffset int64, cloneRegionSize int64) (err error) {
|
||||
var (
|
||||
bytesReturned uint32
|
||||
overlapped windows.Overlapped
|
||||
)
|
||||
|
||||
request := duplicateExtentsData{
|
||||
FileHandle: windows.Handle(src),
|
||||
SourceFileOffset: srcOffset,
|
||||
TargetFileOffset: dstOffset,
|
||||
ByteCount: cloneRegionSize,
|
||||
}
|
||||
|
||||
return windows.DeviceIoControl(
|
||||
windows.Handle(dst),
|
||||
fsctlDuplicateExtentsToFile,
|
||||
(*byte)(unsafe.Pointer(&request)),
|
||||
uint32(unsafe.Sizeof(request)),
|
||||
(*byte)(unsafe.Pointer(nil)), // = nullptr
|
||||
0,
|
||||
&bytesReturned,
|
||||
&overlapped)
|
||||
}
|
||||
|
||||
func roundUp(value, base int64) int64 {
|
||||
mod := value % base
|
||||
if mod == 0 {
|
||||
return value
|
||||
}
|
||||
|
||||
return value - mod + base
|
||||
}
|
@ -12,7 +12,7 @@ func init() {
|
||||
|
||||
func copyRangeAllWithFallback(src, dst File, srcOffset, dstOffset, size int64) error {
|
||||
var err error
|
||||
for _, method := range []CopyRangeMethod{CopyRangeMethodIoctl, CopyRangeMethodCopyFileRange, CopyRangeMethodSendFile, CopyRangeMethodStandard} {
|
||||
for _, method := range []CopyRangeMethod{CopyRangeMethodIoctl, CopyRangeMethodCopyFileRange, CopyRangeMethodSendFile, CopyRangeMethodDuplicateExtents, CopyRangeMethodStandard} {
|
||||
if err = CopyRange(method, src, dst, srcOffset, dstOffset, size); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ const (
|
||||
CopyRangeMethodIoctl
|
||||
CopyRangeMethodCopyFileRange
|
||||
CopyRangeMethodSendFile
|
||||
CopyRangeMethodDuplicateExtents
|
||||
CopyRangeMethodAllWithFallback
|
||||
)
|
||||
|
||||
@ -26,6 +27,8 @@ func (o CopyRangeMethod) String() string {
|
||||
return "copy_file_range"
|
||||
case CopyRangeMethodSendFile:
|
||||
return "sendfile"
|
||||
case CopyRangeMethodDuplicateExtents:
|
||||
return "duplicate_extents"
|
||||
case CopyRangeMethodAllWithFallback:
|
||||
return "all"
|
||||
default:
|
||||
@ -47,6 +50,8 @@ func (o *CopyRangeMethod) UnmarshalText(bs []byte) error {
|
||||
*o = CopyRangeMethodCopyFileRange
|
||||
case "sendfile":
|
||||
*o = CopyRangeMethodSendFile
|
||||
case "duplicate_extents":
|
||||
*o = CopyRangeMethodDuplicateExtents
|
||||
case "all":
|
||||
*o = CopyRangeMethodAllWithFallback
|
||||
default:
|
||||
|
Loading…
Reference in New Issue
Block a user