123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160 |
- // 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/.
- //go:build windows
- // +build windows
- package fs
- import (
- "io"
- "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
- dstFi, err := dst.Stat()
- if err != nil {
- return err
- }
- dstSize := dstFi.Size()
- if dstSize < 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
- }
- dstSize = dstOffset + size
- }
- // The source file has to be big enough
- if fi, err := src.Stat(); err != nil {
- return err
- } else if fi.Size() < srcOffset+size {
- return io.ErrUnexpectedEOF
- }
- // 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
- smallestClusterSize := availableClusterSize[len(availableClusterSize)-1]
- if srcOffset%smallestClusterSize != 0 || dstOffset%smallestClusterSize != 0 {
- return syscall.EINVAL
- }
- // Each file gets allocated multiple of "clusterSize" blocks, yet file size determines how much of the last block
- // is readable/visible.
- // Copies only happen in block sized chunks, hence you can copy non block sized regions of data to a file, as long
- // as the regions are copied at the end of the file where the block visibility is adjusted by the file size.
- if size%smallestClusterSize != 0 && dstOffset+size != dstSize {
- return syscall.EINVAL
- }
- // Clone first xGiB region.
- for size > GiB {
- _, err = withFileDescriptors(src, dst, func(srcFd, dstFd uintptr) (int, error) {
- return 0, callDuplicateExtentsToFile(srcFd, dstFd, 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 = withFileDescriptors(src, dst, func(srcFd, dstFd uintptr) (int, error) {
- return 0, callDuplicateExtentsToFile(srcFd, dstFd, 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/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) 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
- }
|