1978 lines · 55.8 KB · C
RawDownload
/* * 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.