[BACK]Return to main.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / rsync

Annotation of src/usr.bin/rsync/main.c, Revision 1.4

1.4     ! deraadt     1: /*     $Id: main.c,v 1.3 2019/02/10 23:43:31 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:
1.4     ! deraadt   174:        if (!f->remote) {
1.1       benno     175:                if (FARGS_SENDER == f->mode)
                    176:                        for (i = 0; i < f->sourcesz; i++) {
1.4     ! deraadt   177:                                if (!fargs_is_remote(f->sources[i]))
1.1       benno     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]) &&
1.4     ! deraadt   186:                                    !fargs_is_daemon(f->sources[i]))
1.1       benno     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:
1.4     ! deraadt   215:        if (!f->remote) {
1.1       benno     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:
1.4     ! deraadt   438:        if (!c) {
1.1       benno     439:                close(fds[0]);
                    440:                fds[0] = -1;
                    441:        }
                    442:
                    443:        if (-1 == waitpid(child, &st, 0))
                    444:                err(EXIT_FAILURE, "waitpid");
1.4     ! deraadt   445:        if (!(WIFEXITED(st) && EXIT_SUCCESS == WEXITSTATUS(st)))
1.1       benno     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: }