Skip to content

Commit 06711a6

Browse files
committed
feat: Add Linux implementation of ReflinkCopy FileCopyMethod
1 parent 212ece9 commit 06711a6

File tree

3 files changed

+81
-2
lines changed

3 files changed

+81
-2
lines changed

.github/workflows/filecopymethod-test.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
copymethod: ReflinkCopy
2727
- runner: ubuntu-latest
2828
filesystem: btrfs
29-
copymethod: GetBytes
29+
copymethod: ReflinkCopy
3030
steps:
3131
- name: Set up Go
3232
uses: actions/setup-go@v4

copy_methods_linux.go

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
//go:build linux
2+
3+
package copy
4+
5+
import (
6+
"fmt"
7+
"os"
8+
9+
"golang.org/x/sys/unix"
10+
)
11+
12+
// ReflinkCopy tries to copy the file by creating a reflink from the source
13+
// file to the destination file. This asks the filesystem to share the
14+
// contents between the files using a copy-on-write method.
15+
//
16+
// Reflinks are the fastest way to copy large files, but have a few limitations:
17+
//
18+
// - Requires using a supported filesystem (btrfs, xfs, apfs)
19+
// - Source and destination must be on the same filesystem.
20+
//
21+
// See: https://btrfs.readthedocs.io/en/latest/Reflink.html
22+
//
23+
// -------------------- PLATFORM SPECIFIC INFORMATION --------------------
24+
//
25+
// Linux implementation uses the `ficlone` ioctl:
26+
// https://manpages.debian.org/testing/manpages-dev/ioctl_ficlone.2.en.html
27+
//
28+
// Support:
29+
// - BTRFS or XFS filesystem
30+
//
31+
// Considerations:
32+
// - Ownership is not preserved.
33+
// - Setuid and Setgid are not preserved.
34+
// - Times are not preserved.
35+
var ReflinkCopy = FileCopyMethod{
36+
fcopy: func(src, dest string, info os.FileInfo, opt Options) (err error, skipFile bool) {
37+
if opt.FS != nil {
38+
return fmt.Errorf("%w: cannot create reflink from Go's fs.FS interface", ErrUnsupportedCopyMethod), false
39+
}
40+
41+
if opt.WrapReader != nil {
42+
return fmt.Errorf("%w: cannot create reflink when WrapReader option is used", ErrUnsupportedCopyMethod), false
43+
}
44+
45+
// Open source file.
46+
readcloser, err := os.OpenFile(src, os.O_RDONLY, 0)
47+
if err != nil {
48+
if os.IsNotExist(err) {
49+
return nil, true
50+
}
51+
return
52+
}
53+
defer fclose(readcloser, &err)
54+
55+
// Open dest file.
56+
f, err := os.Create(dest)
57+
if err != nil {
58+
return
59+
}
60+
defer fclose(f, &err)
61+
62+
// Do copy.
63+
srcFd := readcloser.Fd()
64+
destFd := f.Fd()
65+
err = unix.IoctlFileClone(int(destFd), int(srcFd))
66+
67+
// Return an error if cloning is not possible.
68+
if err != nil {
69+
_ = os.Remove(dest) // remove the empty file on error
70+
return &os.PathError{
71+
Op: "create reflink",
72+
Path: src,
73+
Err: err,
74+
}, false
75+
}
76+
77+
return nil, false
78+
},
79+
}

copy_methods_x.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build !darwin
1+
//go:build !darwin && !linux
22

33
package copy
44

0 commit comments

Comments
 (0)