X-Git-Url: https://mattmccutchen.net/rsync/rsync.git/blobdiff_plain/60613dc89651a01602e3e2f0c3da6fdec023be55..4e7d07c8d4c75f0fc4207f0d6058db99b61c2f17:/main.c diff --git a/main.c b/main.c index d680f153..b4516e21 100644 --- a/main.c +++ b/main.c @@ -293,10 +293,11 @@ static pid_t do_cmd(char *cmd, char *machine, char *user, char *path, int i, argc = 0; char *args[MAX_ARGS]; pid_t ret; - char *tok, *dir = NULL; + char *dir = NULL; int dash_l_set = 0; if (!read_batch && !local_server) { + char *t, *f, in_quote = '\0'; char *rsh_env = getenv(RSYNC_RSH_ENV); if (!cmd) cmd = rsh_env; @@ -306,13 +307,39 @@ static pid_t do_cmd(char *cmd, char *machine, char *user, char *path, if (!cmd) goto oom; - for (tok = strtok(cmd, " "); tok; tok = strtok(NULL, " ")) { + for (t = f = cmd; *f; f++) { + if (*f == ' ') + continue; /* Comparison leaves rooms for server_options(). */ if (argc >= MAX_ARGS - MAX_SERVER_ARGS) { rprintf(FERROR, "internal: args[] overflowed in do_cmd()\n"); exit_cleanup(RERR_SYNTAX); } - args[argc++] = tok; + args[argc++] = t; + while (*f != ' ' || in_quote) { + if (!*f) { + if (in_quote) { + rprintf(FERROR, + "Missing trailing-%c in remote-shell command.\n", + in_quote); + exit_cleanup(RERR_SYNTAX); + } + f--; + break; + } + if (*f == '\'' || *f == '"') { + if (!in_quote) { + in_quote = *f++; + continue; + } + if (*f == in_quote && *++f != in_quote) { + in_quote = '\0'; + continue; + } + } + *t++ = *f++; + } + *t++ = '\0'; } /* check to see if we've already been given '-l user' in @@ -365,10 +392,9 @@ static pid_t do_cmd(char *cmd, char *machine, char *user, char *path, args[argc] = NULL; if (verbose > 3) { - rprintf(FINFO,"cmd="); for (i = 0; i < argc; i++) - rprintf(FINFO, "%s ", safe_fname(args[i])); - rprintf(FINFO,"\n"); + rprintf(FINFO, "cmd[%d]=%s ", i, args[i]); + rprintf(FINFO, "\n"); } if (read_batch) { @@ -400,57 +426,108 @@ static pid_t do_cmd(char *cmd, char *machine, char *user, char *path, return 0; /* not reached */ } - -static char *get_local_name(struct file_list *flist,char *name) +/* The receiving side operates in one of two modes: + * + * 1. it enters a directory and receives one or more files, placing them + * according to their names in the file-list. + * + * 2. it receives a single file and saves it using the name in the + * destination path instead of its file-list name. This requires a + * "local name" for writing out the destination file. + * + * So, our task is to figure out what mode/local-name we need and return + * either a NULL for mode 1, or the local-name for mode 2. We also + * change directory if there are any path components in dest_path. */ +static char *get_local_name(struct file_list *flist, char *dest_path) { STRUCT_STAT st; - int e; + char *cp; - if (verbose > 2) - rprintf(FINFO,"get_local_name count=%d %s\n", - flist->count, NS(name)); + if (verbose > 2) { + rprintf(FINFO, "get_local_name count=%d %s\n", + flist->count, NS(dest_path)); + } - if (!name) + if (!dest_path) return NULL; - if (do_stat(name,&st) == 0) { + /* If the destination path refers to an existing directory, enter + * it and use mode 1. If there is something other than a directory + * at the destination path, we must be transferring one file + * (anything at the destination will be overwritten). */ + if (do_stat(dest_path, &st) == 0) { if (S_ISDIR(st.st_mode)) { - if (!push_dir(name)) { + if (!push_dir(dest_path)) { rsyserr(FERROR, errno, "push_dir#1 %s failed", - full_fname(name)); + full_fname(dest_path)); exit_cleanup(RERR_FILESELECT); } return NULL; } if (flist->count > 1) { - rprintf(FERROR,"ERROR: destination must be a directory when copying more than 1 file\n"); + rprintf(FERROR, + "ERROR: destination must be a directory when" + " copying more than 1 file\n"); exit_cleanup(RERR_FILESELECT); } - return name; } - if (flist->count <= 1 && ((e = strlen(name)) <= 1 || name[e-1] != '/')) - return name; + cp = strrchr(dest_path, '/'); - if (do_mkdir(name,0777 & ~orig_umask) != 0) { - rsyserr(FERROR, errno, "mkdir %s failed", full_fname(name)); - exit_cleanup(RERR_FILEIO); - } - if (verbose > 0) - rprintf(FINFO, "created directory %s\n", safe_fname(name)); + /* If the destination path ends in a slash or we are transferring + * multiple files, create a directory at the destination path, + * enter the new directory, and use mode 1. */ + if (flist->count > 1 || (cp && !cp[1])) { + /* Lop off the final slash (if any). */ + if (cp && !cp[1]) + *cp = '\0'; + + umask(orig_umask); + if (do_mkdir(dest_path, 0777) != 0) { + rsyserr(FERROR, errno, "mkdir %s failed", + full_fname(dest_path)); + exit_cleanup(RERR_FILEIO); + } + umask(0); + + if (verbose) + rprintf(FINFO, "created directory %s\n", dest_path); + + if (dry_run) { + /* Indicate that the destination directory doesn't + * really exist and return mode 1. */ + dry_run++; + return NULL; + } + + if (!push_dir(dest_path)) { + rsyserr(FERROR, errno, "push_dir#2 %s failed", + full_fname(dest_path)); + exit_cleanup(RERR_FILESELECT); + } - if (dry_run) { - dry_run++; return NULL; } - if (!push_dir(name)) { - rsyserr(FERROR, errno, "push_dir#2 %s failed", - full_fname(name)); + /* Otherwise, we are writing a single file, possibly on top of an + * existing non-directory. Change to the item's parent directory + * (if it has a path component), return the basename of the + * destination file as the local name, and use mode 2. */ + if (!cp) + return dest_path; + + if (cp == dest_path) + dest_path = "/"; + + *cp = '\0'; + if (!push_dir(dest_path)) { + rsyserr(FERROR, errno, "push_dir#3 %s failed", + full_fname(dest_path)); exit_cleanup(RERR_FILESELECT); } + *cp = '/'; - return NULL; + return cp + 1; } @@ -478,7 +555,7 @@ static void read_final_goodbye(int f_in, int f_out) } -static void do_server_sender(int f_in, int f_out, int argc,char *argv[]) +static void do_server_sender(int f_in, int f_out, int argc, char *argv[]) { struct file_list *flist; char *dir = argv[0]; @@ -948,10 +1025,10 @@ static int start_client(int argc, char *argv[]) if (verbose > 3) { rprintf(FINFO,"cmd=%s machine=%s user=%s path=%s\n", - shell_cmd ? safe_fname(shell_cmd) : "", - shell_machine ? safe_fname(shell_machine) : "", - shell_user ? safe_fname(shell_user) : "", - shell_path ? safe_fname(shell_path) : ""); + shell_cmd ? shell_cmd : "", + shell_machine ? shell_machine : "", + shell_user ? shell_user : "", + shell_path ? shell_path : ""); } /* for remote source, only single dest arg can remain ... */ @@ -988,7 +1065,7 @@ static int start_client(int argc, char *argv[]) static RETSIGTYPE sigusr1_handler(UNUSED(int val)) { - exit_cleanup(RERR_SIGNAL); + exit_cleanup(RERR_SIGNAL1); } static RETSIGTYPE sigusr2_handler(UNUSED(int val)) @@ -1107,6 +1184,10 @@ int main(int argc,char *argv[]) * carried across */ orig_umask = (int)umask(0); +#if defined CONFIG_LOCALE && defined HAVE_SETLOCALE + setlocale(LC_CTYPE, ""); +#endif + if (!parse_arguments(&argc, (const char ***) &argv, 1)) { /* FIXME: We ought to call the same error-handling * code here, rather than relying on getopt. */ @@ -1122,10 +1203,6 @@ int main(int argc,char *argv[]) * see the EPIPE. */ signal(SIGPIPE, SIG_IGN); -#if defined CONFIG_LOCALE && defined HAVE_SETLOCALE - setlocale(LC_CTYPE, ""); -#endif - /* Initialize push_dir here because on some old systems getcwd * (implemented by forking "pwd" and reading its output) doesn't * work when there are other child processes. Also, on all systems