copy: tweak reflink logic in copy_bytes() a bit
authorLennart Poettering <lennart@poettering.net>
Fri, 23 Mar 2018 15:36:56 +0000 (16:36 +0100)
committerLennart Poettering <lennart@poettering.net>
Fri, 13 Apr 2018 09:32:46 +0000 (11:32 +0200)
Let's use btrfs_clone_range() if partial copies are desired. And use
btrfs_reflink() only for full-file reflinks.

src/basic/copy.c

index de71132..3e6e18f 100644 (file)
@@ -69,15 +69,59 @@ int copy_bytes(int fdf, int fdt, uint64_t max_bytes, CopyFlags copy_flags) {
          * of 'max_bytes', which may be specified as UINT64_MAX, in which no maximum is applied. Returns negative on
          * error, zero if EOF is hit before the bytes limit is hit and positive otherwise. */
 
-        /* Try btrfs reflinks first. */
-        if ((copy_flags & COPY_REFLINK) &&
-            max_bytes == (uint64_t) -1 &&
-            lseek(fdf, 0, SEEK_CUR) == 0 &&
-            lseek(fdt, 0, SEEK_CUR) == 0) {
-
-                r = btrfs_reflink(fdf, fdt);
-                if (r >= 0)
-                        return 0; /* we copied the whole thing, hence hit EOF, return 0 */
+        /* Try btrfs reflinks first. This only works on regular, seekable files, hence let's check the file offsets of
+         * source and destination first. */
+        if ((copy_flags & COPY_REFLINK)) {
+                off_t foffset;
+
+                foffset = lseek(fdf, 0, SEEK_CUR);
+                if (foffset >= 0) {
+                        off_t toffset;
+
+                        toffset = lseek(fdt, 0, SEEK_CUR);
+                        if (toffset >= 0) {
+
+                                if (foffset == 0 && toffset == 0 && max_bytes == UINT64_MAX)
+                                        r = btrfs_reflink(fdf, fdt); /* full file reflink */
+                                else
+                                        r = btrfs_clone_range(fdf, foffset, fdt, toffset, max_bytes == UINT64_MAX ? 0 : max_bytes); /* partial reflink */
+                                if (r >= 0) {
+                                        off_t t;
+
+                                        /* This worked, yay! Now — to be fully correct — let's adjust the file pointers */
+                                        if (max_bytes == UINT64_MAX) {
+
+                                                /* We cloned to the end of the source file, let's position the read
+                                                 * pointer there, and query it at the same time. */
+                                                t = lseek(fdf, 0, SEEK_END);
+                                                if (t < 0)
+                                                        return -errno;
+                                                if (t < foffset)
+                                                        return -ESPIPE;
+
+                                                /* Let's adjust the destination file write pointer by the same number
+                                                 * of bytes. */
+                                                t = lseek(fdt, toffset + (t - foffset), SEEK_SET);
+                                                if (t < 0)
+                                                        return -errno;
+
+                                                return 0; /* we copied the whole thing, hence hit EOF, return 0 */
+                                        } else {
+                                                t = lseek(fdf, foffset + max_bytes, SEEK_SET);
+                                                if (t < 0)
+                                                        return -errno;
+
+                                                t = lseek(fdt, toffset + max_bytes, SEEK_SET);
+                                                if (t < 0)
+                                                        return -errno;
+
+                                                return 1; /* we copied only some number of bytes, which worked, but this means we didn't hit EOF, return 1 */
+                                        }
+                                }
+
+                                log_debug_errno(r, "Reflinking didn't work, falling back to non-reflink copying: %m");
+                        }
+                }
         }
 
         for (;;) {