xref: /openbsd-src/usr.bin/rsync/main.c (revision 5be4168d1ac40301790c61c3bb556304b22d7a42)
1*5be4168dSjob /*	$OpenBSD: main.c,v 1.72 2025/01/16 14:06:49 job Exp $ */
260a32ee9Sbenno /*
360a32ee9Sbenno  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
460a32ee9Sbenno  *
560a32ee9Sbenno  * Permission to use, copy, modify, and distribute this software for any
660a32ee9Sbenno  * purpose with or without fee is hereby granted, provided that the above
760a32ee9Sbenno  * copyright notice and this permission notice appear in all copies.
860a32ee9Sbenno  *
960a32ee9Sbenno  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1060a32ee9Sbenno  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1160a32ee9Sbenno  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1260a32ee9Sbenno  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1360a32ee9Sbenno  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1460a32ee9Sbenno  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1560a32ee9Sbenno  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1660a32ee9Sbenno  */
1760a32ee9Sbenno #include <sys/stat.h>
1860a32ee9Sbenno #include <sys/socket.h>
1960a32ee9Sbenno #include <sys/wait.h>
2060a32ee9Sbenno 
2160a32ee9Sbenno #include <assert.h>
2260a32ee9Sbenno #include <err.h>
2360a32ee9Sbenno #include <getopt.h>
2460a32ee9Sbenno #include <stdint.h>
2560a32ee9Sbenno #include <stdio.h>
2660a32ee9Sbenno #include <stdlib.h>
2760a32ee9Sbenno #include <string.h>
2860a32ee9Sbenno #include <unistd.h>
2982ecafa1Sclaudio #include <util.h>
3060a32ee9Sbenno 
3160a32ee9Sbenno #include "extern.h"
32*5be4168dSjob #include "version.h"
3360a32ee9Sbenno 
34b2a7eac7Sbenno int verbose;
352eaa6f4eSjob int poll_contimeout;
36a84b4914Sclaudio int poll_timeout;
37b2a7eac7Sbenno 
3860a32ee9Sbenno /*
3960a32ee9Sbenno  * A remote host is has a colon before the first path separator.
4060a32ee9Sbenno  * This works for rsh remote hosts (host:/foo/bar), implicit rsync
4160a32ee9Sbenno  * remote hosts (host::/foo/bar), and explicit (rsync://host/foo).
4260a32ee9Sbenno  * Return zero if local, non-zero if remote.
4360a32ee9Sbenno  */
4460a32ee9Sbenno static int
4560a32ee9Sbenno fargs_is_remote(const char *v)
4660a32ee9Sbenno {
4760a32ee9Sbenno 	size_t	 pos;
4860a32ee9Sbenno 
4960a32ee9Sbenno 	pos = strcspn(v, ":/");
50f1dcb30aSderaadt 	return v[pos] == ':';
5160a32ee9Sbenno }
5260a32ee9Sbenno 
5360a32ee9Sbenno /*
5460a32ee9Sbenno  * Test whether a remote host is specifically an rsync daemon.
5560a32ee9Sbenno  * Return zero if not, non-zero if so.
5660a32ee9Sbenno  */
5760a32ee9Sbenno static int
5860a32ee9Sbenno fargs_is_daemon(const char *v)
5960a32ee9Sbenno {
6060a32ee9Sbenno 	size_t	 pos;
6160a32ee9Sbenno 
62f1dcb30aSderaadt 	if (strncasecmp(v, "rsync://", 8) == 0)
6360a32ee9Sbenno 		return 1;
6460a32ee9Sbenno 
6560a32ee9Sbenno 	pos = strcspn(v, ":/");
66f1dcb30aSderaadt 	return v[pos] == ':' && v[pos + 1] == ':';
6760a32ee9Sbenno }
6860a32ee9Sbenno 
6960a32ee9Sbenno /*
7060a32ee9Sbenno  * Take the command-line filenames (e.g., rsync foo/ bar/ baz/) and
7160a32ee9Sbenno  * determine our operating mode.
7260a32ee9Sbenno  * For example, if the first argument is a remote file, this means that
7360a32ee9Sbenno  * we're going to transfer from the remote to the local.
7460a32ee9Sbenno  * We also make sure that the arguments are consistent, that is, if
7560a32ee9Sbenno  * we're going to transfer from the local to the remote, that no
7660a32ee9Sbenno  * filenames for the local transfer indicate remote hosts.
7760a32ee9Sbenno  * Always returns the parsed and sanitised options.
7860a32ee9Sbenno  */
7960a32ee9Sbenno static struct fargs *
8094851233Sderaadt fargs_parse(size_t argc, char *argv[], struct opts *opts)
8160a32ee9Sbenno {
8260a32ee9Sbenno 	struct fargs	*f = NULL;
8394851233Sderaadt 	char		*cp, *ccp;
8460a32ee9Sbenno 	size_t		 i, j, len = 0;
8560a32ee9Sbenno 
8660a32ee9Sbenno 	/* Allocations. */
8760a32ee9Sbenno 
88f1dcb30aSderaadt 	if ((f = calloc(1, sizeof(struct fargs))) == NULL)
8905e0b7a0Sclaudio 		err(ERR_NOMEM, NULL);
9060a32ee9Sbenno 
9160a32ee9Sbenno 	f->sourcesz = argc - 1;
92f1dcb30aSderaadt 	if ((f->sources = calloc(f->sourcesz, sizeof(char *))) == NULL)
9305e0b7a0Sclaudio 		err(ERR_NOMEM, NULL);
9460a32ee9Sbenno 
9560a32ee9Sbenno 	for (i = 0; i < argc - 1; i++)
96f1dcb30aSderaadt 		if ((f->sources[i] = strdup(argv[i])) == NULL)
9705e0b7a0Sclaudio 			err(ERR_NOMEM, NULL);
9860a32ee9Sbenno 
99f1dcb30aSderaadt 	if ((f->sink = strdup(argv[i])) == NULL)
10005e0b7a0Sclaudio 		err(ERR_NOMEM, NULL);
10160a32ee9Sbenno 
10260a32ee9Sbenno 	/*
10360a32ee9Sbenno 	 * Test files for its locality.
10460a32ee9Sbenno 	 * If the last is a remote host, then we're sending from the
10560a32ee9Sbenno 	 * local to the remote host ("sender" mode).
10660a32ee9Sbenno 	 * If the first, remote to local ("receiver" mode).
10760a32ee9Sbenno 	 * If neither, a local transfer in sender style.
10860a32ee9Sbenno 	 */
10960a32ee9Sbenno 
11060a32ee9Sbenno 	f->mode = FARGS_SENDER;
11160a32ee9Sbenno 
11260a32ee9Sbenno 	if (fargs_is_remote(f->sink)) {
11360a32ee9Sbenno 		f->mode = FARGS_SENDER;
114f1dcb30aSderaadt 		if ((f->host = strdup(f->sink)) == NULL)
11505e0b7a0Sclaudio 			err(ERR_NOMEM, NULL);
11660a32ee9Sbenno 	}
11760a32ee9Sbenno 
11860a32ee9Sbenno 	if (fargs_is_remote(f->sources[0])) {
119f1dcb30aSderaadt 		if (f->host != NULL)
12005e0b7a0Sclaudio 			errx(ERR_SYNTAX, "both source and destination "
12105e0b7a0Sclaudio 			    "cannot be remote files");
12260a32ee9Sbenno 		f->mode = FARGS_RECEIVER;
123f1dcb30aSderaadt 		if ((f->host = strdup(f->sources[0])) == NULL)
12405e0b7a0Sclaudio 			err(ERR_NOMEM, NULL);
12560a32ee9Sbenno 	}
12660a32ee9Sbenno 
127f1dcb30aSderaadt 	if (f->host != NULL) {
128f1dcb30aSderaadt 		if (strncasecmp(f->host, "rsync://", 8) == 0) {
12955cb9f91Sbenno 			/* rsync://host[:port]/module[/path] */
13060a32ee9Sbenno 			f->remote = 1;
13160a32ee9Sbenno 			len = strlen(f->host) - 8 + 1;
13260a32ee9Sbenno 			memmove(f->host, f->host + 8, len);
133f1dcb30aSderaadt 			if ((cp = strchr(f->host, '/')) == NULL)
13405e0b7a0Sclaudio 				errx(ERR_SYNTAX,
13505e0b7a0Sclaudio 				    "rsync protocol requires a module name");
13660a32ee9Sbenno 			*cp++ = '\0';
13760a32ee9Sbenno 			f->module = cp;
138f1dcb30aSderaadt 			if ((cp = strchr(f->module, '/')) != NULL)
13960a32ee9Sbenno 				*cp = '\0';
14088e84a3bSclaudio 			if ((cp = strchr(f->host, ':')) != NULL) {
14194851233Sderaadt 				/* host:port --> extract port */
14294851233Sderaadt 				*cp++ = '\0';
14394851233Sderaadt 				opts->port = cp;
14494851233Sderaadt 			}
14560a32ee9Sbenno 		} else {
14660a32ee9Sbenno 			/* host:[/path] */
14760a32ee9Sbenno 			cp = strchr(f->host, ':');
148f1dcb30aSderaadt 			assert(cp != NULL);
14960a32ee9Sbenno 			*cp++ = '\0';
150f1dcb30aSderaadt 			if (*cp == ':') {
15160a32ee9Sbenno 				/* host::module[/path] */
15260a32ee9Sbenno 				f->remote = 1;
15360a32ee9Sbenno 				f->module = ++cp;
15460a32ee9Sbenno 				cp = strchr(f->module, '/');
155f1dcb30aSderaadt 				if (cp != NULL)
15660a32ee9Sbenno 					*cp = '\0';
15760a32ee9Sbenno 			}
15860a32ee9Sbenno 		}
159f1dcb30aSderaadt 		if ((len = strlen(f->host)) == 0)
16005e0b7a0Sclaudio 			errx(ERR_SYNTAX, "empty remote host");
161f1dcb30aSderaadt 		if (f->remote && strlen(f->module) == 0)
16205e0b7a0Sclaudio 			errx(ERR_SYNTAX, "empty remote module");
16360a32ee9Sbenno 	}
16460a32ee9Sbenno 
16560a32ee9Sbenno 	/* Make sure we have the same "hostspec" for all files. */
16660a32ee9Sbenno 
16760a32ee9Sbenno 	if (!f->remote) {
168f1dcb30aSderaadt 		if (f->mode == FARGS_SENDER)
16960a32ee9Sbenno 			for (i = 0; i < f->sourcesz; i++) {
17060a32ee9Sbenno 				if (!fargs_is_remote(f->sources[i]))
17160a32ee9Sbenno 					continue;
17205e0b7a0Sclaudio 				errx(ERR_SYNTAX,
17302f20df6Sderaadt 				    "remote file in list of local sources: %s",
17460a32ee9Sbenno 				    f->sources[i]);
17560a32ee9Sbenno 			}
176f1dcb30aSderaadt 		if (f->mode == FARGS_RECEIVER)
17760a32ee9Sbenno 			for (i = 0; i < f->sourcesz; i++) {
17860a32ee9Sbenno 				if (fargs_is_remote(f->sources[i]) &&
17960a32ee9Sbenno 				    !fargs_is_daemon(f->sources[i]))
18060a32ee9Sbenno 					continue;
18160a32ee9Sbenno 				if (fargs_is_daemon(f->sources[i]))
18205e0b7a0Sclaudio 					errx(ERR_SYNTAX,
18305e0b7a0Sclaudio 					    "remote daemon in list of remote "
18405e0b7a0Sclaudio 					    "sources: %s", f->sources[i]);
18505e0b7a0Sclaudio 				errx(ERR_SYNTAX, "local file in list of "
18605e0b7a0Sclaudio 				    "remote sources: %s", f->sources[i]);
18760a32ee9Sbenno 			}
18860a32ee9Sbenno 	} else {
189f1dcb30aSderaadt 		if (f->mode != FARGS_RECEIVER)
19005e0b7a0Sclaudio 			errx(ERR_SYNTAX, "sender mode for remote "
19160a32ee9Sbenno 				"daemon receivers not yet supported");
19260a32ee9Sbenno 		for (i = 0; i < f->sourcesz; i++) {
19360a32ee9Sbenno 			if (fargs_is_daemon(f->sources[i]))
19460a32ee9Sbenno 				continue;
19505e0b7a0Sclaudio 			errx(ERR_SYNTAX, "non-remote daemon file "
19660a32ee9Sbenno 				"in list of remote daemon sources: "
19760a32ee9Sbenno 				"%s", f->sources[i]);
19860a32ee9Sbenno 		}
19960a32ee9Sbenno 	}
20060a32ee9Sbenno 
20160a32ee9Sbenno 	/*
20260a32ee9Sbenno 	 * If we're not remote and a sender, strip our hostname.
20360a32ee9Sbenno 	 * Then exit if we're a sender or a local connection.
20460a32ee9Sbenno 	 */
20560a32ee9Sbenno 
20660a32ee9Sbenno 	if (!f->remote) {
207f1dcb30aSderaadt 		if (f->host == NULL)
20860a32ee9Sbenno 			return f;
209f1dcb30aSderaadt 		if (f->mode == FARGS_SENDER) {
210f1dcb30aSderaadt 			assert(f->host != NULL);
21160a32ee9Sbenno 			assert(len > 0);
21260a32ee9Sbenno 			j = strlen(f->sink);
21360a32ee9Sbenno 			memmove(f->sink, f->sink + len + 1, j - len);
21460a32ee9Sbenno 			return f;
215f1dcb30aSderaadt 		} else if (f->mode != FARGS_RECEIVER)
21660a32ee9Sbenno 			return f;
21760a32ee9Sbenno 	}
21860a32ee9Sbenno 
21960a32ee9Sbenno 	/*
22060a32ee9Sbenno 	 * Now strip the hostnames from the remote host.
22160a32ee9Sbenno 	 *   rsync://host/module/path -> module/path
22260a32ee9Sbenno 	 *   host::module/path -> module/path
22360a32ee9Sbenno 	 *   host:path -> path
22460a32ee9Sbenno 	 * Also make sure that the remote hosts are the same.
22560a32ee9Sbenno 	 */
22660a32ee9Sbenno 
227f1dcb30aSderaadt 	assert(f->host != NULL);
22860a32ee9Sbenno 	assert(len > 0);
22960a32ee9Sbenno 
23060a32ee9Sbenno 	for (i = 0; i < f->sourcesz; i++) {
23160a32ee9Sbenno 		cp = f->sources[i];
23260a32ee9Sbenno 		j = strlen(cp);
23360a32ee9Sbenno 		if (f->remote &&
234f1dcb30aSderaadt 		    strncasecmp(cp, "rsync://", 8) == 0) {
235786202c1Sjob 			/* rsync://host[:port]/path */
236786202c1Sjob 			size_t module_offset = len;
23760a32ee9Sbenno 			cp += 8;
238786202c1Sjob 			/* skip :port */
239786202c1Sjob 			if ((ccp = strchr(cp, ':')) != NULL) {
24094851233Sderaadt 				*ccp = '\0';
241786202c1Sjob 				module_offset += strcspn(ccp + 1, "/") + 1;
242786202c1Sjob 			}
24360a32ee9Sbenno 			if (strncmp(cp, f->host, len) ||
244f1dcb30aSderaadt 			    (cp[len] != '/' && cp[len] != '\0'))
24505e0b7a0Sclaudio 				errx(ERR_SYNTAX, "different remote host: %s",
24602f20df6Sderaadt 				    f->sources[i]);
24760a32ee9Sbenno 			memmove(f->sources[i],
248786202c1Sjob 				f->sources[i] + module_offset + 8 + 1,
249786202c1Sjob 				j - module_offset - 8);
250f1dcb30aSderaadt 		} else if (f->remote && strncmp(cp, "::", 2) == 0) {
25160a32ee9Sbenno 			/* ::path */
25260a32ee9Sbenno 			memmove(f->sources[i],
25360a32ee9Sbenno 				f->sources[i] + 2, j - 1);
25460a32ee9Sbenno 		} else if (f->remote) {
25560a32ee9Sbenno 			/* host::path */
25660a32ee9Sbenno 			if (strncmp(cp, f->host, len) ||
257f1dcb30aSderaadt 			    (cp[len] != ':' && cp[len] != '\0'))
25805e0b7a0Sclaudio 				errx(ERR_SYNTAX, "different remote host: %s",
25902f20df6Sderaadt 				    f->sources[i]);
260cfc7c364Sbenno 			memmove(f->sources[i], f->sources[i] + len + 2,
261cfc7c364Sbenno 			    j - len - 1);
262f1dcb30aSderaadt 		} else if (cp[0] == ':') {
26360a32ee9Sbenno 			/* :path */
26460a32ee9Sbenno 			memmove(f->sources[i], f->sources[i] + 1, j);
26560a32ee9Sbenno 		} else {
26660a32ee9Sbenno 			/* host:path */
26760a32ee9Sbenno 			if (strncmp(cp, f->host, len) ||
268f1dcb30aSderaadt 			    (cp[len] != ':' && cp[len] != '\0'))
26905e0b7a0Sclaudio 				errx(ERR_SYNTAX, "different remote host: %s",
27002f20df6Sderaadt 				    f->sources[i]);
27160a32ee9Sbenno 			memmove(f->sources[i],
27260a32ee9Sbenno 				f->sources[i] + len + 1, j - len);
27360a32ee9Sbenno 		}
27460a32ee9Sbenno 	}
27560a32ee9Sbenno 
27660a32ee9Sbenno 	return f;
27760a32ee9Sbenno }
27860a32ee9Sbenno 
279f1521a77Sclaudio static struct opts	 opts;
280f1521a77Sclaudio 
281f1521a77Sclaudio #define OP_ADDRESS	1000
282f1521a77Sclaudio #define OP_PORT		1001
283f1521a77Sclaudio #define OP_RSYNCPATH	1002
284f1521a77Sclaudio #define OP_TIMEOUT	1003
28557987d16Sclaudio #define OP_EXCLUDE	1005
28657987d16Sclaudio #define OP_INCLUDE	1006
28757987d16Sclaudio #define OP_EXCLUDE_FROM	1007
28857987d16Sclaudio #define OP_INCLUDE_FROM	1008
289e397242dSclaudio #define OP_COMP_DEST	1009
290e397242dSclaudio #define OP_COPY_DEST	1010
291e397242dSclaudio #define OP_LINK_DEST	1011
29281855fdeSclaudio #define OP_MAX_SIZE	1012
29381855fdeSclaudio #define OP_MIN_SIZE	1013
2942eaa6f4eSjob #define OP_CONTIMEOUT	1014
295f1521a77Sclaudio 
296f1521a77Sclaudio const struct option	 lopts[] = {
297f1521a77Sclaudio     { "address",	required_argument, NULL,		OP_ADDRESS },
298f1521a77Sclaudio     { "archive",	no_argument,	NULL,			'a' },
299e397242dSclaudio     { "compare-dest",	required_argument, NULL,		OP_COMP_DEST },
300e397242dSclaudio #if 0
301e397242dSclaudio     { "copy-dest",	required_argument, NULL,		OP_COPY_DEST },
302e397242dSclaudio     { "link-dest",	required_argument, NULL,		OP_LINK_DEST },
303e397242dSclaudio #endif
304f1521a77Sclaudio     { "compress",	no_argument,	NULL,			'z' },
3052eaa6f4eSjob     { "contimeout",	required_argument, NULL,		OP_CONTIMEOUT },
306f1521a77Sclaudio     { "del",		no_argument,	&opts.del,		1 },
307f1521a77Sclaudio     { "delete",		no_argument,	&opts.del,		1 },
308f1521a77Sclaudio     { "devices",	no_argument,	&opts.devices,		1 },
309f1521a77Sclaudio     { "no-devices",	no_argument,	&opts.devices,		0 },
310f1521a77Sclaudio     { "dry-run",	no_argument,	&opts.dry_run,		1 },
31157987d16Sclaudio     { "exclude",	required_argument, NULL,		OP_EXCLUDE },
31257987d16Sclaudio     { "exclude-from",	required_argument, NULL,		OP_EXCLUDE_FROM },
313f1521a77Sclaudio     { "group",		no_argument,	&opts.preserve_gids,	1 },
314f1521a77Sclaudio     { "no-group",	no_argument,	&opts.preserve_gids,	0 },
315f1521a77Sclaudio     { "help",		no_argument,	NULL,			'h' },
3168d16211cSclaudio     { "ignore-times",	no_argument,	NULL,			'I' },
31757987d16Sclaudio     { "include",	required_argument, NULL,		OP_INCLUDE },
31857987d16Sclaudio     { "include-from",	required_argument, NULL,		OP_INCLUDE_FROM },
319f1521a77Sclaudio     { "links",		no_argument,	&opts.preserve_links,	1 },
32081855fdeSclaudio     { "max-size",	required_argument, NULL,		OP_MAX_SIZE },
32181855fdeSclaudio     { "min-size",	required_argument, NULL,		OP_MIN_SIZE },
322f1521a77Sclaudio     { "no-links",	no_argument,	&opts.preserve_links,	0 },
323f1521a77Sclaudio     { "no-motd",	no_argument,	&opts.no_motd,		1 },
324f1521a77Sclaudio     { "numeric-ids",	no_argument,	&opts.numeric_ids,	1 },
32597d9fd37Sjob     { "omit-dir-times",	no_argument,	&opts.ignore_dir_times,	1 },
32692709fffSclaudio     { "no-O",		no_argument,	&opts.ignore_dir_times,	0 },
32792709fffSclaudio     { "no-omit-dir-times",	no_argument,	&opts.ignore_dir_times, 0 },
32816f87427Sclaudio     { "omit-link-times",	no_argument,	&opts.ignore_link_times, 1 },
32916f87427Sclaudio     { "no-J",		no_argument,	&opts.ignore_link_times, 0 },
33016f87427Sclaudio     { "no-omit-link-times",	no_argument,	&opts.ignore_link_times, 0 },
331f1521a77Sclaudio     { "owner",		no_argument,	&opts.preserve_uids,	1 },
332f1521a77Sclaudio     { "no-owner",	no_argument,	&opts.preserve_uids,	0 },
333f1521a77Sclaudio     { "perms",		no_argument,	&opts.preserve_perms,	1 },
334f1521a77Sclaudio     { "no-perms",	no_argument,	&opts.preserve_perms,	0 },
335f1521a77Sclaudio     { "port",		required_argument, NULL,		OP_PORT },
336f1521a77Sclaudio     { "recursive",	no_argument,	&opts.recursive,	1 },
337f1521a77Sclaudio     { "no-recursive",	no_argument,	&opts.recursive,	0 },
338f1521a77Sclaudio     { "rsh",		required_argument, NULL,		'e' },
339f1521a77Sclaudio     { "rsync-path",	required_argument, NULL,		OP_RSYNCPATH },
340f1521a77Sclaudio     { "sender",		no_argument,	&opts.sender,		1 },
341f1521a77Sclaudio     { "server",		no_argument,	&opts.server,		1 },
3428d16211cSclaudio     { "size-only",	no_argument,	&opts.size_only,	1 },
343f1521a77Sclaudio     { "specials",	no_argument,	&opts.specials,		1 },
344f1521a77Sclaudio     { "no-specials",	no_argument,	&opts.specials,		0 },
345f1521a77Sclaudio     { "timeout",	required_argument, NULL,		OP_TIMEOUT },
346f1521a77Sclaudio     { "times",		no_argument,	&opts.preserve_times,	1 },
347f1521a77Sclaudio     { "no-times",	no_argument,	&opts.preserve_times,	0 },
348f1521a77Sclaudio     { "verbose",	no_argument,	&verbose,		1 },
349f1521a77Sclaudio     { "no-verbose",	no_argument,	&verbose,		0 },
350d239da0eSclaudio     { "version",	no_argument,	NULL,			'V' },
351f1521a77Sclaudio     { NULL,		0,		NULL,			0 }
352f1521a77Sclaudio };
353f1521a77Sclaudio 
35460a32ee9Sbenno int
35560a32ee9Sbenno main(int argc, char *argv[])
35660a32ee9Sbenno {
35760a32ee9Sbenno 	pid_t		 child;
358e397242dSclaudio 	int		 fds[2], sd = -1, rc, c, st, i, lidx;
359e397242dSclaudio 	size_t		 basedir_cnt = 0;
360ef859540Sderaadt 	struct sess	 sess;
36160a32ee9Sbenno 	struct fargs	*fargs;
362ef859540Sderaadt 	char		**args;
363a84b4914Sclaudio 	const char	*errstr;
36457987d16Sclaudio 
36560a32ee9Sbenno 	/* Global pledge. */
36660a32ee9Sbenno 
367434f41cdSflorian 	if (pledge("stdio unix rpath wpath cpath dpath inet fattr chown dns getpw proc exec unveil",
368f1dcb30aSderaadt 	    NULL) == -1)
36905e0b7a0Sclaudio 		err(ERR_IPC, "pledge");
37060a32ee9Sbenno 
37182ecafa1Sclaudio 	opts.max_size = opts.min_size = -1;
37282ecafa1Sclaudio 
37316f87427Sclaudio 	while ((c = getopt_long(argc, argv, "aDe:ghIJlnOoprtVvxz",
37416f87427Sclaudio 	    lopts, &lidx)) != -1) {
37560a32ee9Sbenno 		switch (c) {
376434f41cdSflorian 		case 'D':
377434f41cdSflorian 			opts.devices = 1;
378434f41cdSflorian 			opts.specials = 1;
379434f41cdSflorian 			break;
380434f41cdSflorian 		case 'a':
381434f41cdSflorian 			opts.recursive = 1;
382434f41cdSflorian 			opts.preserve_links = 1;
383434f41cdSflorian 			opts.preserve_perms = 1;
384434f41cdSflorian 			opts.preserve_times = 1;
385434f41cdSflorian 			opts.preserve_gids = 1;
386434f41cdSflorian 			opts.preserve_uids = 1;
387434f41cdSflorian 			opts.devices = 1;
388434f41cdSflorian 			opts.specials = 1;
389434f41cdSflorian 			break;
39060a32ee9Sbenno 		case 'e':
391474155c9Sderaadt 			opts.ssh_prog = optarg;
39260a32ee9Sbenno 			break;
3934fd63c86Sbenno 		case 'g':
3944fd63c86Sbenno 			opts.preserve_gids = 1;
3954fd63c86Sbenno 			break;
3968d16211cSclaudio 		case 'I':
3978d16211cSclaudio 			opts.ignore_times = 1;
3988d16211cSclaudio 			break;
39916f87427Sclaudio 		case 'J':
40016f87427Sclaudio 			opts.ignore_link_times = 1;
40116f87427Sclaudio 			break;
40260a32ee9Sbenno 		case 'l':
40360a32ee9Sbenno 			opts.preserve_links = 1;
40460a32ee9Sbenno 			break;
40560a32ee9Sbenno 		case 'n':
40660a32ee9Sbenno 			opts.dry_run = 1;
40760a32ee9Sbenno 			break;
40897d9fd37Sjob 		case 'O':
40997d9fd37Sjob 			opts.ignore_dir_times = 1;
41097d9fd37Sjob 			break;
4118f34fbc5Sflorian 		case 'o':
4128f34fbc5Sflorian 			opts.preserve_uids = 1;
4138f34fbc5Sflorian 			break;
41460a32ee9Sbenno 		case 'p':
41560a32ee9Sbenno 			opts.preserve_perms = 1;
41660a32ee9Sbenno 			break;
41760a32ee9Sbenno 		case 'r':
41860a32ee9Sbenno 			opts.recursive = 1;
41960a32ee9Sbenno 			break;
42060a32ee9Sbenno 		case 't':
42160a32ee9Sbenno 			opts.preserve_times = 1;
42260a32ee9Sbenno 			break;
42360a32ee9Sbenno 		case 'v':
424b2a7eac7Sbenno 			verbose++;
42560a32ee9Sbenno 			break;
426d239da0eSclaudio 		case 'V':
427*5be4168dSjob 			fprintf(stderr, "openrsync %s (protocol version %u)\n",
428*5be4168dSjob 			    RSYNC_VERSION, RSYNC_PROTOCOL);
429d239da0eSclaudio 			exit(0);
4301c3d4160Sbket 		case 'x':
4311c3d4160Sbket 			opts.one_file_system++;
4321c3d4160Sbket 			break;
4334eeef55cSderaadt 		case 'z':
4344eeef55cSderaadt 			fprintf(stderr, "%s: -z not supported yet\n", getprogname());
4354eeef55cSderaadt 			break;
43660a32ee9Sbenno 		case 0:
43760a32ee9Sbenno 			/* Non-NULL flag values (e.g., --sender). */
43860a32ee9Sbenno 			break;
439f1521a77Sclaudio 		case OP_ADDRESS:
440a52e5c3aSclaudio 			opts.address = optarg;
441a52e5c3aSclaudio 			break;
4422eaa6f4eSjob 		case OP_CONTIMEOUT:
4432eaa6f4eSjob 			poll_contimeout = strtonum(optarg, 0, 60*60, &errstr);
4442eaa6f4eSjob 			if (errstr != NULL)
4452eaa6f4eSjob 				errx(ERR_SYNTAX, "timeout is %s: %s",
4462eaa6f4eSjob 				    errstr, optarg);
4472eaa6f4eSjob 			break;
448f1521a77Sclaudio 		case OP_PORT:
449f1521a77Sclaudio 			opts.port = optarg;
450f1521a77Sclaudio 			break;
451f1521a77Sclaudio 		case OP_RSYNCPATH:
452f1521a77Sclaudio 			opts.rsync_path = optarg;
453f1521a77Sclaudio 			break;
454f1521a77Sclaudio 		case OP_TIMEOUT:
455a84b4914Sclaudio 			poll_timeout = strtonum(optarg, 0, 60*60, &errstr);
456a84b4914Sclaudio 			if (errstr != NULL)
45705e0b7a0Sclaudio 				errx(ERR_SYNTAX, "timeout is %s: %s",
45805e0b7a0Sclaudio 				    errstr, optarg);
459a84b4914Sclaudio 			break;
46057987d16Sclaudio 		case OP_EXCLUDE:
46157987d16Sclaudio 			if (parse_rule(optarg, RULE_EXCLUDE) == -1)
46257987d16Sclaudio 				errx(ERR_SYNTAX, "syntax error in exclude: %s",
46357987d16Sclaudio 				    optarg);
46457987d16Sclaudio 			break;
46557987d16Sclaudio 		case OP_INCLUDE:
46657987d16Sclaudio 			if (parse_rule(optarg, RULE_INCLUDE) == -1)
46757987d16Sclaudio 				errx(ERR_SYNTAX, "syntax error in include: %s",
46857987d16Sclaudio 				    optarg);
46957987d16Sclaudio 			break;
47057987d16Sclaudio 		case OP_EXCLUDE_FROM:
4710345af14Sclaudio 			parse_file(optarg, RULE_EXCLUDE);
47257987d16Sclaudio 			break;
47357987d16Sclaudio 		case OP_INCLUDE_FROM:
4740345af14Sclaudio 			parse_file(optarg, RULE_INCLUDE);
47557987d16Sclaudio 			break;
476e397242dSclaudio 		case OP_COMP_DEST:
477e397242dSclaudio 			if (opts.alt_base_mode !=0 &&
478e397242dSclaudio 			    opts.alt_base_mode != BASE_MODE_COMPARE) {
479e397242dSclaudio 				errx(1, "option --%s conflicts with %s",
480e397242dSclaudio 				    lopts[lidx].name,
481e397242dSclaudio 				    alt_base_mode(opts.alt_base_mode));
482e397242dSclaudio 			}
483e397242dSclaudio 			opts.alt_base_mode = BASE_MODE_COMPARE;
484e397242dSclaudio #if 0
485e397242dSclaudio 			goto basedir;
486e397242dSclaudio 		case OP_COPY_DEST:
487e397242dSclaudio 			if (opts.alt_base_mode !=0 &&
488e397242dSclaudio 			    opts.alt_base_mode != BASE_MODE_COPY) {
489e397242dSclaudio 				errx(1, "option --%s conflicts with %s",
490e397242dSclaudio 				    lopts[lidx].name,
491e397242dSclaudio 				    alt_base_mode(opts.alt_base_mode));
492e397242dSclaudio 			}
493e397242dSclaudio 			opts.alt_base_mode = BASE_MODE_COPY;
494e397242dSclaudio 			goto basedir;
495e397242dSclaudio 		case OP_LINK_DEST:
496e397242dSclaudio 			if (opts.alt_base_mode !=0 &&
497e397242dSclaudio 			    opts.alt_base_mode != BASE_MODE_LINK) {
498e397242dSclaudio 				errx(1, "option --%s conflicts with %s",
499e397242dSclaudio 				    lopts[lidx].name,
500e397242dSclaudio 				    alt_base_mode(opts.alt_base_mode));
501e397242dSclaudio 			}
502e397242dSclaudio 			opts.alt_base_mode = BASE_MODE_LINK;
503e397242dSclaudio 
504e397242dSclaudio basedir:
505e397242dSclaudio #endif
506e397242dSclaudio 			if (basedir_cnt >= MAX_BASEDIR)
507e397242dSclaudio 				errx(1, "too many --%s directories specified",
508e397242dSclaudio 				    lopts[lidx].name);
509e397242dSclaudio 			opts.basedir[basedir_cnt++] = optarg;
510e397242dSclaudio 			break;
51181855fdeSclaudio 		case OP_MAX_SIZE:
51282ecafa1Sclaudio 			if (scan_scaled(optarg, &opts.max_size) == -1)
51382ecafa1Sclaudio 				err(1, "bad max-size");
51482ecafa1Sclaudio 			break;
51581855fdeSclaudio 		case OP_MIN_SIZE:
51682ecafa1Sclaudio 			if (scan_scaled(optarg, &opts.min_size) == -1)
51782ecafa1Sclaudio 				err(1, "bad min-size");
51881855fdeSclaudio 			break;
519b100cc17Sderaadt 		case 'h':
52060a32ee9Sbenno 		default:
52160a32ee9Sbenno 			goto usage;
52260a32ee9Sbenno 		}
52360a32ee9Sbenno 	}
52460a32ee9Sbenno 
52560a32ee9Sbenno 	argc -= optind;
52660a32ee9Sbenno 	argv += optind;
52760a32ee9Sbenno 
52860a32ee9Sbenno 	/* FIXME: reference implementation rsync accepts this. */
52960a32ee9Sbenno 
53060a32ee9Sbenno 	if (argc < 2)
53160a32ee9Sbenno 		goto usage;
53260a32ee9Sbenno 
53394851233Sderaadt 	if (opts.port == NULL)
5346304135bSbenno 		opts.port = "rsync";
53594851233Sderaadt 
5362eaa6f4eSjob 	/* by default and for --contimeout=0 disable poll_contimeout */
5372eaa6f4eSjob 	if (poll_contimeout == 0)
5382eaa6f4eSjob 		poll_contimeout = -1;
5392eaa6f4eSjob 	else
5402eaa6f4eSjob 		poll_contimeout *= 1000;
5412eaa6f4eSjob 
54245646037Sclaudio 	/* by default and for --timeout=0 disable poll_timeout */
54345646037Sclaudio 	if (poll_timeout == 0)
5442eaa6f4eSjob 		poll_timeout = -1;
5452eaa6f4eSjob 	else
54645646037Sclaudio 		poll_timeout *= 1000;
54745646037Sclaudio 
54860a32ee9Sbenno 	/*
54960a32ee9Sbenno 	 * This is what happens when we're started with the "hidden"
55060a32ee9Sbenno 	 * --server option, which is invoked for the rsync on the remote
55160a32ee9Sbenno 	 * host by the parent.
55260a32ee9Sbenno 	 */
55360a32ee9Sbenno 
554ef859540Sderaadt 	if (opts.server)
555ef859540Sderaadt 		exit(rsync_server(&opts, (size_t)argc, argv));
55660a32ee9Sbenno 
55760a32ee9Sbenno 	/*
55860a32ee9Sbenno 	 * Now we know that we're the client on the local machine
55960a32ee9Sbenno 	 * invoking rsync(1).
56060a32ee9Sbenno 	 * At this point, we need to start the client and server
56160a32ee9Sbenno 	 * initiation logic.
56260a32ee9Sbenno 	 * The client is what we continue running on this host; the
56360a32ee9Sbenno 	 * server is what we'll use to connect to the remote and
56460a32ee9Sbenno 	 * invoke rsync with the --server option.
56560a32ee9Sbenno 	 */
56660a32ee9Sbenno 
56794851233Sderaadt 	fargs = fargs_parse(argc, argv, &opts);
568f1dcb30aSderaadt 	assert(fargs != NULL);
56960a32ee9Sbenno 
57060a32ee9Sbenno 	/*
57160a32ee9Sbenno 	 * If we're contacting an rsync:// daemon, then we don't need to
57260a32ee9Sbenno 	 * fork, because we won't start a server ourselves.
573ac024dd4Snaddy 	 * Route directly into the socket code, unless a remote shell
574ac024dd4Snaddy 	 * has explicitly been specified.
57560a32ee9Sbenno 	 */
57660a32ee9Sbenno 
577ac024dd4Snaddy 	if (fargs->remote && opts.ssh_prog == NULL) {
578f1dcb30aSderaadt 		assert(fargs->mode == FARGS_RECEIVER);
5790724efd0Snaddy 		if ((rc = rsync_connect(&opts, &sd, fargs)) == 0) {
580ac024dd4Snaddy 			rc = rsync_socket(&opts, sd, fargs);
5810724efd0Snaddy 			close(sd);
5820724efd0Snaddy 		}
583ac024dd4Snaddy 		exit(rc);
58460a32ee9Sbenno 	}
58560a32ee9Sbenno 
58660a32ee9Sbenno 	/* Drop the dns/inet possibility. */
58760a32ee9Sbenno 
588434f41cdSflorian 	if (pledge("stdio unix rpath wpath cpath dpath fattr chown getpw proc exec unveil",
589f1dcb30aSderaadt 	    NULL) == -1)
59005e0b7a0Sclaudio 		err(ERR_IPC, "pledge");
59160a32ee9Sbenno 
59260a32ee9Sbenno 	/* Create a bidirectional socket and start our child. */
59360a32ee9Sbenno 
594f1dcb30aSderaadt 	if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, fds) == -1)
59505e0b7a0Sclaudio 		err(ERR_IPC, "socketpair");
59660a32ee9Sbenno 
597ef859540Sderaadt 	switch ((child = fork())) {
598ef859540Sderaadt 	case -1:
59905e0b7a0Sclaudio 		err(ERR_IPC, "fork");
600ef859540Sderaadt 	case 0:
60160a32ee9Sbenno 		close(fds[0]);
602f1dcb30aSderaadt 		if (pledge("stdio exec", NULL) == -1)
60305e0b7a0Sclaudio 			err(ERR_IPC, "pledge");
604ef859540Sderaadt 
605ef859540Sderaadt 		memset(&sess, 0, sizeof(struct sess));
606ef859540Sderaadt 		sess.opts = &opts;
607ef859540Sderaadt 
60805e0b7a0Sclaudio 		args = fargs_cmdline(&sess, fargs, NULL);
60960a32ee9Sbenno 
610ef859540Sderaadt 		for (i = 0; args[i] != NULL; i++)
611b2a7eac7Sbenno 			LOG2("exec[%d] = %s", i, args[i]);
612ef859540Sderaadt 
613ef859540Sderaadt 		/* Make sure the child's stdin is from the sender. */
61405e0b7a0Sclaudio 		if (dup2(fds[1], STDIN_FILENO) == -1)
61505e0b7a0Sclaudio 			err(ERR_IPC, "dup2");
61605e0b7a0Sclaudio 		if (dup2(fds[1], STDOUT_FILENO) == -1)
61705e0b7a0Sclaudio 			err(ERR_IPC, "dup2");
618ef859540Sderaadt 		execvp(args[0], args);
61905e0b7a0Sclaudio 		_exit(ERR_IPC);
620ef859540Sderaadt 		/* NOTREACHED */
621ef859540Sderaadt 	default:
62260a32ee9Sbenno 		close(fds[1]);
623ac024dd4Snaddy 		if (!fargs->remote)
6246304135bSbenno 			rc = rsync_client(&opts, fds[0], fargs);
625ac024dd4Snaddy 		else
626ac024dd4Snaddy 			rc = rsync_socket(&opts, fds[0], fargs);
627ef859540Sderaadt 		break;
628ef859540Sderaadt 	}
62960a32ee9Sbenno 
63060a32ee9Sbenno 	close(fds[0]);
63160a32ee9Sbenno 
632f1dcb30aSderaadt 	if (waitpid(child, &st, 0) == -1)
63305e0b7a0Sclaudio 		err(ERR_WAITPID, "waitpid");
63483649630Sderaadt 
63583649630Sderaadt 	/*
63683649630Sderaadt 	 * If we don't already have an error (rc == 0), then inherit the
63783649630Sderaadt 	 * error code of rsync_server() if it has exited.
63883649630Sderaadt 	 * If it hasn't exited, it overrides our return value.
63983649630Sderaadt 	 */
64083649630Sderaadt 
64105e0b7a0Sclaudio 	if (rc == 0) {
64205e0b7a0Sclaudio 		if (WIFEXITED(st))
64383649630Sderaadt 			rc = WEXITSTATUS(st);
64405e0b7a0Sclaudio 		else if (WIFSIGNALED(st))
64505e0b7a0Sclaudio 			rc = ERR_TERMIMATED;
64605e0b7a0Sclaudio 		else
64705e0b7a0Sclaudio 			rc = ERR_WAITPID;
64805e0b7a0Sclaudio 	}
64983649630Sderaadt 
650ef859540Sderaadt 	exit(rc);
65160a32ee9Sbenno usage:
652cf0720cbSschwarze 	fprintf(stderr, "usage: %s"
65316f87427Sclaudio 	    " [-aDgIJlnOoprtVvx] [-e program] [--address=sourceaddr]\n"
654e878ec53Stb 	    "\t[--contimeout=seconds] [--compare-dest=dir] [--del] [--exclude]\n"
6552eaa6f4eSjob 	    "\t[--exclude-from=file] [--include] [--include-from=file]\n"
6562eaa6f4eSjob 	    "\t[--no-motd] [--numeric-ids] [--port=portnumber]\n"
6578d16211cSclaudio 	    "\t[--rsync-path=program] [--size-only] [--timeout=seconds]\n"
6582eaa6f4eSjob 	    "\tsource ... directory\n",
65960a32ee9Sbenno 	    getprogname());
66005e0b7a0Sclaudio 	exit(ERR_SYNTAX);
66160a32ee9Sbenno }
662