X-Git-Url: https://mattmccutchen.net/rsync/rsync.git/blobdiff_plain/edad5898f2c221ee869b14d5e16c4a8cf8c4e4bb..0f78b81511be65d8fe21af1e6ac674f9e80ac29d:/util.c diff --git a/util.c b/util.c index a01a38d1..2ec56a7c 100644 --- a/util.c +++ b/util.c @@ -1,8 +1,10 @@ -/* -*- c-file-style: "linux" -*- +/* + * Utility routines used in rsync. * - * Copyright (C) 1996-2000 by Andrew Tridgell - * Copyright (C) Paul Mackerras 1996 - * Copyright (C) 2001, 2002 by Martin Pool + * Copyright (C) 1996-2000 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2001, 2002 Martin Pool + * Copyright (C) 2003, 2004, 2005, 2006 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 @@ -16,30 +18,24 @@ * * 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. + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -/** - * @file - * - * Utilities used in rsync - **/ - #include "rsync.h" extern int verbose; extern int dry_run; extern int module_id; extern int modify_window; -extern struct exclude_list_struct server_exclude_list; +extern int relative_paths; +extern int human_readable; +extern mode_t orig_umask; +extern char *partial_dir; +extern struct filter_list_struct server_filter_list; int sanitize_paths = 0; - - -/** - * Set a fd into nonblocking mode - **/ +/* Set a fd into nonblocking mode. */ void set_nonblocking(int fd) { int val; @@ -52,9 +48,7 @@ void set_nonblocking(int fd) } } -/** - * Set a fd into blocking mode - **/ +/* Set a fd into blocking mode. */ void set_blocking(int fd) { int val; @@ -67,7 +61,6 @@ void set_blocking(int fd) } } - /** * Create a file descriptor pair - like pipe() but use socketpair if * possible (because of blocking issues on pipes). @@ -78,7 +71,7 @@ int fd_pair(int fd[2]) { int ret; -#if HAVE_SOCKETPAIR +#ifdef HAVE_SOCKETPAIR ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fd); #else ret = pipe(fd); @@ -92,7 +85,6 @@ int fd_pair(int fd[2]) return ret; } - void print_child_argv(char **cmd) { rprintf(FINFO, "opening connection using "); @@ -112,79 +104,100 @@ void print_child_argv(char **cmd) rprintf(FINFO, "\n"); } - void out_of_memory(char *str) { rprintf(FERROR, "ERROR: out of memory in %s\n", str); exit_cleanup(RERR_MALLOC); } -void overflow(char *str) +void overflow_exit(char *str) { rprintf(FERROR, "ERROR: buffer overflow in %s\n", str); exit_cleanup(RERR_MALLOC); } - - -int set_modtime(char *fname, time_t modtime) +int set_modtime(char *fname, time_t modtime, mode_t mode) { - if (dry_run) - return 0; +#if !defined HAVE_LUTIMES || !defined HAVE_UTIMES + if (S_ISLNK(mode)) + return 1; +#endif if (verbose > 2) { rprintf(FINFO, "set modtime of %s to (%ld) %s", - fname, (long) modtime, + fname, (long)modtime, asctime(localtime(&modtime))); } + if (dry_run) + return 0; + { -#ifdef HAVE_UTIMBUF +#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)) + return lutimes(fname, t); +# endif + return utimes(fname, t); +#elif defined HAVE_UTIMBUF struct utimbuf tbuf; tbuf.actime = time(NULL); tbuf.modtime = modtime; return utime(fname,&tbuf); -#elif defined(HAVE_UTIME) +#elif defined HAVE_UTIME time_t t[2]; t[0] = time(NULL); t[1] = modtime; return utime(fname,t); #else - 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; - return utimes(fname,t); +#error No file-time-modification routine found! #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; -/** - Create any necessary directories in fname. Unfortunately we don't know - what perms to give the directory when this is called so we need to rely - on the umask -**/ -int create_directory_path(char *fname, int base_umask) + umask(orig_umask); + ret = do_mkdir(fname, ACCESSPERMS); + umask(0); + + return ret; +} + +/* Create any necessary directories in fname. Any missing directories are + * created with default permissions. */ +int create_directory_path(char *fname) { char *p; + int ret = 0; while (*fname == '/') fname++; while (strncmp(fname, "./", 2) == 0) fname += 2; + umask(orig_umask); p = fname; while ((p = strchr(p,'/')) != NULL) { - *p = 0; - do_mkdir(fname, 0777 & ~base_umask); - *p = '/'; - p++; + *p = '\0'; + if (do_mkdir(fname, ACCESSPERMS) < 0 && errno != EEXIST) + ret = -1; + *p++ = '/'; } - return 0; -} + umask(0); + return ret; +} /** * Write @p len bytes at @p ptr to descriptor @p desc, retrying if @@ -196,7 +209,7 @@ int create_directory_path(char *fname, int base_umask) * * Derived from GNU C's cccp.c. */ -static int full_write(int desc, char *ptr, size_t len) +int full_write(int desc, char *ptr, size_t len) { int total_written; @@ -215,7 +228,6 @@ static int full_write(int desc, char *ptr, size_t len) return total_written; } - /** * Read @p len bytes at @p ptr from descriptor @p desc, retrying if * interrupted. @@ -241,11 +253,11 @@ static int safe_read(int desc, char *ptr, size_t len) return n_chars; } - /** Copy a file. * - * This is used in conjunction with the --temp-dir option */ -int copy_file(char *source, char *dest, mode_t mode) + * This is used in conjunction with the --temp-dir, --backup, and + * --copy-dest options. */ +int copy_file(const char *source, const char *dest, mode_t mode) { int ifd; int ofd; @@ -314,7 +326,7 @@ int copy_file(char *source, char *dest, mode_t mode) * --delete trying to remove old .rsyncNNN files, hence it renames it * each time. **/ -int robust_unlink(char *fname) +int robust_unlink(const char *fname) { #ifndef ETXTBSY return do_unlink(fname); @@ -361,9 +373,12 @@ int robust_unlink(char *fname) #endif } -/* Returns 0 on success, -1 on most errors, and -2 if we got an error - * trying to copy the file across file systems. */ -int robust_rename(char *from, char *to, int mode) +/* Returns 0 on successful rename, 1 if we successfully copied the file + * across filesystems, -2 if copy_file() failed, and -1 on other errors. + * If partialptr is not NULL and we need to do a copy, copy the file into + * the active partial-dir instead of over the destination file. */ +int robust_rename(char *from, char *to, char *partialptr, + int mode) { int tries = 4; @@ -379,10 +394,15 @@ int robust_rename(char *from, char *to, int mode) break; #endif case EXDEV: + if (partialptr) { + if (!handle_partial_dir(partialptr,PDIR_CREATE)) + return -1; + to = partialptr; + } if (copy_file(from, to, mode) != 0) return -2; do_unlink(from); - return 0; + return 1; default: return -1; } @@ -390,7 +410,6 @@ int robust_rename(char *from, char *to, int mode) return -1; } - static pid_t all_pids[10]; static int num_pids; @@ -434,12 +453,12 @@ void kill_all(int sig) } } - /** Turn a user name into a uid */ int name_to_uid(char *name, uid_t *uid) { struct passwd *pass; - if (!name || !*name) return 0; + if (!name || !*name) + return 0; pass = getpwnam(name); if (pass) { *uid = pass->pw_uid; @@ -452,7 +471,8 @@ int name_to_uid(char *name, uid_t *uid) int name_to_gid(char *name, gid_t *gid) { struct group *grp; - if (!name || !*name) return 0; + if (!name || !*name) + return 0; grp = getgrnam(name); if (grp) { *gid = grp->gr_gid; @@ -461,7 +481,6 @@ int name_to_gid(char *name, gid_t *gid) return 0; } - /** Lock a byte range in a open file */ int lock_range(int fd, int offset, int len) { @@ -476,14 +495,14 @@ int lock_range(int fd, int offset, int len) return fcntl(fd,F_SETLK,&lock) == 0; } -static int exclude_server_path(char *arg) +static int filter_server_path(char *arg) { char *s; - if (server_exclude_list.head) { + if (server_filter_list.head) { for (s = arg; (s = strchr(s, '/')) != NULL; ) { *s = '\0'; - if (check_exclude(&server_exclude_list, arg, 1) < 0) { + if (check_filter(&server_filter_list, arg, 1) < 0) { /* We must leave arg truncated! */ return 1; } @@ -493,77 +512,91 @@ static int exclude_server_path(char *arg) return 0; } -static void glob_expand_one(char *s, char **argv, int *argc, int maxargs) +static void glob_expand_one(char *s, char ***argv_ptr, int *argc_ptr, + int *maxargs_ptr) { -#if !(defined(HAVE_GLOB) && defined(HAVE_GLOB_H)) - if (maxargs <= *argc) - return; + 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_one"); + *argv_ptr = argv; + *maxargs_ptr = maxargs; + } if (!*s) s = "."; - s = argv[*argc] = strdup(s); - exclude_server_path(s); - (*argc)++; + s = argv[argc++] = strdup(s); + filter_server_path(s); #else glob_t globbuf; - int i; + if (maxargs <= argc) + return; if (!*s) s = "."; - s = argv[*argc] = strdup(s); if (sanitize_paths) - sanitize_path(s, NULL); + s = sanitize_path(NULL, s, "", 0); + else + s = strdup(s); memset(&globbuf, 0, sizeof globbuf); - if (!exclude_server_path(s)) + if (!filter_server_path(s)) glob(s, 0, NULL, &globbuf); - if (globbuf.gl_pathc == 0) { - (*argc)++; - globfree(&globbuf); - return; + if (MAX((int)globbuf.gl_pathc, 1) > maxargs - argc) { + maxargs += globbuf.gl_pathc + MAX_ARGS; + if (!(argv = realloc_array(argv, char *, maxargs))) + out_of_memory("glob_expand_one"); + *argv_ptr = argv; + *maxargs_ptr = maxargs; } - for (i = 0; i < maxargs - *argc && i < (int)globbuf.gl_pathc; i++) { - if (i == 0) - free(s); - argv[*argc + i] = strdup(globbuf.gl_pathv[i]); - if (!argv[*argc + i]) - out_of_memory("glob_expand"); + if (globbuf.gl_pathc == 0) + argv[argc++] = s; + else { + int i; + free(s); + for (i = 0; i < (int)globbuf.gl_pathc; i++) { + if (!(argv[argc++] = strdup(globbuf.gl_pathv[i]))) + out_of_memory("glob_expand_one"); + } } globfree(&globbuf); - *argc += i; #endif + *argc_ptr = argc; } /* This routine is only used in daemon mode. */ -void glob_expand(char *base1, char **argv, int *argc, int maxargs) +void glob_expand(char *base1, char ***argv_ptr, int *argc_ptr, int *maxargs_ptr) { - char *s = argv[*argc]; + char *s = (*argv_ptr)[*argc_ptr]; char *p, *q; char *base = base1; int base_len = strlen(base); - if (!s || !*s) return; + if (!s || !*s) + return; if (strncmp(s, base, base_len) == 0) s += base_len; - s = strdup(s); - if (!s) out_of_memory("glob_expand"); + if (!(s = strdup(s))) + out_of_memory("glob_expand"); - if (asprintf(&base," %s/", base1) <= 0) out_of_memory("glob_expand"); + if (asprintf(&base," %s/", base1) <= 0) + out_of_memory("glob_expand"); base_len++; - q = s; - while ((p = strstr(q,base)) != NULL && *argc < maxargs) { - /* split it at this point */ - *p = 0; - glob_expand_one(q, argv, argc, maxargs); - q = p + base_len; + for (q = s; *q; q = p + base_len) { + if ((p = strstr(q, base)) != NULL) + *p = '\0'; /* split it at this point */ + glob_expand_one(q, argv_ptr, argc_ptr, maxargs_ptr); + if (!p) + break; } - if (*q && *argc < maxargs) - glob_expand_one(q, argv, argc, maxargs); - free(s); free(base); } @@ -574,8 +607,8 @@ void glob_expand(char *base1, char **argv, int *argc, int maxargs) void strlower(char *s) { while (*s) { - if (isupper(* (unsigned char *) s)) - *s = tolower(* (unsigned char *) s); + if (isupper(*(unsigned char *)s)) + *s = tolower(*(unsigned char *)s); s++; } } @@ -632,121 +665,147 @@ size_t stringjoin(char *dest, size_t destsize, ...) return ret; } -void clean_fname(char *name) +int count_dir_elements(const char *p) { - char *p; - int l; - int modified = 1; + int cnt = 0, new_component = 1; + while (*p) { + if (*p++ == '/') + new_component = 1; + else if (new_component) { + new_component = 0; + cnt++; + } + } + return cnt; +} - if (!name) return; +/* Turns multiple adjacent slashes into a single slash, gets rid of "./" + * elements (but not a trailing dot dir), removes a trailing slash, and + * optionally collapses ".." elements (except for those at the start of the + * string). If the resulting name would be empty, change it into a ".". */ +unsigned int clean_fname(char *name, BOOL collapse_dot_dot) +{ + char *limit = name - 1, *t = name, *f = name; + int anchored; - while (modified) { - modified = 0; + if (!name) + return 0; - if ((p = strstr(name,"/./")) != NULL) { - modified = 1; - while (*p) { - p[0] = p[2]; - p++; - } + if ((anchored = *f == '/') != 0) + *t++ = *f++; + while (*f) { + /* discard extra slashes */ + if (*f == '/') { + f++; + continue; } - - if ((p = strstr(name,"//")) != NULL) { - modified = 1; - while (*p) { - p[0] = p[1]; - p++; + if (*f == '.') { + /* discard "." dirs (but NOT a trailing '.'!) */ + if (f[1] == '/') { + f += 2; + continue; + } + /* collapse ".." dirs */ + if (collapse_dot_dot + && f[1] == '.' && (f[2] == '/' || !f[2])) { + char *s = t - 1; + if (s == name && anchored) { + f += 2; + continue; + } + while (s > limit && *--s != '/') {} + if (s != t - 1 && (s < name || *s == '/')) { + t = s + 1; + f += 2; + continue; + } + limit = t + 2; } } + while (*f && (*t++ = *f++) != '/') {} + } - if (strncmp(p = name, "./", 2) == 0) { - modified = 1; - do { - p[0] = p[2]; - } while (*p++); - } + if (t > name+anchored && t[-1] == '/') + t--; + if (t == name) + *t++ = '.'; + *t = '\0'; - l = strlen(p = name); - if (l > 1 && p[l-1] == '/') { - modified = 1; - p[l-1] = 0; - } - } + return t - name; } -/** - * Make path appear as if a chroot had occurred: +/* Make path appear as if a chroot had occurred. This handles a leading + * "/" (either removing it or expanding it) and any leading or embedded + * ".." components that attempt to escape past the module's top dir. * - * @li 1. remove leading "/" (or replace with "." if at end) + * If dest is NULL, a buffer is allocated to hold the result. It is legal + * to call with the dest and the path (p) pointing to the same buffer, but + * rootdir will be ignored to avoid expansion of the string. * - * @li 2. remove leading ".." components (except those allowed by @p reldir) + * The rootdir string contains a value to use in place of a leading slash. + * Specify NULL to get the default of lp_path(module_id). * - * @li 3. delete any other "/.." (recursively) + * If depth is >= 0, it is a count of how many '..'s to allow at the start + * of the path. Use -1 to allow unlimited depth. * - * Can only shrink paths, so sanitizes in place. + * We also clean the path in a manner similar to clean_fname() but with a + * few differences: * - * 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. - * - * If @p 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 - */ -void sanitize_path(char *p, char *reldir) + * Turns multiple adjacent slashes into a single slash, gets rid of "." dir + * elements (INCLUDING a trailing dot dir), PRESERVES a trailing slash, and + * 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 *start, *sanp; - int depth = 0; - int allowdotdot = 0; - - if (reldir) { - int new_component = 1; - while (*reldir) { - if (*reldir++ == '/') - new_component = 1; - else if (new_component) { - new_component = 0; - depth++; - } + int rlen = 0, leave_one_dotdir = relative_paths; + + if (dest != p) { + int plen = strlen(p); + if (*p == '/') { + if (!rootdir) + rootdir = lp_path(module_id); + rlen = strlen(rootdir); + depth = 0; + p++; + } + if (dest) { + if (rlen + plen + 1 >= MAXPATHLEN) + return NULL; + } else if (!(dest = new_array(char, rlen + plen + 1))) + out_of_memory("sanitize_path"); + if (rlen) { + memcpy(dest, rootdir, rlen); + if (rlen > 1) + dest[rlen++] = '/'; } } - start = p; - sanp = p; - while (*p == '/') { - /* remove leading slashes */ - p++; - } + + start = sanp = dest + rlen; while (*p != '\0') { + /* 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')) { - /* skip "." component */ - while (*++p == '/') { - /* skip following slashes */ - ; + if (leave_one_dotdir && p[1]) + leave_one_dotdir = 0; + else { + /* skip "." component */ + p++; + continue; } - continue; } - allowdotdot = 0; if (*p == '.' && p[1] == '.' && (p[2] == '/' || p[2] == '\0')) { /* ".." component followed by slash or end */ - if (depth > 0 && sanp == start) { - /* allow depth levels of .. at the beginning */ - --depth; - allowdotdot = 1; - } else { + if (depth <= 0 || sanp != start) { p += 2; - if (*p == '/') - p++; if (sanp != start) { /* back up sanp one level */ --sanp; /* now pointing at slash */ @@ -757,68 +816,21 @@ void sanitize_path(char *p, char *reldir) } continue; } - } - while (1) { - /* copy one component through next slash */ - *sanp++ = *p++; - if (*p == '\0' || p[-1] == '/') { - while (*p == '/') { - /* skip multiple slashes */ - p++; - } - break; - } - } - if (allowdotdot) { + /* allow depth levels of .. at the beginning */ + depth--; /* move the virtual beginning to leave the .. alone */ - start = sanp; + start = sanp + 3; } + /* copy one component through next slash */ + while (*p && (*sanp++ = *p++) != '/') {} } - if (sanp == start && !allowdotdot) { + if (sanp == dest) { /* ended up with nothing, so put in "." component */ - /* - * 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++ = '.'; } *sanp = '\0'; -} - -/* Works much like sanitize_path(), with these differences: (1) a new buffer - * is allocated for the sanitized path rather than modifying it in-place; (2) - * a leading slash gets transformed into the rootdir value (which can be empty - * or NULL if you just want the slash to get dropped); (3) no "reldir" can be - * specified. */ -char *alloc_sanitize_path(const char *path, const char *rootdir) -{ - char *buf; - int rlen, plen = strlen(path); - if (*path == '/' && rootdir) { - rlen = strlen(rootdir); - if (rlen == 1) - path++; - } else - rlen = 0; - if (!(buf = new_array(char, rlen + plen + 1))) - out_of_memory("alloc_sanitize_path"); - if (rlen) - memcpy(buf, rootdir, rlen); - memcpy(buf + rlen, path, plen + 1); - - if (rlen > 1) - rlen++; - sanitize_path(buf + rlen, NULL); - if (rlen && buf[rlen] == '.' && buf[rlen+1] == '\0') { - if (rlen > 1) - rlen--; - buf[rlen] = '\0'; - } - - return buf; + return dest; } char curr_dir[MAXPATHLEN]; @@ -862,7 +874,7 @@ int push_dir(char *dir) curr_dir_len += len; } - clean_fname(curr_dir); + curr_dir_len = clean_fname(curr_dir, 1); return 1; } @@ -901,25 +913,21 @@ char *full_fname(const char *fn) p1 = p2 = ""; else { p1 = curr_dir; - p2 = "/"; + for (p2 = p1; *p2 == '/'; p2++) {} + if (*p2) + p2 = "/"; } if (module_id >= 0) { m1 = " (in "; m2 = lp_name(module_id); m3 = ")"; - if (*p1) { + if (p1 == curr_dir) { if (!lp_use_chroot(module_id)) { char *p = lp_path(module_id); if (*p != '/' || p[1]) p1 += strlen(p); } - if (!*p1) - p2++; - else - p1++; } - else - fn++; } else m1 = m2 = m3 = ""; @@ -928,21 +936,72 @@ char *full_fname(const char *fn) return result; } -/** We need to supply our own strcmp function for file list comparisons - to ensure that signed/unsigned usage is consistent between machines. */ -int u_strcmp(const char *cs1, const char *cs2) -{ - const uchar *s1 = (const uchar *)cs1; - const uchar *s2 = (const uchar *)cs2; +static char partial_fname[MAXPATHLEN]; - while (*s1 && *s2 && (*s1 == *s2)) { - s1++; s2++; +char *partial_dir_fname(const char *fname) +{ + char *t = partial_fname; + int sz = sizeof partial_fname; + const char *fn; + + if ((fn = strrchr(fname, '/')) != NULL) { + fn++; + if (*partial_dir != '/') { + int len = fn - fname; + strncpy(t, fname, len); /* safe */ + t += len; + sz -= len; + } + } else + fn = fname; + if ((int)pathjoin(t, sz, partial_dir, fn) >= sz) + return NULL; + if (server_filter_list.head) { + static int len; + if (!len) + len = strlen(partial_dir); + t[len] = '\0'; + if (check_filter(&server_filter_list, partial_fname, 1) < 0) + return NULL; + t[len] = '/'; + if (check_filter(&server_filter_list, partial_fname, 0) < 0) + return NULL; } - return (int)*s1 - (int)*s2; + return partial_fname; } +/* If no --partial-dir option was specified, we don't need to do anything + * (the partial-dir is essentially '.'), so just return success. */ +int handle_partial_dir(const char *fname, int create) +{ + char *fn, *dir; + if (fname != partial_fname) + return 1; + if (!create && *partial_dir == '/') + return 1; + if (!(fn = strrchr(partial_fname, '/'))) + return 1; + + *fn = '\0'; + dir = partial_fname; + if (create) { + STRUCT_STAT st; + int statret = do_lstat(dir, &st); + if (statret == 0 && !S_ISDIR(st.st_mode)) { + if (do_unlink(dir) < 0) + return 0; + statret = -1; + } + if (statret < 0 && do_mkdir(dir, 0700) < 0) + return 0; + } else + do_rmdir(dir); + *fn = '/'; + + return 1; +} /** * Determine if a symlink points outside the current directory tree. @@ -974,7 +1033,8 @@ int unsafe_symlink(const char *dest, const char *src) int depth = 0; /* all absolute and null symlinks are unsafe */ - if (!dest || !*dest || *dest == '/') return 1; + if (!dest || !*dest || *dest == '/') + return 1; /* find out what our safety margin is */ for (name = src; (slash = strchr(name, '/')) != 0; name = slash+1) { @@ -1007,6 +1067,63 @@ int unsafe_symlink(const char *dest, const char *src) 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) { + sprintf(bufs[n], "%.2f%c", dnum, units); + return bufs[n]; + } + } + + s = bufs[n] + sizeof bufs[0] - 1; + *s = '\0'; + + if (!num) + *--s = '0'; + while (num) { + *--s = (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(*(uchar*)(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 the date and time as a string @@ -1015,6 +1132,7 @@ char *timestring(time_t t) { static char TimeBuf[200]; struct tm *tm = localtime(&t); + char *p; #ifdef HAVE_STRFTIME strftime(TimeBuf, sizeof TimeBuf - 1, "%Y/%m/%d %H:%M:%S", tm); @@ -1022,14 +1140,12 @@ char *timestring(time_t t) strlcpy(TimeBuf, asctime(tm), sizeof TimeBuf); #endif - if (TimeBuf[strlen(TimeBuf)-1] == '\n') { - TimeBuf[strlen(TimeBuf)-1] = 0; - } + if ((p = strchr(TimeBuf, '\n')) != NULL) + *p = '\0'; - return(TimeBuf); + return TimeBuf; } - /** * Sleep for a specified number of milliseconds. * @@ -1058,11 +1174,8 @@ int msleep(int t) return True; } - -/** - * Determine if two file modification times are equivalent (either - * exact or in the modification timestamp window established by - * --modify-window). +/* Determine if two time_t values are equivalent (either exact, or in + * the modification timestamp window established by --modify-window). * * @retval 0 if the times should be treated as the same * @@ -1070,7 +1183,7 @@ int msleep(int t) * * @retval -1 if the 2nd is later **/ -int cmp_modtime(time_t file1, time_t file2) +int cmp_time(time_t file1, time_t file2) { if (file2 > file1) { if (file2 - file1 <= modify_window) @@ -1116,7 +1229,6 @@ int _Insure_trap_error(int a1, int a2, int a3, int a4, int a5, int a6) } #endif - #define MALLOC_MAX 0x40000000 void *_new_array(unsigned int size, unsigned long num) @@ -1135,3 +1247,202 @@ void *_realloc_array(void *ptr, unsigned int size, unsigned long num) return malloc(size * num); return realloc(ptr, size * num); } + +/* Take a filename and filename length and return the most significant + * filename suffix we can find. This ignores suffixes such as "~", + * ".bak", ".orig", ".~1~", etc. */ +const char *find_filename_suffix(const char *fn, int fn_len, int *len_ptr) +{ + const char *suf, *s; + BOOL had_tilde; + int s_len; + + /* One or more dots at the start aren't a suffix. */ + while (fn_len && *fn == '.') fn++, fn_len--; + + /* Ignore the ~ in a "foo~" filename. */ + if (fn_len > 1 && fn[fn_len-1] == '~') + fn_len--, had_tilde = True; + else + had_tilde = False; + + /* Assume we don't find an suffix. */ + suf = ""; + *len_ptr = 0; + + /* Find the last significant suffix. */ + for (s = fn + fn_len; fn_len > 1; ) { + while (*--s != '.' && s != fn) {} + if (s == fn) + break; + s_len = fn_len - (s - fn); + fn_len = s - fn; + if (s_len == 4) { + if (strcmp(s+1, "bak") == 0 + || strcmp(s+1, "old") == 0) + continue; + } else if (s_len == 5) { + if (strcmp(s+1, "orig") == 0) + continue; + } else if (s_len > 2 && had_tilde + && s[1] == '~' && isdigit(*(uchar*)(s+2))) + continue; + *len_ptr = s_len; + suf = s; + if (s_len == 1) + break; + /* Determine if the suffix is all digits. */ + for (s++, s_len--; s_len > 0; s++, s_len--) { + if (!isdigit(*(uchar*)s)) + return suf; + } + /* An all-digit suffix may not be that signficant. */ + s = suf; + } + + return suf; +} + +/* This is an implementation of the Levenshtein distance algorithm. It + * was implemented to avoid needing a two-dimensional matrix (to save + * memory). It was also tweaked to try to factor in the ASCII distance + * between changed characters as a minor distance quantity. The normal + * Levenshtein units of distance (each signifying a single change between + * the two strings) are defined as a "UNIT". */ + +#define UNIT (1 << 16) + +uint32 fuzzy_distance(const char *s1, int len1, const char *s2, int len2) +{ + uint32 a[MAXPATHLEN], diag, above, left, diag_inc, above_inc, left_inc; + int32 cost; + int i1, i2; + + if (!len1 || !len2) { + if (!len1) { + s1 = s2; + len1 = len2; + } + for (i1 = 0, cost = 0; i1 < len1; i1++) + cost += s1[i1]; + return (int32)len1 * UNIT + cost; + } + + for (i2 = 0; i2 < len2; i2++) + a[i2] = (i2+1) * UNIT; + + for (i1 = 0; i1 < len1; i1++) { + diag = i1 * UNIT; + above = (i1+1) * UNIT; + for (i2 = 0; i2 < len2; i2++) { + left = a[i2]; + if ((cost = *((uchar*)s1+i1) - *((uchar*)s2+i2)) != 0) { + if (cost < 0) + cost = UNIT - cost; + else + cost = UNIT + cost; + } + diag_inc = diag + cost; + left_inc = left + UNIT + *((uchar*)s1+i1); + above_inc = above + UNIT + *((uchar*)s2+i2); + a[i2] = above = left < above + ? (left_inc < diag_inc ? left_inc : diag_inc) + : (above_inc < diag_inc ? above_inc : diag_inc); + diag = left; + } + } + + return a[len2-1]; +} + +#define BB_SLOT_SIZE (16*1024) /* Desired size in bytes */ +#define BB_PER_SLOT_BITS (BB_SLOT_SIZE * 8) /* Number of bits per slot */ +#define BB_PER_SLOT_INTS (BB_SLOT_SIZE / 4) /* Number of int32s per slot */ + +struct bitbag { + uint32 **bits; + int slot_cnt; +}; + +struct bitbag *bitbag_create(int max_ndx) +{ + struct bitbag *bb = new(struct bitbag); + bb->slot_cnt = (max_ndx + BB_PER_SLOT_BITS - 1) / BB_PER_SLOT_BITS; + + if (!(bb->bits = (uint32**)calloc(bb->slot_cnt, sizeof (uint32*)))) + out_of_memory("bitbag_create"); + + return bb; +} + +void bitbag_set_bit(struct bitbag *bb, int ndx) +{ + int slot = ndx / BB_PER_SLOT_BITS; + ndx %= BB_PER_SLOT_BITS; + + if (!bb->bits[slot]) { + if (!(bb->bits[slot] = (uint32*)calloc(BB_PER_SLOT_INTS, 4))) + out_of_memory("bitbag_set_bit"); + } + + bb->bits[slot][ndx/32] |= 1u << (ndx % 32); +} + +#if 0 /* not needed yet */ +void bitbag_clear_bit(struct bitbag *bb, int ndx) +{ + int slot = ndx / BB_PER_SLOT_BITS; + ndx %= BB_PER_SLOT_BITS; + + if (!bb->bits[slot]) + return; + + bb->bits[slot][ndx/32] &= ~(1u << (ndx % 32)); +} + +int bitbag_check_bit(struct bitbag *bb, int ndx) +{ + int slot = ndx / BB_PER_SLOT_BITS; + ndx %= BB_PER_SLOT_BITS; + + if (!bb->bits[slot]) + return 0; + + return bb->bits[slot][ndx/32] & (1u << (ndx % 32)) ? 1 : 0; +} +#endif + +/* Call this with -1 to start checking from 0. Returns -1 at the end. */ +int bitbag_next_bit(struct bitbag *bb, int after) +{ + uint32 bits, mask; + int i, ndx = after + 1; + int slot = ndx / BB_PER_SLOT_BITS; + ndx %= BB_PER_SLOT_BITS; + + mask = (1u << (ndx % 32)) - 1; + for (i = ndx / 32; slot < bb->slot_cnt; slot++, i = mask = 0) { + if (!bb->bits[slot]) + continue; + for ( ; i < BB_PER_SLOT_INTS; i++, mask = 0) { + if (!(bits = bb->bits[slot][i] & ~mask)) + continue; + /* The xor magic figures out the lowest enabled bit in + * bits, and the switch quickly computes log2(bit). */ + switch (bits ^ (bits & (bits-1))) { +#define LOG2(n) case 1u << n: return slot*BB_PER_SLOT_BITS + i*32 + n + LOG2(0); LOG2(1); LOG2(2); LOG2(3); + LOG2(4); LOG2(5); LOG2(6); LOG2(7); + LOG2(8); LOG2(9); LOG2(10); LOG2(11); + LOG2(12); LOG2(13); LOG2(14); LOG2(15); + LOG2(16); LOG2(17); LOG2(18); LOG2(19); + LOG2(20); LOG2(21); LOG2(22); LOG2(23); + LOG2(24); LOG2(25); LOG2(26); LOG2(27); + LOG2(28); LOG2(29); LOG2(30); LOG2(31); + } + return -1; /* impossible... */ + } + } + + return -1; +}