Some improvements to the file-name cleaning code:
[rsync/rsync.git] / util.c
diff --git a/util.c b/util.c
index 477f5bc..a8b17d5 100644 (file)
--- 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 <mbp@samba.org>
- * Copyright (C) 2003-2007 Wayne Davison
+ * Copyright (C) 2003-2008 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
@@ -33,7 +33,7 @@ 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 struct filter_list_struct daemon_filter_list;
 
 int sanitize_paths = 0;
 
@@ -147,8 +147,8 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
                t[1].tv_usec = 0;
 # ifdef HAVE_LUTIMES
                if (S_ISLNK(mode)) {
-                       if (lutimes(fname, t) < 0 && errno != ENOSYS)
-                               return -1;
+                       if (lutimes(fname, t) < 0)
+                               return errno == ENOSYS ? 1 : -1;
                        return 0;
                }
 # endif
@@ -503,14 +503,13 @@ int lock_range(int fd, int offset, int len)
        return fcntl(fd,F_SETLK,&lock) == 0;
 }
 
-static int filter_server_path(char *arg)
+static int filter_daemon_path(char *arg)
 {
-       char *s;
-
-       if (server_filter_list.head) {
+       if (daemon_filter_list.head) {
+               char *s;
                for (s = arg; (s = strchr(s, '/')) != NULL; ) {
                        *s = '\0';
-                       if (check_filter(&server_filter_list, arg, 1) < 0) {
+                       if (check_filter(&daemon_filter_list, arg, 1) < 0) {
                                /* We must leave arg truncated! */
                                return 1;
                        }
@@ -525,6 +524,8 @@ void glob_expand(char *s, char ***argv_ptr, int *argc_ptr, int *maxargs_ptr)
        char **argv = *argv_ptr;
        int argc = *argc_ptr;
        int maxargs = *maxargs_ptr;
+       int count, have_glob_results;
+
 #if !defined HAVE_GLOB || !defined HAVE_GLOB_H
        if (argc == maxargs) {
                maxargs += MAX_ARGS;
@@ -536,7 +537,7 @@ void glob_expand(char *s, char ***argv_ptr, int *argc_ptr, int *maxargs_ptr)
        if (!*s)
                s = ".";
        s = argv[argc++] = strdup(s);
-       filter_server_path(s);
+       filter_daemon_path(s);
 #else
        glob_t globbuf;
 
@@ -546,32 +547,63 @@ void glob_expand(char *s, char ***argv_ptr, int *argc_ptr, int *maxargs_ptr)
                s = ".";
 
        if (sanitize_paths)
-               s = sanitize_path(NULL, s, "", 0);
-       else
+               s = sanitize_path(NULL, s, "", 0, SP_KEEP_DOT_DIRS);
+       else {
                s = strdup(s);
-       if (!s)
-               out_of_memory("glob_expand");
+               if (!s)
+                       out_of_memory("glob_expand");
+               clean_fname(s, CFN_KEEP_DOT_DIRS
+                            | CFN_KEEP_TRAILING_SLASH
+                            | CFN_COLLAPSE_DOT_DOT_DIRS);
+       }
 
        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;
+       glob(s, 0, NULL, &globbuf);
+       /* Note: we check the first match against the filter list,
+        * just in case the user specified a wildcard in the path. */
+       if ((count = globbuf.gl_pathc) > 0) {
+               if (filter_daemon_path(globbuf.gl_pathv[0])) {
+                       int slashes = 0;
+                       char *cp;
+                       /* Truncate original arg at glob's truncation point. */
+                       for (cp = globbuf.gl_pathv[0]; *cp; cp++) {
+                               if (*cp == '/')
+                                       slashes++;
+                       }
+                       for (cp = s; *cp; cp++) {
+                               if (*cp == '/') {
+                                       if (slashes-- <= 0) {
+                                               *cp = '\0';
+                                               break;
+                                       }
+                               }
+                       }
+                       have_glob_results = 0;
+                       count = 1;
+               } else
+                       have_glob_results = 1;
+       } else {
+               /* This truncates "s" at a filtered element, if present. */
+               filter_daemon_path(s);
+               have_glob_results = 0;
+               count = 1;
+       }
+       if (count + argc > maxargs) {
+               maxargs += count + MAX_ARGS;
                if (!(argv = realloc_array(argv, char *, maxargs)))
                        out_of_memory("glob_expand");
                *argv_ptr = argv;
                *maxargs_ptr = maxargs;
        }
-       if (globbuf.gl_pathc == 0)
-               argv[argc++] = s;
-       else {
+       if (have_glob_results) {
                int i;
                free(s);
-               for (i = 0; i < (int)globbuf.gl_pathc; i++) {
+               for (i = 0; i < count; i++) {
                        if (!(argv[argc++] = strdup(globbuf.gl_pathv[i])))
                                out_of_memory("glob_expand");
                }
-       }
+       } else
+               argv[argc++] = s;
        globfree(&globbuf);
 #endif
        *argc_ptr = argc;
@@ -687,13 +719,13 @@ 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, 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;
@@ -704,7 +736,7 @@ unsigned int clean_fname(char *name, int flags)
 
        if ((anchored = *f == '/') != 0)
                *t++ = *f++;
-       else if (flags & CFN_KEEP_LEADING_DOT_DIR && *f == '.' && f[1] == '/') {
+       else if (flags & CFN_KEEP_DOT_DIRS && *f == '.' && f[1] == '/') {
                *t++ = *f++;
                *t++ = *f++;
        }
@@ -716,7 +748,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 +805,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 +832,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 +860,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;
                        }
@@ -895,8 +927,8 @@ int push_dir(const char *dir, int set_path_only)
                curr_dir_depth = count_dir_elements(curr_dir + module_dirlen);
        }
 
-       if (verbose >= 5)
-               rprintf(FINFO, "[%s] dir is now %s\n", who_am_i(), curr_dir);
+       if (verbose >= 5 && !set_path_only)
+               rprintf(FINFO, "[%s] push_dir(%s)\n", who_am_i(), curr_dir);
 
        return 1;
 }
@@ -916,6 +948,9 @@ int pop_dir(const char *dir)
        if (sanitize_paths)
                curr_dir_depth = count_dir_elements(curr_dir + module_dirlen);
 
+       if (verbose >= 5)
+               rprintf(FINFO, "[%s] pop_dir(%s)\n", who_am_i(), curr_dir);
+
        return 1;
 }
 
@@ -974,13 +1009,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, partial_fname, 1) < 0)
                        return NULL;
                *t = '/';
-               if (check_filter(&server_filter_list, partial_fname, 0) < 0)
+               if (check_filter(&daemon_filter_list, partial_fname, 0) < 0)
                        return NULL;
        }