Fixed the printf in the support/rsyncsums perl script that can maintain
[rsync/rsync-patches.git] / checksum-updating.diff
CommitLineData
ae10e51e 1This adds a sender optimization feature that allows a cache of checksums
56522462
WD
2to be used when the client specifies the --checksum option, and creates
3and/or updates the .rsyncsums files when --checksum-updating is
4specified.
ae10e51e
WD
5
6To use this patch, run these commands for a successful build:
7
8 patch -p1 <patches/checksum-updating.diff
9 ./configure (optional if already run)
10 make
11
12TODO: when sending individual files (as opposed to an entire directory),
56522462
WD
13we should still update the .rsyncsums file if we compute a new checksum.
14(The file is currently only written if we send an entire dir.)
ae10e51e
WD
15
16--- old/clientserver.c
17+++ new/clientserver.c
18@@ -37,6 +37,7 @@ extern int sanitize_paths;
19 extern int filesfrom_fd;
20 extern int remote_protocol;
21 extern int protocol_version;
22+extern int checksum_updating;
23 extern int io_timeout;
24 extern int no_detach;
25 extern int default_af_hint;
26@@ -634,6 +635,8 @@ static int rsync_module(int f_in, int f_
27 else if (am_root < 0) /* Treat --fake-super from client as --super. */
28 am_root = 2;
29
30+ checksum_updating = lp_checksum_updating(i);
31+
32 if (filesfrom_fd == 0)
33 filesfrom_fd = f_in;
34
35--- old/flist.c
36+++ new/flist.c
37@@ -25,6 +25,7 @@
38 #include "io.h"
39
40 extern int verbose;
41+extern int dry_run;
42 extern int list_only;
43 extern int am_root;
44 extern int am_server;
45@@ -57,6 +58,7 @@ extern int implied_dirs;
46 extern int file_extra_cnt;
47 extern int ignore_perishable;
48 extern int non_perishable_cnt;
49+extern int checksum_updating;
50 extern int prune_empty_dirs;
51 extern int copy_links;
52 extern int copy_unsafe_links;
56522462
WD
53@@ -79,6 +81,9 @@ extern iconv_t ic_send, ic_recv;
54
55 #define PTR_SIZE (sizeof (struct file_struct *))
56
57+#define FLAG_SUM_MISSING (1<<1)
58+#define FLAG_SUM_FOUND (1<<2)
59+
60 int io_error;
61 int checksum_len;
62 dev_t filesystem_dev; /* used to implement -x */
7200c744 63@@ -101,6 +106,9 @@ static char tmp_sum[MAX_DIGEST_LEN];
ae10e51e
WD
64 static char empty_sum[MAX_DIGEST_LEN];
65 static int flist_count_offset; /* for --delete --progress */
66 static int dir_count = 0;
67+static struct file_list *checksum_flist = NULL;
68+static int checksum_matches = 0;
7200c744 69+static int regular_skipped = 0;
ae10e51e
WD
70
71 static void clean_flist(struct file_list *flist, int strip_root);
72 static void output_flist(struct file_list *flist);
7200c744 73@@ -317,6 +325,275 @@ static void flist_done_allocating(struct
ae10e51e
WD
74 flist->pool_boundary = ptr;
75 }
76
77+/* The len count is the length of the basename + 1 for the null. */
78+static void add_checksum(const char *dirname, const char *basename, int len,
7200c744
WD
79+ OFF_T file_length, time_t mtime, time_t ctime,
80+ const char *sum, const char *alt_sum, int flags)
ae10e51e
WD
81+{
82+ struct file_struct *file;
83+ int alloc_len, extra_len;
84+ char *bp;
85+
56522462 86+ if (len == 10+1 && *basename == '.' && strcmp(basename, ".rsyncsums") == 0)
ae10e51e
WD
87+ return;
88+
89+ if (len < 0)
90+ len = strlen(basename) + 1;
91+
7200c744
WD
92+ extra_len = (file_extra_cnt + (file_length > 0xFFFFFFFFu)
93+ + SUM_EXTRA_CNT + TIME_EXTRA_CNT)
94+ * EXTRA_LEN;
ae10e51e
WD
95+#if EXTRA_ROUNDING > 0
96+ if (extra_len & (EXTRA_ROUNDING * EXTRA_LEN))
97+ extra_len = (extra_len | (EXTRA_ROUNDING * EXTRA_LEN)) + EXTRA_LEN;
98+#endif
56522462 99+ alloc_len = FILE_STRUCT_LEN + extra_len + len + checksum_len*2 + 1;
ae10e51e
WD
100+ bp = pool_alloc(checksum_flist->file_pool, alloc_len, "add_checksum");
101+
102+ memset(bp, 0, extra_len + FILE_STRUCT_LEN);
103+ bp += extra_len;
104+ file = (struct file_struct *)bp;
105+ bp += FILE_STRUCT_LEN;
106+
107+ memcpy(bp, basename, len);
56522462
WD
108+ if (alt_sum)
109+ strlcpy(bp+len, alt_sum, checksum_len*2 + 1);
110+ else {
111+ memset(bp+len, '=', checksum_len*2);
112+ bp[len+checksum_len*2] = '\0';
113+ }
ae10e51e
WD
114+
115+ file->flags = flags;
116+ file->mode = S_IFREG;
117+ file->modtime = mtime;
118+ file->len32 = (uint32)file_length;
119+ if (file_length > 0xFFFFFFFFu) {
120+ file->flags |= FLAG_LENGTH64;
121+ OPT_EXTRA(file, 0)->unum = (uint32)(file_length >> 32);
122+ }
123+ file->dirname = dirname;
124+ bp = (char*)F_SUM(file);
125+ memcpy(bp, sum, checksum_len);
7200c744 126+ memcpy(bp - SIZEOF_TIME_T, &ctime, SIZEOF_TIME_T);
ae10e51e
WD
127+
128+ flist_expand(checksum_flist, 1);
129+ checksum_flist->files[checksum_flist->count++] = file;
130+
131+ checksum_flist->sorted = checksum_flist->files;
132+}
133+
134+/* The direname value must remain unchanged during the lifespan of the
135+ * created checksum_flist object because we use it directly. */
136+static void read_checksums(const char *dirname)
137+{
138+ char line[MAXPATHLEN+1024], fbuf[MAXPATHLEN], sum[MAX_DIGEST_LEN];
56522462 139+ const char *alt_sum = NULL;
ae10e51e 140+ OFF_T file_length;
7200c744 141+ time_t mtime, ctime;
ae10e51e
WD
142+ int len, dlen, i, flags;
143+ char *cp;
144+ FILE *fp;
145+
146+ if (checksum_flist) {
147+ /* Reset the pool memory and empty the file-list array. */
148+ pool_free_old(checksum_flist->file_pool,
149+ pool_boundary(checksum_flist->file_pool, 0));
150+ checksum_flist->count = 0;
151+ } else
152+ checksum_flist = flist_new(FLIST_TEMP, "read_checksums");
153+
154+ checksum_flist->low = 0;
155+ checksum_flist->high = -1;
156+ checksum_matches = 0;
7200c744 157+ regular_skipped = 0;
ae10e51e 158+
ae10e51e
WD
159+ if (dirname) {
160+ dlen = strlcpy(fbuf, dirname, sizeof fbuf);
161+ if (dlen >= (int)sizeof fbuf)
162+ return;
163+ fbuf[dlen++] = '/';
164+ } else
165+ dlen = 0;
56522462 166+ strlcpy(fbuf+dlen, ".rsyncsums", sizeof fbuf - dlen);
ae10e51e
WD
167+ if (!(fp = fopen(fbuf, "r")))
168+ return;
169+
170+ while (fgets(line, sizeof line, fp)) {
56522462
WD
171+ cp = line;
172+ if (protocol_version >= 30) {
173+ alt_sum = cp;
174+ if (*cp == '=')
175+ while (*++cp == '=') {}
176+ else
177+ while (isXDigit(cp)) cp++;
178+ if (cp - alt_sum != MD4_DIGEST_LEN*2 || *cp != ' ')
179+ break;
180+ while (*++cp == ' ') {}
181+ }
182+
183+ if (*cp == '=') {
184+ for (i = 0; i < checksum_len*2; i++, cp++) {
185+ if (*cp != '=') {
ae10e51e
WD
186+ cp = "";
187+ break;
188+ }
ae10e51e 189+ }
56522462
WD
190+ memset(sum, 0, checksum_len);
191+ flags = FLAG_SUM_MISSING;
192+ } else {
193+ for (i = 0; i < checksum_len*2; i++, cp++) {
194+ int x;
195+ if (isXDigit(cp)) {
196+ if (isDigit(cp))
197+ x = *cp - '0';
198+ else
199+ x = (*cp & 0xF) + 9;
200+ } else {
201+ cp = "";
202+ break;
203+ }
204+ if (i & 1)
205+ sum[i/2] |= x;
206+ else
207+ sum[i/2] = x << 4;
208+ }
209+ flags = 0;
ae10e51e 210+ }
ae10e51e 211+ if (*cp != ' ')
56522462 212+ break;
ae10e51e
WD
213+ while (*++cp == ' ') {}
214+
56522462
WD
215+ if (protocol_version < 30) {
216+ alt_sum = cp;
217+ if (*cp == '=')
218+ while (*++cp == '=') {}
219+ else
220+ while (isXDigit(cp)) cp++;
221+ if (cp - alt_sum != MD5_DIGEST_LEN*2 || *cp != ' ')
222+ break;
223+ while (*++cp == ' ') {}
224+ }
225+
ae10e51e
WD
226+ file_length = 0;
227+ while (isDigit(cp))
228+ file_length = file_length * 10 + *cp++ - '0';
ae10e51e 229+ if (*cp != ' ')
56522462 230+ break;
ae10e51e
WD
231+ while (*++cp == ' ') {}
232+
233+ mtime = 0;
234+ while (isDigit(cp))
235+ mtime = mtime * 10 + *cp++ - '0';
ae10e51e 236+ if (*cp != ' ')
56522462 237+ break;
ae10e51e
WD
238+ while (*++cp == ' ') {}
239+
7200c744
WD
240+ ctime = 0;
241+ while (isDigit(cp))
242+ ctime = ctime * 10 + *cp++ - '0';
243+ if (*cp != ' ')
244+ break;
245+ while (*++cp == ' ') {}
246+
ae10e51e
WD
247+ len = strlen(cp);
248+ while (len && (cp[len-1] == '\n' || cp[len-1] == '\r'))
249+ len--;
250+ if (!len)
56522462 251+ break;
ae10e51e
WD
252+ cp[len++] = '\0'; /* len now counts the null */
253+ if (strchr(cp, '/') || len > MAXPATHLEN)
56522462 254+ break;
ae10e51e
WD
255+
256+ strlcpy(fbuf+dlen, cp, sizeof fbuf - dlen);
257+ if (is_excluded(fbuf, 0, ALL_FILTERS)) {
56522462 258+ flags |= FLAG_SUM_FOUND;
ae10e51e 259+ checksum_matches++;
56522462 260+ }
ae10e51e 261+
7200c744 262+ add_checksum(dirname, cp, len, file_length, mtime, ctime,
56522462 263+ sum, alt_sum, flags);
ae10e51e
WD
264+ }
265+ fclose(fp);
266+
267+ clean_flist(checksum_flist, 0);
268+}
269+
270+static void write_checksums(const char *dirname)
271+{
7200c744 272+ char fbuf[MAXPATHLEN];
56522462
WD
273+ int count = checksum_flist->count;
274+ int new_entries = count > checksum_flist->high + 1;
7200c744
WD
275+ int counts_match = count == checksum_matches;
276+ int no_skipped = regular_skipped == 0;
277+ time_t latest_time = 0;
ae10e51e
WD
278+ FILE *out_fp;
279+ int i;
280+
ae10e51e 281+ clean_flist(checksum_flist, 0);
7200c744 282+
56522462
WD
283+ checksum_flist->count = 0;
284+ checksum_matches = 0;
7200c744 285+ regular_skipped = 0;
56522462
WD
286+
287+ if (dry_run)
288+ return;
ae10e51e 289+
ae10e51e 290+ if (dirname) {
7200c744 291+ if (pathjoin(fbuf, sizeof fbuf, dirname, ".rsyncsums") >= sizeof fbuf)
ae10e51e
WD
292+ return;
293+ } else
7200c744 294+ strlcpy(fbuf, ".rsyncsums", sizeof fbuf);
ae10e51e 295+
7200c744
WD
296+ if (checksum_flist->high - checksum_flist->low < 0 && no_skipped) {
297+ unlink(fbuf);
ae10e51e
WD
298+ return;
299+ }
300+
7200c744 301+ if (!new_entries && counts_match)
ae10e51e
WD
302+ return;
303+
7200c744 304+ if (!(out_fp = fopen(fbuf, "w")))
ae10e51e
WD
305+ return;
306+
307+ for (i = checksum_flist->low; i <= checksum_flist->high; i++) {
308+ struct file_struct *file = checksum_flist->sorted[i];
309+ const char *cp = F_SUM(file);
310+ const char *end = cp + checksum_len;
7200c744 311+ time_t ctime;
56522462 312+ if (!(file->flags & FLAG_SUM_FOUND))
ae10e51e 313+ continue;
7200c744 314+ memcpy(&ctime, cp - SIZEOF_TIME_T, SIZEOF_TIME_T);
56522462
WD
315+ if (protocol_version >= 30) {
316+ fprintf(out_fp, "%s ",
317+ file->basename + strlen(file->basename) + 1);
318+ }
319+ if (file->flags & FLAG_SUM_MISSING) {
320+ do {
321+ fprintf(out_fp, "==");
322+ } while (++cp != end);
323+ } else {
324+ do {
325+ fprintf(out_fp, "%02x", CVAL(cp, 0));
326+ } while (++cp != end);
327+ }
328+ if (protocol_version < 30) {
329+ fprintf(out_fp, " %s",
330+ file->basename + strlen(file->basename) + 1);
331+ }
7200c744 332+ fprintf(out_fp, " %10.0f %10ld %10ld %s\n",
ae10e51e 333+ (double)F_LENGTH(file), (long)file->modtime,
7200c744
WD
334+ (long)ctime, file->basename);
335+ if (file->modtime > ctime)
336+ ctime = file->modtime;
337+ if (ctime > latest_time)
338+ latest_time = ctime;
ae10e51e
WD
339+ }
340+
341+ fclose(out_fp);
7200c744
WD
342+
343+ set_modtime(fbuf, latest_time, latest_time);
ae10e51e
WD
344+}
345+
346 int push_pathname(const char *dir, int len)
347 {
348 if (dir == pathname)
7200c744 349@@ -973,34 +1250,24 @@ static struct file_struct *recv_file_ent
ae10e51e
WD
350 return file;
351 }
352
353-/**
354- * Create a file_struct for a named file by reading its stat()
355- * information and performing extensive checks against global
356- * options.
357- *
358- * @return the new file, or NULL if there was an error or this file
359- * should be excluded.
360+/* Create a file_struct for a named file by reading its stat() information
361+ * and performing extensive checks against global options.
362 *
363- * @todo There is a small optimization opportunity here to avoid
364- * stat()ing the file in some circumstances, which has a certain cost.
365- * We are called immediately after doing readdir(), and so we may
366- * already know the d_type of the file. We could for example avoid
367- * statting directories if we're not recursing, but this is not a very
368- * important case. Some systems may not have d_type.
369- **/
370+ * Returns a pointer to the new file struct, or NULL if there was an error
371+ * or this file should be excluded. */
372 struct file_struct *make_file(const char *fname, struct file_list *flist,
373 STRUCT_STAT *stp, int flags, int filter_level)
374 {
375 static char *lastdir;
376- static int lastdir_len = -1;
377+ static int lastdir_len = -2;
378 struct file_struct *file;
379- STRUCT_STAT st;
380 char thisname[MAXPATHLEN];
381 char linkname[MAXPATHLEN];
382 int alloc_len, basename_len, linkname_len;
383 int extra_len = file_extra_cnt * EXTRA_LEN;
384 const char *basename;
385 alloc_pool_t *pool;
386+ STRUCT_STAT st;
387 char *bp;
388
389 if (strlcpy(thisname, fname, sizeof thisname)
7200c744
WD
390@@ -1077,6 +1344,8 @@ struct file_struct *make_file(const char
391 if (is_excluded(thisname, S_ISDIR(st.st_mode) != 0, filter_level)) {
392 if (ignore_perishable)
393 non_perishable_cnt++;
394+ if (S_ISREG(st.st_mode))
395+ regular_skipped++;
396 return NULL;
397 }
398
399@@ -1115,9 +1384,16 @@ struct file_struct *make_file(const char
ae10e51e
WD
400 memcpy(lastdir, thisname, len);
401 lastdir[len] = '\0';
402 lastdir_len = len;
403+ if (always_checksum && am_sender && flist)
404+ read_checksums(lastdir);
405 }
406- } else
407+ } else {
408 basename = thisname;
409+ if (always_checksum && am_sender && flist && lastdir_len == -2) {
410+ lastdir_len = -1;
411+ read_checksums(NULL);
412+ }
413+ }
414 basename_len = strlen(basename) + 1; /* count the '\0' */
415
416 #ifdef SUPPORT_LINKS
7200c744 417@@ -1193,11 +1469,40 @@ struct file_struct *make_file(const char
ae10e51e
WD
418 }
419 #endif
420
421- if (always_checksum && am_sender && S_ISREG(st.st_mode))
422- file_checksum(thisname, tmp_sum, st.st_size);
423-
424 F_PATHNAME(file) = pathname;
425
426+ if (always_checksum && am_sender && S_ISREG(st.st_mode)) {
427+ int j;
428+ if (flist && (j = flist_find(checksum_flist, file)) >= 0) {
429+ struct file_struct *fp = checksum_flist->sorted[j];
7200c744
WD
430+ time_t ctime;
431+ memcpy(&ctime, F_SUM(fp) - SIZEOF_TIME_T, SIZEOF_TIME_T);
432+ if (F_LENGTH(fp) == st.st_size
433+ && fp->modtime == st.st_mtime && ctime == st.st_ctime) {
56522462
WD
434+ if (fp->flags & FLAG_SUM_MISSING) {
435+ fp->flags &= ~FLAG_SUM_MISSING;
436+ file_checksum(thisname, tmp_sum, st.st_size);
437+ memcpy((char*)F_SUM(fp), tmp_sum, MAX_DIGEST_LEN);
438+ } else {
439+ checksum_matches++;
440+ memcpy(tmp_sum, F_SUM(fp), MAX_DIGEST_LEN);
441+ }
442+ fp->flags |= FLAG_SUM_FOUND;
ae10e51e
WD
443+ } else {
444+ clear_file(fp);
445+ goto compute_checksum;
446+ }
447+ } else {
448+ compute_checksum:
449+ file_checksum(thisname, tmp_sum, st.st_size);
450+ if (checksum_updating && flist) {
451+ add_checksum(file->dirname, basename, basename_len,
7200c744
WD
452+ st.st_size, st.st_mtime, st.st_ctime,
453+ tmp_sum, NULL, FLAG_SUM_FOUND);
ae10e51e
WD
454+ }
455+ }
456+ }
457+
458 /* This code is only used by the receiver when it is building
459 * a list of files for a delete pass. */
460 if (keep_dirlinks && linkname_len && flist) {
7200c744 461@@ -1241,14 +1546,14 @@ void unmake_file(struct file_struct *fil
ae10e51e
WD
462
463 static struct file_struct *send_file_name(int f, struct file_list *flist,
464 char *fname, STRUCT_STAT *stp,
465- int flags, int filter_flags)
466+ int flags, int filter_level)
467 {
468 struct file_struct *file;
469 #if defined SUPPORT_ACLS || defined SUPPORT_XATTRS
470 statx sx;
471 #endif
472
473- file = make_file(fname, flist, stp, flags, filter_flags);
474+ file = make_file(fname, flist, stp, flags, filter_level);
475 if (!file)
476 return NULL;
477
7200c744 478@@ -1442,7 +1747,7 @@ static void send_directory(int f, struct
ae10e51e
WD
479 DIR *d;
480 int divert_dirs = (flags & FLAG_DIVERT_DIRS) != 0;
481 int start = flist->count;
482- int filter_flags = f == -2 ? SERVER_FILTERS : ALL_FILTERS;
483+ int filter_level = f == -2 ? SERVER_FILTERS : ALL_FILTERS;
484
485 assert(flist != NULL);
486
7200c744 487@@ -1471,7 +1776,7 @@ static void send_directory(int f, struct
ae10e51e
WD
488 continue;
489 }
490
491- send_file_name(f, flist, fbuf, NULL, flags, filter_flags);
492+ send_file_name(f, flist, fbuf, NULL, flags, filter_level);
493 }
494
495 fbuf[len] = '\0';
7200c744 496@@ -1483,6 +1788,9 @@ static void send_directory(int f, struct
ae10e51e
WD
497
498 closedir(d);
499
500+ if (checksum_updating && always_checksum && am_sender && f >= 0)
501+ write_checksums(fbuf);
502+
503 if (f >= 0 && recurse && !divert_dirs) {
504 int i, end = flist->count - 1;
505 /* send_if_directory() bumps flist->count, so use "end". */
7200c744 506@@ -2206,7 +2514,7 @@ void flist_free(struct file_list *flist)
ae10e51e
WD
507
508 if (!flist->prev || !flist_cnt)
509 pool_destroy(flist->file_pool);
510- else
511+ else if (flist->pool_boundary)
512 pool_free_old(flist->file_pool, flist->pool_boundary);
513
514 if (flist->sorted && flist->sorted != flist->files)
7200c744 515@@ -2225,6 +2533,7 @@ static void clean_flist(struct file_list
ae10e51e
WD
516 if (!flist)
517 return;
518 if (flist->count == 0) {
519+ flist->low = 0;
520 flist->high = -1;
521 return;
522 }
523--- old/loadparm.c
524+++ new/loadparm.c
525@@ -149,6 +149,7 @@ typedef struct
526 int syslog_facility;
527 int timeout;
528
529+ BOOL checksum_updating;
530 BOOL fake_super;
531 BOOL ignore_errors;
532 BOOL ignore_nonreadable;
533@@ -197,6 +198,7 @@ static service sDefault =
534 /* syslog_facility; */ LOG_DAEMON,
535 /* timeout; */ 0,
536
537+ /* checksum_updating; */ False,
538 /* fake_super; */ False,
539 /* ignore_errors; */ False,
540 /* ignore_nonreadable; */ False,
541@@ -313,6 +315,7 @@ static struct parm_struct parm_table[] =
542 {"lock file", P_STRING, P_LOCAL, &sDefault.lock_file, NULL,0},
543 {"log file", P_STRING, P_LOCAL, &sDefault.log_file, NULL,0},
544 {"log format", P_STRING, P_LOCAL, &sDefault.log_format, NULL,0},
545+ {"checksum updating", P_BOOL, P_LOCAL, &sDefault.checksum_updating, NULL,0},
546 {"max connections", P_INTEGER,P_LOCAL, &sDefault.max_connections, NULL,0},
547 {"max verbosity", P_INTEGER,P_LOCAL, &sDefault.max_verbosity, NULL,0},
548 {"name", P_STRING, P_LOCAL, &sDefault.name, NULL,0},
549@@ -418,6 +421,7 @@ FN_LOCAL_BOOL(lp_fake_super, fake_super)
550 FN_LOCAL_BOOL(lp_ignore_errors, ignore_errors)
551 FN_LOCAL_BOOL(lp_ignore_nonreadable, ignore_nonreadable)
552 FN_LOCAL_BOOL(lp_list, list)
553+FN_LOCAL_BOOL(lp_checksum_updating, checksum_updating)
554 FN_LOCAL_BOOL(lp_read_only, read_only)
555 FN_LOCAL_BOOL(lp_strict_modes, strict_modes)
556 FN_LOCAL_BOOL(lp_transfer_logging, transfer_logging)
557--- old/options.c
558+++ new/options.c
559@@ -109,6 +109,7 @@ size_t bwlimit_writemax = 0;
560 int ignore_existing = 0;
561 int ignore_non_existing = 0;
562 int need_messages_from_generator = 0;
563+int checksum_updating = 0;
564 int max_delete = -1;
565 OFF_T max_size = 0;
566 OFF_T min_size = 0;
567@@ -302,6 +303,7 @@ void usage(enum logcode F)
568 rprintf(F," -q, --quiet suppress non-error messages\n");
569 rprintf(F," --no-motd suppress daemon-mode MOTD (see manpage caveat)\n");
570 rprintf(F," -c, --checksum skip based on checksum, not mod-time & size\n");
56522462 571+ rprintf(F," --checksum-updating sender updates .rsyncsums files\n");
ae10e51e
WD
572 rprintf(F," -a, --archive archive mode; equals -rlptgoD (no -H,-A,-X)\n");
573 rprintf(F," --no-OPTION turn off an implied OPTION (e.g. --no-D)\n");
574 rprintf(F," -r, --recursive recurse into directories\n");
575@@ -542,6 +544,7 @@ static struct poptOption long_options[]
576 {"checksum", 'c', POPT_ARG_VAL, &always_checksum, 1, 0, 0 },
577 {"no-checksum", 0, POPT_ARG_VAL, &always_checksum, 0, 0, 0 },
578 {"no-c", 0, POPT_ARG_VAL, &always_checksum, 0, 0, 0 },
579+ {"checksum-updating",0, POPT_ARG_NONE, &checksum_updating, 0, 0, 0 },
580 {"block-size", 'B', POPT_ARG_LONG, &block_size, 0, 0, 0 },
581 {"compare-dest", 0, POPT_ARG_STRING, 0, OPT_COMPARE_DEST, 0, 0 },
582 {"copy-dest", 0, POPT_ARG_STRING, 0, OPT_COPY_DEST, 0, 0 },
583@@ -1896,7 +1899,9 @@ void server_options(char **args,int *arg
584 args[ac++] = basis_dir[i];
585 }
586 }
587- }
588+ } else if (checksum_updating)
589+ args[ac++] = "--checksum-updating";
590+
591
592 if (append_mode)
593 args[ac++] = "--append";
594--- old/rsync.h
595+++ new/rsync.h
7200c744
WD
596@@ -589,6 +589,7 @@ extern int preserve_xattrs;
597 #define EXTRA_LEN (sizeof (union file_extras))
598 #define PTR_EXTRA_LEN ((sizeof (char *) + EXTRA_LEN - 1) / EXTRA_LEN)
599 #define SUM_EXTRA_CNT ((MAX_DIGEST_LEN + EXTRA_LEN - 1) / EXTRA_LEN)
600+#define TIME_EXTRA_CNT ((SIZEOF_TIME_T + EXTRA_LEN - 1) / EXTRA_LEN)
601
602 #define REQ_EXTRA(f,ndx) ((union file_extras*)(f) - (ndx))
603 #define OPT_EXTRA(f,bump) ((union file_extras*)(f) - file_extra_cnt - 1 - (bump))
604@@ -1070,6 +1071,12 @@ isDigit(const char *ptr)
ae10e51e
WD
605 }
606
607 static inline int
56522462 608+isXDigit(const char *ptr)
ae10e51e 609+{
56522462 610+ return isxdigit(*(unsigned char *)ptr);
ae10e51e
WD
611+}
612+
613+static inline int
614 isPrint(const char *ptr)
615 {
616 return isprint(*(unsigned char *)ptr);
617--- old/rsync.yo
618+++ new/rsync.yo
619@@ -307,6 +307,7 @@ to the detailed description below for a
620 -q, --quiet suppress non-error messages
621 --no-motd suppress daemon-mode MOTD (see caveat)
622 -c, --checksum skip based on checksum, not mod-time & size
56522462 623+ --checksum-updating sender updates .rsyncsums files
ae10e51e
WD
624 -a, --archive archive mode; equals -rlptgoD (no -H,-A,-X)
625 --no-OPTION turn off an implied OPTION (e.g. --no-D)
626 -r, --recursive recurse into directories
627@@ -502,9 +503,9 @@ uses a "quick check" that (by default) c
628 of last modification match between the sender and receiver. This option
629 changes this to compare a 128-bit MD4 checksum for each file that has a
630 matching size. Generating the checksums means that both sides will expend
631-a lot of disk I/O reading all the data in the files in the transfer (and
632-this is prior to any reading that will be done to transfer changed files),
633-so this can slow things down significantly.
634+a lot of disk I/O reading the data in all the files in the transfer, so
635+this can slow things down significantly (and this is prior to any reading
636+that will be done to transfer the files that have changed).
637
638 The sending side generates its checksums while it is doing the file-system
639 scan that builds the list of the available files. The receiver generates
56522462 640@@ -512,12 +513,42 @@ its checksums when it is scanning for ch
ae10e51e
WD
641 file that has the same size as the corresponding sender's file: files with
642 either a changed size or a changed checksum are selected for transfer.
643
644+Starting with version 3.0.0, the sending side will look for a checksum
645+summary file and use a pre-generated checksum that it reads out of the file
646+(as long as it matches the file's size and modified time). This allows a
647+server to support the --checksum option to clients without having to
648+recompute the checksums for each client. See the bf(--checksum-updating)
56522462 649+option for a way to have rsync create/update these checksum files.
ae10e51e
WD
650+
651 Note that rsync always verifies that each em(transferred) file was
652 correctly reconstructed on the receiving side by checking a whole-file
653 checksum that is generated when as the file is transferred, but that
654 automatic after-the-transfer verification has nothing to do with this
655 option's before-the-transfer "Does this file need to be updated?" check.
656
657+dit(bf(--checksum-updating)) This option tells the sending side to create
658+and/or update per-directory checksum files that are used by the
56522462
WD
659+bf(--checksum) option. The file that is updated is named .rsyncsums. If
660+pre-transfer checksums are not being computed, this option has no effect.
ae10e51e
WD
661+
662+The checksum files stores the computed checksum, last-known size,
663+modification time, and name for each file in the current directory. If a
664+later transfer finds that a file matches its prior size and modification
665+time, the checksum is assumed to still be correct. Otherwise it is
666+recomputed and udpated in the file.
667+
668+To avoid transferring the system's checksum files, you can use an exclude
56522462 669+(e.g. bf(--exclude=.rsyncsums)). To make this easier to type, you can use
ae10e51e
WD
670+a popt alias. For instance, adding the following line in your ~/.popt file
671+defines a bf(-cc) option that enables checksum updating and excludes the
672+checksum files:
673+
56522462 674+verb( rsync alias --cc --checksum-updating --exclude=.rsyncsums)
ae10e51e
WD
675+
676+An rsync daemon does not allow the client to control this setting, so see
677+the "checksum updating" daemon config option for information on how to make
678+a daemon maintain these checksum files.
679+
680 dit(bf(-a, --archive)) This is equivalent to bf(-rlptgoD). It is a quick
681 way of saying you want recursion and want to preserve almost
682 everything (with -H being a notable omission).
683--- old/rsyncd.conf.yo
684+++ new/rsyncd.conf.yo
56522462 685@@ -198,6 +198,20 @@ locking on this file to ensure that the
ae10e51e
WD
686 exceeded for the modules sharing the lock file.
687 The default is tt(/var/run/rsyncd.lock).
688
689+dit(bf(checksum updating)) This option tells rsync to update/create the
690+checksum information in the per-directory checksum files when users copy
691+files using the bf(--checksum) option. Any file that has changed since it
692+was last checksummed (or is not mentioned) has its data updated in the
56522462 693+.rsyncsums file.
ae10e51e
WD
694+
695+Note that this updating will occur even if the module is listed as being
696+read-only. If you want to hide these files (and you will almost always
56522462 697+want to do), add ".rsyncsums" to the module's exclude setting.
ae10e51e
WD
698+
699+Note also that the client's command-line option, bf(--checksum-updating),
700+has no effect on a daemon. A daemon will only update/create checksum files
701+if this config option is true.
702+
703 dit(bf(read only)) The "read only" option determines whether clients
704 will be able to upload files or not. If "read only" is true then any
705 attempted uploads will fail. If "read only" is false then uploads will
7200c744
WD
706--- old/support/rsyncsums
707+++ new/support/rsyncsums
708@@ -0,0 +1,184 @@
709+#!/usr/bin/perl -w
710+use strict;
711+
712+use Getopt::Long;
713+use Cwd qw(abs_path cwd);
714+use Digest::MD4;
715+use Digest::MD5;
716+
717+our $SUMS_FILE = '.rsyncsums';
718+
719+our($recurse_opt, $force_reading, $help_opt);
720+our $verbosity = 0;
721+
722+&Getopt::Long::Configure('bundling');
723+&usage if !&GetOptions(
724+ 'recurse|r' => \$recurse_opt,
725+ 'force|f' => \$force_reading,
726+ 'verbose|v+' => \$verbosity,
727+ 'help|h' => \$help_opt,
728+) || $help_opt;
729+
730+my $start_dir = cwd();
731+
732+my @dirs = @ARGV;
733+@dirs = '.' unless @dirs;
734+foreach (@dirs) {
735+ $_ = abs_path($_);
736+}
737+
738+$| = 1;
739+
740+my $md4 = Digest::MD4->new;
741+my $md5 = Digest::MD5->new;
742+
743+while (@dirs) {
744+ my $dir = shift @dirs;
745+
746+ if (!chdir($dir)) {
747+ warn "Unable to chdir to $dir: $!\n";
748+ next;
749+ }
750+ if (!opendir(DP, '.')) {
751+ warn "Unable to opendir $dir: $!\n";
752+ next;
753+ }
754+
755+ if ($verbosity) {
756+ my $reldir = $dir;
757+ $reldir =~ s#^$start_dir(/|$)# $1 ? '' : '.' #eo;
758+ print "$reldir ... ";
759+ }
760+
761+ my $sums_mtime = (stat($SUMS_FILE))[9];
762+ my %cache;
763+ my @fp;
764+ my @subdirs;
765+ my $cnt = 0;
766+ my $latest_time = 0;
767+ while (defined(my $fn = readdir(DP))) {
768+ next if $fn =~ /^\.\.?$/ || $fn =~ /^\Q$SUMS_FILE\E$/o || -l $fn;
769+ if (-d _) {
770+ push(@subdirs, "$dir/$fn");
771+ next;
772+ }
773+ next unless -f _;
774+
775+ my($size,$mtime,$ctime) = (stat(_))[7,9,10];
776+
777+ $cache{$fn} = [ $size, $mtime, $ctime ];
778+ $cnt++;
779+
780+ $latest_time = $mtime if $mtime > $latest_time;
781+ $latest_time = $ctime if $ctime > $latest_time;
782+ }
783+
784+ closedir DP;
785+
786+ unshift(@dirs, sort @subdirs) if $recurse_opt;
787+
788+ if (!$cnt) {
789+ if (defined $sums_mtime) {
790+ print "(removed $SUMS_FILE) " if $verbosity;
791+ unlink($SUMS_FILE);
792+ }
793+ print "empty\n" if $verbosity;
794+ next;
795+ }
796+
797+ if (defined($sums_mtime) && $sums_mtime == $latest_time && !$force_reading) {
798+ print "OK\n" if $verbosity;
799+ next;
800+ }
801+
802+ if (open(FP, '+<', $SUMS_FILE)) {
803+ while (<FP>) {
804+ chomp;
805+ my($sum4, $sum5, $size, $mtime, $ctime, $fn) = split(' ', $_, 6);
806+ my $ref = $cache{$fn};
807+ if (defined $ref) {
808+ if ($$ref[0] == $size
809+ && $$ref[1] == $mtime && $$ref[2] == $ctime
810+ && $sum4 !~ /=/ && $sum5 !~ /=/) {
811+ $$ref[3] = $sum4;
812+ $$ref[4] = $sum5;
813+ $cnt--;
814+ } else {
815+ $$ref[3] = $$ref[4] = undef;
816+ }
817+ } else {
818+ $cnt = -1; # Force rewrite due to removed line.
819+ }
820+ }
821+ } else {
822+ open(FP, '>', $SUMS_FILE) or die "Unable to write $dir/$SUMS_FILE: $!\n";
823+ $cnt = -1;
824+ }
825+
826+ if ($cnt) {
827+ print "updating\n" if $verbosity;
828+ while (my($fn, $ref) = each %cache) {
829+ next if defined $$ref[3] && defined $$ref[4];
830+ if (!open(IN, $fn)) {
831+ print STDERR "Unable to read $fn: $!\n";
832+ delete $cache{$fn};
833+ next;
834+ }
835+
836+ my($size,$mtime,$ctime) = (stat(IN))[7,9,10];
837+ my($sum4, $sum5);
838+
839+ while (1) {
840+ while (sysread(IN, $_, 64*1024)) {
841+ $md4->add($_);
842+ $md5->add($_);
843+ }
844+ $sum4 = $md4->hexdigest;
845+ $sum5 = $md5->hexdigest;
846+ print " $sum4 $sum5 $fn\n" if $verbosity > 1;
847+ my($size2,$mtime2,$ctime2) = (stat(IN))[7,9,10];
848+ last if $size == $size2 && $mtime == $mtime2 && $ctime == $ctime2;
849+ $size = $size2;
850+ $mtime = $mtime2;
851+ $ctime = $ctime2;
852+ sysseek(IN, 0, 0);
853+ }
854+
855+ close IN;
856+
857+ $cache{$fn} = [ $size, $mtime, $ctime, $sum4, $sum5 ];
858+ }
859+
860+ $latest_time = 0;
861+ seek(FP, 0, 0);
862+ foreach my $fn (sort keys %cache) {
863+ my $ref = $cache{$fn};
864+ my($size, $mtime, $ctime, $sum4, $sum5) = @$ref;
53243b17 865+ printf FP '%s %s %10d %10d %10d %s' . "\n", $sum4, $sum5, $size, $mtime, $ctime, $fn;
7200c744
WD
866+
867+ $latest_time = $mtime if $mtime > $latest_time;
868+ $latest_time = $ctime if $ctime > $latest_time;
869+ }
870+ truncate(FP, tell(FP));
871+ } else {
872+ print "OK.\n" if $verbosity;
873+ }
874+
875+ close FP;
876+
877+ utime $latest_time, $latest_time, $SUMS_FILE;
878+}
879+
880+sub usage
881+{
882+ die <<EOT;
883+Usage: rsyncsums [OPTIONS] [DIRS]
884+
885+Options:
886+ -r, --recurse Update $SUMS_FILE files in subdirectories too.
887+ -f, --force Force the reading of an $SUMS_FILE file that looks to be
888+ up-to-date. (Useful for weeding out old entries.)
889+ -v, --verbose Mention what we're doing. Repeat for more info.
890+ -h, --help Display this help message.
891+EOT
892+}