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