@@ -9,6 +9,7 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
@@ -46,13 +47,24 @@ type duplicateExtentsData struct {
|
||||
func copyRangeDuplicateExtents(src, dst basicFile, srcOffset, dstOffset, size int64) error {
|
||||
var err error
|
||||
// Check that the destination file has sufficient space
|
||||
if fi, err := dst.Stat(); err != nil {
|
||||
dstFi, err := dst.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
} else if fi.Size() < dstOffset+size {
|
||||
}
|
||||
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
|
||||
@@ -60,9 +72,25 @@ func copyRangeDuplicateExtents(src, dst basicFile, srcOffset, dstOffset, size in
|
||||
// * 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 = callDuplicateExtentsToFile(src.Fd(), dst.Fd(), srcOffset, dstOffset, 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)
|
||||
}
|
||||
@@ -73,7 +101,9 @@ func copyRangeDuplicateExtents(src, dst basicFile, srcOffset, dstOffset, size in
|
||||
|
||||
// Clone tail. First try with 64KiB round up, then fallback to 4KiB.
|
||||
for _, cloneRegionSize := range availableClusterSize {
|
||||
err = callDuplicateExtentsToFile(src.Fd(), dst.Fd(), srcOffset, dstOffset, roundUp(size, cloneRegionSize))
|
||||
_, err = withFileDescriptors(src, dst, func(srcFd, dstFd uintptr) (int, error) {
|
||||
return 0, callDuplicateExtentsToFile(srcFd, dstFd, srcOffset, dstOffset, roundUp(size, cloneRegionSize))
|
||||
})
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -94,7 +124,7 @@ func wrapError(err error) error {
|
||||
// see https://docs.microsoft.com/en-us/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) (err error) {
|
||||
func callDuplicateExtentsToFile(src, dst uintptr, srcOffset, dstOffset int64, cloneRegionSize int64) error {
|
||||
var (
|
||||
bytesReturned uint32
|
||||
overlapped windows.Overlapped
|
||||
|
||||
Reference in New Issue
Block a user