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