X-Git-Url: https://mattmccutchen.net/rsync/rsync.git/blobdiff_plain/5dd14f0c3388f69932d521915e039e32b9e6d970..28b519c93b6db30b6520d46f8cd65160213fddd2:/util.c diff --git a/util.c b/util.c index 7569981d..3f611d15 100644 --- 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 - * Copyright (C) 2003-2008 Wayne Davison + * Copyright (C) 2003-2009 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 @@ -25,16 +25,16 @@ #include "itypes.h" #include "inums.h" -extern int dry_run; extern int module_id; extern int modify_window; extern int relative_paths; +extern int preserve_times; extern int preserve_xattrs; +extern int preallocate_files; extern char *module_dir; extern unsigned int module_dirlen; -extern mode_t orig_umask; extern char *partial_dir; -extern struct filter_list_struct daemon_filter_list; +extern filter_rule_list daemon_filter_list; int sanitize_paths = 0; @@ -123,12 +123,11 @@ NORETURN void overflow_exit(const char *str) exit_cleanup(RERR_MALLOC); } -int set_modtime(const char *fname, time_t modtime, mode_t mode) +/* This returns 0 for success, 1 for a symlink if symlink time-setting + * is not possible, or -1 for any other error. */ +int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode) { -#if !defined HAVE_LUTIMES || !defined HAVE_UTIMES - if (S_ISLNK(mode)) - return 1; -#endif + static int switch_step = 0; if (DEBUG_GTE(TIME, 1)) { rprintf(FINFO, "set modtime of %s to (%ld) %s", @@ -136,75 +135,117 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode) asctime(localtime(&modtime))); } - if (dry_run) - return 0; + switch (switch_step) { +#ifdef HAVE_UTIMENSAT +#include "case_N.h" + if (do_utimensat(fname, modtime, mod_nsec) == 0) + break; + if (errno != ENOSYS) + return -1; + switch_step++; + /* FALLTHROUGH */ +#endif - { -#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)) { - if (lutimes(fname, t) < 0) - return errno == ENOSYS ? 1 : -1; - return 0; +#ifdef HAVE_LUTIMES +#include "case_N.h" + if (do_lutimes(fname, modtime, mod_nsec) == 0) + break; + if (errno != ENOSYS) + return -1; + switch_step++; + /* FALLTHROUGH */ +#endif + +#include "case_N.h" + switch_step++; + if (preserve_times & PRESERVE_LINK_TIMES) { + preserve_times &= ~PRESERVE_LINK_TIMES; + if (S_ISLNK(mode)) + return 1; } -# endif - return utimes(fname, t); -#elif defined HAVE_STRUCT_UTIMBUF - struct utimbuf tbuf; - tbuf.actime = time(NULL); - tbuf.modtime = modtime; - return utime(fname,&tbuf); -#elif defined HAVE_UTIME - time_t t[2]; - t[0] = time(NULL); - t[1] = modtime; - return utime(fname,t); + /* FALLTHROUGH */ + +#include "case_N.h" +#ifdef HAVE_UTIMES + if (do_utimes(fname, modtime, mod_nsec) == 0) + break; #else -#error No file-time-modification routine found! + if (do_utime(fname, modtime, mod_nsec) == 0) + break; #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; - umask(orig_umask); - ret = do_mkdir(fname, ACCESSPERMS); - umask(0); + return -1; + } - return ret; + return 0; } /* Create any necessary directories in fname. Any missing directories are - * created with default permissions. */ -int create_directory_path(char *fname) + * created with default permissions. Returns < 0 on error, or the number + * of directories created. */ +int make_path(char *fname, int flags) { - char *p; + char *end, *p; int ret = 0; - while (*fname == '/') - fname++; - while (strncmp(fname, "./", 2) == 0) + if (flags & MKP_SKIP_SLASH) { + while (*fname == '/') + fname++; + } + + while (*fname == '.' && fname[1] == '/') fname += 2; - umask(orig_umask); - p = fname; - while ((p = strchr(p,'/')) != NULL) { - *p = '\0'; - if (do_mkdir(fname, ACCESSPERMS) < 0 && errno != EEXIST) - ret = -1; - *p++ = '/'; + if (flags & MKP_DROP_NAME) { + end = strrchr(fname, '/'); + if (!end) + return 0; + *end = '\0'; + } else + end = fname + strlen(fname); + + /* Try to find an existing dir, starting from the deepest dir. */ + for (p = end; ; ) { + if (do_mkdir(fname, ACCESSPERMS) == 0) { + ret++; + break; + } + if (errno != ENOENT) { + if (errno != EEXIST) + ret = -ret - 1; + break; + } + while (1) { + if (p == fname) { + ret = -ret - 1; + goto double_break; + } + if (*--p == '/') { + if (p == fname) { + ret = -ret - 1; /* impossible... */ + goto double_break; + } + *p = '\0'; + break; + } + } } - umask(0); + double_break: + + /* Make all the dirs that we didn't find on the way here. */ + while (p != end) { + *p = '/'; + p += strlen(p); + if (ret < 0) /* Skip mkdir on error, but keep restoring the path. */ + continue; + if (do_mkdir(fname, ACCESSPERMS) < 0) + ret = -ret - 1; + else + ret++; + } + + if (flags & MKP_DROP_NAME) + *end = '/'; return ret; } @@ -270,12 +311,14 @@ static int safe_read(int desc, char *ptr, size_t len) * * This is used in conjunction with the --temp-dir, --backup, and * --copy-dest options. */ -int copy_file(const char *source, const char *dest, int ofd, - mode_t mode, int create_bak_dir) +int copy_file(const char *source, const char *dest, int ofd, mode_t mode) { int ifd; char buf[1024 * 8]; int len; /* Number of bytes read into `buf'. */ +#ifdef PREALLOCATE_NEEDS_TRUNCATE + OFF_T preallocated_len = 0, offset = 0; +#endif if ((ifd = do_open(source, O_RDONLY, 0)) < 0) { int save_errno = errno; @@ -292,23 +335,39 @@ int copy_file(const char *source, const char *dest, int ofd, return -1; } +#ifdef SUPPORT_XATTRS + if (preserve_xattrs) + mode |= S_IWUSR; +#endif + mode &= INITACCESSPERMS; 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; - } + int save_errno = errno; + rsyserr(FERROR_XFER, save_errno, "open %s", full_fname(dest)); + close(ifd); + errno = save_errno; + return -1; } } +#ifdef SUPPORT_PREALLOCATION + if (preallocate_files) { + STRUCT_STAT srcst; + + /* Try to preallocate enough space for file's eventual length. Can + * reduce fragmentation on filesystems like ext4, xfs, and NTFS. */ + if (do_fstat(ifd, &srcst) < 0) + rsyserr(FWARNING, errno, "fstat %s", full_fname(source)); + else if (srcst.st_size > 0) { + if (do_fallocate(ofd, 0, srcst.st_size) == 0) { +#ifdef PREALLOCATE_NEEDS_TRUNCATE + preallocated_len = srcst.st_size; +#endif + } else + rsyserr(FWARNING, errno, "do_fallocate %s", full_fname(dest)); + } + } +#endif + while ((len = safe_read(ifd, buf, sizeof buf)) > 0) { if (full_write(ofd, buf, len) < 0) { int save_errno = errno; @@ -318,6 +377,9 @@ int copy_file(const char *source, const char *dest, int ofd, errno = save_errno; return -1; } +#ifdef PREALLOCATE_NEEDS_TRUNCATE + offset += len; +#endif } if (len < 0) { @@ -334,6 +396,16 @@ int copy_file(const char *source, const char *dest, int ofd, full_fname(source)); } +#ifdef PREALLOCATE_NEEDS_TRUNCATE + /* Source file might have shrunk since we fstatted it. + * Cut off any extra preallocated zeros from dest file. */ + if (offset < preallocated_len && do_ftruncate(ofd, offset) < 0) { + /* If we fail to truncate, the dest file may be wrong, so we + * must trigger the "partial transfer" error. */ + rsyserr(FERROR_XFER, errno, "ftruncate %s", full_fname(dest)); + } +#endif + if (close(ofd) < 0) { int save_errno = errno; rsyserr(FERROR_XFER, errno, "close failed on %s", @@ -440,7 +512,7 @@ int robust_rename(const char *from, const char *to, const char *partialptr, return -2; to = partialptr; } - if (copy_file(from, to, -1, mode, 0) != 0) + if (copy_file(from, to, -1, mode) != 0) return -2; do_unlink(from); return 1; @@ -494,30 +566,6 @@ void kill_all(int sig) } } -/** Turn a user name into a uid */ -int name_to_uid(const char *name, uid_t *uid_p) -{ - struct passwd *pass; - if (!name || !*name) - return 0; - if (!(pass = getpwnam(name))) - return 0; - *uid_p = pass->pw_uid; - return 1; -} - -/** Turn a group name into a gid */ -int name_to_gid(const char *name, gid_t *gid_p) -{ - struct group *grp; - if (!name || !*name) - return 0; - if (!(grp = getgrnam(name))) - return 0; - *gid_p = grp->gr_gid; - return 1; -} - /** Lock a byte range in a open file */ int lock_range(int fd, int offset, int len) { @@ -806,7 +854,8 @@ int count_dir_elements(const char *p) return cnt; } -/* Turns multiple adjacent slashes into a single slash, drops all leading or +/* Turns multiple adjacent slashes into a single slash (possible exception: + * the preserving of two leading slashes at the start), 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 @@ -821,9 +870,16 @@ unsigned int clean_fname(char *name, int flags) if (!name) return 0; - if ((anchored = *f == '/') != 0) + if ((anchored = *f == '/') != 0) { *t++ = *f++; - else if (flags & CFN_KEEP_DOT_DIRS && *f == '.' && f[1] == '/') { +#ifdef __CYGWIN__ + /* If there are exactly 2 slashes at the start, preserve + * them. Would break daemon excludes unless the paths are + * really treated differently, so used this sparingly. */ + if (*f == '/' && f[1] != '/') + *t++ = *f++; +#endif + } else if (flags & CFN_KEEP_DOT_DIRS && *f == '.' && f[1] == '/') { *t++ = *f++; *t++ = *f++; } @@ -979,7 +1035,10 @@ int change_dir(const char *dir, int set_path_only) if (!initialised) { initialised = 1; - getcwd(curr_dir, sizeof curr_dir - 1); + if (getcwd(curr_dir, sizeof curr_dir - 1) == NULL) { + rsyserr(FERROR, errno, "getcwd()"); + exit_cleanup(RERR_FILESELECT); + } curr_dir_len = strlen(curr_dir); } @@ -1025,6 +1084,34 @@ int change_dir(const char *dir, int set_path_only) return 1; } +/* This will make a relative path absolute and clean it up via clean_fname(). + * Returns the string, which might be newly allocated, or NULL on error. */ +char *normalize_path(char *path, BOOL force_newbuf, unsigned int *len_ptr) +{ + unsigned int len; + + if (*path != '/') { /* Make path absolute. */ + int len = strlen(path); + if (curr_dir_len + 1 + len >= sizeof curr_dir) + return NULL; + curr_dir[curr_dir_len] = '/'; + memcpy(curr_dir + curr_dir_len + 1, path, len + 1); + if (!(path = strdup(curr_dir))) + out_of_memory("normalize_path"); + curr_dir[curr_dir_len] = '\0'; + } else if (force_newbuf) { + if (!(path = strdup(path))) + out_of_memory("normalize_path"); + } + + len = clean_fname(path, CFN_COLLAPSE_DOT_DOT_DIRS | CFN_DROP_TRAILING_DOT_DIR); + + if (len_ptr) + *len_ptr = len; + + return path; +} + /** * Return a quoted string with the full pathname of the indicated filename. * The string " (in MODNAME)" may also be appended. The returned pointer @@ -1129,12 +1216,13 @@ int handle_partial_dir(const char *fname, int create) return 1; } -/** - * Determine if a symlink points outside the current directory tree. +/* Determine if a symlink points outside the current directory tree. * This is considered "unsafe" because e.g. when mirroring somebody * else's machine it might allow them to establish a symlink to * /etc/passwd, and then read it through a web server. * + * Returns 1 if unsafe, 0 if safe. + * * Null symlinks and absolute symlinks are always unsafe. * * Basically here we are concerned with symlinks whose target contains @@ -1142,17 +1230,11 @@ int handle_partial_dir(const char *fname, int create) * transferred directory. We are not allowed to go back up and * reenter. * - * @param dest Target of the symlink in question. + * "dest" is the target of the symlink in question. * - * @param src Top source directory currently applicable. Basically this - * is the first parameter to rsync in a simple invocation, but it's - * modified by flist.c in slightly complex ways. - * - * @retval True if unsafe - * @retval False is unsafe - * - * @sa t_unsafe.c - **/ + * "src" is the top source directory currently applicable at the level + * of the referenced symlink. This is usually the symlink's full path + * (including its name), as referenced from the root of the transfer. */ int unsafe_symlink(const char *dest, const char *src) { const char *name, *slash; @@ -1164,33 +1246,33 @@ int unsafe_symlink(const char *dest, const char *src) /* find out what our safety margin is */ for (name = src; (slash = strchr(name, '/')) != 0; name = slash+1) { - if (strncmp(name, "../", 3) == 0) { - depth = 0; - } else if (strncmp(name, "./", 2) == 0) { - /* nothing */ - } else { + /* ".." segment starts the count over. "." segment is ignored. */ + if (*name == '.' && (name[1] == '/' || (name[1] == '.' && name[2] == '/'))) { + if (name[1] == '.') + depth = 0; + } else depth++; - } + while (slash[1] == '/') slash++; /* just in case src isn't clean */ } - if (strcmp(name, "..") == 0) + if (*name == '.' && name[1] == '.' && name[2] == '\0') depth = 0; for (name = dest; (slash = strchr(name, '/')) != 0; name = slash+1) { - if (strncmp(name, "../", 3) == 0) { - /* if at any point we go outside the current directory - then stop - it is unsafe */ - if (--depth < 0) - return 1; - } else if (strncmp(name, "./", 2) == 0) { - /* nothing */ - } else { + if (*name == '.' && (name[1] == '/' || (name[1] == '.' && name[2] == '/'))) { + if (name[1] == '.') { + /* if at any point we go outside the current directory + then stop - it is unsafe */ + if (--depth < 0) + return 1; + } + } else depth++; - } + while (slash[1] == '/') slash++; } - if (strcmp(name, "..") == 0) + if (*name == '.' && name[1] == '.' && name[2] == '\0') depth--; - return (depth < 0); + return depth < 0; } /* Return the date and time as a string. Some callers tweak returned buf. */ @@ -1377,11 +1459,11 @@ const char *find_filename_suffix(const char *fn, int fn_len, int *len_ptr) #define UNIT (1 << 16) -uint32 fuzzy_distance(const char *s1, int len1, const char *s2, int len2) +uint32 fuzzy_distance(const char *s1, unsigned len1, const char *s2, unsigned len2) { uint32 a[MAXPATHLEN], diag, above, left, diag_inc, above_inc, left_inc; int32 cost; - int i1, i2; + unsigned i1, i2; if (!len1 || !len2) { if (!len1) { @@ -1512,6 +1594,39 @@ int bitbag_next_bit(struct bitbag *bb, int after) return -1; } +void flist_ndx_push(flist_ndx_list *lp, int ndx) +{ + struct flist_ndx_item *item; + + if (!(item = new(struct flist_ndx_item))) + out_of_memory("flist_ndx_push"); + item->next = NULL; + item->ndx = ndx; + if (lp->tail) + lp->tail->next = item; + else + lp->head = item; + lp->tail = item; +} + +int flist_ndx_pop(flist_ndx_list *lp) +{ + struct flist_ndx_item *next; + int ndx; + + if (!lp->head) + return -1; + + ndx = lp->head->ndx; + next = lp->head->next; + free(lp->head); + lp->head = next; + if (!next) + lp->tail = NULL; + + return ndx; +} + void *expand_item_list(item_list *lp, size_t item_size, const char *desc, int incr) {