Improve --clone-dest.
[rsync-patches.git] / clone-dest.diff
1 This patch adds the --clone-dest option that works like --link-dest but
2 without requiring the metadata of the files to match in order to be able
3 to share the file's data.  This currently only supports Linux's FICLONE
4 syscall, and that may only be supported on Btrfs at the moment.
5
6 This patch has had minor testing, including some fixes that make sure
7 that the attributes of a cloned file get set after it is cloned.
8
9 To use this patch, run these commands for a successful build:
10
11     patch -p1 <patches/clone-dest.diff
12     ./configure                         (optional if already run)
13     make
14
15 based-on: a28c4558c5644d4423a937d025ca64fe4e3ce84b
16 diff --git a/Makefile.in b/Makefile.in
17 --- a/Makefile.in
18 +++ b/Makefile.in
19 @@ -50,7 +50,7 @@ popt_OBJS=popt/findme.o  popt/popt.o  popt/poptconfig.o \
20         popt/popthelp.o popt/poptparse.o
21  OBJS=$(OBJS1) $(OBJS2) $(OBJS3) @SIMD@ $(DAEMON_OBJ) $(LIBOBJ) @BUILD_ZLIB@ @BUILD_POPT@
22  
23 -TLS_OBJ = tls.o syscall.o t_stub.o lib/compat.o lib/snprintf.o lib/permstring.o lib/sysxattrs.o @BUILD_POPT@
24 +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@
25  
26  # Programs we must have to run the test cases
27  CHECK_PROGS = rsync$(EXEEXT) tls$(EXEEXT) getgroups$(EXEEXT) getfsdev$(EXEEXT) \
28 @@ -155,7 +155,7 @@ getgroups$(EXEEXT): getgroups.o
29  getfsdev$(EXEEXT): getfsdev.o
30         $(CC) $(CFLAGS) $(LDFLAGS) -o $@ getfsdev.o $(LIBS)
31  
32 -TRIMSLASH_OBJ = trimslash.o syscall.o t_stub.o lib/compat.o lib/snprintf.o
33 +TRIMSLASH_OBJ = trimslash.o syscall.o util.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o
34  trimslash$(EXEEXT): $(TRIMSLASH_OBJ)
35         $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(TRIMSLASH_OBJ) $(LIBS)
36  
37 diff --git a/generator.c b/generator.c
38 --- a/generator.c
39 +++ b/generator.c
40 @@ -957,7 +957,7 @@ static int try_dests_reg(struct file_struct *file, char *fname, int ndx,
41                         best_match = j;
42                         match_level = 2;
43                 }
44 -               if (unchanged_attrs(cmpbuf, file, sxp)) {
45 +               if (alt_dest_type == CLONE_DEST || unchanged_attrs(cmpbuf, file, sxp)) {
46                         best_match = j;
47                         match_level = 3;
48                         break;
49 @@ -983,9 +983,16 @@ static int try_dests_reg(struct file_struct *file, char *fname, int ndx,
50                                 goto got_nothing_for_ya;
51                 }
52  #ifdef SUPPORT_HARD_LINKS
53 -               if (alt_dest_type == LINK_DEST) {
54 -                       if (!hard_link_one(file, fname, cmpbuf, 1))
55 -                               goto try_a_copy;
56 +               if (alt_dest_type == LINK_DEST || alt_dest_type == CLONE_DEST) {
57 +                       if (alt_dest_type == LINK_DEST) {
58 +                               if (!hard_link_one(file, fname, cmpbuf, 1))
59 +                                       goto try_a_copy;
60 +                       } else if (do_clone(cmpbuf, fname, file->mode) == 0) {
61 +                               finish_transfer(fname, fname, cmpbuf, NULL, file, 1, 0);
62 +                       } else {
63 +                               rsyserr(FERROR_XFER, errno, "failed to clone %s to %s", cmpbuf, fname);
64 +                               exit_cleanup(RERR_UNSUPPORTED);
65 +                       }
66                         if (atimes_ndx)
67                                 set_file_attrs(fname, file, sxp, NULL, 0);
68                         if (preserve_hard_links && F_IS_HLINKED(file))
69 @@ -1099,7 +1106,7 @@ static int try_dests_non(struct file_struct *file, char *fname, int ndx,
70  
71         if (match_level == 3) {
72  #ifdef SUPPORT_HARD_LINKS
73 -               if (alt_dest_type == LINK_DEST
74 +               if ((alt_dest_type == LINK_DEST || alt_dest_type == CLONE_DEST)
75  #ifndef CAN_HARDLINK_SYMLINK
76                  && !S_ISLNK(file->mode)
77  #endif
78 diff --git a/options.c b/options.c
79 --- a/options.c
80 +++ b/options.c
81 @@ -573,7 +573,7 @@ enum {OPT_SERVER = 1000, OPT_DAEMON, OPT_SENDER, OPT_EXCLUDE, OPT_EXCLUDE_FROM,
82        OPT_INCLUDE, OPT_INCLUDE_FROM, OPT_MODIFY_WINDOW, OPT_MIN_SIZE, OPT_CHMOD,
83        OPT_READ_BATCH, OPT_WRITE_BATCH, OPT_ONLY_WRITE_BATCH, OPT_MAX_SIZE,
84        OPT_NO_D, OPT_APPEND, OPT_NO_ICONV, OPT_INFO, OPT_DEBUG, OPT_BLOCK_SIZE,
85 -      OPT_USERMAP, OPT_GROUPMAP, OPT_CHOWN, OPT_BWLIMIT, OPT_STDERR,
86 +      OPT_USERMAP, OPT_GROUPMAP, OPT_CHOWN, OPT_BWLIMIT, OPT_STDERR, OPT_CLONE_DEST,
87        OPT_OLD_COMPRESS, OPT_NEW_COMPRESS, OPT_NO_COMPRESS,
88        OPT_STOP_AFTER, OPT_STOP_AT,
89        OPT_REFUSED_BASE = 9000};
90 @@ -733,6 +733,7 @@ static struct poptOption long_options[] = {
91    {"compare-dest",     0,  POPT_ARG_STRING, 0, OPT_COMPARE_DEST, 0, 0 },
92    {"copy-dest",        0,  POPT_ARG_STRING, 0, OPT_COPY_DEST, 0, 0 },
93    {"link-dest",        0,  POPT_ARG_STRING, 0, OPT_LINK_DEST, 0, 0 },
94 +  {"clone-dest",       0,  POPT_ARG_STRING, 0, OPT_CLONE_DEST, 0, 0 },
95    {"fuzzy",           'y', POPT_ARG_NONE,   0, 'y', 0, 0 },
96    {"no-fuzzy",         0,  POPT_ARG_VAL,    &fuzzy_basis, 0, 0, 0 },
97    {"no-y",             0,  POPT_ARG_VAL,    &fuzzy_basis, 0, 0, 0 },
98 @@ -986,6 +987,9 @@ static void set_refuse_options(void)
99  #ifndef SUPPORT_HARD_LINKS
100         parse_one_refuse_match(0, "link-dest", list_end);
101  #endif
102 +#ifndef FICLONE
103 +       parse_one_refuse_match(0, "clone-dest", list_end);
104 +#endif
105  #ifndef HAVE_MKTIME
106         parse_one_refuse_match(0, "stop-at", list_end);
107  #endif
108 @@ -1315,6 +1319,8 @@ char *alt_dest_opt(int type)
109                 return "--copy-dest";
110         case LINK_DEST:
111                 return "--link-dest";
112 +       case CLONE_DEST:
113 +               return "--clone-dest";
114         default:
115                 NOISY_DEATH("Unknown alt_dest_opt type");
116         }
117 @@ -1685,6 +1691,10 @@ int parse_arguments(int *argc_p, const char ***argv_p)
118                         want_dest_type = LINK_DEST;
119                         goto set_dest_dir;
120  
121 +               case OPT_CLONE_DEST:
122 +                       want_dest_type = CLONE_DEST;
123 +                       goto set_dest_dir;
124 +
125                 case OPT_COPY_DEST:
126                         want_dest_type = COPY_DEST;
127                         goto set_dest_dir;
128 diff --git a/rsync.1.md b/rsync.1.md
129 --- a/rsync.1.md
130 +++ b/rsync.1.md
131 @@ -424,6 +424,7 @@ detailed description below for a complete description.
132  --compare-dest=DIR       also compare destination files relative to DIR
133  --copy-dest=DIR          ... and include copies of unchanged files
134  --link-dest=DIR          hardlink to files in DIR when unchanged
135 +--clone-dest=DIR         clone (reflink) files from DIR when unchanged
136  --compress, -z           compress file data during the transfer
137  --compress-choice=STR    choose the compression algorithm (aka --zc)
138  --compress-level=NUM     explicitly set compression level (aka --zl)
139 @@ -2343,6 +2344,17 @@ your home directory (remove the '=' for that).
140      specified (or implied by `-a`).  You can work-around this bug by avoiding
141      the `-o` option when sending to an old rsync.
142  
143 +0.  `--clone-dest=DIR`
144 +
145 +    This option behaves like `--link-dest`, but unchanged files are reflinked
146 +    from _DIR_ to the destination directory.  The files do not need to match
147 +    in attributes, as the data is cloned separately from the attributes.
148 +
149 +    If _DIR_ is a relative path, it is relative to the destination directory.
150 +    See also `--compare-dest` and `--copy-dest`.
151 +
152 +    All non-regular files are hard-linked (when possible).
153 +
154  0.  `--compress`, `-z`
155  
156      With this option, rsync compresses the file data as it is sent to the
157 diff --git a/rsync.h b/rsync.h
158 --- a/rsync.h
159 +++ b/rsync.h
160 @@ -175,6 +175,11 @@
161  #define COMPARE_DEST 1
162  #define COPY_DEST 2
163  #define LINK_DEST 3
164 +#define CLONE_DEST 4
165 +
166 +#if !defined FICLONE && defined __linux__
167 +#define FICLONE _IOW(0x94, 9, int)
168 +#endif
169  
170  #define MPLEX_BASE 7
171  
172 diff --git a/syscall.c b/syscall.c
173 --- a/syscall.c
174 +++ b/syscall.c
175 @@ -142,6 +142,54 @@ int do_link(const char *old_path, const char *new_path)
176  }
177  #endif
178  
179 +int do_clone(const char *old_path, const char *new_path, mode_t mode)
180 +{
181 +#ifdef FICLONE
182 +       int ifd, ofd, ret, save_errno;
183 +
184 +       if (dry_run) return 0;
185 +       RETURN_ERROR_IF_RO_OR_LO;
186 +
187 +       if ((ifd = do_open(old_path, O_RDONLY, 0)) < 0) {
188 +               save_errno = errno;
189 +               rsyserr(FERROR_XFER, errno, "open %s", full_fname(old_path));
190 +               errno = save_errno;
191 +               return -1;
192 +       }
193 +
194 +       if (robust_unlink(new_path) && errno != ENOENT) {
195 +               save_errno = errno;
196 +               rsyserr(FERROR_XFER, errno, "unlink %s", full_fname(new_path));
197 +               close(ifd);
198 +               errno = save_errno;
199 +               return -1;
200 +       }
201 +
202 +       mode &= INITACCESSPERMS;
203 +       if ((ofd = do_open(new_path, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode)) < 0) {
204 +               save_errno = errno;
205 +               rsyserr(FERROR_XFER, save_errno, "open %s", full_fname(new_path));
206 +               close(ifd);
207 +               errno = save_errno;
208 +               return -1;
209 +       }
210 +
211 +       ret = ioctl(ofd, FICLONE, ifd);
212 +       save_errno = errno;
213 +       close(ifd);
214 +       close(ofd);
215 +       if (ret < 0)
216 +               unlink(new_path);
217 +       errno = save_errno;
218 +       return ret;
219 +#else
220 +       (void)old_path;
221 +       (void)new_path;
222 +       errno = ENOTSUP;
223 +       return -1;
224 +#endif
225 +}
226 +
227  int do_lchown(const char *path, uid_t owner, gid_t group)
228  {
229         if (dry_run) return 0;
230 diff --git a/t_stub.c b/t_stub.c
231 --- a/t_stub.c
232 +++ b/t_stub.c
233 @@ -37,6 +37,7 @@ size_t max_alloc = 0; /* max_alloc is needed when combined with util2.o */
234  char *partial_dir;
235  char *module_dir;
236  filter_rule_list daemon_filter_list;
237 +short info_levels[COUNT_INFO], debug_levels[COUNT_DEBUG];
238  
239   void rprintf(UNUSED(enum logcode code), const char *format, ...)
240  {
241 diff --git a/t_unsafe.c b/t_unsafe.c
242 --- a/t_unsafe.c
243 +++ b/t_unsafe.c
244 @@ -28,7 +28,6 @@ int am_root = 0;
245  int am_sender = 1;
246  int read_only = 0;
247  int list_only = 0;
248 -short info_levels[COUNT_INFO], debug_levels[COUNT_DEBUG];
249  
250  int
251  main(int argc, char **argv)