Make sure that we don't try to use iconv() without iconv.h.
[rsync/rsync.git] / main.c
diff --git a/main.c b/main.c
index d680f15..e8b5efd 100644 (file)
--- a/main.c
+++ b/main.c
@@ -68,6 +68,15 @@ struct file_list *the_file_list;
  * but set it higher, just in case. */
 #define MAXCHILDPROCS 7
 
+#ifdef HAVE_SIGACTION
+# ifdef HAVE_SIGPROCMASK
+#  define SIGACTMASK(n,h) SIGACTION(n,h), sigaddset(&sigmask,(n))
+# else
+#  define SIGACTMASK(n,h) SIGACTION(n,h)
+# endif
+static struct sigaction sigact;
+#endif
+
 struct pid_status {
        pid_t pid;
        int status;
@@ -293,10 +302,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 +316,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 +401,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 +435,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 +564,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];
@@ -706,6 +792,9 @@ void start_server(int f_in, int f_out, int argc, char *argv[])
 
        io_set_sock_fds(f_in, f_out);
        setup_protocol(f_out, f_in);
+#if defined HAVE_ICONV_OPEN && defined HAVE_ICONV_H
+       setup_iconv();
+#endif
 
        if (protocol_version >= 23)
                io_start_multiplex_out();
@@ -742,6 +831,9 @@ int client_run(int f_in, int f_out, pid_t pid, int argc, char *argv[])
 
        io_set_sock_fds(f_in, f_out);
        setup_protocol(f_out,f_in);
+#if defined HAVE_ICONV_OPEN && defined HAVE_ICONV_H
+       setup_iconv();
+#endif
 
        if (protocol_version >= 23 && !read_batch)
                io_start_multiplex_in();
@@ -948,10 +1040,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 +1080,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))
@@ -1022,7 +1114,9 @@ static RETSIGTYPE sigchld_handler(UNUSED(int val))
                }
        }
 #endif
+#ifndef HAVE_SIGACTION
        signal(SIGCHLD, sigchld_handler);
+#endif
 }
 
 
@@ -1082,16 +1176,23 @@ int main(int argc,char *argv[])
        int ret;
        int orig_argc = argc;
        char **orig_argv = argv;
+#ifdef HAVE_SIGACTION
+# ifdef HAVE_SIGPROCMASK
+       sigset_t sigmask;
 
-       signal(SIGUSR1, sigusr1_handler);
-       signal(SIGUSR2, sigusr2_handler);
-       signal(SIGCHLD, sigchld_handler);
+       sigemptyset(&sigmask);
+# endif
+       sigact.sa_flags = SA_NOCLDSTOP;
+#endif
+       SIGACTMASK(SIGUSR1, sigusr1_handler);
+       SIGACTMASK(SIGUSR2, sigusr2_handler);
+       SIGACTMASK(SIGCHLD, sigchld_handler);
 #ifdef MAINTAINER_MODE
-       signal(SIGSEGV, rsync_panic_handler);
-       signal(SIGFPE, rsync_panic_handler);
-       signal(SIGABRT, rsync_panic_handler);
-       signal(SIGBUS, rsync_panic_handler);
-#endif /* def MAINTAINER_MODE */
+       SIGACTMASK(SIGSEGV, rsync_panic_handler);
+       SIGACTMASK(SIGFPE, rsync_panic_handler);
+       SIGACTMASK(SIGABRT, rsync_panic_handler);
+       SIGACTMASK(SIGBUS, rsync_panic_handler);
+#endif
 
        starttime = time(NULL);
        am_root = (MY_UID() == 0);
@@ -1107,6 +1208,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. */
@@ -1114,17 +1219,16 @@ int main(int argc,char *argv[])
                exit_cleanup(RERR_SYNTAX);
        }
 
-       signal(SIGINT,SIGNAL_CAST sig_int);
-       signal(SIGHUP,SIGNAL_CAST sig_int);
-       signal(SIGTERM,SIGNAL_CAST sig_int);
+       SIGACTMASK(SIGINT, sig_int);
+       SIGACTMASK(SIGHUP, sig_int);
+       SIGACTMASK(SIGTERM, sig_int);
+#if defined HAVE_SIGACTION && HAVE_SIGPROCMASK
+       sigprocmask(SIG_UNBLOCK, &sigmask, NULL);
+#endif
 
        /* Ignore SIGPIPE; we consistently check error codes and will
         * see the EPIPE. */
-       signal(SIGPIPE, SIG_IGN);
-
-#if defined CONFIG_LOCALE && defined HAVE_SETLOCALE
-       setlocale(LC_CTYPE, "");
-#endif
+       SIGACTION(SIGPIPE, SIG_IGN);
 
        /* Initialize push_dir here because on some old systems getcwd
         * (implemented by forking "pwd" and reading its output) doesn't