basicfs_copy_range_duplicateextents.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  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. //go:build windows
  7. // +build windows
  8. package fs
  9. import (
  10. "io"
  11. "syscall"
  12. "unsafe"
  13. "golang.org/x/sys/windows"
  14. )
  15. func init() {
  16. registerCopyRangeImplementation(CopyRangeMethodDuplicateExtents, copyRangeImplementationForBasicFile(copyRangeDuplicateExtents))
  17. }
  18. // Inspired by https://github.com/git-lfs/git-lfs/blob/master/tools/util_windows.go
  19. var (
  20. availableClusterSize = []int64{64 * 1024, 4 * 1024} // ReFS only supports 64KiB and 4KiB cluster.
  21. GiB = int64(1024 * 1024 * 1024)
  22. )
  23. // fsctlDuplicateExtentsToFile = FSCTL_DUPLICATE_EXTENTS_TO_FILE IOCTL
  24. // Instructs the file system to copy a range of file bytes on behalf of an application.
  25. //
  26. // https://docs.microsoft.com/windows/win32/api/winioctl/ni-winioctl-fsctl_duplicate_extents_to_file
  27. const fsctlDuplicateExtentsToFile = 623428
  28. // duplicateExtentsData = DUPLICATE_EXTENTS_DATA structure
  29. // Contains parameters for the FSCTL_DUPLICATE_EXTENTS control code that performs the Block Cloning operation.
  30. //
  31. // https://docs.microsoft.com/windows/win32/api/winioctl/ns-winioctl-duplicate_extents_data
  32. type duplicateExtentsData struct {
  33. FileHandle windows.Handle
  34. SourceFileOffset int64
  35. TargetFileOffset int64
  36. ByteCount int64
  37. }
  38. func copyRangeDuplicateExtents(src, dst basicFile, srcOffset, dstOffset, size int64) error {
  39. var err error
  40. // Check that the destination file has sufficient space
  41. dstFi, err := dst.Stat()
  42. if err != nil {
  43. return err
  44. }
  45. dstSize := dstFi.Size()
  46. if dstSize < dstOffset+size {
  47. // set file size. There is a requirements "The destination region must not extend past the end of file."
  48. if err = dst.Truncate(dstOffset + size); err != nil {
  49. return err
  50. }
  51. dstSize = dstOffset + size
  52. }
  53. // The source file has to be big enough
  54. if fi, err := src.Stat(); err != nil {
  55. return err
  56. } else if fi.Size() < srcOffset+size {
  57. return io.ErrUnexpectedEOF
  58. }
  59. // Requirement
  60. // * The source and destination regions must begin and end at a cluster boundary. (4KiB or 64KiB)
  61. // * cloneRegionSize less than 4GiB.
  62. // see https://docs.microsoft.com/windows/win32/fileio/block-cloning
  63. smallestClusterSize := availableClusterSize[len(availableClusterSize)-1]
  64. if srcOffset%smallestClusterSize != 0 || dstOffset%smallestClusterSize != 0 {
  65. return syscall.EINVAL
  66. }
  67. // Each file gets allocated multiple of "clusterSize" blocks, yet file size determines how much of the last block
  68. // is readable/visible.
  69. // Copies only happen in block sized chunks, hence you can copy non block sized regions of data to a file, as long
  70. // as the regions are copied at the end of the file where the block visibility is adjusted by the file size.
  71. if size%smallestClusterSize != 0 && dstOffset+size != dstSize {
  72. return syscall.EINVAL
  73. }
  74. // Clone first xGiB region.
  75. for size > GiB {
  76. _, err = withFileDescriptors(src, dst, func(srcFd, dstFd uintptr) (int, error) {
  77. return 0, callDuplicateExtentsToFile(srcFd, dstFd, srcOffset, dstOffset, GiB)
  78. })
  79. if err != nil {
  80. return wrapError(err)
  81. }
  82. size -= GiB
  83. srcOffset += GiB
  84. dstOffset += GiB
  85. }
  86. // Clone tail. First try with 64KiB round up, then fallback to 4KiB.
  87. for _, cloneRegionSize := range availableClusterSize {
  88. _, err = withFileDescriptors(src, dst, func(srcFd, dstFd uintptr) (int, error) {
  89. return 0, callDuplicateExtentsToFile(srcFd, dstFd, srcOffset, dstOffset, roundUp(size, cloneRegionSize))
  90. })
  91. if err != nil {
  92. continue
  93. }
  94. break
  95. }
  96. return wrapError(err)
  97. }
  98. func wrapError(err error) error {
  99. if err == windows.SEVERITY_ERROR {
  100. return syscall.ENOTSUP
  101. }
  102. return err
  103. }
  104. // call FSCTL_DUPLICATE_EXTENTS_TO_FILE IOCTL
  105. // see https://docs.microsoft.com/windows/win32/api/winioctl/ni-winioctl-fsctl_duplicate_extents_to_file
  106. //
  107. // memo: Overflow (cloneRegionSize is greater than file ends) is safe and just ignored by windows.
  108. func callDuplicateExtentsToFile(src, dst uintptr, srcOffset, dstOffset int64, cloneRegionSize int64) error {
  109. var (
  110. bytesReturned uint32
  111. overlapped windows.Overlapped
  112. )
  113. request := duplicateExtentsData{
  114. FileHandle: windows.Handle(src),
  115. SourceFileOffset: srcOffset,
  116. TargetFileOffset: dstOffset,
  117. ByteCount: cloneRegionSize,
  118. }
  119. return windows.DeviceIoControl(
  120. windows.Handle(dst),
  121. fsctlDuplicateExtentsToFile,
  122. (*byte)(unsafe.Pointer(&request)),
  123. uint32(unsafe.Sizeof(request)),
  124. (*byte)(unsafe.Pointer(nil)), // = nullptr
  125. 0,
  126. &bytesReturned,
  127. &overlapped)
  128. }
  129. func roundUp(value, base int64) int64 {
  130. mod := value % base
  131. if mod == 0 {
  132. return value
  133. }
  134. return value - mod + base
  135. }