Annotation of src/usr.bin/rsync/main.c, Revision 1.1
1.1 ! benno 1: /* $Id$ */
! 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;
! 287: pid_t child;
! 288: int fds[2], flags, c, st;
! 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:
! 299: if (-1 == pledge("dns inet unveil exec stdio rpath wpath cpath proc fattr", NULL))
! 300: err(EXIT_FAILURE, "pledge");
! 301:
! 302: memset(&opts, 0, sizeof(struct opts));
! 303:
! 304: for (;;) {
! 305: c = getopt_long(argc, argv, "e:lnprtv", lopts, NULL);
! 306: if (-1 == c)
! 307: break;
! 308: switch (c) {
! 309: case 'e':
! 310: /* Ignore. */
! 311: break;
! 312: case 'l':
! 313: opts.preserve_links = 1;
! 314: break;
! 315: case 'n':
! 316: opts.dry_run = 1;
! 317: break;
! 318: case 'p':
! 319: opts.preserve_perms = 1;
! 320: break;
! 321: case 'r':
! 322: opts.recursive = 1;
! 323: break;
! 324: case 't':
! 325: opts.preserve_times = 1;
! 326: break;
! 327: case 'v':
! 328: opts.verbose++;
! 329: break;
! 330: case 0:
! 331: /* Non-NULL flag values (e.g., --sender). */
! 332: break;
! 333: case 1:
! 334: opts.rsync_path = optarg;
! 335: break;
! 336: default:
! 337: goto usage;
! 338: }
! 339: }
! 340:
! 341: argc -= optind;
! 342: argv += optind;
! 343:
! 344: /* FIXME: reference implementation rsync accepts this. */
! 345:
! 346: if (argc < 2)
! 347: goto usage;
! 348:
! 349: /*
! 350: * This is what happens when we're started with the "hidden"
! 351: * --server option, which is invoked for the rsync on the remote
! 352: * host by the parent.
! 353: */
! 354:
! 355: if (opts.server) {
! 356: if (-1 == pledge("unveil rpath cpath wpath stdio fattr", NULL))
! 357: err(EXIT_FAILURE, "pledge");
! 358: c = rsync_server(&opts, (size_t)argc, argv);
! 359: return c ? EXIT_SUCCESS : EXIT_FAILURE;
! 360: }
! 361:
! 362: /*
! 363: * Now we know that we're the client on the local machine
! 364: * invoking rsync(1).
! 365: * At this point, we need to start the client and server
! 366: * initiation logic.
! 367: * The client is what we continue running on this host; the
! 368: * server is what we'll use to connect to the remote and
! 369: * invoke rsync with the --server option.
! 370: */
! 371:
! 372: fargs = fargs_parse(argc, argv);
! 373: assert(NULL != fargs);
! 374:
! 375: /*
! 376: * If we're contacting an rsync:// daemon, then we don't need to
! 377: * fork, because we won't start a server ourselves.
! 378: * Route directly into the socket code, in that case.
! 379: */
! 380:
! 381: if (fargs->remote) {
! 382: assert(FARGS_RECEIVER == fargs->mode);
! 383: if (-1 == pledge("dns inet unveil stdio rpath wpath cpath fattr", NULL))
! 384: err(EXIT_FAILURE, "pledge");
! 385: c = rsync_socket(&opts, fargs);
! 386: fargs_free(fargs);
! 387: return c ? EXIT_SUCCESS : EXIT_FAILURE;
! 388: }
! 389:
! 390: /* Drop the dns/inet possibility. */
! 391:
! 392: if (-1 == pledge("unveil exec stdio rpath wpath cpath proc fattr", NULL))
! 393: err(EXIT_FAILURE, "pledge");
! 394:
! 395: /* Create a bidirectional socket and start our child. */
! 396:
! 397: flags = SOCK_STREAM | SOCK_NONBLOCK;
! 398:
! 399: if (-1 == socketpair(AF_UNIX, flags, 0, fds))
! 400: err(EXIT_FAILURE, "socketpair");
! 401:
! 402: if (-1 == (child = fork())) {
! 403: close(fds[0]);
! 404: close(fds[1]);
! 405: err(EXIT_FAILURE, "fork");
! 406: }
! 407:
! 408: /* Drop the fork possibility. */
! 409:
! 410: if (-1 == pledge("unveil exec stdio rpath wpath cpath fattr", NULL))
! 411: err(EXIT_FAILURE, "pledge");
! 412:
! 413: if (0 == child) {
! 414: close(fds[0]);
! 415: fds[0] = -1;
! 416: if (-1 == pledge("exec stdio", NULL))
! 417: err(EXIT_FAILURE, "pledge");
! 418: rsync_child(&opts, fds[1], fargs);
! 419: /* NOTREACHED */
! 420: }
! 421:
! 422: close(fds[1]);
! 423: fds[1] = -1;
! 424: if (-1 == pledge("unveil rpath cpath wpath stdio fattr", NULL))
! 425: err(EXIT_FAILURE, "pledge");
! 426: c = rsync_client(&opts, fds[0], fargs);
! 427: fargs_free(fargs);
! 428:
! 429: /*
! 430: * If the client has an error and exits, the server may be
! 431: * sitting around waiting to get data while we waitpid().
! 432: * So close the connection here so that they don't hang.
! 433: */
! 434:
! 435: if ( ! c) {
! 436: close(fds[0]);
! 437: fds[0] = -1;
! 438: }
! 439:
! 440: if (-1 == waitpid(child, &st, 0))
! 441: err(EXIT_FAILURE, "waitpid");
! 442: if ( ! (WIFEXITED(st) && EXIT_SUCCESS == WEXITSTATUS(st)))
! 443: c = 0;
! 444:
! 445: if (-1 != fds[0])
! 446: close(fds[0]);
! 447: return c ? EXIT_SUCCESS : EXIT_FAILURE;
! 448: usage:
! 449: fprintf(stderr, "usage: %s [-lnprtv] "
! 450: "[--delete] [--rsync-path=prog] src ... dst\n",
! 451: getprogname());
! 452: return EXIT_FAILURE;
! 453: }