/* * 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-2022 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 3 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, visit the http://fsf.org website. */ #include "rsync.h"#include "inums.h"#include "ifuncs.h"#include "io.h"#if defined CONFIG_LOCALE && defined HAVE_LOCALE_H#include <locale.h>#endif#include <popt.h>#ifdef __TANDEM#include <floss.h(floss_execlp)>#endif#ifdef HAVE_SYS_RESOURCE_H#include <sys/resource.h>#endif extern int dry_run;extern int list_only;extern int io_timeout;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 called_from_signal_handler;extern int need_messages_from_generator;extern int kluge_around_eof;extern int got_xfer_error;extern volatile sig_atomic_t got_sigusr2;extern int old_style_args;extern int msgs2stderr;extern int module_id;extern int read_only;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 mkpath_dest_arg;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 unsigned 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 sock_f_in;extern int sock_f_out;extern int filesfrom_fd;extern int connect_timeout;extern int send_msgs_to_gen;extern dev_t filesystem_dev;extern pid_t cleanup_child_pid;extern size_t bwlimit_writemax;extern unsigned int module_dirlen;extern BOOL flist_receiving_enabled;extern BOOL want_progress_now;extern BOOL shutting_down;extern int backup_dir_len;extern int basis_dir_cnt;extern int default_af_hint;extern int stdout_format_has_i;extern int trust_sender_filter;extern int trust_sender_args;extern struct stats stats;extern char *stdout_format;extern char *logfile_format;extern char *filesfrom_host;extern char *partial_dir;extern char *rsync_path;extern char *shell_cmd;extern char *password_file;extern char *backup_dir;extern char *copy_as;extern char *tmpdir;extern char curr_dir[MAXPATHLEN];extern char backup_dir_buf[MAXPATHLEN];extern char *basis_dir[MAX_BASIS_DIRS+1];extern struct file_list *first_flist;extern filter_rule_list daemon_filter_list, implied_filter_list; uid_t our_uid;gid_t our_gid;int am_receiver = 0; /* Only set to 1 after the receiver/generator fork. */int am_generator = 0; /* Only set to 1 after the receiver/generator fork. */int local_server = 0;int daemon_connection = 0; /* 0 = no daemon, 1 = daemon via remote shell, -1 = daemon via socket */mode_t orig_umask = 0;int batch_gen_fd = -1;int sender_keeps_checksum = 0;int raw_argc, cooked_argc;char **raw_argv, **cooked_argv; /* 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)# endifstatic 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;} int shell_exec(const char *cmd){ char *shell = getenv("RSYNC_SHELL"); int status; pid_t pid; if (!shell) return system(cmd); if ((pid = fork()) < 0) return -1; if (pid == 0) { execlp(shell, shell, "-c", cmd, NULL); _exit(1); } int ret = wait_process(pid, &status, 0); return ret < 0 ? -1 : status;} /* 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 write_ndx(f, NDX_DEL_STATS); 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_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_files"); stats.deleted_files += stats.deleted_dirs = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_dirs"); stats.deleted_files += stats.deleted_symlinks = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_symlinks"); stats.deleted_files += stats.deleted_devices = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_devices"); stats.deleted_files += stats.deleted_specials = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_specials");} static void become_copy_as_user(){ char *gname; uid_t uid; gid_t gid; if (!copy_as) return; if (DEBUG_GTE(CMD, 2)) rprintf(FINFO, "[%s] copy_as=%s\n", who_am_i(), copy_as); if ((gname = strchr(copy_as, ':')) != NULL) *gname++ = '\0'; if (!user_to_uid(copy_as, &uid, True)) { rprintf(FERROR, "Invalid copy-as user: %s\n", copy_as); exit_cleanup(RERR_SYNTAX); } if (gname) { if (!group_to_gid(gname, &gid, True)) { rprintf(FERROR, "Invalid copy-as group: %s\n", gname); exit_cleanup(RERR_SYNTAX); } } else { struct passwd *pw; if ((pw = getpwuid(uid)) == NULL) { rsyserr(FERROR, errno, "getpwuid failed"); exit_cleanup(RERR_SYNTAX); } gid = pw->pw_gid; } if (setgid(gid) < 0) { rsyserr(FERROR, errno, "setgid failed"); exit_cleanup(RERR_SYNTAX); }#ifdef HAVE_SETGROUPS if (setgroups(1, &gid)) { rsyserr(FERROR, errno, "setgroups failed"); exit_cleanup(RERR_SYNTAX); }#endif#ifdef HAVE_INITGROUPS if (!gname && initgroups(copy_as, gid) < 0) { rsyserr(FERROR, errno, "initgroups failed"); exit_cleanup(RERR_SYNTAX); }#endif if (setuid(uid) < 0#ifdef HAVE_SETEUID || seteuid(uid) < 0#endif ) { rsyserr(FERROR, errno, "setuid failed"); exit_cleanup(RERR_SYNTAX); } our_uid = MY_UID(); our_gid = MY_GID(); am_root = (our_uid == ROOT_UID); if (gname) gname[-1] = ':';} /* 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 *const 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]) { /* snprintf can return more than its size arg * on truncation; keep len <= sizeof buf - 2 so * the closing ')' and trailing NUL always * have room and the next iteration's * sizeof buf - len - 2 cannot underflow. */ if (len >= (int)sizeof buf - 2) break; len += snprintf(buf+len, sizeof buf - len - 2, "%s%s: %s", pre, labels[j], comma_num(counts[j])); if (len > (int)sizeof buf - 2) len = (int)sizeof buf - 2; pre = ", "; } } buf[len++] = ')'; } buf[len] = '\0'; rprintf(FINFO, "%s: %s%s\n", prefix, comma_num(total), buf);} static const char *bytes_per_sec_human_dnum(void){ if (starttime == (time_t)-1 || endtime == (time_t)-1) return "UNKNOWN"; return human_dnum((total_written + total_read) / (0.5 + (endtime - starttime)), 2);} 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), bytes_per_sec_human_dnum()); 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 MEM_ALLOC_INFO struct MEM_ALLOC_INFO mi = MEM_ALLOC_INFO(); /* mallinfo or mallinfo2 */ rprintf(FCLIENT, "\n"); rprintf(FINFO, RSYNC_NAME "[%d] (%s%s%s) heap statistics:\n", (int)getpid(), am_server ? "server " : "", am_daemon ? "daemon " : "", who_am_i()); #define PRINT_ALLOC_NUM(title, descr, num) \ rprintf(FINFO, " %-11s%10" SIZE_T_FMT_MOD "d (" descr ")\n", \ title ":", (SIZE_T_FMT_CAST)(num)); PRINT_ALLOC_NUM("arena", "bytes from sbrk", mi.arena); PRINT_ALLOC_NUM("ordblks", "chunks not in use", mi.ordblks); PRINT_ALLOC_NUM("smblks", "free fastbin blocks", mi.smblks);Lines 1–500 of 1978. Load more
/* * 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-2022 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 3 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, visit the http://fsf.org website. */ #include "rsync.h" #include "inums.h" #include "ifuncs.h" #include "io.h" #if defined CONFIG_LOCALE && defined HAVE_LOCALE_H #include <locale.h> #endif #include <popt.h> #ifdef __TANDEM #include <floss.h(floss_execlp)> #endif #ifdef HAVE_SYS_RESOURCE_H #include <sys/resource.h> #endif extern int dry_run; extern int list_only; extern int io_timeout; 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 called_from_signal_handler; extern int need_messages_from_generator; extern int kluge_around_eof; extern int got_xfer_error; extern volatile sig_atomic_t got_sigusr2; extern int old_style_args; extern int msgs2stderr; extern int module_id; extern int read_only; 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 mkpath_dest_arg; 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 unsigned 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 sock_f_in; extern int sock_f_out; extern int filesfrom_fd; extern int connect_timeout; extern int send_msgs_to_gen; extern dev_t filesystem_dev; extern pid_t cleanup_child_pid; extern size_t bwlimit_writemax; extern unsigned int module_dirlen; extern BOOL flist_receiving_enabled; extern BOOL want_progress_now; extern BOOL shutting_down; extern int backup_dir_len; extern int basis_dir_cnt; extern int default_af_hint; extern int stdout_format_has_i; extern int trust_sender_filter; extern int trust_sender_args; extern struct stats stats; extern char *stdout_format; extern char *logfile_format; extern char *filesfrom_host; extern char *partial_dir; extern char *rsync_path; extern char *shell_cmd; extern char *password_file; extern char *backup_dir; extern char *copy_as; extern char *tmpdir; extern char curr_dir[MAXPATHLEN]; extern char backup_dir_buf[MAXPATHLEN]; extern char *basis_dir[MAX_BASIS_DIRS+1]; extern struct file_list *first_flist; extern filter_rule_list daemon_filter_list, implied_filter_list; uid_t our_uid; gid_t our_gid; int am_receiver = 0; /* Only set to 1 after the receiver/generator fork. */ int am_generator = 0; /* Only set to 1 after the receiver/generator fork. */ int local_server = 0; int daemon_connection = 0; /* 0 = no daemon, 1 = daemon via remote shell, -1 = daemon via socket */ mode_t orig_umask = 0; int batch_gen_fd = -1; int sender_keeps_checksum = 0; int raw_argc, cooked_argc; char **raw_argv, **cooked_argv; /* 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; } int shell_exec(const char *cmd) { char *shell = getenv("RSYNC_SHELL"); int status; pid_t pid; if (!shell) return system(cmd); if ((pid = fork()) < 0) return -1; if (pid == 0) { execlp(shell, shell, "-c", cmd, NULL); _exit(1); } int ret = wait_process(pid, &status, 0); return ret < 0 ? -1 : status; } /* 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 write_ndx(f, NDX_DEL_STATS); 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_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_files"); stats.deleted_files += stats.deleted_dirs = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_dirs"); stats.deleted_files += stats.deleted_symlinks = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_symlinks"); stats.deleted_files += stats.deleted_devices = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_devices"); stats.deleted_files += stats.deleted_specials = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_specials"); } static void become_copy_as_user() { char *gname; uid_t uid; gid_t gid; if (!copy_as) return; if (DEBUG_GTE(CMD, 2)) rprintf(FINFO, "[%s] copy_as=%s\n", who_am_i(), copy_as); if ((gname = strchr(copy_as, ':')) != NULL) *gname++ = '\0'; if (!user_to_uid(copy_as, &uid, True)) { rprintf(FERROR, "Invalid copy-as user: %s\n", copy_as); exit_cleanup(RERR_SYNTAX); } if (gname) { if (!group_to_gid(gname, &gid, True)) { rprintf(FERROR, "Invalid copy-as group: %s\n", gname); exit_cleanup(RERR_SYNTAX); } } else { struct passwd *pw; if ((pw = getpwuid(uid)) == NULL) { rsyserr(FERROR, errno, "getpwuid failed"); exit_cleanup(RERR_SYNTAX); } gid = pw->pw_gid; } if (setgid(gid) < 0) { rsyserr(FERROR, errno, "setgid failed"); exit_cleanup(RERR_SYNTAX); } #ifdef HAVE_SETGROUPS if (setgroups(1, &gid)) { rsyserr(FERROR, errno, "setgroups failed"); exit_cleanup(RERR_SYNTAX); } #endif #ifdef HAVE_INITGROUPS if (!gname && initgroups(copy_as, gid) < 0) { rsyserr(FERROR, errno, "initgroups failed"); exit_cleanup(RERR_SYNTAX); } #endif if (setuid(uid) < 0 #ifdef HAVE_SETEUID || seteuid(uid) < 0 #endif ) { rsyserr(FERROR, errno, "setuid failed"); exit_cleanup(RERR_SYNTAX); } our_uid = MY_UID(); our_gid = MY_GID(); am_root = (our_uid == ROOT_UID); if (gname) gname[-1] = ':'; } /* 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 *const 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]) { /* snprintf can return more than its size arg * on truncation; keep len <= sizeof buf - 2 so * the closing ')' and trailing NUL always * have room and the next iteration's * sizeof buf - len - 2 cannot underflow. */ if (len >= (int)sizeof buf - 2) break; len += snprintf(buf+len, sizeof buf - len - 2, "%s%s: %s", pre, labels[j], comma_num(counts[j])); if (len > (int)sizeof buf - 2) len = (int)sizeof buf - 2; pre = ", "; } } buf[len++] = ')'; } buf[len] = '\0'; rprintf(FINFO, "%s: %s%s\n", prefix, comma_num(total), buf); } static const char *bytes_per_sec_human_dnum(void) { if (starttime == (time_t)-1 || endtime == (time_t)-1) return "UNKNOWN"; return human_dnum((total_written + total_read) / (0.5 + (endtime - starttime)), 2); } 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), bytes_per_sec_human_dnum()); 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 MEM_ALLOC_INFO struct MEM_ALLOC_INFO mi = MEM_ALLOC_INFO(); /* mallinfo or mallinfo2 */ rprintf(FCLIENT, "\n"); rprintf(FINFO, RSYNC_NAME "[%d] (%s%s%s) heap statistics:\n", (int)getpid(), am_server ? "server " : "", am_daemon ? "daemon " : "", who_am_i()); #define PRINT_ALLOC_NUM(title, descr, num) \ rprintf(FINFO, " %-11s%10" SIZE_T_FMT_MOD "d (" descr ")\n", \ title ":", (SIZE_T_FMT_CAST)(num)); PRINT_ALLOC_NUM("arena", "bytes from sbrk", mi.arena); PRINT_ALLOC_NUM("ordblks", "chunks not in use", mi.ordblks); PRINT_ALLOC_NUM("smblks", "free fastbin blocks", mi.smblks);