X-Git-Url: https://mattmccutchen.net/rsync/rsync.git/blobdiff_plain/b5f9e67d57f4e507dcb339a838c959244951f25f..c48b22c8583f6d6c55c6a6f6001cf4fdcc20aed1:/util.c diff --git a/util.c b/util.c index 8c352b2b..bd0af33f 100644 --- a/util.c +++ b/util.c @@ -24,42 +24,83 @@ */ #include "rsync.h" +extern int verbose; + + +/**************************************************************************** +Set a fd into nonblocking mode +****************************************************************************/ +void set_nonblocking(int fd) +{ + int val; + + if((val = fcntl(fd, F_GETFL, 0)) == -1) + return; + if (!(val & NONBLOCK_FLAG)) { + val |= NONBLOCK_FLAG; + fcntl(fd, F_SETFL, val); + } +} + /**************************************************************************** -Set a fd into nonblocking mode. Uses POSIX O_NONBLOCK if available, -else -if SYSV use O_NDELAY -if BSD use FNDELAY +Set a fd into blocking mode ****************************************************************************/ -int set_nonblocking(int fd) +void set_blocking(int fd) { int val; -#ifdef O_NONBLOCK -#define FLAG_TO_SET O_NONBLOCK + + if((val = fcntl(fd, F_GETFL, 0)) == -1) + return; + if (val & NONBLOCK_FLAG) { + val &= ~NONBLOCK_FLAG; + fcntl(fd, F_SETFL, val); + } +} + + +/* create a file descriptor pair - like pipe() but use socketpair if + possible (because of blocking issues on pipes) + + always set non-blocking + */ +int fd_pair(int fd[2]) +{ + int ret; + +#if HAVE_SOCKETPAIR + ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fd); #else -#ifdef SYSV -#define FLAG_TO_SET O_NDELAY -#else /* BSD */ -#define FLAG_TO_SET FNDELAY -#endif + ret = pipe(fd); #endif + + if (ret == 0) { + set_nonblocking(fd[0]); + set_nonblocking(fd[1]); + } - if((val = fcntl(fd, F_GETFL, 0)) == -1) - return -1; - val |= FLAG_TO_SET; - return fcntl( fd, F_SETFL, val); -#undef FLAG_TO_SET + return ret; } -/* this is taken from CVS */ +/* this is derived from CVS code + + note that in the child STDIN is set to blocking and STDOUT + is set to non-blocking. This is necessary as rsh relies on stdin being blocking + and ssh relies on stdout being non-blocking + + 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. + */ int piped_child(char **command,int *f_in,int *f_out) { int pid; int to_child_pipe[2]; int from_child_pipe[2]; + extern int blocking_io; - if (pipe(to_child_pipe) < 0 || - pipe(from_child_pipe) < 0) { + if (fd_pair(to_child_pipe) < 0 || + fd_pair(from_child_pipe) < 0) { rprintf(FERROR,"pipe: %s\n",strerror(errno)); exit_cleanup(RERR_IPC); } @@ -84,6 +125,10 @@ int piped_child(char **command,int *f_in,int *f_out) if (to_child_pipe[0] != STDIN_FILENO) close(to_child_pipe[0]); if (from_child_pipe[1] != STDOUT_FILENO) close(from_child_pipe[1]); umask(orig_umask); + set_blocking(STDIN_FILENO); + if (blocking_io) { + set_blocking(STDOUT_FILENO); + } execvp(command[0], command); rprintf(FERROR,"Failed to exec %s : %s\n", command[0],strerror(errno)); @@ -99,9 +144,6 @@ int piped_child(char **command,int *f_in,int *f_out) *f_in = from_child_pipe[0]; *f_out = to_child_pipe[1]; - set_nonblocking(*f_in); - set_nonblocking(*f_out); - return pid; } @@ -111,8 +153,8 @@ int local_child(int argc, char **argv,int *f_in,int *f_out) int to_child_pipe[2]; int from_child_pipe[2]; - if (pipe(to_child_pipe) < 0 || - pipe(from_child_pipe) < 0) { + if (fd_pair(to_child_pipe) < 0 || + fd_pair(from_child_pipe) < 0) { rprintf(FERROR,"pipe: %s\n",strerror(errno)); exit_cleanup(RERR_IPC); } @@ -280,14 +322,14 @@ int copy_file(char *source, char *dest, mode_t mode) char buf[1024 * 8]; int len; /* Number of bytes read into `buf'. */ - ifd = open(source, O_RDONLY); + ifd = do_open(source, O_RDONLY, 0); if (ifd == -1) { rprintf(FERROR,"open %s: %s\n", source,strerror(errno)); return -1; } - if (do_unlink(dest) && errno != ENOENT) { + if (robust_unlink(dest) && errno != ENOENT) { rprintf(FERROR,"unlink %s: %s\n", dest,strerror(errno)); return -1; @@ -323,14 +365,78 @@ int copy_file(char *source, char *dest, mode_t mode) return 0; } -/* sleep for a while via select */ -void u_sleep(int usec) +/* + Robust unlink: some OS'es (HPUX) refuse to unlink busy files, so + rename to /.rsyncNNN instead. Note that successive rsync runs + will shuffle the filenames around a bit as long as the file is still + busy; this is because this function does not know if the unlink call + is due to a new file coming in, or --delete trying to remove old + .rsyncNNN files, hence it renames it each time. +*/ +/* MAX_RENAMES should be 10**MAX_RENAMES_DIGITS */ +#define MAX_RENAMES_DIGITS 3 +#define MAX_RENAMES 1000 + +int robust_unlink(char *fname) { - struct timeval tv; +#ifndef ETXTBSY + return do_unlink(fname); +#else + static int counter = 1; + int rc, pos, start; + char path[MAXPATHLEN]; + + rc = do_unlink(fname); + if ((rc == 0) || (errno != ETXTBSY)) + return rc; - tv.tv_sec = 0; - tv.tv_usec = usec; - select(0, NULL, NULL, NULL, &tv); + strlcpy(path, fname, MAXPATHLEN); + + pos = strlen(path); + while((path[--pos] != '/') && (pos >= 0)) + ; + ++pos; + strlcpy(&path[pos], ".rsync", MAXPATHLEN-pos); + pos += sizeof(".rsync")-1; + + if (pos > (MAXPATHLEN-MAX_RENAMES_DIGITS-1)) { + errno = ETXTBSY; + return -1; + } + + /* start where the last one left off to reduce chance of clashes */ + start = counter; + do { + sprintf(&path[pos], "%03d", counter); + if (++counter >= MAX_RENAMES) + counter = 1; + } while (((rc = access(path, 0)) == 0) && (counter != start)); + + if (verbose > 0) + rprintf(FINFO,"renaming %s to %s because of text busy\n", + fname, path); + + /* maybe we should return rename()'s exit status? Nah. */ + if (do_rename(fname, path) != 0) { + errno = ETXTBSY; + return -1; + } + return 0; +#endif +} + +int robust_rename(char *from, char *to) +{ +#ifndef ETXTBSY + return do_rename(from, to); +#else + int rc = do_rename(from, to); + if ((rc == 0) || (errno != ETXTBSY)) + return rc; + if (robust_unlink(to) != 0) + return -1; + return do_rename(from, to); +#endif } @@ -358,31 +464,6 @@ void kill_all(int sig) } } -/* like strncpy but does not 0 fill the buffer and always null - terminates (thus it can use maxlen+1 space in d) */ -void strlcpy(char *d, char *s, int maxlen) -{ - int len = strlen(s); - if (len > maxlen) len = maxlen; - memcpy(d, s, len); - d[len] = 0; -} - -/* like strncat but does not 0 fill the buffer and always null - terminates (thus it can use maxlen+1 space in d) */ -void strlcat(char *d, char *s, int maxlen) -{ - int len1 = strlen(d); - int len2 = strlen(s); - if (len1+len2 > maxlen) { - len2 = maxlen-len1; - } - if (len2 > 0) { - memcpy(d+len1, s, len2); - d[len1+len2] = 0; - } -} - /* turn a user name into a uid */ int name_to_uid(char *name, uid_t *uid) { @@ -433,12 +514,16 @@ static void glob_expand_one(char *s, char **argv, int *argc, int maxargs) (*argc)++; return; #else + extern int sanitize_paths; glob_t globbuf; int i; if (!*s) s = "."; argv[*argc] = strdup(s); + if (sanitize_paths) { + sanitize_path(argv[*argc], NULL); + } memset(&globbuf, 0, sizeof(globbuf)); glob(argv[*argc], 0, NULL, &globbuf); @@ -502,14 +587,13 @@ void strlower(char *s) } } -/* this is like vsnprintf but the 'n' limit does not include - the terminating null. So if you have a 1024 byte buffer then - pass 1023 for n */ +/* this is like vsnprintf but it always null terminates, so you + can fit at most n-1 chars in */ int vslprintf(char *str, int n, const char *format, va_list ap) { int ret = vsnprintf(str, n, format, ap); - if (ret > n || ret < 0) { - str[n] = 0; + if (ret >= n || ret < 0) { + str[n-1] = 0; return -1; } str[ret] = 0; @@ -582,28 +666,46 @@ void clean_fname(char *name) /* * Make path appear as if a chroot had occurred: * 1. remove leading "/" (or replace with "." if at end) - * 2. remove leading ".." components + * 2. remove leading ".." components (except those allowed by "reldir") * 3. delete any other "/.." (recursively) + * Can only shrink paths, so sanitizes in place. * While we're at it, remove double slashes and "." components like * clean_fname does(), but DON'T remove a trailing slash because that * is sometimes significant on command line arguments. - * Return a malloc'ed copy. + * If "reldir" is non-null, it is a sanitized directory that the path will be + * relative to, so allow as many ".." at the beginning of the path as + * there are components in reldir. This is used for symbolic link targets. + * If reldir is non-null and the path began with "/", to be completely like + * a chroot we should add in depth levels of ".." at the beginning of the + * path, but that would blow the assumption that the path doesn't grow and + * it is not likely to end up being a valid symlink anyway, so just do + * the normal removal of the leading "/" instead. * Contributed by Dave Dykstra */ -char *sanitize_path(char *p) +void sanitize_path(char *p, char *reldir) { - char *copy, *copyp; + char *start, *sanp; + int depth = 0; + int allowdotdot = 0; - copy = (char *) malloc(strlen(p)+1); - copyp = copy; + if (reldir) { + depth++; + while (*reldir) { + if (*reldir++ == '/') { + depth++; + } + } + } + start = p; + sanp = p; while (*p == '/') { /* remove leading slashes */ p++; } while (*p != '\0') { /* this loop iterates once per filename component in p. - * both p (and copyp if the original had a slash) should + * both p (and sanp if the original had a slash) should * always be left pointing after a slash */ if ((*p == '.') && ((*(p+1) == '/') || (*(p+1) == '\0'))) { @@ -612,40 +714,58 @@ char *sanitize_path(char *p) /* skip following slashes */ ; } - } else if ((*p == '.') && (*(p+1) == '.') && + continue; + } + allowdotdot = 0; + if ((*p == '.') && (*(p+1) == '.') && ((*(p+2) == '/') || (*(p+2) == '\0'))) { - /* skip ".." component followed by slash or end */ - p += 2; - if (*p == '/') - p++; - if (copyp != copy) { - /* back up the copy one level */ - --copyp; /* now pointing at slash */ - while ((copyp > copy) && (*(copyp - 1) != '/')) { - /* skip back up to slash */ - copyp--; + /* ".." component followed by slash or end */ + if ((depth > 0) && (sanp == start)) { + /* allow depth levels of .. at the beginning */ + --depth; + allowdotdot = 1; + } else { + p += 2; + if (*p == '/') + p++; + if (sanp != start) { + /* back up sanp one level */ + --sanp; /* now pointing at slash */ + while ((sanp > start) && (*(sanp - 1) != '/')) { + /* skip back up to slash */ + sanp--; + } } + continue; } - } else { - while (1) { - /* copy one component through next slash */ - *copyp++ = *p++; - if ((*p == '\0') || (*(p-1) == '/')) { - while (*p == '/') { - /* skip multiple slashes */ - p++; - } - break; + } + while (1) { + /* copy one component through next slash */ + *sanp++ = *p++; + if ((*p == '\0') || (*(p-1) == '/')) { + while (*p == '/') { + /* skip multiple slashes */ + p++; } + break; } } + if (allowdotdot) { + /* move the virtual beginning to leave the .. alone */ + start = sanp; + } } - if (copyp == copy) { + if ((sanp == start) && !allowdotdot) { /* ended up with nothing, so put in "." component */ - *copyp++ = '.'; + /* + * note that the !allowdotdot doesn't prevent this from + * happening in all allowed ".." situations, but I didn't + * think it was worth putting in an extra variable to ensure + * it since an extra "." won't hurt in those situations. + */ + *sanp++ = '.'; } - *copyp = '\0'; - return(copy); + *sanp = '\0'; } @@ -663,6 +783,8 @@ char *push_dir(char *dir, int save) getcwd(curr_dir, sizeof(curr_dir)-1); } + if (!dir) return NULL; /* this call was probably just to initialize */ + if (chdir(dir)) return NULL; if (save) { @@ -670,10 +792,10 @@ char *push_dir(char *dir, int save) } if (*dir == '/') { - strlcpy(curr_dir, dir, sizeof(curr_dir)-1); + strlcpy(curr_dir, dir, sizeof(curr_dir)); } else { - strlcat(curr_dir,"/", sizeof(curr_dir)-1); - strlcat(curr_dir,dir, sizeof(curr_dir)-1); + strlcat(curr_dir,"/", sizeof(curr_dir)); + strlcat(curr_dir,dir, sizeof(curr_dir)); } clean_fname(curr_dir); @@ -692,7 +814,7 @@ int pop_dir(char *dir) return ret; } - strlcpy(curr_dir, dir, sizeof(curr_dir)-1); + strlcpy(curr_dir, dir, sizeof(curr_dir)); free(dir); @@ -703,8 +825,8 @@ int pop_dir(char *dir) to ensure that signed/unsigned usage is consistent between machines. */ int u_strcmp(const char *cs1, const char *cs2) { - const uchar *s1 = (uchar *)cs1; - const uchar *s2 = (uchar *)cs2; + const uchar *s1 = (const uchar *)cs1; + const uchar *s2 = (const uchar *)cs2; while (*s1 && *s2 && (*s1 == *s2)) { s1++; s2++; @@ -715,12 +837,12 @@ int u_strcmp(const char *cs1, const char *cs2) static OFF_T last_ofs; -void end_progress(void) +void end_progress(OFF_T size) { extern int do_progress, am_server; if (do_progress && !am_server) { - rprintf(FINFO,"\n"); + rprintf(FINFO,"%.0f (100%%)\n", (double)size); } last_ofs = 0; } @@ -797,7 +919,7 @@ char *timestring(time_t t) #ifdef HAVE_STRFTIME strftime(TimeBuf,sizeof(TimeBuf)-1,"%Y/%m/%d %T",tm); #else - strlcpy(TimeBuf, asctime(tm), sizeof(TimeBuf)-1); + strlcpy(TimeBuf, asctime(tm), sizeof(TimeBuf)); #endif if (TimeBuf[strlen(TimeBuf)-1] == '\n') { @@ -807,3 +929,59 @@ char *timestring(time_t t) return(TimeBuf); } + +/******************************************************************* +sleep for a specified number of milliseconds +********************************************************************/ +void msleep(int t) +{ + int tdiff=0; + struct timeval tval,t1,t2; + + gettimeofday(&t1, NULL); + gettimeofday(&t2, NULL); + + while (tdiff < t) { + tval.tv_sec = (t-tdiff)/1000; + tval.tv_usec = 1000*((t-tdiff)%1000); + + errno = 0; + select(0,NULL,NULL, NULL, &tval); + + gettimeofday(&t2, NULL); + tdiff = (t2.tv_sec - t1.tv_sec)*1000 + + (t2.tv_usec - t1.tv_usec)/1000; + } +} + + +#ifdef __INSURE__ +#include + +/******************************************************************* +This routine is a trick to immediately catch errors when debugging +with insure. A xterm with a gdb is popped up when insure catches +a error. It is Linux specific. +********************************************************************/ +int _Insure_trap_error(int a1, int a2, int a3, int a4, int a5, int a6) +{ + static int (*fn)(); + int ret; + char cmd[1024]; + + sprintf(cmd, "/usr/X11R6/bin/xterm -display :0 -T Panic -n Panic -e /bin/sh -c 'cat /tmp/ierrs.*.%d ; gdb /proc/%d/exe %d'", + getpid(), getpid(), getpid()); + + if (!fn) { + static void *h; + h = dlopen("/usr/local/parasoft/insure++lite/lib.linux2/libinsure.so", RTLD_LAZY); + fn = dlsym(h, "_Insure_trap_error"); + } + + ret = fn(a1, a2, a3, a4, a5, a6); + + system(cmd); + + return ret; +} +#endif