summaryrefslogtreecommitdiff
path: root/src/ficlonerange.py
blob: 8df96ad18323787c8296d19426d23eddcbf38b6b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
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