Annotation of src/usr.bin/rsync/main.c, Revision 1.3
1.3 ! benno 1: /* $Id: main.c,v 1.2 2019/02/10 23:24:14 benno Exp $ */
1.1 benno 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:
30: #include "extern.h"
31:
32: static void
33: fargs_free(struct fargs *p)
34: {
35: size_t i;
36:
37: if (NULL == p)
38: return;
39:
40: if (NULL != p->sources)
41: for (i = 0; i < p->sourcesz; i++)
42: free(p->sources[i]);
43:
44: free(p->sources);
45: free(p->sink);
46: free(p->host);
47: free(p);
48: }
49:
50: /*
51: * A remote host is has a colon before the first path separator.
52: * This works for rsh remote hosts (host:/foo/bar), implicit rsync
53: * remote hosts (host::/foo/bar), and explicit (rsync://host/foo).
54: * Return zero if local, non-zero if remote.
55: */
56: static int
57: fargs_is_remote(const char *v)
58: {
59: size_t pos;
60:
61: pos = strcspn(v, ":/");
62: return ':' == v[pos];
63: }
64:
65: /*
66: * Test whether a remote host is specifically an rsync daemon.
67: * Return zero if not, non-zero if so.
68: */
69: static int
70: fargs_is_daemon(const char *v)
71: {
72: size_t pos;
73:
74: if (0 == strncasecmp(v, "rsync://", 8))
75: return 1;
76:
77: pos = strcspn(v, ":/");
78: return ':' == v[pos] && ':' == v[pos + 1];
79: }
80:
81: /*
82: * Take the command-line filenames (e.g., rsync foo/ bar/ baz/) and
83: * determine our operating mode.
84: * For example, if the first argument is a remote file, this means that
85: * we're going to transfer from the remote to the local.
86: * We also make sure that the arguments are consistent, that is, if
87: * we're going to transfer from the local to the remote, that no
88: * filenames for the local transfer indicate remote hosts.
89: * Always returns the parsed and sanitised options.
90: */
91: static struct fargs *
92: fargs_parse(size_t argc, char *argv[])
93: {
94: struct fargs *f = NULL;
95: char *cp;
96: size_t i, j, len = 0;
97:
98: /* Allocations. */
99:
100: if (NULL == (f = calloc(1, sizeof(struct fargs))))
101: err(EXIT_FAILURE, "calloc");
102:
103: f->sourcesz = argc - 1;
104: if (NULL == (f->sources = calloc(f->sourcesz, sizeof(char *))))
105: err(EXIT_FAILURE, "calloc");
106:
107: for (i = 0; i < argc - 1; i++)
108: if (NULL == (f->sources[i] = strdup(argv[i])))
109: err(EXIT_FAILURE, "strdup");
110:
111: if (NULL == (f->sink = strdup(argv[i])))
112: err(EXIT_FAILURE, "strdup");
113:
114: /*
115: * Test files for its locality.
116: * If the last is a remote host, then we're sending from the
117: * local to the remote host ("sender" mode).
118: * If the first, remote to local ("receiver" mode).
119: * If neither, a local transfer in sender style.
120: */
121:
122: f->mode = FARGS_SENDER;
123:
124: if (fargs_is_remote(f->sink)) {
125: f->mode = FARGS_SENDER;
126: if (NULL == (f->host = strdup(f->sink)))
127: err(EXIT_FAILURE, "strdup");
128: }
129:
130: if (fargs_is_remote(f->sources[0])) {
131: if (NULL != f->host)
132: errx(EXIT_FAILURE, "both source and "
133: "destination cannot be remote files");
134: f->mode = FARGS_RECEIVER;
135: if (NULL == (f->host = strdup(f->sources[0])))
136: err(EXIT_FAILURE, "strdup");
137: }
138:
139: if (NULL != f->host) {
140: if (0 == strncasecmp(f->host, "rsync://", 8)) {
141: /* rsync://host/module[/path] */
142: f->remote = 1;
143: len = strlen(f->host) - 8 + 1;
144: memmove(f->host, f->host + 8, len);
145: if (NULL == (cp = strchr(f->host, '/')))
146: errx(EXIT_FAILURE, "rsync protocol "
147: "requires a module name");
148: *cp++ = '\0';
149: f->module = cp;
150: if (NULL != (cp = strchr(f->module, '/')))
151: *cp = '\0';
152: } else {
153: /* host:[/path] */
154: cp = strchr(f->host, ':');
155: assert(NULL != cp);
156: *cp++ = '\0';
157: if (':' == *cp) {
158: /* host::module[/path] */
159: f->remote = 1;
160: f->module = ++cp;
161: cp = strchr(f->module, '/');
162: if (NULL != cp)
163: *cp = '\0';
164: }
165: }
166: if (0 == (len = strlen(f->host)))
167: errx(EXIT_FAILURE, "empty remote host");
168: if (f->remote && 0 == strlen(f->module))
169: errx(EXIT_FAILURE, "empty remote module");
170: }
171:
172: /* Make sure we have the same "hostspec" for all files. */
173:
174: if ( ! f->remote) {
175: if (FARGS_SENDER == f->mode)
176: for (i = 0; i < f->sourcesz; i++) {
177: if ( ! fargs_is_remote(f->sources[i]))
178: continue;
179: errx(EXIT_FAILURE, "remote file in "
180: "list of local sources: %s",
181: f->sources[i]);
182: }
183: if (FARGS_RECEIVER == f->mode)
184: for (i = 0; i < f->sourcesz; i++) {
185: if (fargs_is_remote(f->sources[i]) &&
186: ! fargs_is_daemon(f->sources[i]))
187: continue;
188: if (fargs_is_daemon(f->sources[i]))
189: errx(EXIT_FAILURE, "remote "
190: "daemon in list of "
191: "remote sources: %s",
192: f->sources[i]);
193: errx(EXIT_FAILURE, "local file in "
194: "list of remote sources: %s",
195: f->sources[i]);
196: }
197: } else {
198: if (FARGS_RECEIVER != f->mode)
199: errx(EXIT_FAILURE, "sender mode for remote "
200: "daemon receivers not yet supported");
201: for (i = 0; i < f->sourcesz; i++) {
202: if (fargs_is_daemon(f->sources[i]))
203: continue;
204: errx(EXIT_FAILURE, "non-remote daemon file "
205: "in list of remote daemon sources: "
206: "%s", f->sources[i]);
207: }
208: }
209:
210: /*
211: * If we're not remote and a sender, strip our hostname.
212: * Then exit if we're a sender or a local connection.
213: */
214:
215: if ( ! f->remote) {
216: if (NULL == f->host)
217: return f;
218: if (FARGS_SENDER == f->mode) {
219: assert(NULL != f->host);
220: assert(len > 0);
221: j = strlen(f->sink);
222: memmove(f->sink, f->sink + len + 1, j - len);
223: return f;
224: } else if (FARGS_RECEIVER != f->mode)
225: return f;
226: }
227:
228: /*
229: * Now strip the hostnames from the remote host.
230: * rsync://host/module/path -> module/path
231: * host::module/path -> module/path
232: * host:path -> path
233: * Also make sure that the remote hosts are the same.
234: */
235:
236: assert(NULL != f->host);
237: assert(len > 0);
238:
239: for (i = 0; i < f->sourcesz; i++) {
240: cp = f->sources[i];
241: j = strlen(cp);
242: if (f->remote &&
243: 0 == strncasecmp(cp, "rsync://", 8)) {
244: /* rsync://path */
245: cp += 8;
246: if (strncmp(cp, f->host, len) ||
247: ('/' != cp[len] && '\0' != cp[len]))
248: errx(EXIT_FAILURE, "different remote "
249: "host: %s", f->sources[i]);
250: memmove(f->sources[i],
251: f->sources[i] + len + 8 + 1,
252: j - len - 8);
253: } else if (f->remote && 0 == strncmp(cp, "::", 2)) {
254: /* ::path */
255: memmove(f->sources[i],
256: f->sources[i] + 2, j - 1);
257: } else if (f->remote) {
258: /* host::path */
259: if (strncmp(cp, f->host, len) ||
260: (':' != cp[len] && '\0' != cp[len]))
261: errx(EXIT_FAILURE, "different remote "
262: "host: %s", f->sources[i]);
263: memmove(f->sources[i],
264: f->sources[i] + len + 2,
265: j - len - 1);
266: } else if (':' == cp[0]) {
267: /* :path */
268: memmove(f->sources[i], f->sources[i] + 1, j);
269: } else {
270: /* host:path */
271: if (strncmp(cp, f->host, len) ||
272: (':' != cp[len] && '\0' != cp[len]))
273: errx(EXIT_FAILURE, "different remote "
274: "host: %s", f->sources[i]);
275: memmove(f->sources[i],
276: f->sources[i] + len + 1, j - len);
277: }
278: }
279:
280: return f;
281: }
282:
283: int
284: main(int argc, char *argv[])
285: {
286: struct opts opts;
1.2 benno 287: pid_t child;
288: int fds[2], flags, c, st;
1.1 benno 289: struct fargs *fargs;
290: struct option lopts[] = {
291: { "delete", no_argument, &opts.del, 1 },
292: { "rsync-path", required_argument, NULL, 1 },
293: { "sender", no_argument, &opts.sender, 1 },
294: { "server", no_argument, &opts.server, 1 },
295: { NULL, 0, NULL, 0 }};
296:
297: /* Global pledge. */
298:
1.3 ! benno 299: if (-1 == pledge("stdio rpath wpath cpath inet fattr dns proc exec "
! 300: "unveil", NULL))
1.1 benno 301: err(EXIT_FAILURE, "pledge");
302:
303: memset(&opts, 0, sizeof(struct opts));
304:
305: for (;;) {
306: c = getopt_long(argc, argv, "e:lnprtv", lopts, NULL);
307: if (-1 == c)
308: break;
309: switch (c) {
310: case 'e':
311: /* Ignore. */
312: break;
313: case 'l':
314: opts.preserve_links = 1;
315: break;
316: case 'n':
317: opts.dry_run = 1;
318: break;
319: case 'p':
320: opts.preserve_perms = 1;
321: break;
322: case 'r':
323: opts.recursive = 1;
324: break;
325: case 't':
326: opts.preserve_times = 1;
327: break;
328: case 'v':
329: opts.verbose++;
330: break;
331: case 0:
332: /* Non-NULL flag values (e.g., --sender). */
333: break;
334: case 1:
335: opts.rsync_path = optarg;
336: break;
337: default:
338: goto usage;
339: }
340: }
341:
342: argc -= optind;
343: argv += optind;
344:
345: /* FIXME: reference implementation rsync accepts this. */
346:
347: if (argc < 2)
348: goto usage;
349:
350: /*
351: * This is what happens when we're started with the "hidden"
352: * --server option, which is invoked for the rsync on the remote
353: * host by the parent.
354: */
355:
356: if (opts.server) {
1.3 ! benno 357: if (-1 == pledge("stdio rpath wpath cpath fattr unveil", NULL))
1.1 benno 358: err(EXIT_FAILURE, "pledge");
359: c = rsync_server(&opts, (size_t)argc, argv);
360: return c ? EXIT_SUCCESS : EXIT_FAILURE;
361: }
362:
363: /*
364: * Now we know that we're the client on the local machine
365: * invoking rsync(1).
366: * At this point, we need to start the client and server
367: * initiation logic.
368: * The client is what we continue running on this host; the
369: * server is what we'll use to connect to the remote and
370: * invoke rsync with the --server option.
371: */
372:
373: fargs = fargs_parse(argc, argv);
374: assert(NULL != fargs);
375:
376: /*
377: * If we're contacting an rsync:// daemon, then we don't need to
378: * fork, because we won't start a server ourselves.
379: * Route directly into the socket code, in that case.
380: */
381:
382: if (fargs->remote) {
383: assert(FARGS_RECEIVER == fargs->mode);
1.3 ! benno 384: if (-1 == pledge("stdio rpath wpath cpath inet fattr dns "
! 385: "unveil", NULL))
1.1 benno 386: err(EXIT_FAILURE, "pledge");
387: c = rsync_socket(&opts, fargs);
388: fargs_free(fargs);
389: return c ? EXIT_SUCCESS : EXIT_FAILURE;
390: }
391:
392: /* Drop the dns/inet possibility. */
393:
1.3 ! benno 394: if (-1 == pledge("stdio rpath wpath cpath fattr proc exec unveil",
! 395: NULL))
1.1 benno 396: err(EXIT_FAILURE, "pledge");
397:
398: /* Create a bidirectional socket and start our child. */
399:
400: flags = SOCK_STREAM | SOCK_NONBLOCK;
401:
402: if (-1 == socketpair(AF_UNIX, flags, 0, fds))
403: err(EXIT_FAILURE, "socketpair");
404:
405: if (-1 == (child = fork())) {
406: close(fds[0]);
407: close(fds[1]);
408: err(EXIT_FAILURE, "fork");
409: }
410:
411: /* Drop the fork possibility. */
412:
1.3 ! benno 413: if (-1 == pledge("stdio rpath wpath cpath fattr exec unveil", NULL))
1.1 benno 414: err(EXIT_FAILURE, "pledge");
415:
416: if (0 == child) {
417: close(fds[0]);
418: fds[0] = -1;
1.3 ! benno 419: if (-1 == pledge("stdio exec", NULL))
1.1 benno 420: err(EXIT_FAILURE, "pledge");
421: rsync_child(&opts, fds[1], fargs);
422: /* NOTREACHED */
423: }
424:
425: close(fds[1]);
426: fds[1] = -1;
1.3 ! benno 427: if (-1 == pledge("stdio rpath wpath cpath fattr unveil", NULL))
1.1 benno 428: err(EXIT_FAILURE, "pledge");
429: c = rsync_client(&opts, fds[0], fargs);
430: fargs_free(fargs);
431:
432: /*
433: * If the client has an error and exits, the server may be
434: * sitting around waiting to get data while we waitpid().
435: * So close the connection here so that they don't hang.
436: */
437:
438: if ( ! c) {
439: close(fds[0]);
440: fds[0] = -1;
441: }
442:
443: if (-1 == waitpid(child, &st, 0))
444: err(EXIT_FAILURE, "waitpid");
445: if ( ! (WIFEXITED(st) && EXIT_SUCCESS == WEXITSTATUS(st)))
446: c = 0;
447:
448: if (-1 != fds[0])
449: close(fds[0]);
450: return c ? EXIT_SUCCESS : EXIT_FAILURE;
451: usage:
452: fprintf(stderr, "usage: %s [-lnprtv] "
453: "[--delete] [--rsync-path=prog] src ... dst\n",
454: getprogname());
455: return EXIT_FAILURE;
456: }