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