From 2cf3de445d8ad9ab69d84694c0a59040a0d9b214 Mon Sep 17 00:00:00 2001 From: Andrew Cady Date: Mon, 18 Jan 2021 21:13:31 -0500 Subject: ficlonerange.py --- Makefile | 2 +- src/ficlonerange.py | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100755 src/ficlonerange.py 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 dnsmasq-dhcp-script.sh samizdat-password-agent samizdat-gpg-agent publish-ip.sh \ selfstrap samizdat-daily-snapshot-root samizdat-diff-root kiki-export-stdout \ kiki-import-stdin store-child-permanently git-ll-remote usb \ -hostname.cryptonomic.net partvi ${dyndns_progs} +hostname.cryptonomic.net partvi ficlonerange.py ${dyndns_progs} bin_programs=$(addprefix src/, $(src_bin_programs)) samizdat-paths.sh ${cc_files} ${btrfs_utils} 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 @@ +#!/usr/bin/python3 + +import sys +import os +from fcntl import ioctl +import struct +import argparse +import errno +import textwrap + +def fail(message): + print(textwrap.fill("FICLONERANGE: " + message)) + sys.exit(3) + +FICLONERANGE = 0x4020940d + +def struct_file_clone_range(src_fd, src_offset, src_length, dst_offset): + return struct.pack("qQQQ", src_fd, src_offset, src_length, dst_offset) + +parser = argparse.ArgumentParser(description="Reflink bytes from source file to destination. " + "If no optional parameters are provided, append contents of src to dest. See ioctl_ficlonerange(2) for details.") +parser.add_argument("-s", type=int, default=0, help="Source file offset in bytes (default: 0)") +parser.add_argument("-l", type=int, default=0, help="Number of bytes to reflink (default: all data)") +parser.add_argument("-d", type=int, help="Destination file offset in bytes (default: end of file)") +parser.add_argument("src", help="Source filename") +parser.add_argument("dest", help="Destination filename") +args = parser.parse_args() + +src_fd = os.open(args.src, os.O_RDONLY) +dst_fd = os.open(args.dest, os.O_WRONLY) + +try: + ioctl(dst_fd, FICLONERANGE, struct_file_clone_range(src_fd, + args.s or 0, + args.l or 0, + args.d if args.d is not None else os.fstat(dst_fd).st_size, + )) +except OSError as e: + if e.errno == errno.EXDEV: + fail("src and dest are not on the same mounted filesystem") + + if e.errno == errno.EISDIR: + fail("One of the files is a directory and the filesystem does not support shared regions in directories.") + + if e.errno == errno.EINVAL: + fail("The filesystem does not support reflinking the ranges of the given files. This error can also appear " + "if either file descriptor represents a device, FIFO, or socket. Disk filesystems generally require the " + "offset and length arguments to be aligned to the fundamental block size ({} bytes). XFS and Btrfs do not support " + "overlapping reflink ranges in the same file.".format(os.fstat(dst_fd).st_blksize)) + + if e.errno == errno.EBADF: + fail("Filesystem does not support reflink (EBADF).") + + if e.errno == errno.EPERM: + fail("dest is immutable.") + + if e.errno == errno.ETXTBSY: + fail("One of the files is a swap file. Swap files cannot share storage.") + + if e.errno == errno.EOPNOTSUPP: + fail("Filesystem does not support reflink (EOPNOTSUPP).") + + raise -- cgit v1.2.3