-/*
- Copyright (C) Andrew Tridgell 1996
- Copyright (C) Paul Mackerras 1996
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-*/
+/*
+ * The startup routines, including main(), for rsync.
+ *
+ * Copyright (C) 1996-2001 Andrew Tridgell <tridge@samba.org>
+ * Copyright (C) 1996 Paul Mackerras
+ * Copyright (C) 2001, 2002 Martin Pool <mbp@samba.org>
+ * Copyright (C) 2003, 2004, 2005, 2006 Wayne Davison
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
#include "rsync.h"
+#if defined CONFIG_LOCALE && defined HAVE_LOCALE_H
+#include <locale.h>
+#endif
+
+extern int verbose;
+extern int dry_run;
+extern int list_only;
+extern int am_root;
+extern int am_server;
+extern int am_sender;
+extern int am_generator;
+extern int am_daemon;
+extern int incremental;
+extern int blocking_io;
+extern int remove_source_files;
+extern int daemon_over_rsh;
+extern int need_messages_from_generator;
+extern int kluge_around_eof;
+extern int do_stats;
+extern int log_got_error;
+extern int module_id;
+extern int copy_links;
+extern int copy_dirlinks;
+extern int keep_dirlinks;
+extern int preserve_hard_links;
+extern int protocol_version;
+extern int file_total;
+extern int recurse;
+extern int relative_paths;
+extern int sanitize_paths;
+extern int curr_dir_depth;
+extern int curr_dir_len;
+extern int module_id;
+extern int rsync_port;
+extern int whole_file;
+extern int read_batch;
+extern int write_batch;
+extern int batch_fd;
+extern int filesfrom_fd;
+extern pid_t cleanup_child_pid;
+extern struct stats stats;
+extern char *filesfrom_host;
+extern char *partial_dir;
+extern char *basis_dir[];
+extern char *rsync_path;
+extern char *shell_cmd;
+extern char *batch_name;
+extern char *password_file;
+extern char curr_dir[MAXPATHLEN];
+extern struct filter_list_struct server_filter_list;
+
+int local_server = 0;
+int new_root_dir = 0;
+mode_t orig_umask = 0;
+int batch_gen_fd = -1;
+
+/* There's probably never more than at most 2 outstanding child processes,
+ * 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;
+} pid_stat_table[MAXCHILDPROCS];
-int verbose = 0;
-int always_checksum = 0;
-time_t starttime;
-off_t total_size = 0;
-int block_size=BLOCK_SIZE;
-
-char *backup_suffix = BACKUP_SUFFIX;
-
-static char *rsync_path = RSYNC_NAME;
-
-int make_backups = 0;
-int preserve_links = 0;
-int preserve_hard_links = 0;
-int preserve_perms = 0;
-int preserve_devices = 0;
-int preserve_uid = 0;
-int preserve_gid = 0;
-int preserve_times = 0;
-int update_only = 0;
-int cvs_exclude = 0;
-int dry_run=0;
-int local_server=0;
-int ignore_times=0;
-int delete_mode=0;
-int one_file_system=0;
-int remote_version=0;
-int sparse_files=0;
-int do_compression=0;
-int am_root=0;
-int orig_umask=0;
-
-extern int csum_length;
-
-int am_server = 0;
-static int sender = 0;
-int recurse = 0;
-
-static void usage(FILE *f);
-
-static void report(int f)
+static time_t starttime, endtime;
+static int64 total_read, total_written;
+
+static void show_malloc_stats(void);
+
+/* Works like waitpid(), but if we already harvested the child pid in our
+ * remember_children(), we succeed instead of returning an error. */
+pid_t wait_process(pid_t pid, int *status_ptr, int flags)
{
- int in,out,tsize;
- time_t t = time(NULL);
-
- if (!verbose) return;
-
- if (am_server && sender) {
- write_int(f,read_total());
- write_int(f,write_total());
- write_int(f,total_size);
- write_flush(f);
- return;
- }
-
- if (sender) {
- in = read_total();
- out = write_total();
- tsize = (int)total_size;
- } else {
- in = read_int(f);
- out = read_int(f);
- tsize = read_int(f);
- }
-
- printf("wrote %d bytes read %d bytes %g bytes/sec\n",
- out,in,(in+out)/(0.5 + (t-starttime)));
- printf("total size is %d speedup is %g\n",
- tsize,(1.0*tsize)/(in+out));
+ pid_t waited_pid;
+
+ do {
+ waited_pid = waitpid(pid, status_ptr, flags);
+ } while (waited_pid == -1 && errno == EINTR);
+
+ if (waited_pid == -1 && errno == ECHILD) {
+ /* Status of requested child no longer available: check to
+ * see if it was processed by remember_children(). */
+ int cnt;
+ for (cnt = 0; cnt < MAXCHILDPROCS; cnt++) {
+ if (pid == pid_stat_table[cnt].pid) {
+ *status_ptr = pid_stat_table[cnt].status;
+ pid_stat_table[cnt].pid = 0;
+ return pid;
+ }
+ }
+ }
+
+ return waited_pid;
}
+/* Wait for a process to exit, calling io_flush while waiting. */
+static void wait_process_with_flush(pid_t pid, int *exit_code_ptr)
+{
+ pid_t waited_pid;
+ int status;
+
+ while ((waited_pid = wait_process(pid, &status, WNOHANG)) == 0) {
+ msleep(20);
+ io_flush(FULL_FLUSH);
+ }
+
+ /* TODO: If the child exited on a signal, then log an
+ * appropriate error message. Perhaps we should also accept a
+ * message describing the purpose of the child. Also indicate
+ * this to the caller so that they know something went wrong. */
+ if (waited_pid < 0) {
+ rsyserr(FERROR, errno, "waitpid");
+ *exit_code_ptr = RERR_WAITCHILD;
+ } else if (!WIFEXITED(status)) {
+#ifdef WCOREDUMP
+ if (WCOREDUMP(status))
+ *exit_code_ptr = RERR_CRASHED;
+ else
+#endif
+ if (WIFSIGNALED(status))
+ *exit_code_ptr = RERR_TERMINATED;
+ else
+ *exit_code_ptr = RERR_WAITCHILD;
+ } else
+ *exit_code_ptr = WEXITSTATUS(status);
+}
+
+/* This function gets called from all 3 processes. We want the client side
+ * to actually output the text, but the sender is the only process that has
+ * all the stats we need. So, if we're a client sender, we do the report.
+ * If we're a server sender, we write the stats on the supplied fd. If
+ * we're the client receiver we read the stats from the supplied fd and do
+ * the report. All processes might also generate a set of debug stats, if
+ * the verbose level is high enough (this is the only thing that the
+ * generator process and the server receiver ever do here). */
+static void handle_stats(int f)
+{
+ endtime = time(NULL);
+
+ /* Cache two stats because the read/write code can change it. */
+ total_read = stats.total_read;
+ total_written = stats.total_written;
+
+ if (do_stats && verbose > 1) {
+ /* These come out from every process */
+ show_malloc_stats();
+ show_flist_stats();
+ }
+
+ if (am_generator)
+ return;
+
+ if (am_daemon) {
+ if (f == -1 || !am_sender)
+ return;
+ }
+
+ if (am_server) {
+ if (am_sender) {
+ write_longint(f, total_read);
+ write_longint(f, total_written);
+ write_longint(f, stats.total_size);
+ if (protocol_version >= 29) {
+ write_longint(f, stats.flist_buildtime);
+ write_longint(f, stats.flist_xfertime);
+ }
+ }
+ return;
+ }
+
+ /* this is the client */
+
+ if (f < 0 && !am_sender) /* e.g. when we got an empty file list. */
+ ;
+ else if (!am_sender) {
+ /* Read the first two in opposite order because the meaning of
+ * read/write swaps when switching from sender to receiver. */
+ total_written = read_longint(f);
+ total_read = read_longint(f);
+ stats.total_size = read_longint(f);
+ if (protocol_version >= 29) {
+ stats.flist_buildtime = read_longint(f);
+ stats.flist_xfertime = read_longint(f);
+ }
+ } else if (write_batch) {
+ /* The --read-batch process is going to be a client
+ * receiver, so we need to give it the stats. */
+ write_longint(batch_fd, total_read);
+ write_longint(batch_fd, total_written);
+ write_longint(batch_fd, stats.total_size);
+ if (protocol_version >= 29) {
+ write_longint(batch_fd, stats.flist_buildtime);
+ write_longint(batch_fd, stats.flist_xfertime);
+ }
+ }
+}
-static void server_options(char **args,int *argc)
+static void output_summary(void)
{
- int ac = *argc;
- static char argstr[50];
- static char bsize[30];
- int i, x;
-
- args[ac++] = "--server";
-
- if (!sender)
- args[ac++] = "--sender";
-
- x = 1;
- argstr[0] = '-';
- for (i=0;i<verbose;i++)
- argstr[x++] = 'v';
- if (make_backups)
- argstr[x++] = 'b';
- if (update_only)
- argstr[x++] = 'u';
- if (dry_run)
- argstr[x++] = 'n';
- if (preserve_links)
- argstr[x++] = 'l';
- if (preserve_hard_links)
- argstr[x++] = 'H';
- if (preserve_uid)
- argstr[x++] = 'o';
- if (preserve_gid)
- argstr[x++] = 'g';
- if (preserve_devices)
- argstr[x++] = 'D';
- if (preserve_times)
- argstr[x++] = 't';
- if (preserve_perms)
- argstr[x++] = 'p';
- if (recurse)
- argstr[x++] = 'r';
- if (always_checksum)
- argstr[x++] = 'c';
- if (cvs_exclude)
- argstr[x++] = 'C';
- if (ignore_times)
- argstr[x++] = 'I';
- if (one_file_system)
- argstr[x++] = 'x';
- if (sparse_files)
- argstr[x++] = 'S';
- if (do_compression)
- argstr[x++] = 'z';
- argstr[x] = 0;
-
- if (x != 1) args[ac++] = argstr;
-
- if (block_size != BLOCK_SIZE) {
- sprintf(bsize,"-B%d",block_size);
- args[ac++] = bsize;
- }
-
- if (delete_mode)
- args[ac++] = "--delete";
-
- *argc = ac;
+ if (do_stats) {
+ rprintf(FCLIENT, "\n");
+ rprintf(FINFO,"Number of files: %d\n", stats.num_files);
+ rprintf(FINFO,"Number of files transferred: %d\n",
+ stats.num_transferred_files);
+ rprintf(FINFO,"Total file size: %s bytes\n",
+ human_num(stats.total_size));
+ rprintf(FINFO,"Total transferred file size: %s bytes\n",
+ human_num(stats.total_transferred_size));
+ rprintf(FINFO,"Literal data: %s bytes\n",
+ human_num(stats.literal_data));
+ rprintf(FINFO,"Matched data: %s bytes\n",
+ human_num(stats.matched_data));
+ rprintf(FINFO,"File list size: %s\n",
+ human_num(stats.flist_size));
+ if (stats.flist_buildtime) {
+ rprintf(FINFO,
+ "File list generation time: %.3f seconds\n",
+ (double)stats.flist_buildtime / 1000);
+ rprintf(FINFO,
+ "File list transfer time: %.3f seconds\n",
+ (double)stats.flist_xfertime / 1000);
+ }
+ rprintf(FINFO,"Total bytes sent: %s\n",
+ human_num(total_written));
+ rprintf(FINFO,"Total bytes received: %s\n",
+ human_num(total_read));
+ }
+
+ if (verbose || do_stats) {
+ rprintf(FCLIENT, "\n");
+ rprintf(FINFO,
+ "sent %s bytes received %s bytes %s bytes/sec\n",
+ human_num(total_written), human_num(total_read),
+ human_dnum((total_written + total_read)/(0.5 + (endtime - starttime)), 2));
+ rprintf(FINFO, "total size is %s speedup is %.2f\n",
+ human_num(stats.total_size),
+ (double)stats.total_size / (total_written+total_read));
+ }
+
+ fflush(stdout);
+ fflush(stderr);
}
+/**
+ * If our C library can get malloc statistics, then show them to FINFO
+ **/
+static void show_malloc_stats(void)
+{
+#ifdef HAVE_MALLINFO
+ struct mallinfo mi;
+
+ mi = mallinfo();
+
+ rprintf(FCLIENT, "\n");
+ rprintf(FINFO, RSYNC_NAME "[%d] (%s%s%s) heap statistics:\n",
+ getpid(), am_server ? "server " : "",
+ am_daemon ? "daemon " : "", who_am_i());
+ rprintf(FINFO, " arena: %10ld (bytes from sbrk)\n",
+ (long)mi.arena);
+ rprintf(FINFO, " ordblks: %10ld (chunks not in use)\n",
+ (long)mi.ordblks);
+ rprintf(FINFO, " smblks: %10ld\n",
+ (long)mi.smblks);
+ rprintf(FINFO, " hblks: %10ld (chunks from mmap)\n",
+ (long)mi.hblks);
+ rprintf(FINFO, " hblkhd: %10ld (bytes from mmap)\n",
+ (long)mi.hblkhd);
+ rprintf(FINFO, " allmem: %10ld (bytes from sbrk + mmap)\n",
+ (long)mi.arena + mi.hblkhd);
+ rprintf(FINFO, " usmblks: %10ld\n",
+ (long)mi.usmblks);
+ rprintf(FINFO, " fsmblks: %10ld\n",
+ (long)mi.fsmblks);
+ rprintf(FINFO, " uordblks: %10ld (bytes used)\n",
+ (long)mi.uordblks);
+ rprintf(FINFO, " fordblks: %10ld (bytes free)\n",
+ (long)mi.fordblks);
+ rprintf(FINFO, " keepcost: %10ld (bytes in releasable chunk)\n",
+ (long)mi.keepcost);
+#endif /* HAVE_MALLINFO */
+}
+
-int do_cmd(char *cmd,char *machine,char *user,char *path,int *f_in,int *f_out)
+/* Start the remote shell. cmd may be NULL to use the default. */
+static pid_t do_cmd(char *cmd, char *machine, char *user, char *path,
+ int *f_in, int *f_out)
{
- char *args[100];
- int i,argc=0;
- char *tok,*p;
-
- if (!local_server) {
- if (!cmd)
- cmd = getenv(RSYNC_RSH_ENV);
- if (!cmd)
- cmd = RSYNC_RSH;
- cmd = strdup(cmd);
- if (!cmd)
- goto oom;
-
- for (tok=strtok(cmd," ");tok;tok=strtok(NULL," ")) {
- args[argc++] = tok;
- }
-
-#if HAVE_REMSH
- /* remsh (on HPUX) takes the arguments the other way around */
- args[argc++] = machine;
- if (user) {
- args[argc++] = "-l";
- args[argc++] = user;
- }
+ int i, argc = 0;
+ char *args[MAX_ARGS];
+ pid_t ret;
+ 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;
+ if (!cmd)
+ cmd = RSYNC_RSH;
+ cmd = strdup(cmd);
+ if (!cmd)
+ goto oom;
+
+ 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++] = 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
+ * the remote-shell command */
+ for (i = 0; i < argc-1; i++) {
+ if (!strcmp(args[i], "-l") && args[i+1][0] != '-')
+ dash_l_set = 1;
+ }
+
+#ifdef HAVE_REMSH
+ /* remsh (on HPUX) takes the arguments the other way around */
+ args[argc++] = machine;
+ if (user && !(daemon_over_rsh && dash_l_set)) {
+ args[argc++] = "-l";
+ args[argc++] = user;
+ }
#else
- if (user) {
- args[argc++] = "-l";
- args[argc++] = user;
- }
- args[argc++] = machine;
+ if (user && !(daemon_over_rsh && dash_l_set)) {
+ args[argc++] = "-l";
+ args[argc++] = user;
+ }
+ args[argc++] = machine;
+#endif
+
+ args[argc++] = rsync_path;
+
+ if (blocking_io < 0) {
+ char *cp;
+ if ((cp = strrchr(cmd, '/')) != NULL)
+ cp++;
+ else
+ cp = cmd;
+ if (strcmp(cp, "rsh") == 0 || strcmp(cp, "remsh") == 0)
+ blocking_io = 1;
+ }
+
+ server_options(args,&argc);
+
+ if (argc >= MAX_ARGS - 2) {
+ rprintf(FERROR, "internal: args[] overflowed in do_cmd()\n");
+ exit_cleanup(RERR_SYNTAX);
+ }
+ }
+
+ args[argc++] = ".";
+
+ if (!daemon_over_rsh && path && *path)
+ args[argc++] = path;
+
+ args[argc] = NULL;
+
+ if (verbose > 3) {
+ for (i = 0; i < argc; i++)
+ rprintf(FCLIENT, "cmd[%d]=%s ", i, args[i]);
+ rprintf(FCLIENT, "\n");
+ }
+
+ if (read_batch) {
+ int from_gen_pipe[2];
+ if (fd_pair(from_gen_pipe) < 0) {
+ rsyserr(FERROR, errno, "pipe");
+ exit_cleanup(RERR_IPC);
+ }
+ batch_gen_fd = from_gen_pipe[0];
+ *f_out = from_gen_pipe[1];
+ *f_in = batch_fd;
+ ret = -1; /* no child pid */
+ } else if (local_server) {
+ /* If the user didn't request --[no-]whole-file, force
+ * it on, but only if we're not batch processing. */
+ if (whole_file < 0 && !write_batch)
+ whole_file = 1;
+ ret = local_child(argc, args, f_in, f_out, child_main);
+ } else
+ ret = piped_child(args,f_in,f_out);
+
+ if (dir)
+ free(dir);
+
+ return ret;
+
+ oom:
+ out_of_memory("do_cmd");
+ return 0; /* not reached */
+}
+
+/* The receiving side operates in one of two modes:
+ *
+ * 1. it receives any number of files into a destination directory,
+ * 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.
+ * For mode 1, we change into the destination directory and return NULL.
+ * For mode 2, we change into the directory containing the destination
+ * file (if we aren't already there) and return the local-name. */
+static char *get_local_name(struct file_list *flist, char *dest_path)
+{
+ STRUCT_STAT st;
+ int statret;
+ char *cp;
+
+ if (verbose > 2) {
+ rprintf(FINFO, "get_local_name count=%d %s\n",
+ file_total, NS(dest_path));
+ }
+
+ if (!dest_path || list_only)
+ return NULL;
+
+ /* See what currently exists at the destination. */
+ if ((statret = do_stat(dest_path, &st)) == 0) {
+ /* If the destination is a dir, enter it and use mode 1. */
+ if (S_ISDIR(st.st_mode)) {
+ if (!push_dir(dest_path, 0)) {
+ rsyserr(FERROR, errno, "push_dir#1 %s failed",
+ full_fname(dest_path));
+ exit_cleanup(RERR_FILESELECT);
+ }
+ return NULL;
+ }
+ if (file_total > 1) {
+ rprintf(FERROR,
+ "ERROR: destination must be a directory when"
+ " copying more than 1 file\n");
+ exit_cleanup(RERR_FILESELECT);
+ }
+ if (file_total == 1 && S_ISDIR(flist->files[0]->mode)) {
+ rprintf(FERROR,
+ "ERROR: cannot overwrite non-directory"
+ " with a directory\n");
+ exit_cleanup(RERR_FILESELECT);
+ }
+ } else if (errno != ENOENT) {
+ /* If we don't know what's at the destination, fail. */
+ rsyserr(FERROR, errno, "ERROR: cannot stat destination %s",
+ full_fname(dest_path));
+ exit_cleanup(RERR_FILESELECT);
+ }
+
+ cp = strrchr(dest_path, '/');
+
+ /* If we need a destination directory because the transfer is not
+ * of a single non-directory or the user has requested one via a
+ * destination path ending in a slash, create one and use mode 1. */
+ if (file_total > 1 || (cp && !cp[1])) {
+ /* Lop off the final slash (if any). */
+ if (cp && !cp[1])
+ *cp = '\0';
+
+ if (statret == 0) {
+ rprintf(FERROR,
+ "ERROR: destination path is not a directory\n");
+ exit_cleanup(RERR_SYNTAX);
+ }
+
+ if (mkdir_defmode(dest_path) != 0) {
+ rsyserr(FERROR, errno, "mkdir %s failed",
+ full_fname(dest_path));
+ exit_cleanup(RERR_FILEIO);
+ }
+
+ new_root_dir = 1;
+
+ if (verbose)
+ rprintf(FINFO, "created directory %s\n", dest_path);
+
+ if (dry_run) {
+ /* Indicate that dest dir doesn't really exist. */
+ dry_run++;
+ }
+
+ if (!push_dir(dest_path, dry_run > 1)) {
+ rsyserr(FERROR, errno, "push_dir#2 %s failed",
+ full_fname(dest_path));
+ exit_cleanup(RERR_FILESELECT);
+ }
+
+ return NULL;
+ }
+
+ /* 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, 0)) {
+ rsyserr(FERROR, errno, "push_dir#3 %s failed",
+ full_fname(dest_path));
+ exit_cleanup(RERR_FILESELECT);
+ }
+ *cp = '/';
+
+ return cp + 1;
+}
+
+/* Call this if the destination dir (which is assumed to be in curr_dir)
+ * does not yet exist and we can't create it due to being in dry-run
+ * mode. We'll fix dirs that can be relative to the non-existent dir. */
+static void fix_basis_dirs(void)
+{
+ char **dir, *new, *slash;
+ int len;
+
+ if (dry_run <= 1)
+ return;
+
+ slash = strrchr(curr_dir, '/');
+
+ for (dir = basis_dir; *dir; dir++) {
+ if (**dir == '/')
+ continue;
+ len = curr_dir_len + 1 + strlen(*dir) + 1;
+ if (!(new = new_array(char, len)))
+ out_of_memory("fix_basis_dirs");
+ if (slash && strncmp(*dir, "../", 3) == 0) {
+ /* We want to remove only one leading "../" prefix for
+ * the directory we couldn't create in dry-run mode:
+ * this ensures that any other ".." references get
+ * evaluated the same as they would for a live copy. */
+ *slash = '\0';
+ pathjoin(new, len, curr_dir, *dir + 3);
+ *slash = '/';
+ } else
+ pathjoin(new, len, curr_dir, *dir);
+ *dir = new;
+ }
+}
+
+/* This is only called by the sender. */
+static void read_final_goodbye(int f_in, int f_out)
+{
+ int i, iflags, xlen;
+ uchar fnamecmp_type;
+ char xname[MAXPATHLEN];
+
+ if (protocol_version < 29)
+ i = read_int(f_in);
+ else {
+ i = read_ndx_and_attrs(f_in, f_out, &iflags,
+ &fnamecmp_type, xname, &xlen);
+ }
+
+ if (i != NDX_DONE) {
+ rprintf(FERROR, "Invalid packet at end of run (%d) [%s]\n",
+ i, who_am_i());
+ exit_cleanup(RERR_PROTOCOL);
+ }
+}
+
+static void do_server_sender(int f_in, int f_out, int argc, char *argv[])
+{
+ struct file_list *flist;
+ char *dir = argv[0];
+
+ if (verbose > 2) {
+ rprintf(FINFO, "server_sender starting pid=%ld\n",
+ (long)getpid());
+ }
+
+ if (am_daemon && lp_write_only(module_id)) {
+ rprintf(FERROR, "ERROR: module is write only\n");
+ exit_cleanup(RERR_SYNTAX);
+ return;
+ }
+ if (am_daemon && lp_read_only(module_id) && remove_source_files) {
+ rprintf(FERROR,
+ "ERROR: --remove-%s-files cannot be used with a read-only module\n",
+ remove_source_files == 1 ? "source" : "sent");
+ exit_cleanup(RERR_SYNTAX);
+ return;
+ }
+
+ if (!relative_paths) {
+ if (!push_dir(dir, 0)) {
+ rsyserr(FERROR, errno, "push_dir#3 %s failed",
+ full_fname(dir));
+ exit_cleanup(RERR_FILESELECT);
+ }
+ }
+ argc--;
+ argv++;
+
+ if (argc == 0 && (recurse || list_only)) {
+ argc = 1;
+ argv--;
+ argv[0] = ".";
+ }
+
+ flist = send_file_list(f_out,argc,argv);
+ if (!flist || flist->count == 0)
+ exit_cleanup(0);
+
+ io_start_buffering_in(f_in);
+
+ send_files(f_in, f_out);
+ io_flush(FULL_FLUSH);
+ handle_stats(f_out);
+ if (protocol_version >= 24)
+ read_final_goodbye(f_in, f_out);
+ io_flush(FULL_FLUSH);
+ exit_cleanup(0);
+}
+
+
+static int do_recv(int f_in, int f_out, char *local_name)
+{
+ int pid;
+ int exit_code = 0;
+ int error_pipe[2];
+
+ /* The receiving side mustn't obey this, or an existing symlink that
+ * points to an identical file won't be replaced by the referent. */
+ copy_links = copy_dirlinks = 0;
+
+#ifdef SUPPORT_HARD_LINKS
+ if (preserve_hard_links)
+ match_hard_links();
#endif
- }
-
- args[argc++] = rsync_path;
-
- server_options(args,&argc);
-
- if (path && *path) {
- char *dir = strdup(path);
- p = strrchr(dir,'/');
- if (p) {
- *p = 0;
- if (!dir[0])
- args[argc++] = "/";
- else
- args[argc++] = dir;
- p++;
- } else {
- args[argc++] = ".";
- p = dir;
- }
- if (p[0])
- args[argc++] = path;
- }
-
- args[argc] = NULL;
-
- if (verbose > 3) {
- fprintf(FERROR,"cmd=");
- for (i=0;i<argc;i++)
- fprintf(FERROR,"%s ",args[i]);
- fprintf(FERROR,"\n");
- }
-
- return piped_child(args,f_in,f_out);
-
-oom:
- out_of_memory("do_cmd");
- return 0; /* not reached */
+
+ if (fd_pair(error_pipe) < 0) {
+ rsyserr(FERROR, errno, "pipe failed in do_recv");
+ exit_cleanup(RERR_IPC);
+ }
+
+ io_flush(NORMAL_FLUSH);
+
+ if ((pid = do_fork()) == -1) {
+ rsyserr(FERROR, errno, "fork failed in do_recv");
+ exit_cleanup(RERR_IPC);
+ }
+
+ if (pid == 0) {
+ close(error_pipe[0]);
+ if (f_in != f_out)
+ close(f_out);
+
+ /* we can't let two processes write to the socket at one time */
+ io_end_multiplex_out();
+
+ /* set place to send errors */
+ set_msg_fd_out(error_pipe[1]);
+ io_start_buffering_out(error_pipe[1]);
+
+ recv_files(f_in, local_name);
+ io_flush(FULL_FLUSH);
+ handle_stats(f_in);
+
+ send_msg(MSG_DONE, "", 0);
+ io_flush(FULL_FLUSH);
+
+ /* Handle any keep-alive packets from the post-processing work
+ * that the generator does. */
+ if (protocol_version >= 29) {
+ int iflags, xlen;
+ uchar fnamecmp_type;
+ char xname[MAXPATHLEN];
+
+ kluge_around_eof = -1;
+
+ /* This should only get stopped via a USR2 signal. */
+ read_ndx_and_attrs(f_in, -1, &iflags, &fnamecmp_type,
+ xname, &xlen);
+
+ rprintf(FERROR, "Invalid packet at end of run [%s]\n",
+ who_am_i());
+ exit_cleanup(RERR_PROTOCOL);
+ }
+
+ /* Finally, we go to sleep until our parent kills us with a
+ * USR2 signal. We sleep for a short time, as on some OSes
+ * a signal won't interrupt a sleep! */
+ while (1)
+ msleep(20);
+ }
+
+ am_generator = 1;
+
+ io_end_multiplex_in();
+ if (write_batch && !am_server)
+ stop_write_batch();
+
+ close(error_pipe[1]);
+ if (f_in != f_out)
+ close(f_in);
+
+ io_start_buffering_out(f_out);
+
+ set_msg_fd_in(error_pipe[0]);
+ io_start_buffering_in(error_pipe[0]);
+
+ generate_files(f_out, local_name);
+
+ handle_stats(-1);
+ io_flush(FULL_FLUSH);
+ if (protocol_version >= 24) {
+ /* send a final goodbye message */
+ write_ndx(f_out, NDX_DONE);
+ }
+ io_flush(FULL_FLUSH);
+
+ set_msg_fd_in(-1);
+ kill(pid, SIGUSR2);
+ wait_process_with_flush(pid, &exit_code);
+ return exit_code;
+}
+
+static void do_server_recv(int f_in, int f_out, int argc, char *argv[])
+{
+ int exit_code;
+ struct file_list *flist;
+ char *local_name = NULL;
+ char *dir = NULL;
+ int save_verbose = verbose;
+
+ if (filesfrom_fd >= 0) {
+ /* We can't mix messages with files-from data on the socket,
+ * so temporarily turn off verbose messages. */
+ verbose = 0;
+ }
+
+ if (verbose > 2) {
+ rprintf(FINFO, "server_recv(%d) starting pid=%ld\n",
+ argc, (long)getpid());
+ }
+
+ if (am_daemon && lp_read_only(module_id)) {
+ rprintf(FERROR,"ERROR: module is read only\n");
+ exit_cleanup(RERR_SYNTAX);
+ return;
+ }
+
+ if (argc > 0) {
+ dir = argv[0];
+ argc--;
+ argv++;
+ if (!am_daemon && !push_dir(dir, 0)) {
+ rsyserr(FERROR, errno, "push_dir#4 %s failed",
+ full_fname(dir));
+ exit_cleanup(RERR_FILESELECT);
+ }
+ }
+
+ if (protocol_version >= 30)
+ io_start_multiplex_in();
+ else
+ io_start_buffering_in(f_in);
+ recv_filter_list(f_in);
+
+ if (filesfrom_fd >= 0) {
+ /* We need to send the files-from names to the sender at the
+ * same time that we receive the file-list from them, so we
+ * need the IO routines to automatically write out the names
+ * onto our f_out socket as we read the file-list. This
+ * avoids both deadlock and extra delays/buffers. */
+ io_set_filesfrom_fds(filesfrom_fd, f_out);
+ filesfrom_fd = -1;
+ }
+
+ flist = recv_file_list(f_in);
+ if (!flist) {
+ rprintf(FERROR,"server_recv: recv_file_list error\n");
+ exit_cleanup(RERR_FILESELECT);
+ }
+ if (incremental && file_total == 1)
+ recv_additional_file_list(f_in);
+ verbose = save_verbose;
+
+ if (argc > 0)
+ local_name = get_local_name(flist,argv[0]);
+
+ /* Now that we know what our destination directory turned out to be,
+ * we can sanitize the --link-/copy-/compare-dest args correctly. */
+ if (sanitize_paths) {
+ char **dir;
+ for (dir = basis_dir; *dir; dir++) {
+ *dir = sanitize_path(NULL, *dir, NULL, curr_dir_depth, NULL);
+ }
+ if (partial_dir) {
+ partial_dir = sanitize_path(NULL, partial_dir, NULL, curr_dir_depth, NULL);
+ }
+ }
+ fix_basis_dirs();
+
+ if (server_filter_list.head) {
+ char **dir;
+ struct filter_list_struct *elp = &server_filter_list;
+
+ for (dir = basis_dir; *dir; dir++) {
+ if (check_filter(elp, *dir, 1) < 0)
+ goto options_rejected;
+ }
+ if (partial_dir && *partial_dir == '/'
+ && check_filter(elp, partial_dir, 1) < 0) {
+ options_rejected:
+ rprintf(FERROR,
+ "Your options have been rejected by the server.\n");
+ exit_cleanup(RERR_SYNTAX);
+ }
+ }
+
+ exit_code = do_recv(f_in, f_out, local_name);
+ exit_cleanup(exit_code);
}
+int child_main(int argc, char *argv[])
+{
+ start_server(STDIN_FILENO, STDOUT_FILENO, argc, argv);
+ return 0;
+}
-static char *get_local_name(struct file_list *flist,char *name)
+void start_server(int f_in, int f_out, int argc, char *argv[])
{
- struct stat st;
-
- if (stat(name,&st) == 0) {
- if (S_ISDIR(st.st_mode)) {
- if (chdir(name) != 0) {
- fprintf(FERROR,"chdir %s : %s\n",name,strerror(errno));
- exit_cleanup(1);
- }
- return NULL;
- }
- if (flist->count > 1) {
- fprintf(FERROR,"ERROR: destination must be a directory when copying more than 1 file\n");
- exit_cleanup(1);
- }
- return name;
- }
-
- if (flist->count == 1)
- return name;
-
- if (!name)
- return NULL;
-
- if (mkdir(name,0777 & ~orig_umask) != 0) {
- fprintf(FERROR,"mkdir %s : %s\n",name,strerror(errno));
- exit_cleanup(1);
- } else {
- fprintf(FINFO,"created directory %s\n",name);
- }
-
- if (chdir(name) != 0) {
- fprintf(FERROR,"chdir %s : %s\n",name,strerror(errno));
- exit_cleanup(1);
- }
-
- return NULL;
+ set_nonblocking(f_in);
+ set_nonblocking(f_out);
+
+ 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();
+
+ if (am_sender) {
+ keep_dirlinks = 0; /* Must be disabled on the sender. */
+ if (need_messages_from_generator)
+ io_start_multiplex_in();
+ recv_filter_list(f_in);
+ do_server_sender(f_in, f_out, argc, argv);
+ } else
+ do_server_recv(f_in, f_out, argc, argv);
+ exit_cleanup(0);
}
+/*
+ * This is called once the connection has been negotiated. It is used
+ * for rsyncd, remote-shell, and local connections.
+ */
+int client_run(int f_in, int f_out, pid_t pid, int argc, char *argv[])
+{
+ struct file_list *flist = NULL;
+ int exit_code = 0, exit_code2 = 0;
+ char *local_name = NULL;
+
+ cleanup_child_pid = pid;
+ if (!read_batch) {
+ set_nonblocking(f_in);
+ set_nonblocking(f_out);
+ }
+
+ 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
+
+ /* We set our stderr file handle to blocking because ssh might have
+ * set it to non-blocking. This can be particularly troublesome if
+ * stderr is a clone of stdout, because ssh would have set our stdout
+ * to non-blocking at the same time (which can easily cause us to lose
+ * output from our print statements). This kluge shouldn't cause ssh
+ * any problems for how we use it. Note also that we delayed setting
+ * this until after the above protocol setup so that we know for sure
+ * that ssh is done twiddling its file descriptors. */
+ set_blocking(STDERR_FILENO);
+
+ if (am_sender) {
+ keep_dirlinks = 0; /* Must be disabled on the sender. */
+ if (protocol_version >= 30)
+ io_start_multiplex_out();
+ else
+ io_start_buffering_out(f_out);
+ if (!filesfrom_host)
+ set_msg_fd_in(f_in);
+ send_filter_list(f_out);
+ if (filesfrom_host)
+ filesfrom_fd = f_in;
+
+ if (write_batch && !am_server)
+ start_write_batch(f_out);
+ flist = send_file_list(f_out, argc, argv);
+ set_msg_fd_in(-1);
+ if (verbose > 3)
+ rprintf(FINFO,"file list sent\n");
+
+ if (protocol_version >= 23)
+ io_start_multiplex_in();
+
+ io_flush(NORMAL_FLUSH);
+ send_files(f_in, f_out);
+ io_flush(FULL_FLUSH);
+ handle_stats(-1);
+ if (protocol_version >= 24)
+ read_final_goodbye(f_in, f_out);
+ if (pid != -1) {
+ if (verbose > 3)
+ rprintf(FINFO,"client_run waiting on %d\n", (int) pid);
+ io_flush(FULL_FLUSH);
+ wait_process_with_flush(pid, &exit_code);
+ }
+ output_summary();
+ io_flush(FULL_FLUSH);
+ exit_cleanup(exit_code);
+ }
+
+ if (!read_batch) {
+ if (protocol_version >= 23)
+ io_start_multiplex_in();
+ if (need_messages_from_generator)
+ io_start_multiplex_out();
+ }
+
+ if (argc == 0)
+ list_only |= 1;
+
+ send_filter_list(read_batch ? -1 : f_out);
+
+ if (filesfrom_fd >= 0) {
+ io_set_filesfrom_fds(filesfrom_fd, f_out);
+ filesfrom_fd = -1;
+ }
+
+ if (write_batch && !am_server)
+ start_write_batch(f_in);
+ flist = recv_file_list(f_in);
+ if (incremental && file_total == 1)
+ recv_additional_file_list(f_in);
+
+ if (flist && flist->count > 0) {
+ local_name = get_local_name(flist, argv[0]);
+
+ fix_basis_dirs();
+
+ exit_code2 = do_recv(f_in, f_out, local_name);
+ } else {
+ handle_stats(-1);
+ output_summary();
+ }
+ if (pid != -1) {
+ if (verbose > 3)
+ rprintf(FINFO,"client_run2 waiting on %d\n", (int) pid);
+ io_flush(FULL_FLUSH);
+ wait_process_with_flush(pid, &exit_code);
+ }
+
+ return MAX(exit_code, exit_code2);
+}
-void do_server_sender(int argc,char *argv[])
+static int copy_argv(char *argv[])
{
- int i;
- char *dir = argv[0];
- struct file_list *flist;
-
- if (verbose > 2)
- fprintf(FERROR,"server_sender starting pid=%d\n",(int)getpid());
-
- if (chdir(dir) != 0) {
- fprintf(FERROR,"chdir %s: %s\n",dir,strerror(errno));
- exit_cleanup(1);
- }
- argc--;
- argv++;
-
- if (strcmp(dir,".")) {
- int l = strlen(dir);
- if (strcmp(dir,"/") == 0)
- l = 0;
- for (i=0;i<argc;i++)
- argv[i] += l+1;
- }
-
- if (argc == 0 && recurse) {
- argc=1;
- argv--;
- argv[0] = ".";
- }
-
-
- flist = send_file_list(STDOUT_FILENO,argc,argv);
- send_files(flist,STDOUT_FILENO,STDIN_FILENO);
- report(STDOUT_FILENO);
- exit_cleanup(0);
+ int i;
+
+ for (i = 0; argv[i]; i++) {
+ if (!(argv[i] = strdup(argv[i]))) {
+ rprintf (FERROR, "out of memory at %s(%d)\n",
+ __FILE__, __LINE__);
+ return RERR_MALLOC;
+ }
+ }
+
+ return 0;
}
-static int do_recv(int f_in,int f_out,struct file_list *flist,char *local_name)
+/**
+ * Start a client for either type of remote connection. Work out
+ * whether the arguments request a remote shell or rsyncd connection,
+ * and call the appropriate connection function, then run_client.
+ *
+ * Calls either start_socket_client (for sockets) or do_cmd and
+ * client_run (for ssh).
+ **/
+static int start_client(int argc, char *argv[])
{
- int pid;
- int status=0;
- int recv_pipe[2];
+ char *p;
+ char *shell_machine = NULL;
+ char *shell_path = NULL;
+ char *shell_user = NULL;
+ int ret;
+ pid_t pid;
+ int f_in,f_out;
+ int rc;
+
+ /* Don't clobber argv[] so that ps(1) can still show the right
+ * command line. */
+ if ((rc = copy_argv(argv)))
+ return rc;
+
+ if (!read_batch) { /* for read_batch, NO source is specified */
+ shell_path = check_for_hostspec(argv[0], &shell_machine, &rsync_port);
+ if (shell_path) { /* source is remote */
+ char *dummy1;
+ int dummy2;
+ if (--argc
+ && check_for_hostspec(argv[argc], &dummy1, &dummy2)) {
+ rprintf(FERROR,
+ "The source and destination cannot both be remote.\n");
+ exit_cleanup(RERR_SYNTAX);
+ }
+ argv++;
+ if (filesfrom_host && *filesfrom_host
+ && strcmp(filesfrom_host, shell_machine) != 0) {
+ rprintf(FERROR,
+ "--files-from hostname is not the same as the transfer hostname\n");
+ exit_cleanup(RERR_SYNTAX);
+ }
+ if (rsync_port) {
+ if (!shell_cmd) {
+ return start_socket_client(shell_machine,
+ shell_path,
+ argc, argv);
+ }
+ daemon_over_rsh = 1;
+ }
+
+ am_sender = 0;
+ } else { /* source is local, check dest arg */
+ am_sender = 1;
+
+ if (argc > 1)
+ p = argv[--argc];
+ else {
+ p = ".";
+ list_only = 1;
+ }
+
+ shell_path = check_for_hostspec(p, &shell_machine, &rsync_port);
+ if (shell_path && filesfrom_host && *filesfrom_host
+ && strcmp(filesfrom_host, shell_machine) != 0) {
+ rprintf(FERROR,
+ "--files-from hostname is not the same as the transfer hostname\n");
+ exit_cleanup(RERR_SYNTAX);
+ }
+ if (!shell_path) { /* no hostspec found, so src & dest are local */
+ local_server = 1;
+ if (filesfrom_host) {
+ rprintf(FERROR,
+ "--files-from cannot be remote when the transfer is local\n");
+ exit_cleanup(RERR_SYNTAX);
+ }
+ shell_machine = NULL;
+ shell_path = p;
+ } else if (rsync_port) {
+ if (!shell_cmd) {
+ return start_socket_client(shell_machine,
+ shell_path,
+ argc, argv);
+ }
+ daemon_over_rsh = 1;
+ }
+ }
+ } else { /* read_batch */
+ local_server = 1;
+ shell_path = argv[argc-1];
+ if (check_for_hostspec(shell_path, &shell_machine, &rsync_port)) {
+ rprintf(FERROR, "remote destination is not allowed with --read-batch\n");
+ exit_cleanup(RERR_SYNTAX);
+ }
+ }
- if (preserve_hard_links)
- init_hard_links(flist);
+ if (password_file && !daemon_over_rsh) {
+ rprintf(FERROR, "The --password-file option may only be "
+ "used when accessing an rsync daemon.\n");
+ exit_cleanup(RERR_SYNTAX);
+ }
- if (pipe(recv_pipe) < 0) {
- fprintf(FERROR,"pipe failed in do_recv\n");
- exit(1);
- }
-
+ if (shell_machine) {
+ p = strrchr(shell_machine,'@');
+ if (p) {
+ *p = 0;
+ shell_user = shell_machine;
+ shell_machine = p+1;
+ }
+ }
- if ((pid=fork()) == 0) {
- recv_files(f_in,flist,local_name,recv_pipe[1]);
- if (preserve_hard_links)
- do_hard_links(flist);
- if (verbose > 2)
- fprintf(FERROR,"receiver read %d\n",read_total());
- exit_cleanup(0);
- }
+ if (verbose > 3) {
+ rprintf(FINFO,"cmd=%s machine=%s user=%s path=%s\n",
+ shell_cmd ? shell_cmd : "",
+ shell_machine ? shell_machine : "",
+ shell_user ? shell_user : "",
+ shell_path ? shell_path : "");
+ }
- generate_files(f_out,flist,local_name,recv_pipe[0]);
+ /* for remote source, only single dest arg can remain ... */
+ if (!am_sender && argc > 1) {
+ usage(FERROR);
+ exit_cleanup(RERR_SYNTAX);
+ }
+
+ /* ... or no dest at all */
+ if (!am_sender && argc == 0)
+ list_only |= 1;
+
+ pid = do_cmd(shell_cmd,shell_machine,shell_user,shell_path,
+ &f_in,&f_out);
+
+ /* if we're running an rsync server on the remote host over a
+ * remote shell command, we need to do the RSYNCD protocol first */
+ if (daemon_over_rsh) {
+ int tmpret;
+ tmpret = start_inband_exchange(shell_user, shell_path,
+ f_in, f_out, argc);
+ if (tmpret < 0)
+ return tmpret;
+ }
- waitpid(pid, &status, 0);
+ ret = client_run(f_in, f_out, pid, argc, argv);
- return status;
+ fflush(stdout);
+ fflush(stderr);
+
+ return ret;
}
-void do_server_recv(int argc,char *argv[])
+static RETSIGTYPE sigusr1_handler(UNUSED(int val))
{
- int status;
- char *dir = NULL;
- struct file_list *flist;
- char *local_name=NULL;
-
- if (verbose > 2)
- fprintf(FERROR,"server_recv(%d) starting pid=%d\n",argc,(int)getpid());
-
- if (argc > 0) {
- dir = argv[0];
- argc--;
- argv++;
- if (chdir(dir) != 0) {
- fprintf(FERROR,"chdir %s : %s\n",dir,strerror(errno));
- exit_cleanup(1);
- }
- }
-
- if (delete_mode)
- recv_exclude_list(STDIN_FILENO);
-
- flist = recv_file_list(STDIN_FILENO);
- if (!flist || flist->count == 0) {
- fprintf(FERROR,"nothing to do\n");
- exit_cleanup(1);
- }
-
- if (argc > 0) {
- if (strcmp(dir,".")) {
- argv[0] += strlen(dir);
- if (argv[0][0] == '/') argv[0]++;
- }
- local_name = get_local_name(flist,argv[0]);
- }
-
- status = do_recv(STDIN_FILENO,STDOUT_FILENO,flist,local_name);
- exit_cleanup(status);
+ exit_cleanup(RERR_SIGNAL1);
}
+static RETSIGTYPE sigusr2_handler(UNUSED(int val))
+{
+ if (!am_server)
+ output_summary();
+ close_all();
+ if (log_got_error)
+ _exit(RERR_PARTIAL);
+ _exit(0);
+}
-static void usage(FILE *f)
+RETSIGTYPE remember_children(UNUSED(int val))
{
- fprintf(f,"rsync version %s Copyright Andrew Tridgell and Paul Mackerras\n\n",
- VERSION);
- fprintf(f,"Usage:\t%s [options] src user@host:dest\nOR",RSYNC_NAME);
- fprintf(f,"\t%s [options] user@host:src dest\n\n",RSYNC_NAME);
- fprintf(f,"Options:\n");
- fprintf(f,"-v, --verbose increase verbosity\n");
- fprintf(f,"-c, --checksum always checksum\n");
- fprintf(f,"-a, --archive archive mode (same as -rlptDog)\n");
- fprintf(f,"-r, --recursive recurse into directories\n");
- fprintf(f,"-b, --backup make backups (default ~ extension)\n");
- fprintf(f,"-u, --update update only (don't overwrite newer files)\n");
- fprintf(f,"-l, --links preserve soft links\n");
- fprintf(f,"-H, --hard-links preserve hard links\n");
- fprintf(f,"-p, --perms preserve permissions\n");
- fprintf(f,"-o, --owner preserve owner (root only)\n");
- fprintf(f,"-g, --group preserve group\n");
- fprintf(f,"-D, --devices preserve devices (root only)\n");
- fprintf(f,"-t, --times preserve times\n");
- fprintf(f,"-S, --sparse handle sparse files efficiently\n");
- fprintf(f,"-n, --dry-run show what would have been transferred\n");
- fprintf(f,"-x, --one-file-system don't cross filesystem boundaries\n");
- fprintf(f,"-B, --block-size SIZE checksum blocking size\n");
- fprintf(f,"-e, --rsh COMMAND specify rsh replacement\n");
- fprintf(f," --rsync-path PATH specify path to rsync on the remote machine\n");
- fprintf(f,"-C, --cvs-exclude auto ignore files in the same way CVS does\n");
- fprintf(f," --delete delete files that don't exist on the sending side\n");
- fprintf(f,"-I, --ignore-times don't exclude files that match length and time\n");
- fprintf(f,"-z, --compress compress file data\n");
- fprintf(f," --exclude FILE exclude file FILE\n");
- fprintf(f," --exclude-from FILE exclude files listed in FILE\n");
- fprintf(f," --suffix SUFFIX override backup suffix\n");
- fprintf(f," --version print version number\n");
-
- fprintf(f,"\n");
- fprintf(f,"the backup suffix defaults to %s\n",BACKUP_SUFFIX);
- fprintf(f,"the block size defaults to %d\n",BLOCK_SIZE);
+#ifdef WNOHANG
+ int cnt, status;
+ pid_t pid;
+ /* An empty waitpid() loop was put here by Tridge and we could never
+ * get him to explain why he put it in, so rather than taking it
+ * out we're instead saving the child exit statuses for later use.
+ * The waitpid() loop presumably eliminates all possibility of leaving
+ * zombie children, maybe that's why he did it. */
+ while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
+ /* save the child's exit status */
+ for (cnt = 0; cnt < MAXCHILDPROCS; cnt++) {
+ if (pid_stat_table[cnt].pid == 0) {
+ pid_stat_table[cnt].pid = pid;
+ pid_stat_table[cnt].status = status;
+ break;
+ }
+ }
+ }
+#endif
+#ifndef HAVE_SIGACTION
+ signal(SIGCHLD, remember_children);
+#endif
}
-enum {OPT_VERSION,OPT_SUFFIX,OPT_SENDER,OPT_SERVER,OPT_EXCLUDE,
- OPT_EXCLUDE_FROM,OPT_DELETE,OPT_RSYNC_PATH};
-
-static char *short_options = "oblHpguDCtcahvrIxnSe:B:z";
-
-static struct option long_options[] = {
- {"version", 0, 0, OPT_VERSION},
- {"server", 0, 0, OPT_SERVER},
- {"sender", 0, 0, OPT_SENDER},
- {"delete", 0, 0, OPT_DELETE},
- {"exclude", 1, 0, OPT_EXCLUDE},
- {"exclude-from",1, 0, OPT_EXCLUDE_FROM},
- {"rsync-path", 1, 0, OPT_RSYNC_PATH},
- {"one-file-system",0, 0, 'x'},
- {"ignore-times",0, 0, 'I'},
- {"help", 0, 0, 'h'},
- {"dry-run", 0, 0, 'n'},
- {"sparse", 0, 0, 'S'},
- {"cvs-exclude", 0, 0, 'C'},
- {"archive", 0, 0, 'a'},
- {"checksum", 0, 0, 'c'},
- {"backup", 0, 0, 'b'},
- {"update", 0, 0, 'u'},
- {"verbose", 0, 0, 'v'},
- {"recursive", 0, 0, 'r'},
- {"devices", 0, 0, 'D'},
- {"perms", 0, 0, 'p'},
- {"links", 0, 0, 'l'},
- {"hard-links", 0, 0, 'H'},
- {"owner", 0, 0, 'o'},
- {"group", 0, 0, 'g'},
- {"times", 0, 0, 't'},
- {"rsh", 1, 0, 'e'},
- {"suffix", 1, 0, OPT_SUFFIX},
- {"block-size", 1, 0, 'B'},
- {"compress", 0, 0, 'z'},
- {0,0,0,0}};
-int main(int argc,char *argv[])
+/**
+ * This routine catches signals and tries to send them to gdb.
+ *
+ * Because it's called from inside a signal handler it ought not to
+ * use too many library routines.
+ *
+ * @todo Perhaps use "screen -X" instead/as well, to help people
+ * debugging without easy access to X. Perhaps use an environment
+ * variable, or just call a script?
+ *
+ * @todo The /proc/ magic probably only works on Linux (and
+ * Solaris?) Can we be more portable?
+ **/
+#ifdef MAINTAINER_MODE
+const char *get_panic_action(void)
+{
+ const char *cmd_fmt = getenv("RSYNC_PANIC_ACTION");
+
+ if (cmd_fmt)
+ return cmd_fmt;
+ else
+ return "xterm -display :0 -T Panic -n Panic "
+ "-e gdb /proc/%d/exe %d";
+}
+
+
+/**
+ * Handle a fatal signal by launching a debugger, controlled by $RSYNC_PANIC_ACTION.
+ *
+ * This signal handler is only installed if we were configured with
+ * --enable-maintainer-mode. Perhaps it should always be on and we
+ * should just look at the environment variable, but I'm a bit leery
+ * of a signal sending us into a busy loop.
+ **/
+static RETSIGTYPE rsync_panic_handler(UNUSED(int whatsig))
{
- int pid, status, status2;
- int opt;
- int option_index;
- char *shell_cmd = NULL;
- char *shell_machine = NULL;
- char *shell_path = NULL;
- char *shell_user = NULL;
- char *p;
- int f_in,f_out;
- struct file_list *flist;
- char *local_name = NULL;
-
- starttime = time(NULL);
- am_root = (getuid() == 0);
-
- /* we set a 0 umask so that correct file permissions can be
- carried across */
- orig_umask = umask(0);
-
- while ((opt = getopt_long(argc, argv,
- short_options, long_options, &option_index))
- != -1) {
- switch (opt)
- {
- case OPT_VERSION:
- printf("rsync version %s protocol version %d\n",
- VERSION,PROTOCOL_VERSION);
- exit_cleanup(0);
-
- case OPT_SUFFIX:
- backup_suffix = optarg;
- break;
-
- case OPT_RSYNC_PATH:
- rsync_path = optarg;
- break;
-
- case 'I':
- ignore_times = 1;
- break;
-
- case 'x':
- one_file_system=1;
- break;
-
- case OPT_DELETE:
- delete_mode = 1;
- break;
-
- case OPT_EXCLUDE:
- add_exclude(optarg);
- break;
-
- case OPT_EXCLUDE_FROM:
- add_exclude_file(optarg,1);
- break;
-
- case 'h':
- usage(FINFO);
- exit_cleanup(0);
-
- case 'b':
- make_backups=1;
- break;
-
- case 'n':
- dry_run=1;
- break;
-
- case 'S':
- sparse_files=1;
- break;
-
- case 'C':
- cvs_exclude=1;
- break;
-
- case 'u':
- update_only=1;
- break;
-
- case 'l':
-#if SUPPORT_LINKS
- preserve_links=1;
+ char cmd_buf[300];
+ int ret;
+
+ snprintf(cmd_buf, sizeof cmd_buf, get_panic_action(),
+ getpid(), getpid());
+
+ /* Unless we failed to execute gdb, we allow the process to
+ * continue. I'm not sure if that's right. */
+ ret = system(cmd_buf);
+ if (ret)
+ _exit(ret);
+}
#endif
- break;
- case 'H':
-#if SUPPORT_HARD_LINKS
- preserve_hard_links=1;
+
+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;
+
+ sigemptyset(&sigmask);
+# endif
+ sigact.sa_flags = SA_NOCLDSTOP;
+#endif
+ SIGACTMASK(SIGUSR1, sigusr1_handler);
+ SIGACTMASK(SIGUSR2, sigusr2_handler);
+ SIGACTMASK(SIGCHLD, remember_children);
+#ifdef MAINTAINER_MODE
+ SIGACTMASK(SIGSEGV, rsync_panic_handler);
+ SIGACTMASK(SIGFPE, rsync_panic_handler);
+ SIGACTMASK(SIGABRT, rsync_panic_handler);
+ SIGACTMASK(SIGBUS, rsync_panic_handler);
#endif
- break;
- case 'p':
- preserve_perms=1;
- break;
+ starttime = time(NULL);
+ am_root = (MY_UID() == 0);
- case 'o':
- preserve_uid=1;
- break;
+ memset(&stats, 0, sizeof(stats));
- case 'g':
- preserve_gid=1;
- break;
+ if (argc < 2) {
+ usage(FERROR);
+ exit_cleanup(RERR_SYNTAX);
+ }
- case 'D':
- preserve_devices=1;
- break;
+ /* we set a 0 umask so that correct file permissions can be
+ * carried across */
+ orig_umask = umask(0);
- case 't':
- preserve_times=1;
- break;
+#if defined CONFIG_LOCALE && defined HAVE_SETLOCALE
+ setlocale(LC_CTYPE, "");
+#endif
- case 'c':
- always_checksum=1;
- break;
+ if (!parse_arguments(&argc, (const char ***) &argv, 1)) {
+ /* FIXME: We ought to call the same error-handling
+ * code here, rather than relying on getopt. */
+ option_error();
+ exit_cleanup(RERR_SYNTAX);
+ }
- case 'v':
- verbose++;
- break;
+ SIGACTMASK(SIGINT, sig_int);
+ SIGACTMASK(SIGHUP, sig_int);
+ SIGACTMASK(SIGTERM, sig_int);
+#if defined HAVE_SIGACTION && HAVE_SIGPROCMASK
+ sigprocmask(SIG_UNBLOCK, &sigmask, NULL);
+#endif
- case 'a':
- recurse=1;
-#if SUPPORT_LINKS
- preserve_links=1;
+ /* Ignore SIGPIPE; we consistently check error codes and will
+ * see the EPIPE. */
+ SIGACTION(SIGPIPE, SIG_IGN);
+#ifdef SIGXFSZ
+ SIGACTION(SIGXFSZ, SIG_IGN);
#endif
- preserve_perms=1;
- preserve_times=1;
- preserve_gid=1;
- if (am_root) {
- preserve_devices=1;
- preserve_uid=1;
- }
- break;
-
- case OPT_SERVER:
- am_server = 1;
- break;
-
- case OPT_SENDER:
- if (!am_server) {
- usage(FERROR);
- exit_cleanup(1);
- }
- sender = 1;
- break;
-
- case 'r':
- recurse = 1;
- break;
-
- case 'e':
- shell_cmd = optarg;
- break;
-
- case 'B':
- block_size = atoi(optarg);
- break;
-
- case 'z':
- do_compression = 1;
- break;
-
- default:
- /* fprintf(FERROR,"bad option -%c\n",opt); */
- exit_cleanup(1);
- }
- }
-
- while (optind--) {
- argc--;
- argv++;
- }
-
- signal(SIGCHLD,SIG_IGN);
- signal(SIGINT,SIGNAL_CAST sig_int);
- signal(SIGPIPE,SIGNAL_CAST sig_int);
- signal(SIGHUP,SIGNAL_CAST sig_int);
-
- if (dry_run)
- verbose = MAX(verbose,1);
-
- if (am_server) {
- setup_protocol(STDOUT_FILENO,STDIN_FILENO);
-
- if (sender) {
- recv_exclude_list(STDIN_FILENO);
- if (cvs_exclude)
- add_cvs_excludes();
- do_server_sender(argc,argv);
- } else {
- do_server_recv(argc,argv);
- }
- exit_cleanup(0);
- }
-
- if (argc < 2) {
- usage(FERROR);
- exit_cleanup(1);
- }
-
- p = strchr(argv[0],':');
-
- if (p) {
- sender = 0;
- *p = 0;
- shell_machine = argv[0];
- shell_path = p+1;
- argc--;
- argv++;
- } else {
- sender = 1;
-
- p = strchr(argv[argc-1],':');
- if (!p) {
- local_server = 1;
- }
-
- if (local_server) {
- shell_machine = NULL;
- shell_path = argv[argc-1];
- } else {
- *p = 0;
- shell_machine = argv[argc-1];
- shell_path = p+1;
- }
- argc--;
- }
-
- if (shell_machine) {
- p = strchr(shell_machine,'@');
- if (p) {
- *p = 0;
- shell_user = shell_machine;
- shell_machine = p+1;
- }
- }
-
- if (verbose > 3) {
- fprintf(FERROR,"cmd=%s machine=%s user=%s path=%s\n",
- shell_cmd?shell_cmd:"",
- shell_machine?shell_machine:"",
- shell_user?shell_user:"",
- shell_path?shell_path:"");
- }
-
- if (!sender && argc != 1) {
- usage(FERROR);
- exit_cleanup(1);
- }
-
- pid = do_cmd(shell_cmd,shell_machine,shell_user,shell_path,&f_in,&f_out);
-
- setup_protocol(f_out,f_in);
-
- if (verbose > 3)
- fprintf(FERROR,"parent=%d child=%d sender=%d recurse=%d\n",
- (int)getpid(),pid,sender,recurse);
-
- if (sender) {
- if (cvs_exclude)
- add_cvs_excludes();
- if (delete_mode)
- send_exclude_list(f_out);
- flist = send_file_list(f_out,argc,argv);
- if (verbose > 3)
- fprintf(FERROR,"file list sent\n");
- send_files(flist,f_out,f_in);
- if (verbose > 3)
- fprintf(FERROR,"waiting on %d\n",pid);
- waitpid(pid, &status, 0);
- report(-1);
- exit_cleanup(status);
- }
-
- send_exclude_list(f_out);
-
- flist = recv_file_list(f_in);
- if (!flist || flist->count == 0) {
- fprintf(FERROR,"nothing to do\n");
- exit_cleanup(0);
- }
-
- local_name = get_local_name(flist,argv[0]);
-
- status2 = do_recv(f_in,f_out,flist,local_name);
-
- report(f_in);
-
- waitpid(pid, &status, 0);
-
- return status | status2;
+
+ /* 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
+ * that implement getcwd that way "pwd" can't be found after chroot. */
+ push_dir(NULL, 0);
+
+ init_flist();
+
+ if ((write_batch || read_batch) && !am_server) {
+ if (write_batch)
+ write_batch_shell_file(orig_argc, orig_argv, argc);
+
+ if (read_batch && strcmp(batch_name, "-") == 0)
+ batch_fd = STDIN_FILENO;
+ else {
+ batch_fd = do_open(batch_name,
+ write_batch ? O_WRONLY | O_CREAT | O_TRUNC
+ : O_RDONLY, S_IRUSR | S_IWUSR);
+ }
+ if (batch_fd < 0) {
+ rsyserr(FERROR, errno, "Batch file %s open error",
+ full_fname(batch_name));
+ exit_cleanup(RERR_FILEIO);
+ }
+ if (read_batch)
+ read_stream_flags(batch_fd);
+ else
+ write_stream_flags(batch_fd);
+
+ }
+ if (write_batch < 0)
+ dry_run = 1;
+
+ if (am_daemon && !am_server)
+ return daemon_main();
+
+ if (argc < 1) {
+ usage(FERROR);
+ exit_cleanup(RERR_SYNTAX);
+ }
+
+ if (am_server) {
+ set_nonblocking(STDIN_FILENO);
+ set_nonblocking(STDOUT_FILENO);
+ if (am_daemon)
+ return start_daemon(STDIN_FILENO, STDOUT_FILENO);
+ start_server(STDIN_FILENO, STDOUT_FILENO, argc, argv);
+ }
+
+ ret = start_client(argc, argv);
+ if (ret == -1)
+ exit_cleanup(RERR_STARTCLIENT);
+ else
+ exit_cleanup(ret);
+
+ return ret;
}