Preserve the right errno value when trying adjunct functions during
[rsync/rsync.git] / util.c
diff --git a/util.c b/util.c
index ad29be3..c437517 100644 (file)
--- a/util.c
+++ b/util.c
@@ -275,38 +275,54 @@ int copy_file(const char *source, const char *dest, int ofd,
        int len;   /* Number of bytes read into `buf'. */
 
        if ((ifd = do_open(source, O_RDONLY, 0)) < 0) {
+               int save_errno = errno;
                rsyserr(FERROR_XFER, errno, "open %s", full_fname(source));
+               errno = save_errno;
                return -1;
        }
 
        if (ofd < 0) {
                if (robust_unlink(dest) && errno != ENOENT) {
+                       int save_errno = errno;
                        rsyserr(FERROR_XFER, errno, "unlink %s", full_fname(dest));
+                       errno = save_errno;
                        return -1;
                }
 
-               if ((ofd = do_open(dest, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode)) < 0
-                && (!create_bak_dir || errno != ENOENT || make_bak_dir(dest) < 0
-                 || (ofd = do_open(dest, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode)) < 0)) {
-                       rsyserr(FERROR_XFER, errno, "open %s", full_fname(dest));
-                       close(ifd);
-                       return -1;
+               if ((ofd = do_open(dest, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode)) < 0) {
+                       int save_errno = errno ? errno : EINVAL; /* 0 paranoia */
+                       if (create_bak_dir && errno == ENOENT && make_bak_dir(dest) == 0) {
+                               if ((ofd = do_open(dest, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode)) < 0)
+                                       save_errno = errno ? errno : save_errno;
+                               else
+                                       save_errno = 0;
+                       }
+                       if (save_errno) {
+                               rsyserr(FERROR_XFER, save_errno, "open %s", full_fname(dest));
+                               close(ifd);
+                               errno = save_errno;
+                               return -1;
+                       }
                }
        }
 
        while ((len = safe_read(ifd, buf, sizeof buf)) > 0) {
                if (full_write(ofd, buf, len) < 0) {
+                       int save_errno = errno;
                        rsyserr(FERROR_XFER, errno, "write %s", full_fname(dest));
                        close(ifd);
                        close(ofd);
+                       errno = save_errno;
                        return -1;
                }
        }
 
        if (len < 0) {
+               int save_errno = errno;
                rsyserr(FERROR_XFER, errno, "read %s", full_fname(source));
                close(ifd);
                close(ofd);
+               errno = save_errno;
                return -1;
        }
 
@@ -316,8 +332,10 @@ int copy_file(const char *source, const char *dest, int ofd,
        }
 
        if (close(ofd) < 0) {
+               int save_errno = errno;
                rsyserr(FERROR_XFER, errno, "close failed on %s",
                        full_fname(dest));
+               errno = save_errno;
                return -1;
        }
 
@@ -401,8 +419,11 @@ int robust_rename(const char *from, const char *to, const char *partialptr,
                switch (errno) {
 #ifdef ETXTBSY
                case ETXTBSY:
-                       if (robust_unlink(to) != 0)
+                       if (robust_unlink(to) != 0) {
+                               errno = ETXTBSY;
                                return -1;
+                       }
+                       errno = ETXTBSY;
                        break;
 #endif
                case EXDEV:
@@ -504,14 +525,16 @@ int lock_range(int fd, int offset, int len)
 }
 
 #define ENSURE_MEMSPACE(buf, type, sz, req) \
-       if ((req) >= sz && !(buf = realloc_array(buf, type, sz *= 2))) \
-               out_of_memory("ENSURE_MEMSPACE")
+       if ((req) > sz && !(buf = realloc_array(buf, type, sz = MAX(sz * 2, req)))) \
+               out_of_memory("glob_expand")
 
 static inline void call_glob_match(const char *name, int len, int from_glob,
                                   char *arg, int abpos, int fbpos);
 
-static char *arg_buf, *filt_buf, **glob_argv;
-static int absize, fbsize, glob_maxargs, glob_argc;
+static struct glob_data {
+       char *arg_buf, *filt_buf, **argv;
+       int absize, fbsize, maxargs, argc;
+} glob;
 
 static void glob_match(char *arg, int abpos, int fbpos)
 {
@@ -520,15 +543,14 @@ static void glob_match(char *arg, int abpos, int fbpos)
 
        while (*arg == '.' && arg[1] == '/') {
                if (fbpos < 0) {
-                       if (fbsize < absize)
-                               filt_buf = realloc_array(filt_buf, char, fbsize = absize);
-                       memcpy(filt_buf, arg_buf, abpos + 1);
+                       ENSURE_MEMSPACE(glob.filt_buf, char, glob.fbsize, glob.absize);
+                       memcpy(glob.filt_buf, glob.arg_buf, abpos + 1);
                        fbpos = abpos;
                }
-               ENSURE_MEMSPACE(arg_buf, char, absize, abpos + 2);
-               arg_buf[abpos++] = *arg++;
-               arg_buf[abpos++] = *arg++;
-               arg_buf[abpos] = '\0';
+               ENSURE_MEMSPACE(glob.arg_buf, char, glob.absize, abpos + 3);
+               glob.arg_buf[abpos++] = *arg++;
+               glob.arg_buf[abpos++] = *arg++;
+               glob.arg_buf[abpos] = '\0';
        }
        if ((slash = strchr(arg, '/')) != NULL) {
                *slash = '\0';
@@ -539,7 +561,7 @@ static void glob_match(char *arg, int abpos, int fbpos)
                struct dirent *di;
                DIR *d;
 
-               if (!(d = opendir(abpos ? arg_buf : ".")))
+               if (!(d = opendir(abpos ? glob.arg_buf : ".")))
                        return;
                while ((di = readdir(d)) != NULL) {
                        char *dname = d_name(di);
@@ -567,63 +589,65 @@ static inline void call_glob_match(const char *name, int len, int from_glob,
 {
        char *use_buf;
 
-       ENSURE_MEMSPACE(arg_buf, char, absize, abpos + len + 2);
-       memcpy(arg_buf + abpos, name, len);
+       ENSURE_MEMSPACE(glob.arg_buf, char, glob.absize, abpos + len + 2);
+       memcpy(glob.arg_buf + abpos, name, len);
        abpos += len;
-       arg_buf[abpos] = '\0';
+       glob.arg_buf[abpos] = '\0';
 
        if (fbpos >= 0) {
-               ENSURE_MEMSPACE(filt_buf, char, fbsize, fbpos + len + 2);
-               memcpy(filt_buf + fbpos, name, len);
+               ENSURE_MEMSPACE(glob.filt_buf, char, glob.fbsize, fbpos + len + 2);
+               memcpy(glob.filt_buf + fbpos, name, len);
                fbpos += len;
-               filt_buf[fbpos] = '\0';
-               use_buf = filt_buf;
+               glob.filt_buf[fbpos] = '\0';
+               use_buf = glob.filt_buf;
        } else
-               use_buf = arg_buf;
+               use_buf = glob.arg_buf;
 
-       if (from_glob || arg) {
+       if (from_glob || (arg && len)) {
                STRUCT_STAT st;
                int is_dir;
 
-               if (do_stat(arg_buf, &st) != 0) {
-                       if (from_glob)
-                               return;
-                       is_dir = 0;
-               } else {
-                       is_dir = S_ISDIR(st.st_mode) != 0;
-                       if (arg && !is_dir)
-                               return;
-               }
+               if (do_stat(glob.arg_buf, &st) != 0)
+                       return;
+               is_dir = S_ISDIR(st.st_mode) != 0;
+               if (arg && !is_dir)
+                       return;
 
                if (daemon_filter_list.head
-                && check_filter(&daemon_filter_list, use_buf, is_dir) < 0) {
-                       if (from_glob)
-                               return;
-                       arg = NULL;
-               }
+                && check_filter(&daemon_filter_list, FLOG, use_buf, is_dir) < 0)
+                       return;
        }
 
        if (arg) {
-               arg_buf[abpos++] = '/';
-               arg_buf[abpos] = '\0';
+               glob.arg_buf[abpos++] = '/';
+               glob.arg_buf[abpos] = '\0';
                if (fbpos >= 0) {
-                       filt_buf[fbpos++] = '/';
-                       filt_buf[fbpos] = '\0';
+                       glob.filt_buf[fbpos++] = '/';
+                       glob.filt_buf[fbpos] = '\0';
                }
                glob_match(arg, abpos, fbpos);
        } else {
-               ENSURE_MEMSPACE(glob_argv, char *, glob_maxargs, glob_argc + 1);
-               if (!(glob_argv[glob_argc++] = strdup(arg_buf)))
+               ENSURE_MEMSPACE(glob.argv, char *, glob.maxargs, glob.argc + 1);
+               if (!(glob.argv[glob.argc++] = strdup(glob.arg_buf)))
                        out_of_memory("glob_match");
        }
 }
 
 /* This routine performs wild-card expansion of the pathname in "arg".  Any
- * daemon-excluded files/dirs will not be matched by the wildcards. */
-void glob_expand(const char *arg, char ***argv_p, int *argc_p, int *maxargs_p)
+ * daemon-excluded files/dirs will not be matched by the wildcards.  Returns 0
+ * if a wild-card string is the only returned item (due to matching nothing). */
+int glob_expand(const char *arg, char ***argv_p, int *argc_p, int *maxargs_p)
 {
+       int ret, save_argc;
        char *s;
-       int save_argc = *argc_p;
+
+       if (!arg) {
+               if (glob.filt_buf)
+                       free(glob.filt_buf);
+               free(glob.arg_buf);
+               memset(&glob, 0, sizeof glob);
+               return -1;
+       }
 
        if (sanitize_paths)
                s = sanitize_path(NULL, arg, "", 0, SP_KEEP_DOT_DIRS);
@@ -636,36 +660,32 @@ void glob_expand(const char *arg, char ***argv_p, int *argc_p, int *maxargs_p)
                             | CFN_COLLAPSE_DOT_DOT_DIRS);
        }
 
-       if (!(arg_buf = new_array(char, absize = MAXPATHLEN)))
-               out_of_memory("glob_expand");
-       *arg_buf = '\0';
-       filt_buf = NULL;
-       fbsize = 0;
+       ENSURE_MEMSPACE(glob.arg_buf, char, glob.absize, MAXPATHLEN);
+       *glob.arg_buf = '\0';
 
-       glob_argc = *argc_p;
-       glob_argv = *argv_p;
-       glob_maxargs = *maxargs_p;
+       glob.argc = save_argc = *argc_p;
+       glob.argv = *argv_p;
+       glob.maxargs = *maxargs_p;
 
-       if (glob_maxargs < MAX_ARGS
-        && !(glob_argv = realloc_array(glob_argv, char *, glob_maxargs = MAX_ARGS)))
-               out_of_memory("glob_expand");
+       ENSURE_MEMSPACE(glob.argv, char *, glob.maxargs, 100);
 
        glob_match(s, 0, -1);
 
        /* The arg didn't match anything, so add the failed arg to the list. */
-       if (glob_argc == save_argc) {
-               ENSURE_MEMSPACE(glob_argv, char *, glob_maxargs, glob_argc + 1);
-               glob_argv[glob_argc++] = s;
-       } else
+       if (glob.argc == save_argc) {
+               ENSURE_MEMSPACE(glob.argv, char *, glob.maxargs, glob.argc + 1);
+               glob.argv[glob.argc++] = s;
+               ret = 0;
+       } else {
                free(s);
+               ret = 1;
+       }
 
-       *maxargs_p = glob_maxargs;
-       *argv_p = glob_argv;
-       *argc_p = glob_argc;
+       *maxargs_p = glob.maxargs;
+       *argv_p = glob.argv;
+       *argc_p = glob.argc;
 
-       if (filt_buf)
-               free(filt_buf);
-       free(arg_buf);
+       return ret;
 }
 
 /* This routine is only used in daemon mode. */
@@ -944,7 +964,7 @@ char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth,
 /* Like chdir(), but it keeps track of the current directory (in the
  * global "curr_dir"), and ensures that the path size doesn't overflow.
  * Also cleans the path using the clean_fname() function. */
-int push_dir(const char *dir, int set_path_only)
+int change_dir(const char *dir, int set_path_only)
 {
        static int initialised;
        unsigned int len;
@@ -962,21 +982,26 @@ int push_dir(const char *dir, int set_path_only)
        if (len == 1 && *dir == '.')
                return 1;
 
-       if ((*dir == '/' ? len : curr_dir_len + 1 + len) >= sizeof curr_dir) {
-               errno = ENAMETOOLONG;
-               return 0;
-       }
-
-       if (!set_path_only && chdir(dir))
-               return 0;
-
        if (*dir == '/') {
+               if (len >= sizeof curr_dir) {
+                       errno = ENAMETOOLONG;
+                       return 0;
+               }
+               if (!set_path_only && chdir(dir))
+                       return 0;
                memcpy(curr_dir, dir, len + 1);
-               curr_dir_len = len;
        } else {
-               curr_dir[curr_dir_len++] = '/';
-               memcpy(curr_dir + curr_dir_len, dir, len + 1);
-               curr_dir_len += len;
+               if (curr_dir_len + 1 + len >= sizeof curr_dir) {
+                       errno = ENAMETOOLONG;
+                       return 0;
+               }
+               curr_dir[curr_dir_len] = '/';
+               memcpy(curr_dir + curr_dir_len + 1, dir, len + 1);
+
+               if (!set_path_only && chdir(curr_dir)) {
+                       curr_dir[curr_dir_len] = '\0';
+                       return 0;
+               }
        }
 
        curr_dir_len = clean_fname(curr_dir, CFN_COLLAPSE_DOT_DOT_DIRS);
@@ -987,28 +1012,7 @@ int push_dir(const char *dir, int set_path_only)
        }
 
        if (verbose >= 5 && !set_path_only)
-               rprintf(FINFO, "[%s] push_dir(%s)\n", who_am_i(), curr_dir);
-
-       return 1;
-}
-
-/**
- * Reverse a push_dir() call.  You must pass in an absolute path
- * that was copied from a prior value of "curr_dir".
- **/
-int pop_dir(const char *dir)
-{
-       if (chdir(dir))
-               return 0;
-
-       curr_dir_len = strlcpy(curr_dir, dir, sizeof curr_dir);
-       if (curr_dir_len >= sizeof curr_dir)
-               curr_dir_len = sizeof curr_dir - 1;
-       if (sanitize_paths)
-               curr_dir_depth = count_dir_elements(curr_dir + module_dirlen);
-
-       if (verbose >= 5)
-               rprintf(FINFO, "[%s] pop_dir(%s)\n", who_am_i(), curr_dir);
+               rprintf(FINFO, "[%s] change_dir(%s)\n", who_am_i(), curr_dir);
 
        return 1;
 }
@@ -1071,10 +1075,10 @@ char *partial_dir_fname(const char *fname)
        if (daemon_filter_list.head) {
                t = strrchr(partial_fname, '/');
                *t = '\0';
-               if (check_filter(&daemon_filter_list, partial_fname, 1) < 0)
+               if (check_filter(&daemon_filter_list, FLOG, partial_fname, 1) < 0)
                        return NULL;
                *t = '/';
-               if (check_filter(&daemon_filter_list, partial_fname, 0) < 0)
+               if (check_filter(&daemon_filter_list, FLOG, partial_fname, 0) < 0)
                        return NULL;
        }
 
@@ -1346,7 +1350,7 @@ void *_new_array(unsigned long num, unsigned int size, int use_calloc)
        return use_calloc ? calloc(num, size) : malloc(num * size);
 }
 
-void *_realloc_array(void *ptr, unsigned int size, unsigned long num)
+void *_realloc_array(void *ptr, unsigned int size, size_t num)
 {
        if (num >= MALLOC_MAX/size)
                return NULL;
@@ -1567,7 +1571,10 @@ void *expand_item_list(item_list *lp, size_t item_size,
                        new_size += incr;
                else
                        new_size *= 2;
-               new_ptr = realloc_array(lp->items, char, new_size * item_size);
+               if (new_size < lp->malloced)
+                       overflow_exit("expand_item_list");
+               /* Using _realloc_array() lets us pass the size, not a type. */
+               new_ptr = _realloc_array(lp->items, item_size, new_size);
                if (verbose >= 4) {
                        rprintf(FINFO, "[%s] expand %s to %.0f bytes, did%s move\n",
                                who_am_i(), desc, (double)new_size * item_size,