basicfs_copy_range_duplicateextents.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. // Copyright (C) 2020 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. // +build windows
  7. package fs
  8. import (
  9. "syscall"
  10. "unsafe"
  11. "golang.org/x/sys/windows"
  12. )
  13. func init() {
  14. registerCopyRangeImplementation(CopyRangeMethodDuplicateExtents, copyRangeImplementationForBasicFile(copyRangeDuplicateExtents))
  15. }
  16. // Inspired by https://github.com/git-lfs/git-lfs/blob/master/tools/util_windows.go
  17. var (
  18. availableClusterSize = []int64{64 * 1024, 4 * 1024} // ReFS only supports 64KiB and 4KiB cluster.
  19. GiB = int64(1024 * 1024 * 1024)
  20. )
  21. // fsctlDuplicateExtentsToFile = FSCTL_DUPLICATE_EXTENTS_TO_FILE IOCTL
  22. // Instructs the file system to copy a range of file bytes on behalf of an application.
  23. //
  24. // https://docs.microsoft.com/windows/win32/api/winioctl/ni-winioctl-fsctl_duplicate_extents_to_file
  25. const fsctlDuplicateExtentsToFile = 623428
  26. // duplicateExtentsData = DUPLICATE_EXTENTS_DATA structure
  27. // Contains parameters for the FSCTL_DUPLICATE_EXTENTS control code that performs the Block Cloning operation.
  28. //
  29. // https://docs.microsoft.com/windows/win32/api/winioctl/ns-winioctl-duplicate_extents_data
  30. type duplicateExtentsData struct {
  31. FileHandle windows.Handle
  32. SourceFileOffset int64
  33. TargetFileOffset int64
  34. ByteCount int64
  35. }
  36. func copyRangeDuplicateExtents(src, dst basicFile, srcOffset, dstOffset, size int64) error {
  37. var err error
  38. // Check that the destination file has sufficient space
  39. if fi, err := dst.Stat(); err != nil {
  40. return err
  41. } else if fi.Size() < dstOffset+size {
  42. // set file size. There is a requirements "The destination region must not extend past the end of file."
  43. if err = dst.Truncate(dstOffset + size); err != nil {
  44. return err
  45. }
  46. }
  47. // Requirement
  48. // * The source and destination regions must begin and end at a cluster boundary. (4KiB or 64KiB)
  49. // * cloneRegionSize less than 4GiB.
  50. // see https://docs.microsoft.com/windows/win32/fileio/block-cloning
  51. // Clone first xGiB region.
  52. for size > GiB {
  53. err = callDuplicateExtentsToFile(src.Fd(), dst.Fd(), srcOffset, dstOffset, GiB)
  54. if err != nil {
  55. return wrapError(err)
  56. }
  57. size -= GiB
  58. srcOffset += GiB
  59. dstOffset += GiB
  60. }
  61. // Clone tail. First try with 64KiB round up, then fallback to 4KiB.
  62. for _, cloneRegionSize := range availableClusterSize {
  63. err = callDuplicateExtentsToFile(src.Fd(), dst.Fd(), srcOffset, dstOffset, roundUp(size, cloneRegionSize))
  64. if err != nil {
  65. continue
  66. }
  67. break
  68. }
  69. return wrapError(err)
  70. }
  71. func wrapError(err error) error {
  72. if err == windows.SEVERITY_ERROR {
  73. return syscall.ENOTSUP
  74. }
  75. return err
  76. }
  77. // call FSCTL_DUPLICATE_EXTENTS_TO_FILE IOCTL
  78. // see https://docs.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_duplicate_extents_to_file
  79. //
  80. // memo: Overflow (cloneRegionSize is greater than file ends) is safe and just ignored by windows.
  81. func callDuplicateExtentsToFile(src, dst uintptr, srcOffset, dstOffset int64, cloneRegionSize int64) (err error) {
  82. var (
  83. bytesReturned uint32
  84. overlapped windows.Overlapped
  85. )
  86. request := duplicateExtentsData{
  87. FileHandle: windows.Handle(src),
  88. SourceFileOffset: srcOffset,
  89. TargetFileOffset: dstOffset,
  90. ByteCount: cloneRegionSize,
  91. }
  92. return windows.DeviceIoControl(
  93. windows.Handle(dst),
  94. fsctlDuplicateExtentsToFile,
  95. (*byte)(unsafe.Pointer(&request)),
  96. uint32(unsafe.Sizeof(request)),
  97. (*byte)(unsafe.Pointer(nil)), // = nullptr
  98. 0,
  99. &bytesReturned,
  100. &overlapped)
  101. }
  102. func roundUp(value, base int64) int64 {
  103. mod := value % base
  104. if mod == 0 {
  105. return value
  106. }
  107. return value - mod + base
  108. }