X-Git-Url: https://mattmccutchen.net/rsync/rsync.git/blobdiff_plain/beef86d0dda124ca59005846049cf7d60efb69c7..bd685982389b78a158921b7839bdeca501338d19:/util.c diff --git a/util.c b/util.c index 03f4054a..abf0cd48 100644 --- a/util.c +++ b/util.c @@ -4,7 +4,7 @@ * Copyright (C) 1996-2000 Andrew Tridgell * Copyright (C) 1996 Paul Mackerras * Copyright (C) 2001, 2002 Martin Pool - * Copyright (C) 2003-2007 Wayne Davison + * Copyright (C) 2003-2009 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 @@ -22,18 +22,19 @@ #include "rsync.h" #include "ifuncs.h" +#include "itypes.h" +#include "inums.h" -extern int verbose; -extern int dry_run; extern int module_id; extern int modify_window; extern int relative_paths; -extern int human_readable; +extern int preserve_times; +extern int preserve_xattrs; +extern int preallocate_files; extern char *module_dir; extern unsigned int module_dirlen; -extern mode_t orig_umask; extern char *partial_dir; -extern struct filter_list_struct server_filter_list; +extern filter_rule_list daemon_filter_list; int sanitize_paths = 0; @@ -93,6 +94,7 @@ int fd_pair(int fd[2]) void print_child_argv(const char *prefix, char **cmd) { + int cnt = 0; rprintf(FCLIENT, "%s ", prefix); for (; *cmd; cmd++) { /* Look for characters that ought to be quoted. This @@ -106,8 +108,9 @@ void print_child_argv(const char *prefix, char **cmd) } else { rprintf(FCLIENT, "%s ", *cmd); } + cnt++; } - rprintf(FCLIENT, "\n"); + rprintf(FCLIENT, " (%d args)\n", cnt); } NORETURN void out_of_memory(const char *str) @@ -122,88 +125,129 @@ NORETURN void overflow_exit(const char *str) exit_cleanup(RERR_MALLOC); } -int set_modtime(const char *fname, time_t modtime, mode_t mode) +/* This returns 0 for success, 1 for a symlink if symlink time-setting + * is not possible, or -1 for any other error. */ +int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode) { -#if !defined HAVE_LUTIMES || !defined HAVE_UTIMES - if (S_ISLNK(mode)) - return 1; -#endif + static int switch_step = 0; - if (verbose > 2) { + if (DEBUG_GTE(TIME, 1)) { rprintf(FINFO, "set modtime of %s to (%ld) %s", fname, (long)modtime, asctime(localtime(&modtime))); } - if (dry_run) - return 0; + switch (switch_step) { +#ifdef HAVE_UTIMENSAT +#include "case_N.h" + if (do_utimensat(fname, modtime, mod_nsec) == 0) + break; + if (errno != ENOSYS) + return -1; + switch_step++; + /* FALLTHROUGH */ +#endif - { -#ifdef HAVE_UTIMES - struct timeval t[2]; - t[0].tv_sec = time(NULL); - t[0].tv_usec = 0; - t[1].tv_sec = modtime; - t[1].tv_usec = 0; -# ifdef HAVE_LUTIMES - if (S_ISLNK(mode)) { - if (lutimes(fname, t) < 0) - return errno == ENOSYS ? 1 : -1; - return 0; +#ifdef HAVE_LUTIMES +#include "case_N.h" + if (do_lutimes(fname, modtime, mod_nsec) == 0) + break; + if (errno != ENOSYS) + return -1; + switch_step++; + /* FALLTHROUGH */ +#endif + +#include "case_N.h" + switch_step++; + if (preserve_times & PRESERVE_LINK_TIMES) { + preserve_times &= ~PRESERVE_LINK_TIMES; + if (S_ISLNK(mode)) + return 1; } -# endif - return utimes(fname, t); -#elif defined HAVE_STRUCT_UTIMBUF - struct utimbuf tbuf; - tbuf.actime = time(NULL); - tbuf.modtime = modtime; - return utime(fname,&tbuf); -#elif defined HAVE_UTIME - time_t t[2]; - t[0] = time(NULL); - t[1] = modtime; - return utime(fname,t); + /* FALLTHROUGH */ + +#include "case_N.h" +#ifdef HAVE_UTIMES + if (do_utimes(fname, modtime, mod_nsec) == 0) + break; #else -#error No file-time-modification routine found! + if (do_utime(fname, modtime, mod_nsec) == 0) + break; #endif - } -} - -/* This creates a new directory with default permissions. Since there - * might be some directory-default permissions affecting this, we can't - * force the permissions directly using the original umask and mkdir(). */ -int mkdir_defmode(char *fname) -{ - int ret; - umask(orig_umask); - ret = do_mkdir(fname, ACCESSPERMS); - umask(0); + return -1; + } - return ret; + return 0; } /* Create any necessary directories in fname. Any missing directories are - * created with default permissions. */ -int create_directory_path(char *fname) + * created with default permissions. Returns < 0 on error, or the number + * of directories created. */ +int make_path(char *fname, int flags) { - char *p; + char *end, *p; int ret = 0; - while (*fname == '/') - fname++; - while (strncmp(fname, "./", 2) == 0) + if (flags & MKP_SKIP_SLASH) { + while (*fname == '/') + fname++; + } + + while (*fname == '.' && fname[1] == '/') fname += 2; - umask(orig_umask); - p = fname; - while ((p = strchr(p,'/')) != NULL) { - *p = '\0'; - if (do_mkdir(fname, ACCESSPERMS) < 0 && errno != EEXIST) - ret = -1; - *p++ = '/'; + if (flags & MKP_DROP_NAME) { + end = strrchr(fname, '/'); + if (!end) + return 0; + *end = '\0'; + } else + end = fname + strlen(fname); + + /* Try to find an existing dir, starting from the deepest dir. */ + for (p = end; ; ) { + if (do_mkdir(fname, ACCESSPERMS) == 0) { + ret++; + break; + } + if (errno != ENOENT) { + if (errno != EEXIST) + ret = -ret - 1; + break; + } + while (1) { + if (p == fname) { + ret = -ret - 1; + goto double_break; + } + if (*--p == '/') { + if (p == fname) { + ret = -ret - 1; /* impossible... */ + goto double_break; + } + *p = '\0'; + break; + } + } } - umask(0); + double_break: + + /* Make all the dirs that we didn't find on the way here. */ + while (p != end) { + *p = '/'; + p += strlen(p); + if (ret < 0) /* Skip mkdir on error, but keep restoring the path. */ + continue; + if (do_mkdir(fname, ACCESSPERMS) < 0) + ret = -ret - 1; + else + ret++; + } + + if (flags & MKP_DROP_NAME) + *end = '/'; return ret; } @@ -264,49 +308,88 @@ static int safe_read(int desc, char *ptr, size_t len) /* Copy a file. If ofd < 0, copy_file unlinks and opens the "dest" file. * Otherwise, it just writes to and closes the provided file descriptor. + * In either case, if --xattrs are being preserved, the dest file will + * have its xattrs set from the source file. * * This is used in conjunction with the --temp-dir, --backup, and * --copy-dest options. */ -int copy_file(const char *source, const char *dest, int ofd, - mode_t mode, int create_bak_dir) +int copy_file(const char *source, const char *dest, int ofd, mode_t mode) { int ifd; char buf[1024 * 8]; int len; /* Number of bytes read into `buf'. */ +#ifdef PREALLOCATE_NEEDS_TRUNCATE + OFF_T preallocated_len = 0, offset = 0; +#endif if ((ifd = do_open(source, O_RDONLY, 0)) < 0) { + int save_errno = errno; rsyserr(FERROR_XFER, errno, "open %s", full_fname(source)); + errno = save_errno; return -1; } if (ofd < 0) { if (robust_unlink(dest) && errno != ENOENT) { + int save_errno = errno; rsyserr(FERROR_XFER, errno, "unlink %s", full_fname(dest)); + errno = save_errno; return -1; } - if ((ofd = do_open(dest, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode)) < 0 - && (!create_bak_dir || errno != ENOENT || make_bak_dir(dest) < 0 - || (ofd = do_open(dest, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode)) < 0)) { - rsyserr(FERROR_XFER, errno, "open %s", full_fname(dest)); +#ifdef SUPPORT_XATTRS + if (preserve_xattrs) + mode |= S_IWUSR; +#endif + mode &= INITACCESSPERMS; + if ((ofd = do_open(dest, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode)) < 0) { + int save_errno = errno; + rsyserr(FERROR_XFER, save_errno, "open %s", full_fname(dest)); close(ifd); + errno = save_errno; return -1; } } +#ifdef SUPPORT_PREALLOCATION + if (preallocate_files) { + STRUCT_STAT srcst; + + /* Try to preallocate enough space for file's eventual length. Can + * reduce fragmentation on filesystems like ext4, xfs, and NTFS. */ + if (do_fstat(ifd, &srcst) < 0) + rsyserr(FWARNING, errno, "fstat %s", full_fname(source)); + else if (srcst.st_size > 0) { + if (do_fallocate(ofd, 0, srcst.st_size) == 0) { +#ifdef PREALLOCATE_NEEDS_TRUNCATE + preallocated_len = srcst.st_size; +#endif + } else + rsyserr(FWARNING, errno, "do_fallocate %s", full_fname(dest)); + } + } +#endif + while ((len = safe_read(ifd, buf, sizeof buf)) > 0) { if (full_write(ofd, buf, len) < 0) { + int save_errno = errno; rsyserr(FERROR_XFER, errno, "write %s", full_fname(dest)); close(ifd); close(ofd); + errno = save_errno; return -1; } +#ifdef PREALLOCATE_NEEDS_TRUNCATE + offset += len; +#endif } if (len < 0) { + int save_errno = errno; rsyserr(FERROR_XFER, errno, "read %s", full_fname(source)); close(ifd); close(ofd); + errno = save_errno; return -1; } @@ -315,12 +398,29 @@ int copy_file(const char *source, const char *dest, int ofd, full_fname(source)); } +#ifdef PREALLOCATE_NEEDS_TRUNCATE + /* Source file might have shrunk since we fstatted it. + * Cut off any extra preallocated zeros from dest file. */ + if (offset < preallocated_len && do_ftruncate(ofd, offset) < 0) { + /* If we fail to truncate, the dest file may be wrong, so we + * must trigger the "partial transfer" error. */ + rsyserr(FERROR_XFER, errno, "ftruncate %s", full_fname(dest)); + } +#endif + if (close(ofd) < 0) { + int save_errno = errno; rsyserr(FERROR_XFER, errno, "close failed on %s", full_fname(dest)); + errno = save_errno; return -1; } +#ifdef SUPPORT_XATTRS + if (preserve_xattrs) + copy_xattrs(source, dest); +#endif + return 0; } @@ -371,7 +471,7 @@ int robust_unlink(const char *fname) counter = 1; } while ((rc = access(path, 0)) == 0 && counter != start); - if (verbose > 0) { + if (INFO_GTE(MISC, 1)) { rprintf(FWARNING, "renaming %s to %s because of text busy\n", fname, path); } @@ -401,17 +501,20 @@ int robust_rename(const char *from, const char *to, const char *partialptr, switch (errno) { #ifdef ETXTBSY case ETXTBSY: - if (robust_unlink(to) != 0) + if (robust_unlink(to) != 0) { + errno = ETXTBSY; return -1; + } + errno = ETXTBSY; break; #endif case EXDEV: if (partialptr) { if (!handle_partial_dir(partialptr,PDIR_CREATE)) - return -1; + return -2; to = partialptr; } - if (copy_file(from, to, -1, mode, 0) != 0) + if (copy_file(from, to, -1, mode) != 0) return -2; do_unlink(from); return 1; @@ -465,30 +568,6 @@ void kill_all(int sig) } } -/** Turn a user name into a uid */ -int name_to_uid(const char *name, uid_t *uid_p) -{ - struct passwd *pass; - if (!name || !*name) - return 0; - if (!(pass = getpwnam(name))) - return 0; - *uid_p = pass->pw_uid; - return 1; -} - -/** Turn a group name into a gid */ -int name_to_gid(const char *name, gid_t *gid_p) -{ - struct group *grp; - if (!name || !*name) - return 0; - if (!(grp = getgrnam(name))) - return 0; - *gid_p = grp->gr_gid; - return 1; -} - /** Lock a byte range in a open file */ int lock_range(int fd, int offset, int len) { @@ -503,82 +582,172 @@ int lock_range(int fd, int offset, int len) return fcntl(fd,F_SETLK,&lock) == 0; } -static int filter_server_path(char *arg) -{ - char *s; +#define ENSURE_MEMSPACE(buf, type, sz, req) \ + if ((req) > sz && !(buf = realloc_array(buf, type, sz = MAX(sz * 2, req)))) \ + out_of_memory("glob_expand") - if (server_filter_list.head) { - for (s = arg; (s = strchr(s, '/')) != NULL; ) { - *s = '\0'; - if (check_filter(&server_filter_list, arg, 1) < 0) { - /* We must leave arg truncated! */ - return 1; - } - *s++ = '/'; +static inline void call_glob_match(const char *name, int len, int from_glob, + char *arg, int abpos, int fbpos); + +static struct glob_data { + char *arg_buf, *filt_buf, **argv; + int absize, fbsize, maxargs, argc; +} glob; + +static void glob_match(char *arg, int abpos, int fbpos) +{ + int len; + char *slash; + + while (*arg == '.' && arg[1] == '/') { + if (fbpos < 0) { + ENSURE_MEMSPACE(glob.filt_buf, char, glob.fbsize, glob.absize); + memcpy(glob.filt_buf, glob.arg_buf, abpos + 1); + fbpos = abpos; } + ENSURE_MEMSPACE(glob.arg_buf, char, glob.absize, abpos + 3); + glob.arg_buf[abpos++] = *arg++; + glob.arg_buf[abpos++] = *arg++; + glob.arg_buf[abpos] = '\0'; } - return 0; + if ((slash = strchr(arg, '/')) != NULL) { + *slash = '\0'; + len = slash - arg; + } else + len = strlen(arg); + if (strpbrk(arg, "*?[")) { + struct dirent *di; + DIR *d; + + if (!(d = opendir(abpos ? glob.arg_buf : "."))) + return; + while ((di = readdir(d)) != NULL) { + char *dname = d_name(di); + if (dname[0] == '.' && (dname[1] == '\0' + || (dname[1] == '.' && dname[2] == '\0'))) + continue; + if (!wildmatch(arg, dname)) + continue; + call_glob_match(dname, strlen(dname), 1, + slash ? arg + len + 1 : NULL, + abpos, fbpos); + } + closedir(d); + } else { + call_glob_match(arg, len, 0, + slash ? arg + len + 1 : NULL, + abpos, fbpos); + } + if (slash) + *slash = '/'; } -void glob_expand(char *s, char ***argv_ptr, int *argc_ptr, int *maxargs_ptr) +static inline void call_glob_match(const char *name, int len, int from_glob, + char *arg, int abpos, int fbpos) { - char **argv = *argv_ptr; - int argc = *argc_ptr; - int maxargs = *maxargs_ptr; -#if !defined HAVE_GLOB || !defined HAVE_GLOB_H - if (argc == maxargs) { - maxargs += MAX_ARGS; - if (!(argv = realloc_array(argv, char *, maxargs))) - out_of_memory("glob_expand"); - *argv_ptr = argv; - *maxargs_ptr = maxargs; + char *use_buf; + + ENSURE_MEMSPACE(glob.arg_buf, char, glob.absize, abpos + len + 2); + memcpy(glob.arg_buf + abpos, name, len); + abpos += len; + glob.arg_buf[abpos] = '\0'; + + if (fbpos >= 0) { + ENSURE_MEMSPACE(glob.filt_buf, char, glob.fbsize, fbpos + len + 2); + memcpy(glob.filt_buf + fbpos, name, len); + fbpos += len; + glob.filt_buf[fbpos] = '\0'; + use_buf = glob.filt_buf; + } else + use_buf = glob.arg_buf; + + if (from_glob || (arg && len)) { + STRUCT_STAT st; + int is_dir; + + if (do_stat(glob.arg_buf, &st) != 0) + return; + is_dir = S_ISDIR(st.st_mode) != 0; + if (arg && !is_dir) + return; + + if (daemon_filter_list.head + && check_filter(&daemon_filter_list, FLOG, use_buf, is_dir) < 0) + return; } - if (!*s) - s = "."; - s = argv[argc++] = strdup(s); - filter_server_path(s); -#else - glob_t globbuf; - if (maxargs <= argc) - return; - if (!*s) - s = "."; + if (arg) { + glob.arg_buf[abpos++] = '/'; + glob.arg_buf[abpos] = '\0'; + if (fbpos >= 0) { + glob.filt_buf[fbpos++] = '/'; + glob.filt_buf[fbpos] = '\0'; + } + glob_match(arg, abpos, fbpos); + } else { + ENSURE_MEMSPACE(glob.argv, char *, glob.maxargs, glob.argc + 1); + if (!(glob.argv[glob.argc++] = strdup(glob.arg_buf))) + out_of_memory("glob_match"); + } +} + +/* This routine performs wild-card expansion of the pathname in "arg". Any + * daemon-excluded files/dirs will not be matched by the wildcards. Returns 0 + * if a wild-card string is the only returned item (due to matching nothing). */ +int glob_expand(const char *arg, char ***argv_p, int *argc_p, int *maxargs_p) +{ + int ret, save_argc; + char *s; + + if (!arg) { + if (glob.filt_buf) + free(glob.filt_buf); + free(glob.arg_buf); + memset(&glob, 0, sizeof glob); + return -1; + } if (sanitize_paths) - s = sanitize_path(NULL, s, "", 0); - else - s = strdup(s); - if (!s) - out_of_memory("glob_expand"); - - memset(&globbuf, 0, sizeof globbuf); - if (!filter_server_path(s)) - glob(s, 0, NULL, &globbuf); - if (MAX((int)globbuf.gl_pathc, 1) > maxargs - argc) { - maxargs += globbuf.gl_pathc + MAX_ARGS; - if (!(argv = realloc_array(argv, char *, maxargs))) + s = sanitize_path(NULL, arg, "", 0, SP_KEEP_DOT_DIRS); + else { + s = strdup(arg); + if (!s) out_of_memory("glob_expand"); - *argv_ptr = argv; - *maxargs_ptr = maxargs; + clean_fname(s, CFN_KEEP_DOT_DIRS + | CFN_KEEP_TRAILING_SLASH + | CFN_COLLAPSE_DOT_DOT_DIRS); } - if (globbuf.gl_pathc == 0) - argv[argc++] = s; - else { - int i; + + ENSURE_MEMSPACE(glob.arg_buf, char, glob.absize, MAXPATHLEN); + *glob.arg_buf = '\0'; + + glob.argc = save_argc = *argc_p; + glob.argv = *argv_p; + glob.maxargs = *maxargs_p; + + ENSURE_MEMSPACE(glob.argv, char *, glob.maxargs, 100); + + glob_match(s, 0, -1); + + /* The arg didn't match anything, so add the failed arg to the list. */ + if (glob.argc == save_argc) { + ENSURE_MEMSPACE(glob.argv, char *, glob.maxargs, glob.argc + 1); + glob.argv[glob.argc++] = s; + ret = 0; + } else { free(s); - for (i = 0; i < (int)globbuf.gl_pathc; i++) { - if (!(argv[argc++] = strdup(globbuf.gl_pathv[i]))) - out_of_memory("glob_expand"); - } + ret = 1; } - globfree(&globbuf); -#endif - *argc_ptr = argc; + + *maxargs_p = glob.maxargs; + *argv_p = glob.argv; + *argc_p = glob.argc; + + return ret; } /* This routine is only used in daemon mode. */ -void glob_expand_module(char *base1, char *arg, char ***argv_ptr, int *argc_ptr, int *maxargs_ptr) +void glob_expand_module(char *base1, char *arg, char ***argv_p, int *argc_p, int *maxargs_p) { char *p, *s; char *base = base1; @@ -600,7 +769,7 @@ void glob_expand_module(char *base1, char *arg, char ***argv_ptr, int *argc_ptr, for (s = arg; *s; s = p + base_len) { if ((p = strstr(s, base)) != NULL) *p = '\0'; /* split it at this point */ - glob_expand(s, argv_ptr, argc_ptr, maxargs_ptr); + glob_expand(s, argv_p, argc_p, maxargs_p); if (!p) break; } @@ -687,13 +856,14 @@ int count_dir_elements(const char *p) return cnt; } -/* Turns multiple adjacent slashes into a single slash, drops interior "." - * elements, drops an intial "./" unless CFN_KEEP_LEADING_DOT_DIR is flagged, - * will even drop a trailing '.' after a '/' if CFN_DROP_TRAILING_DOT_DIR is - * flagged, removes a trailing slash (perhaps after removing the aforementioned - * dot) unless CFN_KEEP_TRAILING_SLASH is flagged, will even collapse ".." - * elements (except at the start of the string) if CFN_COLLAPSE_DOT_DOT_DIRS - * is flagged. If the resulting name would be empty, we return ".". */ +/* Turns multiple adjacent slashes into a single slash (possible exception: + * the preserving of two leading slashes at the start), drops all leading or + * interior "." elements unless CFN_KEEP_DOT_DIRS is flagged. Will also drop + * a trailing '.' after a '/' if CFN_DROP_TRAILING_DOT_DIR is flagged, removes + * a trailing slash (perhaps after removing the aforementioned dot) unless + * CFN_KEEP_TRAILING_SLASH is flagged, and will also collapse ".." elements + * (except at the start) if CFN_COLLAPSE_DOT_DOT_DIRS is flagged. If the + * resulting name would be empty, returns ".". */ unsigned int clean_fname(char *name, int flags) { char *limit = name - 1, *t = name, *f = name; @@ -702,9 +872,16 @@ unsigned int clean_fname(char *name, int flags) if (!name) return 0; - if ((anchored = *f == '/') != 0) + if ((anchored = *f == '/') != 0) { *t++ = *f++; - else if (flags & CFN_KEEP_LEADING_DOT_DIR && *f == '.' && f[1] == '/') { +#ifdef __CYGWIN__ + /* If there are exactly 2 slashes at the start, preserve + * them. Would break daemon excludes unless the paths are + * really treated differently, so used this sparingly. */ + if (*f == '/' && f[1] != '/') + *t++ = *f++; +#endif + } else if (flags & CFN_KEEP_DOT_DIRS && *f == '.' && f[1] == '/') { *t++ = *f++; *t++ = *f++; } @@ -716,7 +893,7 @@ unsigned int clean_fname(char *name, int flags) } if (*f == '.') { /* discard interior "." dirs */ - if (f[1] == '/') { + if (f[1] == '/' && !(flags & CFN_KEEP_DOT_DIRS)) { f += 2; continue; } @@ -773,10 +950,11 @@ unsigned int clean_fname(char *name, int flags) * ALWAYS collapses ".." elements (except for those at the start of the * string up to "depth" deep). If the resulting name would be empty, * change it into a ".". */ -char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth) +char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth, + int flags) { char *start, *sanp; - int rlen = 0, leave_one_dotdir = relative_paths; + int rlen = 0, drop_dot_dirs = !relative_paths || !(flags & SP_KEEP_DOT_DIRS); if (dest != p) { int plen = strlen(p); @@ -799,21 +977,22 @@ char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth) } } + if (drop_dot_dirs) { + while (*p == '.' && p[1] == '/') + p += 2; + } + start = sanp = dest + rlen; + /* This loop iterates once per filename component in p, pointing at + * the start of the name (past any prior slash) for each iteration. */ while (*p) { /* discard leading or extra slashes */ if (*p == '/') { p++; continue; } - /* this loop iterates once per filename component in p. - * 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')) { - if (leave_one_dotdir && p[1]) - leave_one_dotdir = 0; - else { + if (drop_dot_dirs) { + if (*p == '.' && (p[1] == '/' || p[1] == '\0')) { /* skip "." component */ p++; continue; @@ -826,10 +1005,8 @@ char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth) if (sanp != start) { /* back up sanp one level */ --sanp; /* now pointing at slash */ - while (sanp > start && sanp[-1] != '/') { - /* skip back up to slash */ + while (sanp > start && sanp[-1] != '/') sanp--; - } } continue; } @@ -853,14 +1030,17 @@ char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth) /* Like chdir(), but it keeps track of the current directory (in the * global "curr_dir"), and ensures that the path size doesn't overflow. * Also cleans the path using the clean_fname() function. */ -int push_dir(const char *dir, int set_path_only) +int change_dir(const char *dir, int set_path_only) { static int initialised; unsigned int len; if (!initialised) { initialised = 1; - getcwd(curr_dir, sizeof curr_dir - 1); + if (getcwd(curr_dir, sizeof curr_dir - 1) == NULL) { + rsyserr(FERROR, errno, "getcwd()"); + exit_cleanup(RERR_FILESELECT); + } curr_dir_len = strlen(curr_dir); } @@ -871,21 +1051,27 @@ int push_dir(const char *dir, int set_path_only) if (len == 1 && *dir == '.') return 1; - if ((*dir == '/' ? len : curr_dir_len + 1 + len) >= sizeof curr_dir) { - errno = ENAMETOOLONG; - return 0; - } - - if (!set_path_only && chdir(dir)) - return 0; - if (*dir == '/') { + if (len >= sizeof curr_dir) { + errno = ENAMETOOLONG; + return 0; + } + if (!set_path_only && chdir(dir)) + return 0; memcpy(curr_dir, dir, len + 1); - curr_dir_len = len; } else { - curr_dir[curr_dir_len++] = '/'; + if (curr_dir_len + 1 + len >= sizeof curr_dir) { + errno = ENAMETOOLONG; + return 0; + } + if (!(curr_dir_len && curr_dir[curr_dir_len-1] == '/')) + curr_dir[curr_dir_len++] = '/'; memcpy(curr_dir + curr_dir_len, dir, len + 1); - curr_dir_len += len; + + if (!set_path_only && chdir(curr_dir)) { + curr_dir[curr_dir_len] = '\0'; + return 0; + } } curr_dir_len = clean_fname(curr_dir, CFN_COLLAPSE_DOT_DOT_DIRS); @@ -895,31 +1081,38 @@ int push_dir(const char *dir, int set_path_only) curr_dir_depth = count_dir_elements(curr_dir + module_dirlen); } - if (verbose >= 5 && !set_path_only) - rprintf(FINFO, "[%s] push_dir(%s)\n", who_am_i(), curr_dir); + if (DEBUG_GTE(CHDIR, 1) && !set_path_only) + rprintf(FINFO, "[%s] change_dir(%s)\n", who_am_i(), curr_dir); return 1; } -/** - * Reverse a push_dir() call. You must pass in an absolute path - * that was copied from a prior value of "curr_dir". - **/ -int pop_dir(const char *dir) +/* This will make a relative path absolute and clean it up via clean_fname(). + * Returns the string, which might be newly allocated, or NULL on error. */ +char *normalize_path(char *path, BOOL force_newbuf, unsigned int *len_ptr) { - if (chdir(dir)) - return 0; + unsigned int len; - curr_dir_len = strlcpy(curr_dir, dir, sizeof curr_dir); - if (curr_dir_len >= sizeof curr_dir) - curr_dir_len = sizeof curr_dir - 1; - if (sanitize_paths) - curr_dir_depth = count_dir_elements(curr_dir + module_dirlen); + if (*path != '/') { /* Make path absolute. */ + int len = strlen(path); + if (curr_dir_len + 1 + len >= sizeof curr_dir) + return NULL; + curr_dir[curr_dir_len] = '/'; + memcpy(curr_dir + curr_dir_len + 1, path, len + 1); + if (!(path = strdup(curr_dir))) + out_of_memory("normalize_path"); + curr_dir[curr_dir_len] = '\0'; + } else if (force_newbuf) { + if (!(path = strdup(path))) + out_of_memory("normalize_path"); + } - if (verbose >= 5) - rprintf(FINFO, "[%s] pop_dir(%s)\n", who_am_i(), curr_dir); + len = clean_fname(path, CFN_COLLAPSE_DOT_DOT_DIRS | CFN_DROP_TRAILING_DOT_DIR); - return 1; + if (len_ptr) + *len_ptr = len; + + return path; } /** @@ -977,13 +1170,13 @@ char *partial_dir_fname(const char *fname) fn = fname; if ((int)pathjoin(t, sz, partial_dir, fn) >= sz) return NULL; - if (server_filter_list.head) { + if (daemon_filter_list.head) { t = strrchr(partial_fname, '/'); *t = '\0'; - if (check_filter(&server_filter_list, partial_fname, 1) < 0) + if (check_filter(&daemon_filter_list, FLOG, partial_fname, 1) < 0) return NULL; *t = '/'; - if (check_filter(&server_filter_list, partial_fname, 0) < 0) + if (check_filter(&daemon_filter_list, FLOG, partial_fname, 0) < 0) return NULL; } @@ -1009,12 +1202,16 @@ int handle_partial_dir(const char *fname, int create) STRUCT_STAT st; int statret = do_lstat(dir, &st); if (statret == 0 && !S_ISDIR(st.st_mode)) { - if (do_unlink(dir) < 0) + if (do_unlink(dir) < 0) { + *fn = '/'; return 0; + } statret = -1; } - if (statret < 0 && do_mkdir(dir, 0700) < 0) + if (statret < 0 && do_mkdir(dir, 0700) < 0) { + *fn = '/'; return 0; + } } else do_rmdir(dir); *fn = '/'; @@ -1022,12 +1219,13 @@ int handle_partial_dir(const char *fname, int create) return 1; } -/** - * Determine if a symlink points outside the current directory tree. +/* Determine if a symlink points outside the current directory tree. * This is considered "unsafe" because e.g. when mirroring somebody * else's machine it might allow them to establish a symlink to * /etc/passwd, and then read it through a web server. * + * Returns 1 if unsafe, 0 if safe. + * * Null symlinks and absolute symlinks are always unsafe. * * Basically here we are concerned with symlinks whose target contains @@ -1035,17 +1233,11 @@ int handle_partial_dir(const char *fname, int create) * transferred directory. We are not allowed to go back up and * reenter. * - * @param dest Target of the symlink in question. - * - * @param src Top source directory currently applicable. Basically this - * is the first parameter to rsync in a simple invocation, but it's - * modified by flist.c in slightly complex ways. - * - * @retval True if unsafe - * @retval False is unsafe + * "dest" is the target of the symlink in question. * - * @sa t_unsafe.c - **/ + * "src" is the top source directory currently applicable at the level + * of the referenced symlink. This is usually the symlink's full path + * (including its name), as referenced from the root of the transfer. */ int unsafe_symlink(const char *dest, const char *src) { const char *name, *slash; @@ -1057,91 +1249,33 @@ int unsafe_symlink(const char *dest, const char *src) /* find out what our safety margin is */ for (name = src; (slash = strchr(name, '/')) != 0; name = slash+1) { - if (strncmp(name, "../", 3) == 0) { - depth = 0; - } else if (strncmp(name, "./", 2) == 0) { - /* nothing */ - } else { + /* ".." segment starts the count over. "." segment is ignored. */ + if (*name == '.' && (name[1] == '/' || (name[1] == '.' && name[2] == '/'))) { + if (name[1] == '.') + depth = 0; + } else depth++; - } + while (slash[1] == '/') slash++; /* just in case src isn't clean */ } - if (strcmp(name, "..") == 0) + if (*name == '.' && name[1] == '.' && name[2] == '\0') depth = 0; for (name = dest; (slash = strchr(name, '/')) != 0; name = slash+1) { - if (strncmp(name, "../", 3) == 0) { - /* if at any point we go outside the current directory - then stop - it is unsafe */ - if (--depth < 0) - return 1; - } else if (strncmp(name, "./", 2) == 0) { - /* nothing */ - } else { + if (*name == '.' && (name[1] == '/' || (name[1] == '.' && name[2] == '/'))) { + if (name[1] == '.') { + /* if at any point we go outside the current directory + then stop - it is unsafe */ + if (--depth < 0) + return 1; + } + } else depth++; - } + while (slash[1] == '/') slash++; } - if (strcmp(name, "..") == 0) + if (*name == '.' && name[1] == '.' && name[2] == '\0') depth--; - return (depth < 0); -} - -/* Return the int64 number as a string. If the --human-readable option was - * specified, we may output the number in K, M, or G units. We can return - * up to 4 buffers at a time. */ -char *human_num(int64 num) -{ - static char bufs[4][128]; /* more than enough room */ - static unsigned int n; - char *s; - - n = (n + 1) % (sizeof bufs / sizeof bufs[0]); - - if (human_readable) { - char units = '\0'; - int mult = human_readable == 1 ? 1000 : 1024; - double dnum = 0; - if (num > mult*mult*mult) { - dnum = (double)num / (mult*mult*mult); - units = 'G'; - } else if (num > mult*mult) { - dnum = (double)num / (mult*mult); - units = 'M'; - } else if (num > mult) { - dnum = (double)num / mult; - units = 'K'; - } - if (units) { - snprintf(bufs[n], sizeof bufs[0], "%.2f%c", dnum, units); - return bufs[n]; - } - } - - s = bufs[n] + sizeof bufs[0] - 1; - *s = '\0'; - - if (!num) - *--s = '0'; - while (num) { - *--s = (char)(num % 10) + '0'; - num /= 10; - } - return s; -} - -/* Return the double number as a string. If the --human-readable option was - * specified, we may output the number in K, M, or G units. We use a buffer - * from human_num() to return our result. */ -char *human_dnum(double dnum, int decimal_digits) -{ - char *buf = human_num(dnum); - int len = strlen(buf); - if (isDigit(buf + len - 1)) { - /* There's extra room in buf prior to the start of the num. */ - buf -= decimal_digits + 1; - snprintf(buf, len + decimal_digits + 2, "%.*f", decimal_digits, dnum); - } - return buf; + return depth < 0; } /* Return the date and time as a string. Some callers tweak returned buf. */ @@ -1255,7 +1389,7 @@ void *_new_array(unsigned long num, unsigned int size, int use_calloc) return use_calloc ? calloc(num, size) : malloc(num * size); } -void *_realloc_array(void *ptr, unsigned int size, unsigned long num) +void *_realloc_array(void *ptr, unsigned int size, size_t num) { if (num >= MALLOC_MAX/size) return NULL; @@ -1328,11 +1462,11 @@ const char *find_filename_suffix(const char *fn, int fn_len, int *len_ptr) #define UNIT (1 << 16) -uint32 fuzzy_distance(const char *s1, int len1, const char *s2, int len2) +uint32 fuzzy_distance(const char *s1, unsigned len1, const char *s2, unsigned len2) { uint32 a[MAXPATHLEN], diag, above, left, diag_inc, above_inc, left_inc; int32 cost; - int i1, i2; + unsigned i1, i2; if (!len1 || !len2) { if (!len1) { @@ -1463,6 +1597,39 @@ int bitbag_next_bit(struct bitbag *bb, int after) return -1; } +void flist_ndx_push(flist_ndx_list *lp, int ndx) +{ + struct flist_ndx_item *item; + + if (!(item = new(struct flist_ndx_item))) + out_of_memory("flist_ndx_push"); + item->next = NULL; + item->ndx = ndx; + if (lp->tail) + lp->tail->next = item; + else + lp->head = item; + lp->tail = item; +} + +int flist_ndx_pop(flist_ndx_list *lp) +{ + struct flist_ndx_item *next; + int ndx; + + if (!lp->head) + return -1; + + ndx = lp->head->ndx; + next = lp->head->next; + free(lp->head); + lp->head = next; + if (!next) + lp->tail = NULL; + + return ndx; +} + void *expand_item_list(item_list *lp, size_t item_size, const char *desc, int incr) { @@ -1476,10 +1643,13 @@ void *expand_item_list(item_list *lp, size_t item_size, new_size += incr; else new_size *= 2; - new_ptr = realloc_array(lp->items, char, new_size * item_size); - if (verbose >= 4) { - rprintf(FINFO, "[%s] expand %s to %.0f bytes, did%s move\n", - who_am_i(), desc, (double)new_size * item_size, + if (new_size < lp->malloced) + overflow_exit("expand_item_list"); + /* Using _realloc_array() lets us pass the size, not a type. */ + new_ptr = _realloc_array(lp->items, item_size, new_size); + if (DEBUG_GTE(FLIST, 3)) { + rprintf(FINFO, "[%s] expand %s to %s bytes, did%s move\n", + who_am_i(), desc, big_num(new_size * item_size), new_ptr == lp->items ? " not" : ""); } if (!new_ptr)