summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Cady <d@cryptonomic.net>2021-01-18 21:13:31 -0500
committerAndrew Cady <d@cryptonomic.net>2021-01-18 21:13:31 -0500
commit2cf3de445d8ad9ab69d84694c0a59040a0d9b214 (patch)
tree8e037416f6c01568be1392d4ca39013dab665486
parentadea2c61018244e156fb94f6ef842f1f141b776f (diff)
ficlonerange.py
-rw-r--r--Makefile2
-rwxr-xr-xsrc/ficlonerange.py63
2 files changed, 64 insertions, 1 deletions
diff --git a/Makefile b/Makefile
index 0914597..fd53307 100644
--- a/Makefile
+++ b/Makefile
@@ -49,7 +49,7 @@ btrfs-send-root.sh var.sh grub-efi.sh keygen.sh initrd.sh qemu.sh
49dnsmasq-dhcp-script.sh samizdat-password-agent samizdat-gpg-agent publish-ip.sh \ 49dnsmasq-dhcp-script.sh samizdat-password-agent samizdat-gpg-agent publish-ip.sh \
50selfstrap samizdat-daily-snapshot-root samizdat-diff-root kiki-export-stdout \ 50selfstrap samizdat-daily-snapshot-root samizdat-diff-root kiki-export-stdout \
51kiki-import-stdin store-child-permanently git-ll-remote usb \ 51kiki-import-stdin store-child-permanently git-ll-remote usb \
52hostname.cryptonomic.net partvi ${dyndns_progs} 52hostname.cryptonomic.net partvi ficlonerange.py ${dyndns_progs}
53 53
54bin_programs=$(addprefix src/, $(src_bin_programs)) samizdat-paths.sh ${cc_files} ${btrfs_utils} 54bin_programs=$(addprefix src/, $(src_bin_programs)) samizdat-paths.sh ${cc_files} ${btrfs_utils}
55 55
diff --git a/src/ficlonerange.py b/src/ficlonerange.py
new file mode 100755
index 0000000..8df96ad
--- /dev/null
+++ b/src/ficlonerange.py
@@ -0,0 +1,63 @@
1#!/usr/bin/python3
2
3import sys
4import os
5from fcntl import ioctl
6import struct
7import argparse
8import errno
9import textwrap
10
11def fail(message):
12 print(textwrap.fill("FICLONERANGE: " + message))
13 sys.exit(3)
14
15FICLONERANGE = 0x4020940d
16
17def struct_file_clone_range(src_fd, src_offset, src_length, dst_offset):
18 return struct.pack("qQQQ", src_fd, src_offset, src_length, dst_offset)
19
20parser = argparse.ArgumentParser(description="Reflink bytes from source file to destination. "
21 "If no optional parameters are provided, append contents of src to dest. See ioctl_ficlonerange(2) for details.")
22parser.add_argument("-s", type=int, default=0, help="Source file offset in bytes (default: 0)")
23parser.add_argument("-l", type=int, default=0, help="Number of bytes to reflink (default: all data)")
24parser.add_argument("-d", type=int, help="Destination file offset in bytes (default: end of file)")
25parser.add_argument("src", help="Source filename")
26parser.add_argument("dest", help="Destination filename")
27args = parser.parse_args()
28
29src_fd = os.open(args.src, os.O_RDONLY)
30dst_fd = os.open(args.dest, os.O_WRONLY)
31
32try:
33 ioctl(dst_fd, FICLONERANGE, struct_file_clone_range(src_fd,
34 args.s or 0,
35 args.l or 0,
36 args.d if args.d is not None else os.fstat(dst_fd).st_size,
37 ))
38except OSError as e:
39 if e.errno == errno.EXDEV:
40 fail("src and dest are not on the same mounted filesystem")
41
42 if e.errno == errno.EISDIR:
43 fail("One of the files is a directory and the filesystem does not support shared regions in directories.")
44
45 if e.errno == errno.EINVAL:
46 fail("The filesystem does not support reflinking the ranges of the given files. This error can also appear "
47 "if either file descriptor represents a device, FIFO, or socket. Disk filesystems generally require the "
48 "offset and length arguments to be aligned to the fundamental block size ({} bytes). XFS and Btrfs do not support "
49 "overlapping reflink ranges in the same file.".format(os.fstat(dst_fd).st_blksize))
50
51 if e.errno == errno.EBADF:
52 fail("Filesystem does not support reflink (EBADF).")
53
54 if e.errno == errno.EPERM:
55 fail("dest is immutable.")
56
57 if e.errno == errno.ETXTBSY:
58 fail("One of the files is a swap file. Swap files cannot share storage.")
59
60 if e.errno == errno.EOPNOTSUPP:
61 fail("Filesystem does not support reflink (EOPNOTSUPP).")
62
63 raise