xref: /openbsd-src/usr.bin/rsync/main.c (revision ff0e7be1ebbcc809ea8ad2b6dafe215824da9e46)
1 /*	$OpenBSD: main.c,v 1.68 2023/04/28 10:24:38 claudio Exp $ */
2 /*
3  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 #include <sys/stat.h>
18 #include <sys/socket.h>
19 #include <sys/wait.h>
20 
21 #include <assert.h>
22 #include <err.h>
23 #include <getopt.h>
24 #include <stdint.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <util.h>
30 
31 #include "extern.h"
32 
33 int verbose;
34 int poll_contimeout;
35 int poll_timeout;
36 
37 /*
38  * A remote host is has a colon before the first path separator.
39  * This works for rsh remote hosts (host:/foo/bar), implicit rsync
40  * remote hosts (host::/foo/bar), and explicit (rsync://host/foo).
41  * Return zero if local, non-zero if remote.
42  */
43 static int
44 fargs_is_remote(const char *v)
45 {
46 	size_t	 pos;
47 
48 	pos = strcspn(v, ":/");
49 	return v[pos] == ':';
50 }
51 
52 /*
53  * Test whether a remote host is specifically an rsync daemon.
54  * Return zero if not, non-zero if so.
55  */
56 static int
57 fargs_is_daemon(const char *v)
58 {
59 	size_t	 pos;
60 
61 	if (strncasecmp(v, "rsync://", 8) == 0)
62 		return 1;
63 
64 	pos = strcspn(v, ":/");
65 	return v[pos] == ':' && v[pos + 1] == ':';
66 }
67 
68 /*
69  * Take the command-line filenames (e.g., rsync foo/ bar/ baz/) and
70  * determine our operating mode.
71  * For example, if the first argument is a remote file, this means that
72  * we're going to transfer from the remote to the local.
73  * We also make sure that the arguments are consistent, that is, if
74  * we're going to transfer from the local to the remote, that no
75  * filenames for the local transfer indicate remote hosts.
76  * Always returns the parsed and sanitised options.
77  */
78 static struct fargs *
79 fargs_parse(size_t argc, char *argv[], struct opts *opts)
80 {
81 	struct fargs	*f = NULL;
82 	char		*cp, *ccp;
83 	size_t		 i, j, len = 0;
84 
85 	/* Allocations. */
86 
87 	if ((f = calloc(1, sizeof(struct fargs))) == NULL)
88 		err(ERR_NOMEM, NULL);
89 
90 	f->sourcesz = argc - 1;
91 	if ((f->sources = calloc(f->sourcesz, sizeof(char *))) == NULL)
92 		err(ERR_NOMEM, NULL);
93 
94 	for (i = 0; i < argc - 1; i++)
95 		if ((f->sources[i] = strdup(argv[i])) == NULL)
96 			err(ERR_NOMEM, NULL);
97 
98 	if ((f->sink = strdup(argv[i])) == NULL)
99 		err(ERR_NOMEM, NULL);
100 
101 	/*
102 	 * Test files for its locality.
103 	 * If the last is a remote host, then we're sending from the
104 	 * local to the remote host ("sender" mode).
105 	 * If the first, remote to local ("receiver" mode).
106 	 * If neither, a local transfer in sender style.
107 	 */
108 
109 	f->mode = FARGS_SENDER;
110 
111 	if (fargs_is_remote(f->sink)) {
112 		f->mode = FARGS_SENDER;
113 		if ((f->host = strdup(f->sink)) == NULL)
114 			err(ERR_NOMEM, NULL);
115 	}
116 
117 	if (fargs_is_remote(f->sources[0])) {
118 		if (f->host != NULL)
119 			errx(ERR_SYNTAX, "both source and destination "
120 			    "cannot be remote files");
121 		f->mode = FARGS_RECEIVER;
122 		if ((f->host = strdup(f->sources[0])) == NULL)
123 			err(ERR_NOMEM, NULL);
124 	}
125 
126 	if (f->host != NULL) {
127 		if (strncasecmp(f->host, "rsync://", 8) == 0) {
128 			/* rsync://host[:port]/module[/path] */
129 			f->remote = 1;
130 			len = strlen(f->host) - 8 + 1;
131 			memmove(f->host, f->host + 8, len);
132 			if ((cp = strchr(f->host, '/')) == NULL)
133 				errx(ERR_SYNTAX,
134 				    "rsync protocol requires a module name");
135 			*cp++ = '\0';
136 			f->module = cp;
137 			if ((cp = strchr(f->module, '/')) != NULL)
138 				*cp = '\0';
139 			if ((cp = strchr(f->host, ':')) != NULL) {
140 				/* host:port --> extract port */
141 				*cp++ = '\0';
142 				opts->port = cp;
143 			}
144 		} else {
145 			/* host:[/path] */
146 			cp = strchr(f->host, ':');
147 			assert(cp != NULL);
148 			*cp++ = '\0';
149 			if (*cp == ':') {
150 				/* host::module[/path] */
151 				f->remote = 1;
152 				f->module = ++cp;
153 				cp = strchr(f->module, '/');
154 				if (cp != NULL)
155 					*cp = '\0';
156 			}
157 		}
158 		if ((len = strlen(f->host)) == 0)
159 			errx(ERR_SYNTAX, "empty remote host");
160 		if (f->remote && strlen(f->module) == 0)
161 			errx(ERR_SYNTAX, "empty remote module");
162 	}
163 
164 	/* Make sure we have the same "hostspec" for all files. */
165 
166 	if (!f->remote) {
167 		if (f->mode == FARGS_SENDER)
168 			for (i = 0; i < f->sourcesz; i++) {
169 				if (!fargs_is_remote(f->sources[i]))
170 					continue;
171 				errx(ERR_SYNTAX,
172 				    "remote file in list of local sources: %s",
173 				    f->sources[i]);
174 			}
175 		if (f->mode == FARGS_RECEIVER)
176 			for (i = 0; i < f->sourcesz; i++) {
177 				if (fargs_is_remote(f->sources[i]) &&
178 				    !fargs_is_daemon(f->sources[i]))
179 					continue;
180 				if (fargs_is_daemon(f->sources[i]))
181 					errx(ERR_SYNTAX,
182 					    "remote daemon in list of remote "
183 					    "sources: %s", f->sources[i]);
184 				errx(ERR_SYNTAX, "local file in list of "
185 				    "remote sources: %s", f->sources[i]);
186 			}
187 	} else {
188 		if (f->mode != FARGS_RECEIVER)
189 			errx(ERR_SYNTAX, "sender mode for remote "
190 				"daemon receivers not yet supported");
191 		for (i = 0; i < f->sourcesz; i++) {
192 			if (fargs_is_daemon(f->sources[i]))
193 				continue;
194 			errx(ERR_SYNTAX, "non-remote daemon file "
195 				"in list of remote daemon sources: "
196 				"%s", f->sources[i]);
197 		}
198 	}
199 
200 	/*
201 	 * If we're not remote and a sender, strip our hostname.
202 	 * Then exit if we're a sender or a local connection.
203 	 */
204 
205 	if (!f->remote) {
206 		if (f->host == NULL)
207 			return f;
208 		if (f->mode == FARGS_SENDER) {
209 			assert(f->host != NULL);
210 			assert(len > 0);
211 			j = strlen(f->sink);
212 			memmove(f->sink, f->sink + len + 1, j - len);
213 			return f;
214 		} else if (f->mode != FARGS_RECEIVER)
215 			return f;
216 	}
217 
218 	/*
219 	 * Now strip the hostnames from the remote host.
220 	 *   rsync://host/module/path -> module/path
221 	 *   host::module/path -> module/path
222 	 *   host:path -> path
223 	 * Also make sure that the remote hosts are the same.
224 	 */
225 
226 	assert(f->host != NULL);
227 	assert(len > 0);
228 
229 	for (i = 0; i < f->sourcesz; i++) {
230 		cp = f->sources[i];
231 		j = strlen(cp);
232 		if (f->remote &&
233 		    strncasecmp(cp, "rsync://", 8) == 0) {
234 			/* rsync://host[:port]/path */
235 			size_t module_offset = len;
236 			cp += 8;
237 			/* skip :port */
238 			if ((ccp = strchr(cp, ':')) != NULL) {
239 				*ccp = '\0';
240 				module_offset += strcspn(ccp + 1, "/") + 1;
241 			}
242 			if (strncmp(cp, f->host, len) ||
243 			    (cp[len] != '/' && cp[len] != '\0'))
244 				errx(ERR_SYNTAX, "different remote host: %s",
245 				    f->sources[i]);
246 			memmove(f->sources[i],
247 				f->sources[i] + module_offset + 8 + 1,
248 				j - module_offset - 8);
249 		} else if (f->remote && strncmp(cp, "::", 2) == 0) {
250 			/* ::path */
251 			memmove(f->sources[i],
252 				f->sources[i] + 2, j - 1);
253 		} else if (f->remote) {
254 			/* host::path */
255 			if (strncmp(cp, f->host, len) ||
256 			    (cp[len] != ':' && cp[len] != '\0'))
257 				errx(ERR_SYNTAX, "different remote host: %s",
258 				    f->sources[i]);
259 			memmove(f->sources[i], f->sources[i] + len + 2,
260 			    j - len - 1);
261 		} else if (cp[0] == ':') {
262 			/* :path */
263 			memmove(f->sources[i], f->sources[i] + 1, j);
264 		} else {
265 			/* host:path */
266 			if (strncmp(cp, f->host, len) ||
267 			    (cp[len] != ':' && cp[len] != '\0'))
268 				errx(ERR_SYNTAX, "different remote host: %s",
269 				    f->sources[i]);
270 			memmove(f->sources[i],
271 				f->sources[i] + len + 1, j - len);
272 		}
273 	}
274 
275 	return f;
276 }
277 
278 static struct opts	 opts;
279 
280 #define OP_ADDRESS	1000
281 #define OP_PORT		1001
282 #define OP_RSYNCPATH	1002
283 #define OP_TIMEOUT	1003
284 #define OP_EXCLUDE	1005
285 #define OP_INCLUDE	1006
286 #define OP_EXCLUDE_FROM	1007
287 #define OP_INCLUDE_FROM	1008
288 #define OP_COMP_DEST	1009
289 #define OP_COPY_DEST	1010
290 #define OP_LINK_DEST	1011
291 #define OP_MAX_SIZE	1012
292 #define OP_MIN_SIZE	1013
293 #define OP_CONTIMEOUT	1014
294 
295 const struct option	 lopts[] = {
296     { "address",	required_argument, NULL,		OP_ADDRESS },
297     { "archive",	no_argument,	NULL,			'a' },
298     { "compare-dest",	required_argument, NULL,		OP_COMP_DEST },
299 #if 0
300     { "copy-dest",	required_argument, NULL,		OP_COPY_DEST },
301     { "link-dest",	required_argument, NULL,		OP_LINK_DEST },
302 #endif
303     { "compress",	no_argument,	NULL,			'z' },
304     { "contimeout",	required_argument, NULL,		OP_CONTIMEOUT },
305     { "del",		no_argument,	&opts.del,		1 },
306     { "delete",		no_argument,	&opts.del,		1 },
307     { "devices",	no_argument,	&opts.devices,		1 },
308     { "no-devices",	no_argument,	&opts.devices,		0 },
309     { "dry-run",	no_argument,	&opts.dry_run,		1 },
310     { "exclude",	required_argument, NULL,		OP_EXCLUDE },
311     { "exclude-from",	required_argument, NULL,		OP_EXCLUDE_FROM },
312     { "group",		no_argument,	&opts.preserve_gids,	1 },
313     { "no-group",	no_argument,	&opts.preserve_gids,	0 },
314     { "help",		no_argument,	NULL,			'h' },
315     { "ignore-times",	no_argument,	NULL,			'I' },
316     { "include",	required_argument, NULL,		OP_INCLUDE },
317     { "include-from",	required_argument, NULL,		OP_INCLUDE_FROM },
318     { "links",		no_argument,	&opts.preserve_links,	1 },
319     { "max-size",	required_argument, NULL,		OP_MAX_SIZE },
320     { "min-size",	required_argument, NULL,		OP_MIN_SIZE },
321     { "no-links",	no_argument,	&opts.preserve_links,	0 },
322     { "no-motd",	no_argument,	&opts.no_motd,		1 },
323     { "numeric-ids",	no_argument,	&opts.numeric_ids,	1 },
324     { "owner",		no_argument,	&opts.preserve_uids,	1 },
325     { "no-owner",	no_argument,	&opts.preserve_uids,	0 },
326     { "perms",		no_argument,	&opts.preserve_perms,	1 },
327     { "no-perms",	no_argument,	&opts.preserve_perms,	0 },
328     { "port",		required_argument, NULL,		OP_PORT },
329     { "recursive",	no_argument,	&opts.recursive,	1 },
330     { "no-recursive",	no_argument,	&opts.recursive,	0 },
331     { "rsh",		required_argument, NULL,		'e' },
332     { "rsync-path",	required_argument, NULL,		OP_RSYNCPATH },
333     { "sender",		no_argument,	&opts.sender,		1 },
334     { "server",		no_argument,	&opts.server,		1 },
335     { "size-only",	no_argument,	&opts.size_only,	1 },
336     { "specials",	no_argument,	&opts.specials,		1 },
337     { "no-specials",	no_argument,	&opts.specials,		0 },
338     { "timeout",	required_argument, NULL,		OP_TIMEOUT },
339     { "times",		no_argument,	&opts.preserve_times,	1 },
340     { "no-times",	no_argument,	&opts.preserve_times,	0 },
341     { "verbose",	no_argument,	&verbose,		1 },
342     { "no-verbose",	no_argument,	&verbose,		0 },
343     { "version",	no_argument,	NULL,			'V' },
344     { NULL,		0,		NULL,			0 }
345 };
346 
347 int
348 main(int argc, char *argv[])
349 {
350 	pid_t		 child;
351 	int		 fds[2], sd = -1, rc, c, st, i, lidx;
352 	size_t		 basedir_cnt = 0;
353 	struct sess	 sess;
354 	struct fargs	*fargs;
355 	char		**args;
356 	const char	*errstr;
357 
358 	/* Global pledge. */
359 
360 	if (pledge("stdio unix rpath wpath cpath dpath inet fattr chown dns getpw proc exec unveil",
361 	    NULL) == -1)
362 		err(ERR_IPC, "pledge");
363 
364 	opts.max_size = opts.min_size = -1;
365 
366 	while ((c = getopt_long(argc, argv, "aDe:ghIlnoprtVvxz", lopts, &lidx))
367 	    != -1) {
368 		switch (c) {
369 		case 'D':
370 			opts.devices = 1;
371 			opts.specials = 1;
372 			break;
373 		case 'a':
374 			opts.recursive = 1;
375 			opts.preserve_links = 1;
376 			opts.preserve_perms = 1;
377 			opts.preserve_times = 1;
378 			opts.preserve_gids = 1;
379 			opts.preserve_uids = 1;
380 			opts.devices = 1;
381 			opts.specials = 1;
382 			break;
383 		case 'e':
384 			opts.ssh_prog = optarg;
385 			break;
386 		case 'g':
387 			opts.preserve_gids = 1;
388 			break;
389 		case 'I':
390 			opts.ignore_times = 1;
391 			break;
392 		case 'l':
393 			opts.preserve_links = 1;
394 			break;
395 		case 'n':
396 			opts.dry_run = 1;
397 			break;
398 		case 'o':
399 			opts.preserve_uids = 1;
400 			break;
401 		case 'p':
402 			opts.preserve_perms = 1;
403 			break;
404 		case 'r':
405 			opts.recursive = 1;
406 			break;
407 		case 't':
408 			opts.preserve_times = 1;
409 			break;
410 		case 'v':
411 			verbose++;
412 			break;
413 		case 'V':
414 			fprintf(stderr, "openrsync: protocol version %u\n",
415 			    RSYNC_PROTOCOL);
416 			exit(0);
417 		case 'x':
418 			opts.one_file_system++;
419 			break;
420 		case 'z':
421 			fprintf(stderr, "%s: -z not supported yet\n", getprogname());
422 			break;
423 		case 0:
424 			/* Non-NULL flag values (e.g., --sender). */
425 			break;
426 		case OP_ADDRESS:
427 			opts.address = optarg;
428 			break;
429 		case OP_CONTIMEOUT:
430 			poll_contimeout = strtonum(optarg, 0, 60*60, &errstr);
431 			if (errstr != NULL)
432 				errx(ERR_SYNTAX, "timeout is %s: %s",
433 				    errstr, optarg);
434 			break;
435 		case OP_PORT:
436 			opts.port = optarg;
437 			break;
438 		case OP_RSYNCPATH:
439 			opts.rsync_path = optarg;
440 			break;
441 		case OP_TIMEOUT:
442 			poll_timeout = strtonum(optarg, 0, 60*60, &errstr);
443 			if (errstr != NULL)
444 				errx(ERR_SYNTAX, "timeout is %s: %s",
445 				    errstr, optarg);
446 			break;
447 		case OP_EXCLUDE:
448 			if (parse_rule(optarg, RULE_EXCLUDE) == -1)
449 				errx(ERR_SYNTAX, "syntax error in exclude: %s",
450 				    optarg);
451 			break;
452 		case OP_INCLUDE:
453 			if (parse_rule(optarg, RULE_INCLUDE) == -1)
454 				errx(ERR_SYNTAX, "syntax error in include: %s",
455 				    optarg);
456 			break;
457 		case OP_EXCLUDE_FROM:
458 			parse_file(optarg, RULE_EXCLUDE);
459 			break;
460 		case OP_INCLUDE_FROM:
461 			parse_file(optarg, RULE_INCLUDE);
462 			break;
463 		case OP_COMP_DEST:
464 			if (opts.alt_base_mode !=0 &&
465 			    opts.alt_base_mode != BASE_MODE_COMPARE) {
466 				errx(1, "option --%s conflicts with %s",
467 				    lopts[lidx].name,
468 				    alt_base_mode(opts.alt_base_mode));
469 			}
470 			opts.alt_base_mode = BASE_MODE_COMPARE;
471 #if 0
472 			goto basedir;
473 		case OP_COPY_DEST:
474 			if (opts.alt_base_mode !=0 &&
475 			    opts.alt_base_mode != BASE_MODE_COPY) {
476 				errx(1, "option --%s conflicts with %s",
477 				    lopts[lidx].name,
478 				    alt_base_mode(opts.alt_base_mode));
479 			}
480 			opts.alt_base_mode = BASE_MODE_COPY;
481 			goto basedir;
482 		case OP_LINK_DEST:
483 			if (opts.alt_base_mode !=0 &&
484 			    opts.alt_base_mode != BASE_MODE_LINK) {
485 				errx(1, "option --%s conflicts with %s",
486 				    lopts[lidx].name,
487 				    alt_base_mode(opts.alt_base_mode));
488 			}
489 			opts.alt_base_mode = BASE_MODE_LINK;
490 
491 basedir:
492 #endif
493 			if (basedir_cnt >= MAX_BASEDIR)
494 				errx(1, "too many --%s directories specified",
495 				    lopts[lidx].name);
496 			opts.basedir[basedir_cnt++] = optarg;
497 			break;
498 		case OP_MAX_SIZE:
499 			if (scan_scaled(optarg, &opts.max_size) == -1)
500 				err(1, "bad max-size");
501 			break;
502 		case OP_MIN_SIZE:
503 			if (scan_scaled(optarg, &opts.min_size) == -1)
504 				err(1, "bad min-size");
505 			break;
506 		case 'h':
507 		default:
508 			goto usage;
509 		}
510 	}
511 
512 	argc -= optind;
513 	argv += optind;
514 
515 	/* FIXME: reference implementation rsync accepts this. */
516 
517 	if (argc < 2)
518 		goto usage;
519 
520 	if (opts.port == NULL)
521 		opts.port = "rsync";
522 
523 	/* by default and for --contimeout=0 disable poll_contimeout */
524 	if (poll_contimeout == 0)
525 		poll_contimeout = -1;
526 	else
527 		poll_contimeout *= 1000;
528 
529 	/* by default and for --timeout=0 disable poll_timeout */
530 	if (poll_timeout == 0)
531 		poll_timeout = -1;
532 	else
533 		poll_timeout *= 1000;
534 
535 	/*
536 	 * This is what happens when we're started with the "hidden"
537 	 * --server option, which is invoked for the rsync on the remote
538 	 * host by the parent.
539 	 */
540 
541 	if (opts.server)
542 		exit(rsync_server(&opts, (size_t)argc, argv));
543 
544 	/*
545 	 * Now we know that we're the client on the local machine
546 	 * invoking rsync(1).
547 	 * At this point, we need to start the client and server
548 	 * initiation logic.
549 	 * The client is what we continue running on this host; the
550 	 * server is what we'll use to connect to the remote and
551 	 * invoke rsync with the --server option.
552 	 */
553 
554 	fargs = fargs_parse(argc, argv, &opts);
555 	assert(fargs != NULL);
556 
557 	/*
558 	 * If we're contacting an rsync:// daemon, then we don't need to
559 	 * fork, because we won't start a server ourselves.
560 	 * Route directly into the socket code, unless a remote shell
561 	 * has explicitly been specified.
562 	 */
563 
564 	if (fargs->remote && opts.ssh_prog == NULL) {
565 		assert(fargs->mode == FARGS_RECEIVER);
566 		if ((rc = rsync_connect(&opts, &sd, fargs)) == 0) {
567 			rc = rsync_socket(&opts, sd, fargs);
568 			close(sd);
569 		}
570 		exit(rc);
571 	}
572 
573 	/* Drop the dns/inet possibility. */
574 
575 	if (pledge("stdio unix rpath wpath cpath dpath fattr chown getpw proc exec unveil",
576 	    NULL) == -1)
577 		err(ERR_IPC, "pledge");
578 
579 	/* Create a bidirectional socket and start our child. */
580 
581 	if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, fds) == -1)
582 		err(ERR_IPC, "socketpair");
583 
584 	switch ((child = fork())) {
585 	case -1:
586 		err(ERR_IPC, "fork");
587 	case 0:
588 		close(fds[0]);
589 		if (pledge("stdio exec", NULL) == -1)
590 			err(ERR_IPC, "pledge");
591 
592 		memset(&sess, 0, sizeof(struct sess));
593 		sess.opts = &opts;
594 
595 		args = fargs_cmdline(&sess, fargs, NULL);
596 
597 		for (i = 0; args[i] != NULL; i++)
598 			LOG2("exec[%d] = %s", i, args[i]);
599 
600 		/* Make sure the child's stdin is from the sender. */
601 		if (dup2(fds[1], STDIN_FILENO) == -1)
602 			err(ERR_IPC, "dup2");
603 		if (dup2(fds[1], STDOUT_FILENO) == -1)
604 			err(ERR_IPC, "dup2");
605 		execvp(args[0], args);
606 		_exit(ERR_IPC);
607 		/* NOTREACHED */
608 	default:
609 		close(fds[1]);
610 		if (!fargs->remote)
611 			rc = rsync_client(&opts, fds[0], fargs);
612 		else
613 			rc = rsync_socket(&opts, fds[0], fargs);
614 		break;
615 	}
616 
617 	close(fds[0]);
618 
619 	if (waitpid(child, &st, 0) == -1)
620 		err(ERR_WAITPID, "waitpid");
621 
622 	/*
623 	 * If we don't already have an error (rc == 0), then inherit the
624 	 * error code of rsync_server() if it has exited.
625 	 * If it hasn't exited, it overrides our return value.
626 	 */
627 
628 	if (rc == 0) {
629 		if (WIFEXITED(st))
630 			rc = WEXITSTATUS(st);
631 		else if (WIFSIGNALED(st))
632 			rc = ERR_TERMIMATED;
633 		else
634 			rc = ERR_WAITPID;
635 	}
636 
637 	exit(rc);
638 usage:
639 	fprintf(stderr, "usage: %s"
640 	    " [-aDgIlnoprtVvx] [-e program] [--address=sourceaddr]\n"
641 	    "\t[--contimeout=seconds] [--compare-dest=dir] [--del] [--exclude]\n"
642 	    "\t[--exclude-from=file] [--include] [--include-from=file]\n"
643 	    "\t[--no-motd] [--numeric-ids] [--port=portnumber]\n"
644 	    "\t[--rsync-path=program] [--size-only] [--timeout=seconds]\n"
645 	    "\tsource ... directory\n",
646 	    getprogname());
647 	exit(ERR_SYNTAX);
648 }
649