+#include "ifuncs.h"
+#include "io.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 inc_recurse;
+extern int blocking_io;
+extern int remove_source_files;
+extern int need_messages_from_generator;
+extern int kluge_around_eof;
+extern int do_stats;
+extern int got_xfer_error;
+extern int module_id;
+extern int copy_links;
+extern int copy_dirlinks;
+extern int copy_unsafe_links;
+extern int keep_dirlinks;
+extern int preserve_hard_links;
+extern int protocol_version;
+extern int file_total;
+extern int recurse;
+extern int xfer_dirs;
+extern int protect_args;
+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 int connect_timeout;
+extern pid_t cleanup_child_pid;
+extern unsigned int module_dirlen;
+extern struct stats stats;
+extern char *filesfrom_host;
+extern char *partial_dir;
+extern char *dest_option;
+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 file_list *first_flist;
+extern struct filter_list_struct server_filter_list;
+
+uid_t our_uid;
+int local_server = 0;
+int daemon_over_rsh = 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];
+
+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)
+{
+ 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_varlong30(f, total_read, 3);
+ write_varlong30(f, total_written, 3);
+ write_varlong30(f, stats.total_size, 3);
+ if (protocol_version >= 29) {
+ write_varlong30(f, stats.flist_buildtime, 3);
+ write_varlong30(f, stats.flist_xfertime, 3);
+ }
+ }
+ 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_varlong30(f, 3);
+ total_read = read_varlong30(f, 3);
+ stats.total_size = read_varlong30(f, 3);
+ if (protocol_version >= 29) {
+ stats.flist_buildtime = read_varlong30(f, 3);
+ stats.flist_xfertime = read_varlong30(f, 3);
+ }
+ } else if (write_batch) {
+ /* The --read-batch process is going to be a client
+ * receiver, so we need to give it the stats. */
+ write_varlong30(batch_fd, total_read, 3);
+ write_varlong30(batch_fd, total_written, 3);
+ write_varlong30(batch_fd, stats.total_size, 3);
+ if (protocol_version >= 29) {
+ write_varlong30(batch_fd, stats.flist_buildtime, 3);
+ write_varlong30(batch_fd, stats.flist_xfertime, 3);
+ }
+ }
+}
+
+static void output_summary(void)
+{
+ 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%s\n",
+ human_num(stats.total_size),
+ (double)stats.total_size / (total_written+total_read),
+ write_batch < 0 ? " (BATCH ONLY)" : dry_run ? " (DRY RUN)" : "");
+ }
+
+ 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 */
+}
+
+
+/* Start the remote shell. cmd may be NULL to use the default. */
+static pid_t do_cmd(char *cmd, char *machine, char *user, char **remote_argv, int remote_argc,
+ int *f_in_p, int *f_out_p)
+{
+ int i, argc = 0;
+ char *args[MAX_ARGS];
+ pid_t pid;
+ 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); /* MEMORY LEAK */
+ 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)
+ goto arg_overflow;
+ 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 && !(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)
+ goto arg_overflow;
+ }
+
+ args[argc++] = ".";
+
+ if (!daemon_over_rsh) {
+ while (remote_argc > 0) {
+ if (argc >= MAX_ARGS - 1) {
+ arg_overflow:
+ rprintf(FERROR, "internal: args[] overflowed in do_cmd()\n");
+ exit_cleanup(RERR_SYNTAX);
+ }
+ args[argc++] = *remote_argv++;
+ remote_argc--;
+ }
+ }
+
+ 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];
+ set_allow_inc_recurse();
+ if (fd_pair(from_gen_pipe) < 0) {
+ rsyserr(FERROR, errno, "pipe");
+ exit_cleanup(RERR_IPC);
+ }
+ batch_gen_fd = from_gen_pipe[0];
+ *f_out_p = from_gen_pipe[1];
+ *f_in_p = batch_fd;
+ pid = (pid_t)-1; /* no child pid */
+#ifdef ICONV_CONST
+ setup_iconv();
+#endif
+ } 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;
+ set_allow_inc_recurse();
+ pid = local_child(argc, args, f_in_p, f_out_p, child_main);
+#ifdef ICONV_CONST
+ setup_iconv();
+#endif
+ } else {
+ pid = piped_child(args, f_in_p, f_out_p);
+#ifdef ICONV_CONST
+ setup_iconv();
+#endif
+ if (protect_args)
+ send_protected_args(*f_out_p, args);
+ }