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  }