+#include "inums.h"
+#include "io.h"
+#if defined CONFIG_LOCALE && defined HAVE_LOCALE_H
+#include <locale.h>
+#endif
+
+extern int dry_run;
+extern int list_only;
+extern int am_root;
+extern int am_server;
+extern int am_sender;
+extern int am_daemon;
+extern int inc_recurse;
+extern int blocking_io;
+extern int always_checksum;
+extern int remove_source_files;
+extern int output_needs_newline;
+extern int need_messages_from_generator;
+extern int kluge_around_eof;
+extern int got_xfer_error;
+extern int msgs2stderr;
+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 *stdout_format;
+extern char *logfile_format;
+extern char *filesfrom_host;
+extern char *partial_dir;
+extern char *dest_option;
+extern char *rsync_path;
+extern char *shell_cmd;
+extern char *batch_name;
+extern char *password_file;
+extern char curr_dir[MAXPATHLEN];
+extern char *basis_dir[MAX_BASIS_DIRS+1];
+extern struct file_list *first_flist;
+extern struct filter_list_struct daemon_filter_list;
+
+uid_t our_uid;
+int am_generator = 0;
+int local_server = 0;
+int daemon_over_rsh = 0;
+mode_t orig_umask = 0;
+int batch_gen_fd = -1;
+int sender_keeps_checksum = 0;
+
+/* 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);
+}
+
+void write_del_stats(int f)
+{
+ if (read_batch)
+ write_int(f, NDX_DEL_STATS);
+ else
+ send_msg(MSG_DEL_STATS, "", 0, 0);
+ write_varint(f, stats.deleted_files - stats.deleted_dirs
+ - stats.deleted_symlinks - stats.deleted_devices
+ - stats.deleted_specials);
+ write_varint(f, stats.deleted_dirs);
+ write_varint(f, stats.deleted_symlinks);
+ write_varint(f, stats.deleted_devices);
+ write_varint(f, stats.deleted_specials);
+}
+
+void read_del_stats(int f)
+{
+ stats.deleted_files = read_varint(f);
+ stats.deleted_files += stats.deleted_dirs = read_varint(f);
+ stats.deleted_files += stats.deleted_symlinks = read_varint(f);
+ stats.deleted_files += stats.deleted_devices = read_varint(f);
+ stats.deleted_files += stats.deleted_specials = read_varint(f);
+}
+
+/* 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 (INFO_GTE(STATS, 3)) {
+ /* 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_itemized_counts(const char *prefix, int *counts)
+{
+ static char *labels[] = { "reg", "dir", "link", "dev", "special" };
+ char buf[1024], *pre = " (";
+ int j, len = 0;
+ int total = counts[0];
+ if (total) {
+ counts[0] -= counts[1] + counts[2] + counts[3] + counts[4];
+ for (j = 0; j < 5; j++) {
+ if (counts[j]) {
+ len += snprintf(buf+len, sizeof buf - len - 2,
+ "%s%s: %s",
+ pre, labels[j], comma_num(counts[j]));
+ pre = ", ";
+ }
+ }
+ buf[len++] = ')';
+ }
+ buf[len] = '\0';
+ rprintf(FINFO, "%s: %s%s\n", prefix, comma_num(total), buf);
+}
+
+static void output_summary(void)
+{
+ if (INFO_GTE(STATS, 2)) {
+ rprintf(FCLIENT, "\n");
+ output_itemized_counts("Number of files", &stats.num_files);
+ if (protocol_version >= 29)
+ output_itemized_counts("Number of created files", &stats.created_files);
+ if (protocol_version >= 31)
+ output_itemized_counts("Number of deleted files", &stats.deleted_files);
+ rprintf(FINFO,"Number of regular files transferred: %s\n",
+ comma_num(stats.xferred_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: %s seconds\n",
+ comma_dnum((double)stats.flist_buildtime / 1000, 3));
+ rprintf(FINFO,
+ "File list transfer time: %s seconds\n",
+ comma_dnum((double)stats.flist_xfertime / 1000, 3));
+ }
+ rprintf(FINFO,"Total bytes sent: %s\n",
+ human_num(total_written));
+ rprintf(FINFO,"Total bytes received: %s\n",
+ human_num(total_read));
+ }
+
+ if (INFO_GTE(STATS, 1)) {
+ 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 %s%s\n",
+ human_num(stats.total_size),
+ comma_dnum((double)stats.total_size / (total_written+total_read), 2),
+ 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 */
+}
+