[patch] Filename conversion

Eran Tromer rsync2eran at tromer.org
Sat May 29 19:20:19 GMT 2004


Hi,

One feature missing from rsync, and requested on this list before, is
on-the-fly conversion of filename character encoding. For example, I
often need to sync files having Hebrew filenames from a UTF-8 system
(Linux) to an ISO8859-8 system (Cygwin on Windows 2000 using the
non-Unicode Win32 interface). Other circumstances surely abound.

Attached is a patch against rsync 2.6.2 that adds an "--fname-convert"
option. When the argument "--fname-convert CONV" is given, rsync pipes
every filename through the program CONV, and filename presented to the
server will be CONV's output instead of the raw filename.

Artificial example:
$ touch /tmp/xyz
$ rsync -fname-convert 'tr y Y' /tmp/xyz /tmp/
$ ls /tmp/x?z
/tmp/xyz  /tmp/xYz

Perhaps the most useful case is using iconv:
$ rsync --fname-convert 'iconv -f utf8 -t iso8859-8' ...

I chose to allow invocation of arbitrary programs instead of using
libiconv (or equivalent) in order to avoid external dependencies, and to
offer more flexibility. The price is that some heuristics were needed to
avoid the deadlock problems that tend to occur when filtering data
through a program that uses buffered I/O -- see the comments at the top
of the new file fnameconv.c. The delay you may have noticed in the above
artificial example using "tr" is due to these heuristics; it occurs just
once per rsync invocation, not for every file.

I believe there are no server-side security implications, since all
conversion is done at the client and the server is oblivious to it. On
the client, conversion is done before sanitize_path() and besides,
providing a sane converter program is the client's responsibility anyway.

In verbose mode the updating of non-regular files is reported via
rprintf() by the server, so the client will see the converted filename
instead the raw filename -- see my comment in recv_generator(). Fixing
this requires some delicate changes so I left it as is, but it seems
like a minor concern.

Most of the new code is in the new file fnameconv.c. The patch lightly
touches some other files, mostly flist.c and the addition/extension of
some utility functions. I took the opportunity to fix an argument
parsing buffer overflow bug in main.c. Note that you'll need to run
autoconf and 'make proto'.

  Eran
-------------- next part --------------
diff -rupNP rsync-2.6.2/cleanup.c rsync-2.6.2-fnameconv.clean/cleanup.c
--- rsync-2.6.2/cleanup.c	2004-01-27 10:14:33.000000000 +0200
+++ rsync-2.6.2-fnameconv.clean/cleanup.c	2004-05-29 21:17:08.000000000 +0300
@@ -119,6 +119,7 @@ void _exit_cleanup(int code, const char 
 		finish_transfer(cleanup_new_fname, fname, cleanup_file);
 	}
 	io_flush(FULL_FLUSH);
+	cleanup_fname_convert();
 	if (cleanup_fname)
 		do_unlink(cleanup_fname);
 	if (code) {
diff -rupNP rsync-2.6.2/errcode.h rsync-2.6.2-fnameconv.clean/errcode.h
--- rsync-2.6.2/errcode.h	2003-12-15 10:04:14.000000000 +0200
+++ rsync-2.6.2-fnameconv.clean/errcode.h	2004-05-29 21:17:08.000000000 +0300
@@ -34,6 +34,7 @@
 #define RERR_STREAMIO   12      /* error in rsync protocol data stream */
 #define RERR_MESSAGEIO  13      /* errors with program diagnostics */
 #define RERR_IPC        14      /* error in IPC code */
+#define RERR_FNAMECONV  15      /* error in filename conversion */
 
 #define RERR_SIGNAL     20      /* status returned when sent SIGUSR1, SIGINT */
 #define RERR_WAITCHILD  21      /* some error returned by waitpid() */
diff -rupNP rsync-2.6.2/flist.c rsync-2.6.2-fnameconv.clean/flist.c
--- rsync-2.6.2/flist.c	2004-04-29 22:37:15.000000000 +0300
+++ rsync-2.6.2-fnameconv.clean/flist.c	2004-05-29 21:17:08.000000000 +0300
@@ -333,7 +333,7 @@ void send_file_entry(struct file_struct 
 	static uid_t uid;
 	static gid_t gid;
 	static char lastname[MAXPATHLEN];
-	char *fname, fbuf[MAXPATHLEN];
+	char fname[MAXPATHLEN];
 	int l1, l2;
 
 	if (f == -1)
@@ -351,7 +351,11 @@ void send_file_entry(struct file_struct 
 
 	io_write_phase = "send_file_entry";
 
-	fname = f_name_to(file, fbuf);
+	if (am_server)
+		f_name_to(file, fname); /* fname conversion always done on client */
+	else
+		convert_fname(fname, f_name(file), MAXPATHLEN);
+	
 
 	flags = base_flags;
 
@@ -563,6 +567,9 @@ void receive_file_entry(struct file_stru
 
 	strlcpy(lastname, thisname, MAXPATHLEN);
 
+	if (!am_server) /* fname conversion always done on client */
+		convert_fname(thisname, lastname, MAXPATHLEN);
+
 	clean_fname(thisname);
 
 	if (sanitize_paths)
@@ -1043,6 +1050,9 @@ struct file_list *send_file_list(int f, 
 
 	start_write = stats.total_written;
 
+	if (!am_server)
+		init_fname_convert();
+
 	flist = flist_new(f == -1 ? WITHOUT_HLINK : WITH_HLINK,
 	    "send_file_list");
 
@@ -1217,6 +1227,9 @@ struct file_list *send_file_list(int f, 
 			write_batch_flist_info(flist->count, flist->files);
 	}
 
+	if (!am_server)
+		cleanup_fname_convert();
+
 	if (verbose > 3)
 		output_flist(flist);
 
@@ -1239,6 +1252,9 @@ struct file_list *recv_file_list(int f)
 
 	start_read = stats.total_read;
 
+	if (!am_server)
+		init_fname_convert();
+
 	flist = flist_new(WITH_HLINK, "recv_file_list");
 
 	flist->count = 0;
@@ -1293,6 +1309,9 @@ struct file_list *recv_file_list(int f)
 		}
 	}
 
+	if (!am_server)
+		cleanup_fname_convert();
+
 	if (verbose > 3)
 		output_flist(flist);
 
diff -rupNP rsync-2.6.2/fnameconv.c rsync-2.6.2-fnameconv.clean/fnameconv.c
--- rsync-2.6.2/fnameconv.c	1970-01-01 02:00:00.000000000 +0200
+++ rsync-2.6.2-fnameconv.clean/fnameconv.c	2004-05-29 21:17:09.000000000 +0300
@@ -0,0 +1,224 @@
+/*  -*- c-file-style: "linux" -*-
+ * 
+ * Copyright (C) 2004 by Eran Tromer
+ * 
+ * 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.
+ */
+
+/* Handles filename conversion through an external process. Implements
+ * two modes of operation:
+ * In persistent mode, a single filename converter is kept running;
+ * for each query we feed it a single line and read back a single
+ * line. This will fail for programs that used buffered I/O, and will
+ * get into a deadlock.  
+ * In non-persistent mode, a converter is invoked and killed for each
+ * query. This has a very high overhead, but will work for any
+ * program.
+ * We start in persistence mode, and if we suspect a deadlock (i.e., 
+ * nothing happens for FNAME_CONV_PERSISTENCE_TIMEOUT milliseconds)
+ * then we smoothly fall back to non-persistent mode.
+ *
+ * Filename conversion errors are always considered fatal, since an
+ * incorrectly named file could cause unpredictable damage.
+ */
+
+#include <rsync.h>
+
+#define FNAME_CONV_PERSISTENCE_TIMEOUT 3000 /* milliseconds */
+
+static int conv_persistent = 1;
+static pid_t conv_pid = -1;
+static int conv_write_fd = -1, conv_read_fd;
+extern char* fname_convert_cmd;
+extern int blocking_io;
+
+/**
+ * Splits cmd on spaces.
+ */
+static void split_on_spaces(char* cmd, char** parts) {
+	int nparts=0;
+	char *tok;
+	char *cmd2 = strdup(cmd);
+	if (!cmd2) {
+		rprintf(FERROR, "Out of memory while parsing filename filter %s\n", cmd);
+		exit_cleanup(RERR_MALLOC);
+	}
+
+	for (tok=strtok(cmd2," ");tok;tok=strtok(NULL," ")) {
+		if (nparts>=MAX_ARGS) {
+			rprintf(FERROR, "Filename conversion command is too long: %s\n", cmd);
+			exit_cleanup(RERR_SYNTAX);
+		}
+		parts[nparts++] = tok;
+	}
+	parts[nparts] = NULL;
+}
+
+
+/**
+ * Runs the filename converter process. Should be called before filename
+ * conversion begins (actually it's not necessarh, but it keeps the proress report
+ * nice and clean.
+ **/
+void init_fname_convert()
+{
+	if (fname_convert_cmd && conv_pid<0) {
+		char *args[MAX_ARGS];
+	 
+		if (verbose > 2)
+			rprintf(FINFO, "Running filename converter: %s\n", fname_convert_cmd);
+		split_on_spaces(fname_convert_cmd, args);
+		/* Invoke child pipe with non-blocking IO and without registering it for
+		   autocleanup (the latter may blow up the all_pids table, and is not needed
+		   since we have our own cleanup handler. */
+		conv_pid = piped_child(args, &conv_read_fd, &conv_write_fd, 0, 0);
+		set_nonblocking(conv_write_fd);
+		set_nonblocking(conv_read_fd);
+	}
+}
+
+/**
+ * Kills the filename converter process. Should be called when the file
+ * list creation is done. We assume that the converter will terminate
+ * soon after its standard input is closed.
+ **/
+void cleanup_fname_convert()
+{
+	if (conv_pid>=0) {
+		int status;
+		if (conv_write_fd>=0) {
+			close(conv_write_fd);
+			conv_write_fd = -1;
+		}
+		close(conv_read_fd);
+		waitpid(conv_pid, &status, 0);
+		conv_pid = -1;
+	}
+}
+
+/**
+ * Converts the filename from src into dest, using at most maxlen
+ * characters of dest.
+ **/
+void convert_fname(char *dest, const char *src, unsigned int maxlen)
+{
+	if (!fname_convert_cmd) {
+		strlcpy(dest, src, maxlen);
+	} else {
+		int res;
+		const char *srcp;
+		char *destp;
+		unsigned int srcrem, dstrem;
+
+		init_fname_convert();
+
+		/* Send and receive strings simultaneously to avoid deadlock: */
+		srcrem = strlen(src)+1; /* chars left to send (incl. terminating LF) */
+		dstrem = maxlen-1;      /* free chars left in dest                   */
+		srcp = src; 
+		destp = dest;
+		while(1) {
+			/* Write as much as possible: */
+			if (srcrem>1) {
+				res = write(conv_write_fd, srcp, srcrem-1);
+				if (res<0 && errno!=EAGAIN) {
+					rprintf(FERROR, "Error writing to fname converter (filename: %s): %s\n", strerror(errno), src);
+					exit_cleanup(RERR_FNAMECONV);
+				}
+				if (res>0) { /* wrote something */
+					srcp += res; 
+					srcrem -= res;
+				}
+			}
+			if (srcrem==1) { /* final LF */
+				res = write(conv_write_fd, "\n", 1);
+				if (res<0 && errno!=EAGAIN) {
+					rprintf(FERROR, "Error writing to fname converter (filename: %s): %s\n", strerror(errno), src);
+					exit_cleanup(RERR_FNAMECONV);
+				}
+				if (res>0) { /* wrote final LF */
+					srcrem = 0;
+					if (!conv_persistent) {
+						close(conv_write_fd);
+						conv_write_fd = -1;
+					}
+				}
+			}
+			
+			/* Read as much as possible: */
+			res = read(conv_read_fd, destp, dstrem);
+			if (res<0 && errno!=EAGAIN) {
+				rprintf(FERROR, "Error reading from filename converter (filename: %s):%s \n", strerror(errno), src);
+				exit_cleanup(RERR_FNAMECONV);
+			}
+			if (res==0) { /* EOF */
+				rprintf(FERROR, "EOF from filename converter (filename: %s)\n", src);
+				exit_cleanup(RERR_FNAMECONV);
+			}
+			if (res>0) {
+				destp += res; 
+				dstrem -= res;
+				if (*(destp-1)=='\n' || *(destp-1)=='\r')
+					break; /* End of line. Yippy! */
+				if (dstrem==0) {
+					rprintf(FINFO, "Name converter output too long (filename: %s)\n", src);
+					exit_cleanup(RERR_FNAMECONV);
+				}
+			}
+			
+			/* Await activity */
+			if (!await_fds(conv_read_fd, (srcrem==0) ? -1 : conv_write_fd, FNAME_CONV_PERSISTENCE_TIMEOUT))
+				if (srcrem==0 && conv_persistent) {
+					/* We finished writing but nothing happens. It looks like the converter program
+					   is using buffered I/O and thus wait to read more input, but we can't give it
+					   the next filename yet. Fall back to non-persistent mode.                      */
+					if (verbose > 0)
+						rprintf(FINFO, "Filename converter blocked, disabling persistence to recover.\n");
+					
+					conv_persistent=0;
+					close(conv_write_fd);
+					conv_write_fd = -1;
+				}
+		}
+		
+		/* Cleanup and sanity check */
+		if (!conv_persistent)
+			cleanup_fname_convert();
+		if (srcrem>0) {
+			close(conv_write_fd);
+			rprintf(FERROR, "Name converter produced output before reading all its input for file: %s\n", src);
+			exit_cleanup(RERR_FNAMECONV);
+		}
+
+		/* Chop newline chars */
+		destp--;
+		if (destp>dest && *destp=='\n') 
+			--destp;
+		if (destp>dest && *destp=='\r') 
+			--destp;
+		if (++destp==dest) {
+			rprintf(FERROR, "Name converter output is empty (filename: %s)\n", src);
+			exit_cleanup(RERR_FNAMECONV);
+		}
+		*destp=0;
+		/* Also, we may have a leading CR left over from a CRLF of the previous line */
+		if (*dest=='\n')
+			memmove(dest, dest+1, destp-dest-1);
+
+		if (verbose>2)
+			rprintf(FINFO, "Converted filename: %s -> %s\n", src, dest);
+
+	}
+}
diff -rupNP rsync-2.6.2/generator.c rsync-2.6.2-fnameconv.clean/generator.c
--- rsync-2.6.2/generator.c	2004-04-15 19:55:23.000000000 +0300
+++ rsync-2.6.2-fnameconv.clean/generator.c	2004-05-29 21:17:09.000000000 +0300
@@ -264,6 +264,12 @@ static void generate_and_send_sums(struc
  *
  * @note This comment was added later by mbp who was trying to work it
  * out.  It might be wrong.
+ *
+ * TODO: The filename seen in recv_generator is after filename
+ * conversion.  In verbose mode, directories, symlinks and device
+ * files are printf()ed here but regular files are rprintf()ed on the
+ * sender (unconverted). To solve the above, move all progress
+ * reporting to the sender.
  **/
 void recv_generator(char *fname, struct file_struct *file, int i, int f_out)
 {
diff -rupNP rsync-2.6.2/log.c rsync-2.6.2-fnameconv.clean/log.c
--- rsync-2.6.2/log.c	2004-04-29 22:34:31.000000000 +0300
+++ rsync-2.6.2-fnameconv.clean/log.c	2004-05-29 21:17:09.000000000 +0300
@@ -57,6 +57,7 @@ struct {
 	{ RERR_STREAMIO   , "error in rsync protocol data stream" },
 	{ RERR_MESSAGEIO  , "errors with program diagnostics" },
 	{ RERR_IPC        , "error in IPC code" },
+	{ RERR_FNAMECONV  , "error in filename conversion" },
 	{ RERR_SIGNAL     , "received SIGUSR1 or SIGINT" },
 	{ RERR_WAITCHILD  , "some error returned by waitpid()" },
 	{ RERR_MALLOC     , "error allocating core memory buffers" },
diff -rupNP rsync-2.6.2/main.c rsync-2.6.2-fnameconv.clean/main.c
--- rsync-2.6.2/main.c	2004-02-10 05:54:47.000000000 +0200
+++ rsync-2.6.2-fnameconv.clean/main.c	2004-05-29 21:17:09.000000000 +0300
@@ -217,7 +217,7 @@ static pid_t do_cmd(char *cmd, char *mac
 		    int *f_in, int *f_out)
 {
 	int i, argc = 0;
-	char *args[100];
+	char *args[MAX_ARGS];
 	pid_t ret;
 	char *tok, *dir = NULL;
 	int dash_l_set = 0;
@@ -232,8 +232,13 @@ static pid_t do_cmd(char *cmd, char *mac
 		if (!cmd)
 			goto oom;
 
-		for (tok = strtok(cmd, " "); tok; tok = strtok(NULL, " "))
+		for (tok = strtok(cmd, " "); tok; tok = strtok(NULL, " ")) {
+			if (argc>=MAX_ARGS) {
+				rprintf(FERROR, "Command is too long\n");
+				exit_cleanup(RERR_SYNTAX);
+			}
 			args[argc++] = tok;
+		}
 
 		/* check to see if we've already been given '-l user' in
 		 * the remote-shell command */
@@ -296,7 +301,7 @@ static pid_t do_cmd(char *cmd, char *mac
 			create_flist_from_batch(); /* sets batch_flist */
 		ret = local_child(argc, args, f_in, f_out, child_main);
 	} else {
-		ret = piped_child(args,f_in,f_out);
+		ret = piped_child(args,f_in,f_out,blocking_io,1);
 	}
 
 	if (dir) free(dir);
diff -rupNP rsync-2.6.2/Makefile.in rsync-2.6.2-fnameconv.clean/Makefile.in
--- rsync-2.6.2/Makefile.in	2004-02-10 19:06:11.000000000 +0200
+++ rsync-2.6.2-fnameconv.clean/Makefile.in	2004-05-29 21:17:09.000000000 +0300
@@ -35,7 +35,7 @@ OBJS1=rsync.o generator.o receiver.o cle
 	main.o checksum.o match.o syscall.o log.o backup.o
 OBJS2=options.o flist.o io.o compat.o hlink.o token.o uidlist.o socket.o \
 	fileio.o batch.o clientname.o
-OBJS3=progress.o pipe.o
+OBJS3=progress.o pipe.o fnameconv.o
 DAEMON_OBJ = params.o loadparm.o clientserver.o access.o connection.o authenticate.o
 popt_OBJS=popt/findme.o  popt/popt.o  popt/poptconfig.o \
 	popt/popthelp.o popt/poptparse.o
diff -rupNP rsync-2.6.2/options.c rsync-2.6.2-fnameconv.clean/options.c
--- rsync-2.6.2/options.c	2004-04-17 20:07:23.000000000 +0300
+++ rsync-2.6.2-fnameconv.clean/options.c	2004-05-29 21:17:09.000000000 +0300
@@ -124,6 +124,7 @@ char *backup_dir = NULL;
 char backup_dir_buf[MAXPATHLEN];
 int rsync_port = RSYNC_PORT;
 int link_dest = 0;
+char *fname_convert_cmd = NULL;
 
 int verbose = 0;
 int quiet = 0;
@@ -290,6 +291,7 @@ void usage(enum logcode F)
   rprintf(F,"     --bwlimit=KBPS          limit I/O bandwidth, KBytes per second\n");
   rprintf(F,"     --write-batch=PREFIX    write batch fileset starting with PREFIX\n");
   rprintf(F,"     --read-batch=PREFIX     read batch fileset starting with PREFIX\n");
+  rprintf(F,"     --fname-convert=CMD     invoke CMD for filename conversion\n");
   rprintf(F," -h, --help                  show this help screen\n");
 #ifdef INET6
   rprintf(F," -4                          prefer IPv4\n");
@@ -382,6 +384,7 @@ static struct poptOption long_options[] 
   {"hard-links",      'H', POPT_ARG_NONE,   &preserve_hard_links, 0, 0, 0 },
   {"read-batch",       0,  POPT_ARG_STRING, &batch_prefix,  OPT_READ_BATCH, 0, 0 },
   {"write-batch",      0,  POPT_ARG_STRING, &batch_prefix,  OPT_WRITE_BATCH, 0, 0 },
+  {"fname-convert",    0,  POPT_ARG_STRING, &fname_convert_cmd, 0, 0, 0 },
   {"files-from",       0,  POPT_ARG_STRING, &files_from, 0, 0, 0 },
   {"from0",           '0', POPT_ARG_NONE,   &eol_nulls, 0, 0, 0},
   {"no-implied-dirs",  0,  POPT_ARG_VAL,    &implied_dirs, 0, 0, 0 },
@@ -796,6 +799,7 @@ void server_options(char **args,int *arg
 		argstr[x++] = 'v';
 
 	/* the -q option is intentionally left out */
+	/* and so is --fname-convert (which is always handled by the client). */
 	if (make_backups)
 		argstr[x++] = 'b';
 	if (update_only)
diff -rupNP rsync-2.6.2/pipe.c rsync-2.6.2-fnameconv.clean/pipe.c
--- rsync-2.6.2/pipe.c	2004-01-28 00:35:15.000000000 +0200
+++ rsync-2.6.2-fnameconv.clean/pipe.c	2004-05-29 21:17:09.000000000 +0300
@@ -23,7 +23,6 @@
 
 extern int am_sender;
 extern int am_server;
-extern int blocking_io;
 extern int orig_umask;
 extern int read_batch;
 extern int filesfrom_fd;
@@ -40,8 +39,10 @@ extern int filesfrom_fd;
  * If blocking_io is set then use blocking io on both fds. That can be
  * used to cope with badly broken rsh implementations like the one on
  * Solaris.
+ *
+ * If register_child is nonzero then the child is registered for autocleanup.
  **/
-pid_t piped_child(char **command, int *f_in, int *f_out)
+pid_t piped_child(char **command, int *f_in, int *f_out, int blocking_io, int register_child)
 {
 	pid_t pid;
 	int to_child_pipe[2];
@@ -57,7 +58,7 @@ pid_t piped_child(char **command, int *f
 	}
 
 
-	pid = do_fork();
+	pid = register_child ? do_fork() : fork();
 	if (pid == -1) {
 		rprintf(FERROR, "fork: %s\n", strerror(errno));
 		exit_cleanup(RERR_IPC);
diff -rupNP rsync-2.6.2/syscall.c rsync-2.6.2-fnameconv.clean/syscall.c
--- rsync-2.6.2/syscall.c	2004-02-19 00:33:21.000000000 +0200
+++ rsync-2.6.2-fnameconv.clean/syscall.c	2004-05-29 21:17:09.000000000 +0300
@@ -231,3 +231,34 @@ char *d_name(struct dirent *di)
 	return di->d_name;
 #endif
 }
+
+/**
+ * A wrapper around select(2) that guarantees Linux-like updating of
+ * the timeout argument to contain the time left, so we can simply
+ * re-invoke in case of EINTR or EAGAIN.  On BSD, select(2) doesn't
+ * change the timeout argument by itself.
+ **/
+int do_select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
+{
+	if (timeout==NULL) {
+		return select(n, readfds, writefds, exceptfds, timeout);
+	} else {
+	  	struct timeval intended, before, after;
+		int result;
+		
+		intended = *timeout;
+		gettimeofday(&before, NULL);
+		result = select(n, readfds, writefds, exceptfds, timeout);
+		gettimeofday(&after, NULL);
+		timeout->tv_sec = intended.tv_sec - (after.tv_sec - before.tv_sec);
+		timeout->tv_usec = intended.tv_usec - (after.tv_usec - before.tv_usec); 
+		if (timeout->tv_usec >= 1000000) {
+			++timeout->tv_sec;
+			timeout->tv_usec -= 1000000;
+		} else if (timeout->tv_usec < 0) {
+			--(timeout)->tv_sec;
+			timeout->tv_usec += 1000000;
+		}
+		return result;
+	}
+}
diff -rupNP rsync-2.6.2/util.c rsync-2.6.2-fnameconv.clean/util.c
--- rsync-2.6.2/util.c	2004-04-27 22:59:37.000000000 +0300
+++ rsync-2.6.2-fnameconv.clean/util.c	2004-05-29 21:17:09.000000000 +0300
@@ -1135,3 +1135,52 @@ void *_realloc_array(void *ptr, unsigned
 		return malloc(size * num);
 	return realloc(ptr, size * num);
 }
+
+/** 
+ * Blocks until one of the following happens:
+ * - read_fd is nonnegative and has data to read
+ * - write_fd is nonnegative and can be written to
+ * - something terrible happened to either
+ * - the timeout (in milliseconds) has elapsed
+ * Return value is zero iff the timeout occured.
+ */
+char await_fds(int read_fd, int write_fd, int timeout_ms)
+{
+	fd_set read_fds, write_fds, except_fds;
+	struct timeval tv;
+	int res;
+	
+	tv.tv_sec = timeout_ms/1000;
+	tv.tv_usec = (timeout_ms%1000)*1000;
+	
+	while (1) {
+		FD_ZERO(&read_fds);   
+		FD_ZERO(&write_fds);  
+		FD_ZERO(&except_fds); 
+		if (write_fd>=0) {
+			FD_SET(write_fd, &write_fds);
+			FD_SET(write_fd, &except_fds);
+		}
+		if (read_fd>=0) {
+			FD_SET(read_fd, &read_fds);
+			FD_SET(read_fd, &except_fds); 
+		}
+		
+		res = do_select(MAX(0,MAX(read_fd, write_fd)+1), &read_fds, &write_fds, &except_fds, &tv);
+		if (res>0)
+			return 1;
+		if (read_fd>=0 && ( FD_ISSET(read_fd, &read_fds) || FD_ISSET(read_fd, &except_fds) ) )
+			return 1;
+		if (write_fd>=0 && ( FD_ISSET(write_fd, &write_fds) || FD_ISSET(write_fd, &except_fds) ) )
+			return 1;
+		if (res==EINTR || res==EAGAIN) {
+			continue; /* Retry */
+		}
+		if (res<0) {
+			rprintf(FERROR, "Error awaiting fname converter: %s\n", strerror(errno));
+			exit_cleanup(RERR_FNAMECONV);
+		}
+		return 0; /* res==0 and no FDs set, hence a timeout. */
+	}
+}
+


More information about the rsync mailing list