1: CAUTION: This patch compiles, but is otherwise totally untested!
2:
3: This patch also implements --times-only.
4:
5: Implementation details for the --source-filter and -dest-filter options:
6:
7: - These options open a *HUGE* security hole in daemon mode unless they
8: are refused in your rsyncd.conf!
9:
10: - Filtering disables rsync alogrithm. (This should be fixed.)
11:
12: - Source filter makes temporary files in /tmp. (Should be overridable.)
13:
14: - If source filter fails, data is send unfiltered. (Should be changed
15: to abort.)
16:
17: - Failure of destination filter, causes data loss!!! (Should be changed
18: to abort.)
19:
20: - If filter changes size of file, you should use --times-only option to
21: prevent repeated transfers of unchanged files.
22:
23: - If the COMMAND contains single quotes, option-passing breaks. (Needs
24: to be fixed.)
25:
26: To use this patch, run these commands for a successful build:
27:
28: patch -p1 <patches/source-filter_dest-filter.diff
29: ./prepare-source
30: ./configure (optional if already run)
31: make
32:
33: based-on: e94bad1c156fc3910f24e2b3b71a81b0b0bdeb70
34: diff --git a/generator.c b/generator.c
35: --- a/generator.c
36: +++ b/generator.c
37: @@ -65,6 +65,7 @@ extern int append_mode;
38: extern int make_backups;
39: extern int csum_length;
40: extern int ignore_times;
41: +extern int times_only;
42: extern int size_only;
43: extern OFF_T max_size;
44: extern OFF_T min_size;
45: @@ -603,7 +604,7 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
46: /* Perform our quick-check heuristic for determining if a file is unchanged. */
47: int unchanged_file(char *fn, struct file_struct *file, STRUCT_STAT *st)
48: {
49: - if (st->st_size != F_LENGTH(file))
50: + if (!times_only && st->st_size != F_LENGTH(file))
51: return 0;
52:
53: /* if always checksum is set then we use the checksum instead
54: diff --git a/main.c b/main.c
55: --- a/main.c
56: +++ b/main.c
57: @@ -187,7 +187,7 @@ int shell_exec(const char *cmd)
58: }
59:
60: /* Wait for a process to exit, calling io_flush while waiting. */
61: -static void wait_process_with_flush(pid_t pid, int *exit_code_ptr)
62: +void wait_process_with_flush(pid_t pid, int *exit_code_ptr)
63: {
64: pid_t waited_pid;
65: int status;
66: diff --git a/options.c b/options.c
67: --- a/options.c
68: +++ b/options.c
69: @@ -110,6 +110,7 @@ int safe_symlinks = 0;
70: int copy_unsafe_links = 0;
71: int munge_symlinks = 0;
72: int size_only = 0;
73: +int times_only = 0;
74: int daemon_bwlimit = 0;
75: int bwlimit = 0;
76: int fuzzy_basis = 0;
77: @@ -170,6 +171,8 @@ char *logfile_name = NULL;
78: char *logfile_format = NULL;
79: char *stdout_format = NULL;
80: char *password_file = NULL;
81: +char *source_filter = NULL;
82: +char *dest_filter = NULL;
83: char *early_input_file = NULL;
84: char *rsync_path = RSYNC_PATH;
85: char *backup_dir = NULL;
86: @@ -679,6 +682,7 @@ static struct poptOption long_options[] = {
87: {"chmod", 0, POPT_ARG_STRING, 0, OPT_CHMOD, 0, 0 },
88: {"ignore-times", 'I', POPT_ARG_NONE, &ignore_times, 0, 0, 0 },
89: {"size-only", 0, POPT_ARG_NONE, &size_only, 0, 0, 0 },
90: + {"times-only", 0, POPT_ARG_NONE, ×_only , 0, 0, 0 },
91: {"one-file-system", 'x', POPT_ARG_NONE, 0, 'x', 0, 0 },
92: {"no-one-file-system",0, POPT_ARG_VAL, &one_file_system, 0, 0, 0 },
93: {"no-x", 0, POPT_ARG_VAL, &one_file_system, 0, 0, 0 },
94: @@ -813,6 +817,8 @@ static struct poptOption long_options[] = {
95: {"early-input", 0, POPT_ARG_STRING, &early_input_file, 0, 0, 0 },
96: {"blocking-io", 0, POPT_ARG_VAL, &blocking_io, 1, 0, 0 },
97: {"no-blocking-io", 0, POPT_ARG_VAL, &blocking_io, 0, 0, 0 },
98: + {"source-filter", 0, POPT_ARG_STRING, &source_filter, 0, 0, 0 },
99: + {"dest-filter", 0, POPT_ARG_STRING, &dest_filter, 0, 0, 0 },
100: {"outbuf", 0, POPT_ARG_STRING, &outbuf_mode, 0, 0, 0 },
101: {"remote-option", 'M', POPT_ARG_STRING, 0, 'M', 0, 0 },
102: {"protocol", 0, POPT_ARG_INT, &protocol_version, 0, 0, 0 },
103: @@ -2390,6 +2396,16 @@ int parse_arguments(int *argc_p, const char ***argv_p)
104: }
105: }
106:
107: + if (source_filter || dest_filter) {
108: + if (whole_file == 0) {
109: + snprintf(err_buf, sizeof err_buf,
110: + "--no-whole-file cannot be used with --%s-filter\n",
111: + source_filter ? "source" : "dest");
112: + return 0;
113: + }
114: + whole_file = 1;
115: + }
116: +
117: if (files_from) {
118: char *h, *p;
119: int q;
120: @@ -2782,6 +2798,25 @@ void server_options(char **args, int *argc_p)
121: else if (missing_args == 1 && !am_sender)
122: args[ac++] = "--ignore-missing-args";
123:
124: + if (times_only && am_sender)
125: + args[ac++] = "--times-only";
126: +
127: + if (source_filter && !am_sender) {
128: + /* Need to single quote the arg to keep the remote shell
129: + * from splitting it. FIXME: breaks if command has single quotes. */
130: + if (asprintf(&arg, "--source-filter='%s'", source_filter) < 0)
131: + goto oom;
132: + args[ac++] = arg;
133: + }
134: +
135: + if (dest_filter && am_sender) {
136: + /* Need to single quote the arg to keep the remote shell
137: + * from splitting it. FIXME: breaks if command has single quotes. */
138: + if (asprintf(&arg, "--dest-filter='%s'", dest_filter) < 0)
139: + goto oom;
140: + args[ac++] = arg;
141: + }
142: +
143: if (modify_window_set && am_sender) {
144: char *fmt = modify_window < 0 ? "-@%d" : "--modify-window=%d";
145: if (asprintf(&arg, fmt, modify_window) < 0)
146: diff --git a/pipe.c b/pipe.c
147: --- a/pipe.c
148: +++ b/pipe.c
149: @@ -27,6 +27,7 @@ extern int am_server;
150: extern int blocking_io;
151: extern int filesfrom_fd;
152: extern int munge_symlinks;
153: +extern mode_t orig_umask;
154: extern char *logfile_name;
155: extern int remote_option_cnt;
156: extern const char **remote_options;
157: @@ -176,3 +177,77 @@ pid_t local_child(int argc, char **argv, int *f_in, int *f_out,
158:
159: return pid;
160: }
161: +
162: +pid_t run_filter(char *command[], int out, int *pipe_to_filter)
163: +{
164: + pid_t pid;
165: + int pipefds[2];
166: +
167: + if (DEBUG_GTE(CMD, 1))
168: + print_child_argv("opening connection using:", command);
169: +
170: + if (pipe(pipefds) < 0) {
171: + rsyserr(FERROR, errno, "pipe");
172: + exit_cleanup(RERR_IPC);
173: + }
174: +
175: + pid = do_fork();
176: + if (pid == -1) {
177: + rsyserr(FERROR, errno, "fork");
178: + exit_cleanup(RERR_IPC);
179: + }
180: +
181: + if (pid == 0) {
182: + if (dup2(pipefds[0], STDIN_FILENO) < 0
183: + || close(pipefds[1]) < 0
184: + || dup2(out, STDOUT_FILENO) < 0) {
185: + rsyserr(FERROR, errno, "Failed dup/close");
186: + exit_cleanup(RERR_IPC);
187: + }
188: + umask(orig_umask);
189: + set_blocking(STDIN_FILENO);
190: + if (blocking_io)
191: + set_blocking(STDOUT_FILENO);
192: + execvp(command[0], command);
193: + rsyserr(FERROR, errno, "Failed to exec %s", command[0]);
194: + exit_cleanup(RERR_IPC);
195: + }
196: +
197: + if (close(pipefds[0]) < 0) {
198: + rsyserr(FERROR, errno, "Failed to close");
199: + exit_cleanup(RERR_IPC);
200: + }
201: +
202: + *pipe_to_filter = pipefds[1];
203: +
204: + return pid;
205: +}
206: +
207: +pid_t run_filter_on_file(char *command[], int out, int in)
208: +{
209: + pid_t pid;
210: +
211: + if (DEBUG_GTE(CMD, 1))
212: + print_child_argv("opening connection using:", command);
213: +
214: + pid = do_fork();
215: + if (pid == -1) {
216: + rsyserr(FERROR, errno, "fork");
217: + exit_cleanup(RERR_IPC);
218: + }
219: +
220: + if (pid == 0) {
221: + if (dup2(in, STDIN_FILENO) < 0
222: + || dup2(out, STDOUT_FILENO) < 0) {
223: + rsyserr(FERROR, errno, "Failed to dup2");
224: + exit_cleanup(RERR_IPC);
225: + }
226: + if (blocking_io)
227: + set_blocking(STDOUT_FILENO);
228: + execvp(command[0], command);
229: + rsyserr(FERROR, errno, "Failed to exec %s", command[0]);
230: + exit_cleanup(RERR_IPC);
231: + }
232: +
233: + return pid;
234: +}
235: diff --git a/receiver.c b/receiver.c
236: --- a/receiver.c
237: +++ b/receiver.c
238: @@ -60,6 +60,7 @@ extern BOOL want_progress_now;
239: extern mode_t orig_umask;
240: extern struct stats stats;
241: extern char *tmpdir;
242: +extern char *dest_filter;
243: extern char *partial_dir;
244: extern char *basis_dir[MAX_BASIS_DIRS+1];
245: extern char sender_file_sum[MAX_DIGEST_LEN];
246: @@ -522,6 +523,7 @@ int recv_files(int f_in, int f_out, char *local_name)
247: char *fnametmp, fnametmpbuf[MAXPATHLEN];
248: char *fnamecmp, *partialptr;
249: char fnamecmpbuf[MAXPATHLEN];
250: + char *filter_argv[MAX_FILTER_ARGS + 1];
251: uchar fnamecmp_type;
252: struct file_struct *file;
253: int itemizing = am_server ? logfile_format_has_i : stdout_format_has_i;
254: @@ -532,6 +534,7 @@ int recv_files(int f_in, int f_out, char *local_name)
255: const char *parent_dirname = "";
256: #endif
257: int ndx, recv_ok, one_inplace;
258: + pid_t pid = 0;
259:
260: if (DEBUG_GTE(RECV, 1))
261: rprintf(FINFO, "recv_files(%d) starting\n", cur_flist->used);
262: @@ -539,6 +542,23 @@ int recv_files(int f_in, int f_out, char *local_name)
263: if (delay_updates)
264: delayed_bits = bitbag_create(cur_flist->used + 1);
265:
266: + if (dest_filter) {
267: + char *p;
268: + char *sep = " \t";
269: + int i;
270: + for (p = strtok(dest_filter, sep), i = 0;
271: + p && i < MAX_FILTER_ARGS;
272: + p = strtok(0, sep))
273: + filter_argv[i++] = p;
274: + filter_argv[i] = NULL;
275: + if (p) {
276: + rprintf(FERROR,
277: + "Too many arguments to dest-filter (> %d)\n",
278: + MAX_FILTER_ARGS);
279: + exit_cleanup(RERR_SYNTAX);
280: + }
281: + }
282: +
283: progress_init();
284:
285: while (1) {
286: @@ -853,6 +873,9 @@ int recv_files(int f_in, int f_out, char *local_name)
287: else if (!am_server && INFO_GTE(NAME, 1) && INFO_EQ(PROGRESS, 1))
288: rprintf(FINFO, "%s\n", fname);
289:
290: + if (dest_filter)
291: + pid = run_filter(filter_argv, fd2, &fd2);
292: +
293: /* recv file data */
294: recv_ok = receive_data(f_in, fnamecmp, fd1, st.st_size, fname, fd2, file, inplace || one_inplace);
295:
296: @@ -868,6 +891,16 @@ int recv_files(int f_in, int f_out, char *local_name)
297: exit_cleanup(RERR_FILEIO);
298: }
299:
300: + if (dest_filter) {
301: + int status;
302: + wait_process_with_flush(pid, &status);
303: + if (status != 0) {
304: + rprintf(FERROR, "filter %s exited code: %d\n",
305: + dest_filter, status);
306: + continue;
307: + }
308: + }
309: +
310: if ((recv_ok && (!delay_updates || !partialptr)) || inplace) {
311: if (partialptr == fname)
312: partialptr = NULL;
313: diff --git a/rsync.1.md b/rsync.1.md
314: --- a/rsync.1.md
315: +++ b/rsync.1.md
316: @@ -418,6 +418,7 @@ detailed description below for a complete description.
317: --contimeout=SECONDS set daemon connection timeout in seconds
318: --ignore-times, -I don't skip files that match size and time
319: --size-only skip files that match in size
320: +--times-only skip files that match in mod-time
321: --modify-window=NUM, -@ set the accuracy for mod-time comparisons
322: --temp-dir=DIR, -T create temporary files in directory DIR
323: --fuzzy, -y find similar file for basis if no dest file
324: @@ -464,6 +465,8 @@ detailed description below for a complete description.
325: --write-batch=FILE write a batched update to FILE
326: --only-write-batch=FILE like --write-batch but w/o updating dest
327: --read-batch=FILE read a batched update from FILE
328: +--source-filter=COMMAND filter file through COMMAND at source
329: +--dest-filter=COMMAND filter file through COMMAND at destination
330: --protocol=NUM force an older protocol version to be used
331: --iconv=CONVERT_SPEC request charset conversion of filenames
332: --checksum-seed=NUM set block/file checksum seed (advanced)
333: @@ -3277,6 +3280,36 @@ your home directory (remove the '=' for that).
334: `--write-batch`. If _FILE_ is `-`, the batch data will be read from
335: standard input. See the "BATCH MODE" section for details.
336:
337: +0. `--source-filter=COMMAND`
338: +
339: + This option allows the user to specify a filter program that will be
340: + applied to the contents of all transferred regular files before the data is
341: + sent to destination. COMMAND will receive the data on its standard input
342: + and it should write the filtered data to standard output. COMMAND should
343: + exit non-zero if it cannot process the data or if it encounters an error
344: + when writing the data to stdout.
345: +
346: + Example: `--source-filter="gzip -9"` will cause remote files to be
347: + compressed. Use of `--source-filter` automatically enables `--whole-file`.
348: + If your filter does not output the same number of bytes that it received on
349: + input, you should use `--times-only` to disable size and content checks on
350: + subsequent rsync runs.
351: +
352: +0. `--dest-filter=COMMAND`
353: +
354: + This option allows you to specify a filter program that will be applied to
355: + the contents of all transferred regular files before the data is written to
356: + disk. COMMAND will receive the data on its standard input and it should
357: + write the filtered data to standard output. COMMAND should exit non-zero
358: + if it cannot process the data or if it encounters an error when writing the
359: + data to stdout.
360: +
361: + Example: --dest-filter="gzip -9" will cause remote files to be compressed.
362: + Use of --dest-filter automatically enables --whole-file. If your filter
363: + does not output the same number of bytes that it received on input, you
364: + should use --times-only to disable size and content checks on subsequent
365: + rsync runs.
366: +
367: 0. `--protocol=NUM`
368:
369: Force an older protocol version to be used. This is useful for creating a
370: diff --git a/rsync.h b/rsync.h
371: --- a/rsync.h
372: +++ b/rsync.h
373: @@ -159,6 +159,7 @@
374: #define IOERR_DEL_LIMIT (1<<2)
375:
376: #define MAX_ARGS 1000
377: +#define MAX_FILTER_ARGS 100
378: #define MAX_BASIS_DIRS 20
379: #define MAX_SERVER_ARGS (MAX_BASIS_DIRS*2 + 100)
380:
381: diff --git a/sender.c b/sender.c
382: --- a/sender.c
383: +++ b/sender.c
384: @@ -21,6 +21,7 @@
385:
386: #include "rsync.h"
387: #include "inums.h"
388: +#include "ifuncs.h"
389:
390: extern int do_xfers;
391: extern int am_server;
392: @@ -47,6 +48,7 @@ extern int batch_fd;
393: extern int write_batch;
394: extern int file_old_total;
395: extern BOOL want_progress_now;
396: +extern char *source_filter;
397: extern struct stats stats;
398: extern struct file_list *cur_flist, *first_flist, *dir_flist;
399:
400: @@ -200,6 +202,26 @@ void send_files(int f_in, int f_out)
401: int f_xfer = write_batch < 0 ? batch_fd : f_out;
402: int save_io_error = io_error;
403: int ndx, j;
404: + char *filter_argv[MAX_FILTER_ARGS + 1];
405: + char *tmp = 0;
406: + int unlink_tmp = 0;
407: +
408: + if (source_filter) {
409: + char *p;
410: + char *sep = " \t";
411: + int i;
412: + for (p = strtok(source_filter, sep), i = 0;
413: + p && i < MAX_FILTER_ARGS;
414: + p = strtok(0, sep))
415: + filter_argv[i++] = p;
416: + filter_argv[i] = NULL;
417: + if (p) {
418: + rprintf(FERROR,
419: + "Too many arguments to source-filter (> %d)\n",
420: + MAX_FILTER_ARGS);
421: + exit_cleanup(RERR_SYNTAX);
422: + }
423: + }
424:
425: if (DEBUG_GTE(SEND, 1))
426: rprintf(FINFO, "send_files starting\n");
427: @@ -334,6 +356,7 @@ void send_files(int f_in, int f_out)
428: exit_cleanup(RERR_PROTOCOL);
429: }
430:
431: + unlink_tmp = 0;
432: fd = do_open(fname, O_RDONLY, 0);
433: if (fd == -1) {
434: if (errno == ENOENT) {
435: @@ -353,6 +376,33 @@ void send_files(int f_in, int f_out)
436: continue;
437: }
438:
439: + if (source_filter) {
440: + int fd2;
441: + char *tmpl = "/tmp/rsync-filtered_sourceXXXXXX";
442: +
443: + tmp = strdup(tmpl);
444: + fd2 = mkstemp(tmp);
445: + if (fd2 == -1) {
446: + rprintf(FERROR, "mkstemp %s failed: %s\n",
447: + tmp, strerror(errno));
448: + } else {
449: + int status;
450: + pid_t pid = run_filter_on_file(filter_argv, fd2, fd);
451: + close(fd);
452: + close(fd2);
453: + wait_process_with_flush(pid, &status);
454: + if (status != 0) {
455: + rprintf(FERROR,
456: + "bypassing source filter %s; exited with code: %d\n",
457: + source_filter, status);
458: + fd = do_open(fname, O_RDONLY, 0);
459: + } else {
460: + fd = do_open(tmp, O_RDONLY, 0);
461: + unlink_tmp = 1;
462: + }
463: + }
464: + }
465: +
466: /* map the local file */
467: if (do_fstat(fd, &st) != 0) {
468: io_error |= IOERR_GENERAL;
469: @@ -404,6 +454,8 @@ void send_files(int f_in, int f_out)
470: }
471: }
472: close(fd);
473: + if (unlink_tmp)
474: + unlink(tmp);
475:
476: free_sums(s);
477:
478: diff -Nurp a/rsync.1 b/rsync.1
479: --- a/rsync.1
480: +++ b/rsync.1
481: @@ -494,6 +494,7 @@ detailed description below for a complet
482: --contimeout=SECONDS set daemon connection timeout in seconds
483: --ignore-times, -I don't skip files that match size and time
484: --size-only skip files that match in size
485: +--times-only skip files that match in mod-time
486: --modify-window=NUM, -@ set the accuracy for mod-time comparisons
487: --temp-dir=DIR, -T create temporary files in directory DIR
488: --fuzzy, -y find similar file for basis if no dest file
489: @@ -540,6 +541,8 @@ detailed description below for a complet
490: --write-batch=FILE write a batched update to FILE
491: --only-write-batch=FILE like --write-batch but w/o updating dest
492: --read-batch=FILE read a batched update from FILE
493: +--source-filter=COMMAND filter file through COMMAND at source
494: +--dest-filter=COMMAND filter file through COMMAND at destination
495: --protocol=NUM force an older protocol version to be used
496: --iconv=CONVERT_SPEC request charset conversion of filenames
497: --checksum-seed=NUM set block/file checksum seed (advanced)
498: @@ -3334,6 +3337,32 @@ into the batch file without having to fl
499: Apply all of the changes stored in FILE, a file previously generated by
500: \fB\-\-write-batch\fP. If \fIFILE\fP is \fB\-\fP, the batch data will be read from
501: standard input. See the "BATCH MODE" section for details.
502: +.IP "\fB\-\-source-filter=COMMAND\fP"
503: +This option allows the user to specify a filter program that will be
504: +applied to the contents of all transferred regular files before the data is
505: +sent to destination. COMMAND will receive the data on its standard input
506: +and it should write the filtered data to standard output. COMMAND should
507: +exit non-zero if it cannot process the data or if it encounters an error
508: +when writing the data to stdout.
509: +.IP
510: +Example: \fB\-\-source-filter="gzip\ \-9"\fP will cause remote files to be
511: +compressed. Use of \fB\-\-source-filter\fP automatically enables \fB\-\-whole-file\fP.
512: +If your filter does not output the same number of bytes that it received on
513: +input, you should use \fB\-\-times-only\fP to disable size and content checks on
514: +subsequent rsync runs.
515: +.IP "\fB\-\-dest-filter=COMMAND\fP"
516: +This option allows you to specify a filter program that will be applied to
517: +the contents of all transferred regular files before the data is written to
518: +disk. COMMAND will receive the data on its standard input and it should
519: +write the filtered data to standard output. COMMAND should exit non-zero
520: +if it cannot process the data or if it encounters an error when writing the
521: +data to stdout.
522: +.IP
523: +Example: \-\-dest-filter="gzip \-9" will cause remote files to be compressed.
524: +Use of \-\-dest-filter automatically enables \-\-whole-file. If your filter
525: +does not output the same number of bytes that it received on input, you
526: +should use \-\-times-only to disable size and content checks on subsequent
527: +rsync runs.
528: .IP "\fB\-\-protocol=NUM\fP"
529: Force an older protocol version to be used. This is useful for creating a
530: batch file that is compatible with an older version of rsync. For
531: diff -Nurp a/rsync.1.html b/rsync.1.html
532: --- a/rsync.1.html
533: +++ b/rsync.1.html
534: @@ -409,6 +409,7 @@ detailed description below for a complet
535: --contimeout=SECONDS set daemon connection timeout in seconds
536: --ignore-times, -I don't skip files that match size and time
537: --size-only skip files that match in size
538: +--times-only skip files that match in mod-time
539: --modify-window=NUM, -@ set the accuracy for mod-time comparisons
540: --temp-dir=DIR, -T create temporary files in directory DIR
541: --fuzzy, -y find similar file for basis if no dest file
542: @@ -455,6 +456,8 @@ detailed description below for a complet
543: --write-batch=FILE write a batched update to FILE
544: --only-write-batch=FILE like --write-batch but w/o updating dest
545: --read-batch=FILE read a batched update from FILE
546: +--source-filter=COMMAND filter file through COMMAND at source
547: +--dest-filter=COMMAND filter file through COMMAND at destination
548: --protocol=NUM force an older protocol version to be used
549: --iconv=CONVERT_SPEC request charset conversion of filenames
550: --checksum-seed=NUM set block/file checksum seed (advanced)
551: @@ -3091,6 +3094,34 @@ into the batch file without having to fl
552: standard input. See the "BATCH MODE" section for details.</p>
553: </dd>
554:
555: +<dt><code>--source-filter=COMMAND</code></dt><dd>
556: +<p>This option allows the user to specify a filter program that will be
557: +applied to the contents of all transferred regular files before the data is
558: +sent to destination. COMMAND will receive the data on its standard input
559: +and it should write the filtered data to standard output. COMMAND should
560: +exit non-zero if it cannot process the data or if it encounters an error
561: +when writing the data to stdout.</p>
562: +<p>Example: <code>--source-filter="gzip -9"</code> will cause remote files to be
563: +compressed. Use of <code>--source-filter</code> automatically enables <code>--whole-file</code>.
564: +If your filter does not output the same number of bytes that it received on
565: +input, you should use <code>--times-only</code> to disable size and content checks on
566: +subsequent rsync runs.</p>
567: +</dd>
568: +
569: +<dt><code>--dest-filter=COMMAND</code></dt><dd>
570: +<p>This option allows you to specify a filter program that will be applied to
571: +the contents of all transferred regular files before the data is written to
572: +disk. COMMAND will receive the data on its standard input and it should
573: +write the filtered data to standard output. COMMAND should exit non-zero
574: +if it cannot process the data or if it encounters an error when writing the
575: +data to stdout.</p>
576: +<p>Example: -⁠-⁠dest-filter="gzip -⁠9" will cause remote files to be compressed.
577: +Use of -⁠-⁠dest-filter automatically enables -⁠-⁠whole-file. If your filter
578: +does not output the same number of bytes that it received on input, you
579: +should use -⁠-⁠times-only to disable size and content checks on subsequent
580: +rsync runs.</p>
581: +</dd>
582: +
583: <dt><code>--protocol=NUM</code></dt><dd>
584: <p>Force an older protocol version to be used. This is useful for creating a
585: batch file that is compatible with an older version of rsync. For
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>