I realize the clone-dest patch is unsupported, but I've updated it to the most
recent git tree here, and included some tests:
https://github.com/josephmaher/rsync
I've also attached a patch for the current version of rsync in debian
stable/trixie in case that is of any use to anyone...
Obviously, use at your own risk - I got stuck and had to ask claude.ai for
advice at various points, so these changes include suggestions from claude.ai.
Joseph
Common subdirectories: rsync-3.4.1+ds1/debian and ../trixie4/rsync-3.4.1+ds1/debian
Common subdirectories: rsync-3.4.1+ds1/doc and ../trixie4/rsync-3.4.1+ds1/doc
diff -U3 rsync-3.4.1+ds1/generator.c ../trixie4/rsync-3.4.1+ds1/generator.c
--- rsync-3.4.1+ds1/generator.c 2026-05-20 20:19:47.000000000 -0400
+++ ../trixie4/rsync-3.4.1+ds1/generator.c 2026-05-20 20:18:25.730823635 -0400
@@ -968,7 +968,7 @@
best_match = j;
match_level = 2;
}
- if (unchanged_attrs(cmpbuf, file, sxp)) {
+ if (alt_dest_type == CLONE_DEST || unchanged_attrs(cmpbuf, file, sxp)) {
best_match = j;
match_level = 3;
break;
@@ -994,9 +994,16 @@
goto got_nothing_for_ya;
}
#ifdef SUPPORT_HARD_LINKS
- if (alt_dest_type == LINK_DEST) {
- if (!hard_link_one(file, fname, cmpbuf, 1))
- goto try_a_copy;
+ if (alt_dest_type == LINK_DEST || alt_dest_type == CLONE_DEST) {
+ if (alt_dest_type == LINK_DEST) {
+ if (!hard_link_one(file, fname, cmpbuf, 1))
+ goto try_a_copy;
+ } else if (do_clone(cmpbuf, fname, file->mode) == 0) {
+ finish_transfer(fname, fname, cmpbuf, NULL, file, 1, 0);
+ } else {
+ rsyserr(FERROR_XFER, errno, "failed to clone %s to %s", cmpbuf, fname);
+ exit_cleanup(RERR_UNSUPPORTED);
+ }
if (atimes_ndx)
set_file_attrs(fname, file, sxp, NULL, 0);
if (preserve_hard_links && F_IS_HLINKED(file))
@@ -1110,7 +1117,7 @@
if (match_level == 3) {
#ifdef SUPPORT_HARD_LINKS
- if (alt_dest_type == LINK_DEST
+ if ((alt_dest_type == LINK_DEST || alt_dest_type == CLONE_DEST)
#ifndef CAN_HARDLINK_SYMLINK
&& !S_ISLNK(file->mode)
#endif
Common subdirectories: rsync-3.4.1+ds1/lib and ../trixie4/rsync-3.4.1+ds1/lib
Common subdirectories: rsync-3.4.1+ds1/m4 and ../trixie4/rsync-3.4.1+ds1/m4
diff -U3 rsync-3.4.1+ds1/Makefile.in ../trixie4/rsync-3.4.1+ds1/Makefile.in
--- rsync-3.4.1+ds1/Makefile.in 2026-05-20 20:19:47.000000000 -0400
+++ ../trixie4/rsync-3.4.1+ds1/Makefile.in 2026-05-20 20:18:42.473243691 -0400
@@ -53,7 +53,7 @@
popt/popthelp.o popt/poptparse.o popt/poptint.o
OBJS=$(OBJS1) $(OBJS2) $(OBJS3) $(DAEMON_OBJ) $(LIBOBJ) @BUILD_ZLIB@ @BUILD_POPT@
-TLS_OBJ = tls.o syscall.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/permstring.o lib/sysxattrs.o @BUILD_POPT@
+TLS_OBJ = tls.o syscall.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/permstring.o lib/sysxattrs.o lib/wildmatch.o @BUILD_POPT@
# Programs we must have to run the test cases
CHECK_PROGS = rsync$(EXEEXT) tls$(EXEEXT) getgroups$(EXEEXT) getfsdev$(EXEEXT) \
@@ -171,7 +171,7 @@
getfsdev$(EXEEXT): getfsdev.o
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ getfsdev.o $(LIBS)
-TRIMSLASH_OBJ = trimslash.o syscall.o util2.o t_stub.o lib/compat.o lib/snprintf.o
+TRIMSLASH_OBJ = trimslash.o syscall.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o
trimslash$(EXEEXT): $(TRIMSLASH_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(TRIMSLASH_OBJ) $(LIBS)
diff -U3 rsync-3.4.1+ds1/options.c ../trixie4/rsync-3.4.1+ds1/options.c
--- rsync-3.4.1+ds1/options.c 2026-05-20 20:19:47.000000000 -0400
+++ ../trixie4/rsync-3.4.1+ds1/options.c 2026-05-20 20:18:25.730823635 -0400
@@ -591,7 +591,7 @@
OPT_INCLUDE, OPT_INCLUDE_FROM, OPT_MODIFY_WINDOW, OPT_MIN_SIZE, OPT_CHMOD,
OPT_READ_BATCH, OPT_WRITE_BATCH, OPT_ONLY_WRITE_BATCH, OPT_MAX_SIZE,
OPT_NO_D, OPT_APPEND, OPT_NO_ICONV, OPT_INFO, OPT_DEBUG, OPT_BLOCK_SIZE,
- OPT_USERMAP, OPT_GROUPMAP, OPT_CHOWN, OPT_BWLIMIT, OPT_STDERR,
+ OPT_USERMAP, OPT_GROUPMAP, OPT_CHOWN, OPT_BWLIMIT, OPT_STDERR, OPT_CLONE_DEST,
OPT_OLD_COMPRESS, OPT_NEW_COMPRESS, OPT_NO_COMPRESS, OPT_OLD_ARGS,
OPT_STOP_AFTER, OPT_STOP_AT,
OPT_REFUSED_BASE = 9000};
@@ -752,6 +752,7 @@
{"compare-dest", 0, POPT_ARG_STRING, 0, OPT_COMPARE_DEST, 0, 0 },
{"copy-dest", 0, POPT_ARG_STRING, 0, OPT_COPY_DEST, 0, 0 },
{"link-dest", 0, POPT_ARG_STRING, 0, OPT_LINK_DEST, 0, 0 },
+ {"clone-dest", 0, POPT_ARG_STRING, 0, OPT_CLONE_DEST, 0, 0 },
{"fuzzy", 'y', POPT_ARG_NONE, 0, 'y', 0, 0 },
{"no-fuzzy", 0, POPT_ARG_VAL, &fuzzy_basis, 0, 0, 0 },
{"no-y", 0, POPT_ARG_VAL, &fuzzy_basis, 0, 0, 0 },
@@ -1013,6 +1014,9 @@
#ifndef SUPPORT_HARD_LINKS
parse_one_refuse_match(0, "link-dest", list_end);
#endif
+#ifndef FICLONE
+ parse_one_refuse_match(0, "clone-dest", list_end);
+#endif
#ifndef HAVE_MKTIME
parse_one_refuse_match(0, "stop-at", list_end);
#endif
@@ -1342,6 +1346,8 @@
return "--copy-dest";
case LINK_DEST:
return "--link-dest";
+ case CLONE_DEST:
+ return "--clone-dest";
default:
NOISY_DEATH("Unknown alt_dest_opt type");
}
@@ -1727,6 +1733,10 @@
want_dest_type = LINK_DEST;
goto set_dest_dir;
+ case OPT_CLONE_DEST:
+ want_dest_type = CLONE_DEST;
+ goto set_dest_dir;
+
case OPT_COPY_DEST:
want_dest_type = COPY_DEST;
goto set_dest_dir;
Common subdirectories: rsync-3.4.1+ds1/packaging and ../trixie4/rsync-3.4.1+ds1/packaging
Common subdirectories: rsync-3.4.1+ds1/.pc and ../trixie4/rsync-3.4.1+ds1/.pc
diff -U3 rsync-3.4.1+ds1/rsync.1.md ../trixie4/rsync-3.4.1+ds1/rsync.1.md
--- rsync-3.4.1+ds1/rsync.1.md 2024-11-20 00:45:50.000000000 -0500
+++ ../trixie4/rsync-3.4.1+ds1/rsync.1.md 2026-05-20 20:18:25.730823635 -0400
@@ -510,6 +510,7 @@
--compare-dest=DIR also compare destination files relative to DIR
--copy-dest=DIR ... and include copies of unchanged files
--link-dest=DIR hardlink to files in DIR when unchanged
+--clone-dest=DIR clone (reflink) files from DIR when unchanged
--compress, -z compress file data during the transfer
--compress-choice=STR choose the compression algorithm (aka --zc)
--compress-level=NUM explicitly set compression level (aka --zl)
@@ -2720,6 +2721,18 @@
this bug by avoiding the `-o` option (or using `--no-o`) when sending to an
old rsync.
+0. `--clone-dest=DIR`
+
+ This option behaves like [`--link-dest`](#opt), but unchanged files are
+ reflinked from _DIR_ to the destination directory. The files do not need
+ to match in attributes, as the data is cloned separately from the
+ attributes.
+
+ If _DIR_ is a relative path, it is relative to the destination directory.
+ See also [`--compare-dest`](#opt) and [`--copy-dest`](#opt).
+
+ All non-regular files are hard-linked (when possible).
+
0. `--compress`, `-z`
With this option, rsync compresses the file data as it is sent to the
diff -U3 rsync-3.4.1+ds1/rsync.h ../trixie4/rsync-3.4.1+ds1/rsync.h
--- rsync-3.4.1+ds1/rsync.h 2026-05-20 20:19:47.000000000 -0400
+++ ../trixie4/rsync-3.4.1+ds1/rsync.h 2026-05-20 20:18:25.734823257 -0400
@@ -193,6 +193,11 @@
#define COMPARE_DEST 1
#define COPY_DEST 2
#define LINK_DEST 3
+#define CLONE_DEST 4
+
+#if !defined FICLONE && defined __linux__
+#define FICLONE _IOW(0x94, 9, int)
+#endif
#define MPLEX_BASE 7
Common subdirectories: rsync-3.4.1+ds1/support and ../trixie4/rsync-3.4.1+ds1/support
diff -U3 rsync-3.4.1+ds1/syscall.c ../trixie4/rsync-3.4.1+ds1/syscall.c
--- rsync-3.4.1+ds1/syscall.c 2026-05-20 20:19:47.000000000 -0400
+++ ../trixie4/rsync-3.4.1+ds1/syscall.c 2026-05-20 20:18:25.734823257 -0400
@@ -399,6 +399,54 @@
}
#endif
+int do_clone(const char *old_path, const char *new_path, mode_t mode)
+{
+#ifdef FICLONE
+ int ifd, ofd, ret, save_errno;
+
+ if (dry_run) return 0;
+ RETURN_ERROR_IF_RO_OR_LO;
+
+ if ((ifd = do_open(old_path, O_RDONLY, 0)) < 0) {
+ save_errno = errno;
+ rsyserr(FERROR_XFER, errno, "open %s", full_fname(old_path));
+ errno = save_errno;
+ return -1;
+ }
+
+ if (robust_unlink(new_path) && errno != ENOENT) {
+ save_errno = errno;
+ rsyserr(FERROR_XFER, errno, "unlink %s", full_fname(new_path));
+ close(ifd);
+ errno = save_errno;
+ return -1;
+ }
+
+ mode &= INITACCESSPERMS;
+ if ((ofd = do_open(new_path, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode)) < 0) {
+ save_errno = errno;
+ rsyserr(FERROR_XFER, save_errno, "open %s", full_fname(new_path));
+ close(ifd);
+ errno = save_errno;
+ return -1;
+ }
+
+ ret = ioctl(ofd, FICLONE, ifd);
+ save_errno = errno;
+ close(ifd);
+ close(ofd);
+ if (ret < 0)
+ unlink(new_path);
+ errno = save_errno;
+ return ret;
+#else
+ (void)old_path;
+ (void)new_path;
+ errno = ENOTSUP;
+ return -1;
+#endif
+}
+
int do_lchown(const char *path, uid_t owner, gid_t group)
{
if (dry_run) return 0;
Common subdirectories: rsync-3.4.1+ds1/testhelp and ../trixie4/rsync-3.4.1+ds1/testhelp
Common subdirectories: rsync-3.4.1+ds1/testsuite and ../trixie4/rsync-3.4.1+ds1/testsuite
diff -U3 rsync-3.4.1+ds1/tls.c ../trixie4/rsync-3.4.1+ds1/tls.c
--- rsync-3.4.1+ds1/tls.c 2026-05-20 20:19:47.000000000 -0400
+++ ../trixie4/rsync-3.4.1+ds1/tls.c 2026-05-20 20:18:25.734823257 -0400
@@ -53,6 +53,8 @@
int copy_links = 0;
int copy_unsafe_links = 0;
+short info_levels[COUNT_INFO], debug_levels[COUNT_DEBUG];
+
#ifdef SUPPORT_XATTRS
#ifdef HAVE_LINUX_XATTRS
diff -U3 rsync-3.4.1+ds1/trimslash.c ../trixie4/rsync-3.4.1+ds1/trimslash.c
--- rsync-3.4.1+ds1/trimslash.c 2025-01-14 13:30:32.000000000 -0500
+++ ../trixie4/rsync-3.4.1+ds1/trimslash.c 2026-05-20 20:18:25.734823257 -0400
@@ -29,6 +29,8 @@
int copy_links = 0;
int copy_unsafe_links = 0;
+short info_levels[COUNT_INFO], debug_levels[COUNT_DEBUG];
+
int
main(int argc, char **argv)
{
--
Please use reply-all for most replies to avoid omitting the mailing list.
To unsubscribe or change options: https://lists.samba.org/mailman/listinfo/rsync
Before posting, read: http://www.catb.org/~esr/faqs/smart-questions.html