github.com/josephvusich/fdf@v0.0.0-20230522095411-9326dd32e33f/clonefile_windows.go (about) 1 package main 2 3 // Adapted from https://github.com/git-lfs/git-lfs/blob/master/tools/util_windows.go 4 // Includes some bug fixes, such as file handles not being closed properly. 5 6 import ( 7 "os" 8 "unsafe" 9 10 "golang.org/x/sys/windows" 11 ) 12 13 var ( 14 availableClusterSize = []int64{64 * 1024, 4 * 1024} // ReFS only supports 64KiB and 4KiB cluster. 15 gigabyte = int64(1024 * 1024 * 1024) 16 ) 17 18 // Instructs the file system to copy a range of file bytes on behalf of an application. 19 // 20 // https://docs.microsoft.com/windows/win32/api/winioctl/ni-winioctl-fsctl_duplicate_extents_to_file 21 const fsctl_DUPLICATE_EXTENTS_TO_FILE = 623428 22 23 // Contains parameters for the FSCTL_DUPLICATE_EXTENTS control code that performs the Block Cloning operation. 24 // 25 // https://docs.microsoft.com/windows/win32/api/winioctl/ns-winioctl-duplicate_extents_data 26 type struct_DUPLICATE_EXTENTS_DATA struct { 27 FileHandle windows.Handle 28 SourceFileOffset int64 29 TargetFileOffset int64 30 ByteCount int64 31 } 32 33 // The source and destination regions must begin and end at a cluster boundary. 34 // The cloned region must be less than 4GB in length. 35 // The destination region must not extend past the end of file. If the application 36 // wishes to extend the destination with cloned data, it must first call SetEndOfFile. 37 // https://docs.microsoft.com/en-us/windows/win32/fileio/block-cloning 38 func cloneFile(src, dst string) error { 39 sf, err := os.Open(src) 40 if err != nil { 41 return err 42 } 43 defer sf.Close() 44 45 srcStat, err := sf.Stat() 46 if err != nil { 47 return err 48 } 49 50 df, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE, srcStat.Mode()) // No truncate version of os.Create 51 if err != nil { 52 return err 53 } 54 defer df.Close() 55 56 fileSize := srcStat.Size() 57 58 err = df.Truncate(fileSize) // set file size. There is a requirements "The destination region must not extend past the end of file." 59 if err != nil { 60 return err 61 } 62 63 offset := int64(0) 64 65 // Requirement 66 // * The source and destination regions must begin and end at a cluster boundary. (4KiB or 64KiB) 67 // * cloneRegionSize less than 4GiB. 68 // see https://docs.microsoft.com/windows/win32/fileio/block-cloning 69 70 // Clone first xGiB region. 71 for ; offset+gigabyte < fileSize; offset += gigabyte { 72 err = callDuplicateExtentsToFile(df, sf, offset, gigabyte) 73 if err != nil { 74 return err 75 } 76 } 77 78 // Clone tail. First try with 64KiB round up, then fallback to 4KiB. 79 for _, cloneRegionSize := range availableClusterSize { 80 err = callDuplicateExtentsToFile(df, sf, offset, roundUp(fileSize-offset, cloneRegionSize)) 81 if err != nil { 82 continue 83 } 84 break 85 } 86 87 return err 88 } 89 90 // call FSCTL_DUPLICATE_EXTENTS_TO_FILE IOCTL 91 // see https://docs.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_duplicate_extents_to_file 92 // 93 // memo: Overflow (cloneRegionSize is greater than file ends) is safe and just ignored by windows. 94 func callDuplicateExtentsToFile(dst, src *os.File, offset int64, cloneRegionSize int64) (err error) { 95 var ( 96 bytesReturned uint32 97 overlapped windows.Overlapped 98 ) 99 100 request := struct_DUPLICATE_EXTENTS_DATA{ 101 FileHandle: windows.Handle(src.Fd()), 102 SourceFileOffset: offset, 103 TargetFileOffset: offset, 104 ByteCount: cloneRegionSize, 105 } 106 107 return windows.DeviceIoControl( 108 windows.Handle(dst.Fd()), 109 fsctl_DUPLICATE_EXTENTS_TO_FILE, 110 (*byte)(unsafe.Pointer(&request)), 111 uint32(unsafe.Sizeof(request)), 112 (*byte)(unsafe.Pointer(nil)), // = nullptr 113 0, 114 &bytesReturned, 115 &overlapped) 116 } 117 118 func roundUp(value, base int64) int64 { 119 mod := value % base 120 if mod == 0 { 121 return value 122 } 123 124 return value - mod + base 125 }