Preparing for release of 2.6.6pre1
[rsync.git] / util.c
diff --git a/util.c b/util.c
index bda3bc21cade63e9a95321e146a579524241e040..234a61e89bad108d9cd4782e944fb70d5ac303be 100644 (file)
--- a/util.c
+++ b/util.c
@@ -1,19 +1,19 @@
 /*  -*- c-file-style: "linux" -*-
- * 
- * Copyright (C) 1996-2000 by Andrew Tridgell 
+ *
+ * Copyright (C) 1996-2000 by Andrew Tridgell
  * Copyright (C) Paul Mackerras 1996
  * Copyright (C) 2001, 2002 by Martin Pool <mbp@samba.org>
- * 
+ *
  * 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.
 /**
  * @file
  *
- * Utilities used in rsync 
+ * Utilities used in rsync
  **/
 
 #include "rsync.h"
 
 extern int verbose;
+extern int dry_run;
+extern int module_id;
+extern int modify_window;
+extern char *partial_dir;
+extern struct filter_list_struct server_filter_list;
 
 int sanitize_paths = 0;
 
@@ -67,14 +72,14 @@ void set_blocking(int fd)
 /**
  * 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
+#ifdef HAVE_SOCKETPAIR
        ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fd);
 #else
        ret = pipe(fd);
@@ -100,9 +105,9 @@ void print_child_argv(char **cmd)
                           "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                           "0123456789"
                           ",.-_=+@/") != strlen(*cmd)) {
-                       rprintf(FINFO, "\"%s\" ", *cmd);
+                       rprintf(FINFO, "\"%s\" ", safe_fname(*cmd));
                } else {
-                       rprintf(FINFO, "%s ", *cmd);
+                       rprintf(FINFO, "%s ", safe_fname(*cmd));
                }
        }
        rprintf(FINFO, "\n");
@@ -111,37 +116,36 @@ void print_child_argv(char **cmd)
 
 void out_of_memory(char *str)
 {
-  rprintf(FERROR,"ERROR: out of memory in %s\n",str);
-  exit_cleanup(RERR_MALLOC);
+       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);
+       rprintf(FERROR, "ERROR: buffer overflow in %s\n", str);
+       exit_cleanup(RERR_MALLOC);
 }
 
 
 
 int set_modtime(char *fname, time_t modtime)
 {
-       extern int dry_run;
-       if (dry_run)
-               return 0;
-
        if (verbose > 2) {
                rprintf(FINFO, "set modtime of %s to (%ld) %s",
-                       fname, (long) modtime,
+                       safe_fname(fname), (long)modtime,
                        asctime(localtime(&modtime)));
        }
-       
+
+       if (dry_run)
+               return 0;
+
        {
 #ifdef HAVE_UTIMBUF
-               struct utimbuf tbuf;  
+               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;
@@ -167,13 +171,15 @@ int create_directory_path(char *fname, int base_umask)
 {
        char *p;
 
-       while (*fname == '/') fname++;
-       while (strncmp(fname,"./",2)==0) fname += 2;
+       while (*fname == '/')
+               fname++;
+       while (strncmp(fname, "./", 2) == 0)
+               fname += 2;
 
        p = fname;
-       while ((p=strchr(p,'/'))) {
+       while ((p = strchr(p,'/')) != NULL) {
                *p = 0;
-               do_mkdir(fname, 0777 & ~base_umask); 
+               do_mkdir(fname, 0777 & ~base_umask);
                *p = '/';
                p++;
        }
@@ -191,10 +197,10 @@ 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;
-       
+
        total_written = 0;
        while (len > 0) {
                int written = write(desc, ptr, len);
@@ -225,21 +231,22 @@ static int full_write(int desc, char *ptr, size_t len)
 static int safe_read(int desc, char *ptr, size_t len)
 {
        int n_chars;
+
        if (len == 0)
                return len;
+
        do {
                n_chars = read(desc, ptr, len);
        } while (n_chars < 0 && errno == EINTR);
+
        return n_chars;
 }
 
 
 /** Copy a file.
  *
- * This is used in conjunction with the --temp-dir option */
+ * This is used in conjunction with the --temp-dir, --backup, and
+ * --copy-dest options. */
 int copy_file(char *source, char *dest, mode_t mode)
 {
        int ifd;
@@ -249,41 +256,46 @@ int copy_file(char *source, char *dest, mode_t mode)
 
        ifd = do_open(source, O_RDONLY, 0);
        if (ifd == -1) {
-               rprintf(FERROR,"open %s: %s\n",
-                       source,strerror(errno));
+               rsyserr(FERROR, errno, "open %s", full_fname(source));
                return -1;
        }
 
        if (robust_unlink(dest) && errno != ENOENT) {
-               rprintf(FERROR,"unlink %s: %s\n",
-                       dest,strerror(errno));
+               rsyserr(FERROR, errno, "unlink %s", full_fname(dest));
                return -1;
        }
 
        ofd = do_open(dest, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode);
        if (ofd == -1) {
-               rprintf(FERROR,"open %s: %s\n",
-                       dest,strerror(errno));
+               rsyserr(FERROR, errno, "open %s", full_fname(dest));
                close(ifd);
                return -1;
        }
 
-       while ((len = safe_read(ifd, buf, sizeof(buf))) > 0) {
+       while ((len = safe_read(ifd, buf, sizeof buf)) > 0) {
                if (full_write(ofd, buf, len) < 0) {
-                       rprintf(FERROR,"write %s: %s\n",
-                               dest,strerror(errno));
+                       rsyserr(FERROR, errno, "write %s", full_fname(dest));
                        close(ifd);
                        close(ofd);
                        return -1;
                }
        }
 
-       close(ifd);
-       close(ofd);
-
        if (len < 0) {
-               rprintf(FERROR,"read %s: %s\n",
-                       source,strerror(errno));
+               rsyserr(FERROR, errno, "read %s", full_fname(source));
+               close(ifd);
+               close(ofd);
+               return -1;
+       }
+
+       if (close(ifd) < 0) {
+               rsyserr(FINFO, errno, "close failed on %s",
+                       full_fname(source));
+       }
+
+       if (close(ofd) < 0) {
+               rsyserr(FERROR, errno, "close failed on %s",
+                       full_fname(dest));
                return -1;
        }
 
@@ -314,17 +326,15 @@ int robust_unlink(char *fname)
        char path[MAXPATHLEN];
 
        rc = do_unlink(fname);
-       if ((rc == 0) || (errno != ETXTBSY))
+       if (rc == 0 || errno != ETXTBSY)
                return rc;
 
-       strlcpy(path, fname, MAXPATHLEN);
+       if ((pos = strlcpy(path, fname, MAXPATHLEN)) >= MAXPATHLEN)
+               pos = MAXPATHLEN - 1;
 
-       pos = strlen(path);
-       while((path[--pos] != '/') && (pos >= 0))
-               ;
-       ++pos;
-       strlcpy(&path[pos], ".rsync", MAXPATHLEN-pos);
-       pos += sizeof(".rsync")-1;
+       while (pos > 0 && path[pos-1] != '/')
+               pos--;
+       pos += strlcpy(path+pos, ".rsync", MAXPATHLEN-pos);
 
        if (pos > (MAXPATHLEN-MAX_RENAMES_DIGITS-1)) {
                errno = ETXTBSY;
@@ -337,11 +347,11 @@ int robust_unlink(char *fname)
                sprintf(&path[pos], "%03d", counter);
                if (++counter >= MAX_RENAMES)
                        counter = 1;
-       } while (((rc = access(path, 0)) == 0) && (counter != start));
+       } while ((rc = access(path, 0)) == 0 && counter != start);
 
        if (verbose > 0) {
                rprintf(FINFO,"renaming %s to %s because of text busy\n",
-                       fname, path);
+                       safe_fname(fname), safe_fname(path));
        }
 
        /* maybe we should return rename()'s exit status? Nah. */
@@ -353,18 +363,33 @@ int robust_unlink(char *fname)
 #endif
 }
 
-int robust_rename(char *from, char *to)
+/* Returns 0 on successful rename, 1 if we successfully copied the file
+ * across filesystems, -2 if copy_file() failed, and -1 on other errors. */
+int robust_rename(char *from, char *to, int mode)
 {
-#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);
+       int tries = 4;
+
+       while (tries--) {
+               if (do_rename(from, to) == 0)
+                       return 0;
+
+               switch (errno) {
+#ifdef ETXTBSY
+               case ETXTBSY:
+                       if (robust_unlink(to) != 0)
+                               return -1;
+                       break;
 #endif
+               case EXDEV:
+                       if (copy_file(from, to, mode) != 0)
+                               return -2;
+                       do_unlink(from);
+                       return 1;
+               default:
+                       return -1;
+               }
+       }
+       return -1;
 }
 
 
@@ -375,7 +400,7 @@ static int num_pids;
 pid_t do_fork(void)
 {
        pid_t newpid = fork();
-       
+
        if (newpid != 0  &&  newpid != -1) {
                all_pids[num_pids++] = newpid;
        }
@@ -416,7 +441,8 @@ void kill_all(int sig)
 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;
@@ -429,7 +455,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;
@@ -449,19 +476,18 @@ int lock_range(int fd, int offset, int len)
        lock.l_start = offset;
        lock.l_len = len;
        lock.l_pid = 0;
-       
+
        return fcntl(fd,F_SETLK,&lock) == 0;
 }
 
-static int exclude_server_path(char *arg)
+static int filter_server_path(char *arg)
 {
        char *s;
-       extern struct exclude_struct **server_exclude_list;
 
-       if (server_exclude_list) {
+       if (server_filter_list.head) {
                for (s = arg; (s = strchr(s, '/')) != NULL; ) {
                        *s = '\0';
-                       if (check_exclude(server_exclude_list, arg, 1)) {
+                       if (check_filter(&server_filter_list, arg, 1) < 0) {
                                /* We must leave arg truncated! */
                                return 1;
                        }
@@ -471,72 +497,92 @@ 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 (!*s) s = ".";
-       s = argv[*argc] = strdup(s);
-       exclude_server_path(s);
-       (*argc)++;
+       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);
+       filter_server_path(s);
 #else
-       extern int sanitize_paths;
        glob_t globbuf;
        int i;
 
-       if (!*s) s = ".";
+       if (maxargs <= argc)
+               return;
+       if (!*s)
+               s = ".";
 
-       s = argv[*argc] = strdup(s);
-       if (sanitize_paths) {
-               sanitize_path(s, NULL);
-       }
+       if (sanitize_paths)
+               s = sanitize_path(NULL, s, "", 0);
+       else
+               s = strdup(s);
 
-       memset(&globbuf, 0, sizeof(globbuf));
-       if (!exclude_server_path(s))
+       memset(&globbuf, 0, sizeof globbuf);
+       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 j = globbuf.gl_pathc;
+               free(s);
+               for (i = 0; i < j; 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)) && ((*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);
 }
@@ -547,16 +593,16 @@ 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++;
        }
 }
 
 /* Join strings p1 & p2 into "dest" with a guaranteed '/' between them.  (If
  * p1 ends with a '/', no extra '/' is inserted.)  Returns the length of both
- * strings + 1 (if '/' was inserted), regardless of whether the whole thing
- * fits into destsize (including the terminating '\0'). */
+ * strings + 1 (if '/' was inserted), regardless of whether the null-terminated
+ * string fits into destsize. */
 size_t pathjoin(char *dest, size_t destsize, const char *p1, const char *p2)
 {
        size_t len = strlcpy(dest, p1, destsize);
@@ -576,12 +622,12 @@ size_t pathjoin(char *dest, size_t destsize, const char *p1, const char *p2)
 }
 
 /* Join any number of strings together, putting them in "dest".  The return
- * value is the length of all the strings, regardless of whether they fit in
- * destsize (including the terminating '\0').  Your list of string pointers
- * should end with a NULL to indicate the end of the list. */
+ * value is the length of all the strings, regardless of whether the null-
+ * terminated whole fits in destsize.  Your list of string pointers must end
+ * with a NULL to indicate the end of the list. */
 size_t stringjoin(char *dest, size_t destsize, ...)
 {
-       va_list ap;  
+       va_list ap;
        size_t len, ret = 0;
        const char *src;
 
@@ -605,166 +651,177 @@ 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,"/./"))) {
-                       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,"//"))) {
-                       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:
- *
- * @li 1. remove leading "/" (or replace with "." if at end)
- *
- * @li 2. remove leading ".." components (except those allowed by @p reldir)
+/* 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 3. delete any other "<dir>/.." (recursively)
+ * 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.
  *
- * Can only shrink paths, so sanitizes in place.
+ * 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).
  *
- * 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 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.
  *
- * 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.
+ * We also clean the path in a manner similar to clean_fname() but with a
+ * few differences: 
  *
- * Contributed by Dave Dykstra <dwd@bell-labs.com>
- */
-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) {
-               depth++;
-               while (*reldir) {
-                       if (*reldir++ == '/') {
-                               depth++;
-                       }
+       int rlen = 0;
+
+       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'))) {
+               if (*p == '.' && (p[1] == '/' || p[1] == '\0')) {
                        /* skip "." component */
-                       while (*++p == '/') {
-                               /* skip following slashes */
-                               ;
-                       }
+                       p++;
                        continue;
                }
-               allowdotdot = 0;
-               if ((*p == '.') && (*(p+1) == '.') &&
-                   ((*(p+2) == '/') || (*(p+2) == '\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 */
-                                       while ((sanp > start) && (*(sanp - 1) != '/')) {
+                                       while (sanp > start && sanp[-1] != '/') {
                                                /* skip back up to slash */
                                                sanp--;
                                        }
                                }
                                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';
-}
 
+       return dest;
+}
 
 char curr_dir[MAXPATHLEN];
 unsigned int curr_dir_len;
 
 /**
- * Like chdir() but can be reversed with pop_dir() if @p save is set.
- * It is also much faster as it remembers where we have been.
+ * 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(char *dir)
 {
@@ -773,7 +830,7 @@ int push_dir(char *dir)
 
        if (!initialised) {
                initialised = 1;
-               getcwd(curr_dir, sizeof(curr_dir)-1);
+               getcwd(curr_dir, sizeof curr_dir - 1);
                curr_dir_len = strlen(curr_dir);
        }
 
@@ -799,12 +856,15 @@ int push_dir(char *dir)
                curr_dir_len += len;
        }
 
-       clean_fname(curr_dir);
+       curr_dir_len = clean_fname(curr_dir, 1);
 
        return 1;
 }
 
-/** Reverse a push_dir() call */
+/**
+ * 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(char *dir)
 {
        if (chdir(dir))
@@ -817,14 +877,50 @@ int pop_dir(char *dir)
        return 1;
 }
 
+/* Return the filename, turning any non-printable characters into escaped
+ * characters (e.g. \n -> \012, \ -> \\).  This ensures that outputting it
+ * cannot generate an empty line nor corrupt the screen.  This function can
+ * return only MAX_SAFE_NAMES values at a time!  The returned value can be
+ * longer than MAXPATHLEN (because we may be trying to output an error about
+ * a too-long filename)! */
+char *safe_fname(const char *fname)
+{
+#define MAX_SAFE_NAMES 4
+       static char fbuf[MAX_SAFE_NAMES][MAXPATHLEN*2];
+       static int ndx = 0;
+       int limit = sizeof fbuf / MAX_SAFE_NAMES - 1;
+       char *t;
+
+       ndx = (ndx + 1) % MAX_SAFE_NAMES;
+       for (t = fbuf[ndx]; *fname; fname++) {
+               if (*fname == '\\') {
+                       if ((limit -= 2) < 0)
+                               break;
+                       *t++ = '\\';
+                       *t++ = '\\';
+               } else if (!isprint(*(uchar*)fname)) {
+                       if ((limit -= 4) < 0)
+                               break;
+                       sprintf(t, "\\%03o", *(uchar*)fname);
+                       t += 4;
+               } else {
+                       if (--limit < 0)
+                               break;
+                       *t++ = *fname;
+               }
+       }
+       *t = '\0';
+
+       return fbuf[ndx];
+}
+
 /**
  * Return a quoted string with the full pathname of the indicated filename.
  * The string " (in MODNAME)" may also be appended.  The returned pointer
  * remains valid until the next time full_fname() is called.
  **/
-char *full_fname(char *fn)
+char *full_fname(const char *fn)
 {
-       extern int module_id;
        static char *result = NULL;
        char *m1, *m2, *m3;
        char *p1, *p2;
@@ -832,29 +928,26 @@ char *full_fname(char *fn)
        if (result)
                free(result);
 
+       fn = safe_fname(fn);
        if (*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 = "";
 
@@ -863,6 +956,73 @@ char *full_fname(char *fn)
        return result;
 }
 
+static char partial_fname[MAXPATHLEN];
+
+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 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;
+}
+
 /** 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)
@@ -873,7 +1033,7 @@ int u_strcmp(const char *cs1, const char *cs2)
        while (*s1 && *s2 && (*s1 == *s2)) {
                s1++; s2++;
        }
-       
+
        return (int)*s1 - (int)*s2;
 }
 
@@ -909,12 +1069,13 @@ 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) {
                if (strncmp(name, "../", 3) == 0) {
-                       depth=0;
+                       depth = 0;
                } else if (strncmp(name, "./", 2) == 0) {
                        /* nothing */
                } else {
@@ -952,9 +1113,9 @@ char *timestring(time_t t)
        struct tm *tm = localtime(&t);
 
 #ifdef HAVE_STRFTIME
-       strftime(TimeBuf,sizeof(TimeBuf)-1,"%Y/%m/%d %T",tm);
+       strftime(TimeBuf, sizeof TimeBuf - 1, "%Y/%m/%d %H:%M:%S", tm);
 #else
-       strlcpy(TimeBuf, asctime(tm), sizeof(TimeBuf));
+       strlcpy(TimeBuf, asctime(tm), sizeof TimeBuf);
 #endif
 
        if (TimeBuf[strlen(TimeBuf)-1] == '\n') {
@@ -973,21 +1134,20 @@ char *timestring(time_t t)
  **/
 int msleep(int t)
 {
-       int tdiff=0;
-       struct timeval tval,t1,t2;  
+       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 + 
+               tdiff = (t2.tv_sec - t1.tv_sec)*1000 +
                        (t2.tv_usec - t1.tv_usec)/1000;
        }
 
@@ -1008,13 +1168,13 @@ int msleep(int t)
  **/
 int cmp_modtime(time_t file1, time_t file2)
 {
-       extern int modify_window;
-
        if (file2 > file1) {
-               if (file2 - file1 <= modify_window) return 0;
+               if (file2 - file1 <= modify_window)
+                       return 0;
                return -1;
        }
-       if (file1 - file2 <= modify_window) return 0;
+       if (file1 - file2 <= modify_window)
+               return 0;
        return 1;
 }
 
@@ -1033,7 +1193,7 @@ int _Insure_trap_error(int a1, int a2, int a3, int a4, int a5, int a6)
        int ret;
        char *cmd;
 
-       asprintf(&cmd, "/usr/X11R6/bin/xterm -display :0 -T Panic -n Panic -e /bin/sh -c 'cat /tmp/ierrs.*.%d ; gdb /proc/%d/exe %d'", 
+       asprintf(&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) {
@@ -1071,3 +1231,110 @@ 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];
+}