1 This patch adds the --clone-dest option that works link --link-dest
2 but without requiring the metadata of the files to match in order
3 to be able to share the file's data.
5 NOTE: this patch is mostly untested because I don't currently have
6 a btrfs mount to test it out on. I still need to make sure that a
7 cloned file gets its destination attributes set correctly after the
10 To use this patch, run these commands for a successful build:
12 patch -p1 <patches/clone-dest.diff
13 ./configure (optional if already run)
16 based-on: af531cf787995f6a3bc381cd1da1988192e7ef59
17 diff --git a/Makefile.in b/Makefile.in
20 @@ -50,7 +50,7 @@ popt_OBJS=popt/findme.o popt/popt.o popt/poptconfig.o \
21 popt/popthelp.o popt/poptparse.o
22 OBJS=$(OBJS1) $(OBJS2) $(OBJS3) @SIMD@ $(DAEMON_OBJ) $(LIBOBJ) @BUILD_ZLIB@ @BUILD_POPT@
24 -TLS_OBJ = tls.o syscall.o t_stub.o lib/compat.o lib/snprintf.o lib/permstring.o lib/sysxattrs.o @BUILD_POPT@
25 +TLS_OBJ = tls.o syscall.o util.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/permstring.o lib/sysxattrs.o lib/wildmatch.o @BUILD_POPT@
27 # Programs we must have to run the test cases
28 CHECK_PROGS = rsync$(EXEEXT) tls$(EXEEXT) getgroups$(EXEEXT) getfsdev$(EXEEXT) \
29 @@ -150,7 +150,7 @@ getgroups$(EXEEXT): getgroups.o
30 getfsdev$(EXEEXT): getfsdev.o
31 $(CC) $(CFLAGS) $(LDFLAGS) -o $@ getfsdev.o $(LIBS)
33 -TRIMSLASH_OBJ = trimslash.o syscall.o t_stub.o lib/compat.o lib/snprintf.o
34 +TRIMSLASH_OBJ = trimslash.o syscall.o util.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o
35 trimslash$(EXEEXT): $(TRIMSLASH_OBJ)
36 $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(TRIMSLASH_OBJ) $(LIBS)
38 diff --git a/generator.c b/generator.c
41 @@ -892,7 +892,7 @@ static int try_dests_reg(struct file_struct *file, char *fname, int ndx,
45 - if (unchanged_attrs(cmpbuf, file, sxp)) {
46 + if (alt_dest_type == CLONE_DEST || unchanged_attrs(cmpbuf, file, sxp)) {
50 @@ -932,7 +932,12 @@ static int try_dests_reg(struct file_struct *file, char *fname, int ndx,
55 + if (alt_dest_type == CLONE_DEST) {
56 + if (do_clone(cmpbuf, fname, file->mode) < 0) {
57 + rsyserr(FERROR_XFER, errno, "failed to clone %s to %s", cmpbuf, fname);
58 + exit_cleanup(RERR_UNSUPPORTED);
62 itemize(cmpbuf, file, ndx, 0, sxp, 0, 0, NULL);
64 @@ -1088,7 +1093,7 @@ static int try_dests_non(struct file_struct *file, char *fname, int ndx,
66 if (match_level == 3) {
67 #ifdef SUPPORT_HARD_LINKS
68 - if (alt_dest_type == LINK_DEST
69 + if ((alt_dest_type == LINK_DEST || alt_dest_type == CLONE_DEST)
70 #ifndef CAN_HARDLINK_SYMLINK
71 && !S_ISLNK(file->mode)
73 diff --git a/options.c b/options.c
76 @@ -783,7 +783,7 @@ enum {OPT_SERVER = 1000, OPT_DAEMON, OPT_SENDER, OPT_EXCLUDE, OPT_EXCLUDE_FROM,
77 OPT_INCLUDE, OPT_INCLUDE_FROM, OPT_MODIFY_WINDOW, OPT_MIN_SIZE, OPT_CHMOD,
78 OPT_READ_BATCH, OPT_WRITE_BATCH, OPT_ONLY_WRITE_BATCH, OPT_MAX_SIZE,
79 OPT_NO_D, OPT_APPEND, OPT_NO_ICONV, OPT_INFO, OPT_DEBUG, OPT_BLOCK_SIZE,
80 - OPT_USERMAP, OPT_GROUPMAP, OPT_CHOWN, OPT_BWLIMIT,
81 + OPT_USERMAP, OPT_GROUPMAP, OPT_CHOWN, OPT_BWLIMIT, OPT_CLONE_DEST,
82 OPT_OLD_COMPRESS, OPT_NEW_COMPRESS, OPT_NO_COMPRESS,
83 OPT_STOP_AFTER, OPT_STOP_AT,
84 OPT_REFUSED_BASE = 9000};
85 @@ -939,6 +939,7 @@ static struct poptOption long_options[] = {
86 {"compare-dest", 0, POPT_ARG_STRING, 0, OPT_COMPARE_DEST, 0, 0 },
87 {"copy-dest", 0, POPT_ARG_STRING, 0, OPT_COPY_DEST, 0, 0 },
88 {"link-dest", 0, POPT_ARG_STRING, 0, OPT_LINK_DEST, 0, 0 },
89 + {"clone-dest", 0, POPT_ARG_STRING, 0, OPT_CLONE_DEST, 0, 0 },
90 {"fuzzy", 'y', POPT_ARG_NONE, 0, 'y', 0, 0 },
91 {"no-fuzzy", 0, POPT_ARG_VAL, &fuzzy_basis, 0, 0, 0 },
92 {"no-y", 0, POPT_ARG_VAL, &fuzzy_basis, 0, 0, 0 },
93 @@ -1202,6 +1203,9 @@ static void set_refuse_options(void)
94 #ifndef SUPPORT_HARD_LINKS
95 parse_one_refuse_match(0, "link-dest", list_end);
98 + parse_one_refuse_match(0, "clone-dest", list_end);
101 parse_one_refuse_match(0, "stop-at", list_end);
103 @@ -1528,6 +1532,8 @@ char *alt_dest_opt(int type)
104 return "--copy-dest";
106 return "--link-dest";
108 + return "--clone-dest";
110 NOISY_DEATH("Unknown alt_dest_opt type");
112 @@ -1898,6 +1904,10 @@ int parse_arguments(int *argc_p, const char ***argv_p)
113 want_dest_type = LINK_DEST;
116 + case OPT_CLONE_DEST:
117 + want_dest_type = CLONE_DEST;
121 want_dest_type = COPY_DEST;
123 diff --git a/rsync.1.md b/rsync.1.md
126 @@ -422,6 +422,7 @@ detailed description below for a complete description.
127 --compare-dest=DIR also compare destination files relative to DIR
128 --copy-dest=DIR ... and include copies of unchanged files
129 --link-dest=DIR hardlink to files in DIR when unchanged
130 +--clone-dest=DIR clone (reflink) files from DIR when unchanged
131 --compress, -z compress file data during the transfer
132 --compress-choice=STR choose the compression algorithm (aka --zc)
133 --compress-level=NUM explicitly set compression level (aka --zl)
134 @@ -2290,6 +2291,17 @@ your home directory (remove the '=' for that).
135 specified (or implied by `-a`). You can work-around this bug by avoiding
136 the `-o` option when sending to an old rsync.
138 +0. `--clone-dest=DIR`
140 + This option behaves like `--link-dest`, but unchanged files are reflinked
141 + from _DIR_ to the destination directory. The files do not need to match
142 + in attributes, as the data is cloned separately from the attributes.
144 + If _DIR_ is a relative path, it is relative to the destination directory.
145 + See also `--compare-dest` and `--copy-dest`.
147 + All non-regular files are hard-linked (when possible).
149 0. `--compress`, `-z`
151 With this option, rsync compresses the file data as it is sent to the
152 diff --git a/rsync.h b/rsync.h
156 #define COMPARE_DEST 1
159 +#define CLONE_DEST 4
161 +#if !defined FICLONE && defined __linux__
162 +#define FICLONE _IOW(0x94, 9, int)
167 diff --git a/syscall.c b/syscall.c
170 @@ -129,6 +129,54 @@ int do_link(const char *old_path, const char *new_path)
174 +int do_clone(const char *old_path, const char *new_path, mode_t mode)
177 + int ifd, ofd, ret, save_errno;
179 + if (dry_run) return 0;
180 + RETURN_ERROR_IF_RO_OR_LO;
182 + if ((ifd = do_open(old_path, O_RDONLY, 0)) < 0) {
183 + save_errno = errno;
184 + rsyserr(FERROR_XFER, errno, "open %s", full_fname(old_path));
185 + errno = save_errno;
189 + if (robust_unlink(new_path) && errno != ENOENT) {
190 + save_errno = errno;
191 + rsyserr(FERROR_XFER, errno, "unlink %s", full_fname(new_path));
193 + errno = save_errno;
197 + mode &= INITACCESSPERMS;
198 + if ((ofd = do_open(new_path, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode)) < 0) {
199 + save_errno = errno;
200 + rsyserr(FERROR_XFER, save_errno, "open %s", full_fname(new_path));
202 + errno = save_errno;
206 + ret = ioctl(ofd, FICLONE, ifd);
207 + save_errno = errno;
212 + errno = save_errno;
222 int do_lchown(const char *path, uid_t owner, gid_t group)
224 if (dry_run) return 0;
225 diff --git a/t_stub.c b/t_stub.c
228 @@ -37,6 +37,7 @@ size_t max_alloc = 0; /* max_alloc is needed when combined with util2.o */
231 filter_rule_list daemon_filter_list;
232 +short info_levels[COUNT_INFO], debug_levels[COUNT_DEBUG];
234 void rprintf(UNUSED(enum logcode code), const char *format, ...)
236 diff --git a/t_unsafe.c b/t_unsafe.c
239 @@ -28,7 +28,6 @@ int am_root = 0;
243 -short info_levels[COUNT_INFO], debug_levels[COUNT_DEBUG];
246 main(int argc, char **argv)