Skip to content

Commit

Permalink
lib/fs: Unwrap mtimeFile, get fd the "correct" way (ref #6875) (#6877)
Browse files Browse the repository at this point in the history
  • Loading branch information
AudriusButkevicius authored and calmh committed Aug 7, 2020
1 parent 0000899 commit b2e7ecd
Show file tree
Hide file tree
Showing 8 changed files with 314 additions and 134 deletions.
38 changes: 38 additions & 0 deletions lib/fs/basicfs_copy_range.go
Expand Up @@ -14,6 +14,9 @@ type copyRangeImplementationBasicFile func(src, dst basicFile, srcOffset, dstOff

func copyRangeImplementationForBasicFile(impl copyRangeImplementationBasicFile) copyRangeImplementation {
return func(src, dst File, srcOffset, dstOffset, size int64) error {
src = unwrap(src)
dst = unwrap(dst)
// Then see if it's basic files
srcFile, srcOk := src.(basicFile)
dstFile, dstOk := dst.(basicFile)
if !srcOk || !dstOk {
Expand All @@ -22,3 +25,38 @@ func copyRangeImplementationForBasicFile(impl copyRangeImplementationBasicFile)
return impl(srcFile, dstFile, srcOffset, dstOffset, size)
}
}

func withFileDescriptors(first, second basicFile, fn func(first, second uintptr) (int, error)) (int, error) {
fc, err := first.SyscallConn()
if err != nil {
return 0, err
}
sc, err := second.SyscallConn()
if err != nil {
return 0, err
}
var n int
var ferr, serr, fnerr error
ferr = fc.Control(func(first uintptr) {
serr = sc.Control(func(second uintptr) {
n, fnerr = fn(first, second)
})
})
if ferr != nil {
return n, ferr
}
if serr != nil {
return n, serr
}
return n, fnerr
}

func unwrap(f File) File {
for {
if wrapped, ok := f.(interface{ unwrap() File }); ok {
f = wrapped.unwrap()
} else {
return f
}
}
}
4 changes: 3 additions & 1 deletion lib/fs/basicfs_copy_range_copyfilerange.go
Expand Up @@ -29,7 +29,9 @@ func copyRangeCopyFileRange(src, dst basicFile, srcOffset, dstOffset, size int64
// appropriately.
//
// Also, even if explicitly not stated, the same is true for dstOffset
n, err := unix.CopyFileRange(int(src.Fd()), &srcOffset, int(dst.Fd()), &dstOffset, int(size), 0)
n, err := withFileDescriptors(src, dst, func(srcFd, dstFd uintptr) (int, error) {
return unix.CopyFileRange(int(srcFd), &srcOffset, int(dstFd), &dstOffset, int(size), 0)
})
if n == 0 && err == nil {
return io.ErrUnexpectedEOF
}
Expand Down
40 changes: 35 additions & 5 deletions lib/fs/basicfs_copy_range_duplicateextents.go
Expand Up @@ -9,6 +9,7 @@
package fs

import (
"io"
"syscall"
"unsafe"

Expand Down Expand Up @@ -46,23 +47,50 @@ 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
// * The source and destination regions must begin and end at a cluster boundary. (4KiB or 64KiB)
// * 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)
}
Expand All @@ -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
}
Expand All @@ -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
Expand Down
37 changes: 29 additions & 8 deletions lib/fs/basicfs_copy_range_ioctl.go
Expand Up @@ -4,11 +4,12 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

// +build !windows,!solaris,!darwin
// +build linux

package fs

import (
"io"
"syscall"
"unsafe"
)
Expand Down Expand Up @@ -43,6 +44,10 @@ func copyRangeIoctl(src, dst basicFile, srcOffset, dstOffset, size int64) error
return err
}

if srcOffset+size > fi.Size() {
return io.ErrUnexpectedEOF
}

// https://www.man7.org/linux/man-pages/man2/ioctl_ficlonerange.2.html
// If src_length is zero, the ioctl reflinks to the end of the source file.
if srcOffset+size == fi.Size() {
Expand All @@ -51,20 +56,36 @@ func copyRangeIoctl(src, dst basicFile, srcOffset, dstOffset, size int64) error

if srcOffset == 0 && dstOffset == 0 && size == 0 {
// Optimization for whole file copies.
_, _, errNo := syscall.Syscall(syscall.SYS_IOCTL, dst.Fd(), FICLONE, src.Fd())
var errNo syscall.Errno
_, err := withFileDescriptors(src, dst, func(srcFd, dstFd uintptr) (int, error) {
_, _, errNo = syscall.Syscall(syscall.SYS_IOCTL, dstFd, FICLONE, srcFd)
return 0, nil
})
// Failure in withFileDescriptors
if err != nil {
return err
}
if errNo != 0 {
return errNo
}
return nil
}

params := fileCloneRange{
srcFd: int64(src.Fd()),
srcOffset: uint64(srcOffset),
srcLength: uint64(size),
dstOffset: uint64(dstOffset),
var errNo syscall.Errno
_, err = withFileDescriptors(src, dst, func(srcFd, dstFd uintptr) (int, error) {
params := fileCloneRange{
srcFd: int64(srcFd),
srcOffset: uint64(srcOffset),
srcLength: uint64(size),
dstOffset: uint64(dstOffset),
}
_, _, errNo = syscall.Syscall(syscall.SYS_IOCTL, dstFd, FICLONERANGE, uintptr(unsafe.Pointer(&params)))
return 0, nil
})
// Failure in withFileDescriptors
if err != nil {
return err
}
_, _, errNo := syscall.Syscall(syscall.SYS_IOCTL, dst.Fd(), FICLONERANGE, uintptr(unsafe.Pointer(&params)))
if errNo != 0 {
return errNo
}
Expand Down
6 changes: 4 additions & 2 deletions lib/fs/basicfs_copy_range_sendfile.go
Expand Up @@ -4,7 +4,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

// +build !windows,!darwin
// +build linux solaris

package fs

Expand Down Expand Up @@ -51,7 +51,9 @@ func copyRangeSendFile(src, dst basicFile, srcOffset, dstOffset, size int64) err
// following the last byte that was read. If offset is not NULL, then sendfile() does not modify the current
// file offset of in_fd; otherwise the current file offset is adjusted to reflect the number of bytes read from
// in_fd.
n, err := syscall.Sendfile(int(dst.Fd()), int(src.Fd()), &srcOffset, int(size))
n, err := withFileDescriptors(dst, src, func(dstFd, srcFd uintptr) (int, error) {
return syscall.Sendfile(int(dstFd), int(srcFd), &srcOffset, int(size))
})
if n == 0 && err == nil {
err = io.ErrUnexpectedEOF
}
Expand Down

0 comments on commit b2e7ecd

Please sign in to comment.