[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.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: }