If there is no lchown(), don't try to set the user & group of a symlink.
[rsync/rsync.git] / util.c
diff --git a/util.c b/util.c
index a878720..4f1b59a 100644 (file)
--- a/util.c
+++ b/util.c
@@ -197,7 +197,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;
 
@@ -245,7 +245,7 @@ static int safe_read(int desc, char *ptr, size_t len)
 
 /** Copy a file.
  *
- * This is used in conjunction with the --temp-dir option */
+ * This is used in conjunction with the --temp-dir and --backup options */
 int copy_file(char *source, char *dest, mode_t mode)
 {
        int ifd;
@@ -362,8 +362,8 @@ 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. */
+/* 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)
 {
        int tries = 4;
@@ -383,7 +383,7 @@ int robust_rename(char *from, char *to, int mode)
                        if (copy_file(from, to, mode) != 0)
                                return -2;
                        do_unlink(from);
-                       return 0;
+                       return 1;
                default:
                        return -1;
                }
@@ -524,7 +524,7 @@ static void glob_expand_one(char *s, char ***argv_ptr, int *argc_ptr,
                s = ".";
 
        if (sanitize_paths)
-               s = sanitize_path(NULL, s, NULL);
+               s = sanitize_path(NULL, s, "", 0);
        else
                s = strdup(s);
 
@@ -650,9 +650,27 @@ size_t stringjoin(char *dest, size_t destsize, ...)
        return ret;
 }
 
-unsigned int clean_fname(char *name)
+int count_dir_elements(const char *p)
 {
-       char *limit = name, *t = name, *f = name;
+       int cnt = 0, new_component = 1;
+       while (*p) {
+               if (*p++ == '/')
+                       new_component = 1;
+               else if (new_component) {
+                       new_component = 0;
+                       cnt++;
+               }
+       }
+       return cnt;
+}
+
+/* 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;
 
        if (!name)
@@ -669,25 +687,24 @@ unsigned int clean_fname(char *name)
                if (*f == '.') {
                        /* discard "." dirs (but NOT a trailing '.'!) */
                        if (f[1] == '/') {
-                               f++; /* not += 2! */
+                               f += 2;
                                continue;
                        }
                        /* collapse ".." dirs */
-                       if (f[1] == '.' && (f[2] == '/' || !f[2])) {
+                       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 == '/') {
+                               if (s != t - 1 && (s < name || *s == '/')) {
                                        t = s + 1;
                                        f += 2;
                                        continue;
                                }
-                               *t++ = *f++;
-                               *t++ = *f++;
-                               limit = t;
+                               limit = t + 2;
                        }
                }
                while (*f && (*t++ = *f++) != '/') {}
@@ -706,37 +723,36 @@ unsigned int clean_fname(char *name)
  * "/" (either removing it or expanding it) and any leading or embedded
  * ".." components that attempt to escape past the module's top dir.
  *
- * If dest is NULL, a buffer is allocated to hold the result.  If dest is
- * the same buffer as p (the path) OR if reldir is NULL, a leading slash
- * is dropped instead of being expanded to be the module's top dir.
+ * 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.
  *
- * If reldir is non-NULL (and non-empty), it is a sanitized directory that
- * the path will be relative to, so allow as many '..'s 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.
+ * 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 the resulting path would be empty, change it into ".".
- */
-char *sanitize_path(char *dest, const char *p, const char *reldir)
+ * We also clean the path in a manner similar to clean_fname() but with a
+ * few differences: 
+ *
+ * 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;
        int rlen = 0;
 
        if (dest != p) {
                int plen = strlen(p);
-               if (*p == '/' && reldir) {
-                       rlen = strlen(lp_path(module_id));
-                       reldir = NULL;
+               if (*p == '/') {
+                       if (!rootdir)
+                               rootdir = lp_path(module_id);
+                       rlen = strlen(rootdir);
+                       depth = 0;
                        p++;
                }
                if (dest) {
@@ -745,53 +761,32 @@ char *sanitize_path(char *dest, const char *p, const char *reldir)
                } else if (!(dest = new_array(char, rlen + plen + 1)))
                        out_of_memory("sanitize_path");
                if (rlen) {
-                       memcpy(dest, lp_path(module_id), rlen);
+                       memcpy(dest, rootdir, rlen);
                        if (rlen > 1)
                                dest[rlen++] = '/';
                }
        }
 
-       if (reldir) {
-               int new_component = 1;
-               while (*reldir) {
-                       if (*reldir++ == '/')
-                               new_component = 1;
-                       else if (new_component) {
-                               new_component = 0;
-                               depth++;
-                       }
-               }
-       }
-
        start = sanp = dest + rlen;
-       while (*p == '/') {
-               /* remove leading slashes */
-               p++;
-       }
        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 */
-                               ;
-                       }
+                       p++;
                        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 */
@@ -802,22 +797,13 @@ char *sanitize_path(char *dest, const char *p, const 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 == dest) {
                /* ended up with nothing, so put in "." component */
@@ -869,7 +855,7 @@ int push_dir(char *dir)
                curr_dir_len += len;
        }
 
-       curr_dir_len = clean_fname(curr_dir);
+       curr_dir_len = clean_fname(curr_dir, 1);
 
        return 1;
 }