diff options
author | Florian Obser <florian@cvs.openbsd.org> | 2019-02-16 10:46:23 +0000 |
---|---|---|
committer | Florian Obser <florian@cvs.openbsd.org> | 2019-02-16 10:46:23 +0000 |
commit | 603c710d890537089cde0824b4af8935d3960296 (patch) | |
tree | 46fb35b290c8cca86b11770bd809fe27266a8acd /usr.bin/rsync | |
parent | ffb81903474e9f6ce0fe506577905da85f07f179 (diff) |
Introduce mkstempat(), a variation on mkstemp(3) and mkstemplinkat().
mkstempat() works exactly like mkstemp(3) except that it replaces
open(2) with openat(2) so that it can be used in rsync_downloader()
to easily deal with relative paths.
mkstemplinkat() works somewhat like mkdtemp() to create a template
symlink.
Use the mkstemplinkat() to create or update symlinks and overwrite
existing objects including empty directories that might exist under
the destination name.
"you snooze, you get collisions" deraadt@
Diffstat (limited to 'usr.bin/rsync')
-rw-r--r-- | usr.bin/rsync/Makefile | 4 | ||||
-rw-r--r-- | usr.bin/rsync/downloader.c | 43 | ||||
-rw-r--r-- | usr.bin/rsync/extern.h | 6 | ||||
-rw-r--r-- | usr.bin/rsync/mktemp.c | 171 | ||||
-rw-r--r-- | usr.bin/rsync/uploader.c | 101 |
5 files changed, 254 insertions, 71 deletions
diff --git a/usr.bin/rsync/Makefile b/usr.bin/rsync/Makefile index 5b3aee20532..887858031fe 100644 --- a/usr.bin/rsync/Makefile +++ b/usr.bin/rsync/Makefile @@ -1,8 +1,8 @@ -# $OpenBSD: Makefile,v 1.5 2019/02/13 05:41:35 tb Exp $ +# $OpenBSD: Makefile,v 1.6 2019/02/16 10:46:22 florian Exp $ PROG= rsync SRCS= blocks.c child.c client.c downloader.c fargs.c flist.c hash.c ids.c \ - io.c log.c mkpath.c receiver.c sender.c server.c session.c \ + io.c log.c mkpath.c mktemp.c receiver.c sender.c server.c session.c \ socket.c symlinks.c uploader.c main.c LDADD+= -lcrypto -lm DPADD+= ${LIBCRYPTO} ${LIBM} diff --git a/usr.bin/rsync/downloader.c b/usr.bin/rsync/downloader.c index 62914437607..03b5452845c 100644 --- a/usr.bin/rsync/downloader.c +++ b/usr.bin/rsync/downloader.c @@ -1,4 +1,4 @@ -/* $Id: downloader.c,v 1.9 2019/02/14 18:29:08 florian Exp $ */ +/* $Id: downloader.c,v 1.10 2019/02/16 10:46:22 florian Exp $ */ /* * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv> * @@ -297,10 +297,8 @@ int rsync_downloader(struct download *p, struct sess *sess, int *ofd) { int32_t idx, rawtok; - uint32_t hash; const struct flist *f; - size_t sz, dirlen, tok; - const char *cp; + size_t sz, tok; mode_t perm; struct stat st; char *buf = NULL; @@ -415,32 +413,19 @@ rsync_downloader(struct download *p, struct sess *sess, int *ofd) *ofd = -1; - /* - * Create the temporary file. - * Use a simple scheme of path/.FILE.RANDOM, where we - * fill in RANDOM with an arc4random number. - * The tricky part is getting into the directory if - * we're in recursive mode. - */ + /* Create the temporary file. */ - hash = arc4random(); - if (sess->opts->recursive && - NULL != (cp = strrchr(f->path, '/'))) { - dirlen = cp - f->path; - if (asprintf(&p->fname, "%.*s/.%s.%" PRIu32, - (int)dirlen, f->path, - f->path + dirlen + 1, hash) < 0) - p->fname = NULL; - } else { - if (asprintf(&p->fname, ".%s.%" PRIu32, - f->path, hash) < 0) - p->fname = NULL; - } - if (p->fname == NULL) { + if (mktemplate(&p->fname, f->path, sess->opts->recursive) + == -1) { ERR(sess, "asprintf"); goto out; } + if ((p->fd = mkstempat(p->rootfd, p->fname)) == -1) { + ERR(sess, "%s: openat", p->fname); + goto out; + } + /* * Inherit permissions from the source file if we're new * or specifically told with -p. @@ -451,11 +436,9 @@ rsync_downloader(struct download *p, struct sess *sess, int *ofd) else perm = f->st.mode; - p->fd = openat(p->rootfd, p->fname, - O_APPEND|O_WRONLY|O_CREAT|O_EXCL, perm); - - if (p->fd == -1) { - ERR(sess, "%s: openat", p->fname); + if (fchmod(p->fd, perm) == -1) { + ERR(sess, "%s: fchmod", p->fname); + (void)unlinkat(p->rootfd, p->fname, 0); goto out; } diff --git a/usr.bin/rsync/extern.h b/usr.bin/rsync/extern.h index da9bbac9fda..08a3549b8c9 100644 --- a/usr.bin/rsync/extern.h +++ b/usr.bin/rsync/extern.h @@ -1,4 +1,4 @@ -/* $Id: extern.h,v 1.10 2019/02/14 18:29:08 florian Exp $ */ +/* $Id: extern.h,v 1.11 2019/02/16 10:46:22 florian Exp $ */ /* * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv> * @@ -298,6 +298,10 @@ void hash_file(const void *, size_t, int mkpath(struct sess *, char *); +int mkstempat(int, char *); +char *mkstemplinkat(char*, int, char *); +int mktemplate(char **, const char *, int); + char *symlink_read(struct sess *, const char *); char *symlinkat_read(struct sess *, int, const char *); diff --git a/usr.bin/rsync/mktemp.c b/usr.bin/rsync/mktemp.c new file mode 100644 index 00000000000..f97f3c8ba7c --- /dev/null +++ b/usr.bin/rsync/mktemp.c @@ -0,0 +1,171 @@ +/* $OpenBSD: mktemp.c,v 1.1 2019/02/16 10:46:22 florian Exp $ */ +/* + * Copyright (c) 1996-1998, 2008 Theo de Raadt + * Copyright (c) 1997, 2008-2009 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> + +#define MKTEMP_NAME 0 +#define MKTEMP_FILE 1 +#define MKTEMP_DIR 2 +#define MKTEMP_LINK 3 + +#define TEMPCHARS "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" +#define NUM_CHARS (sizeof(TEMPCHARS) - 1) +#define MIN_X 6 + +#define MKOTEMP_FLAGS (O_APPEND | O_CLOEXEC | O_DSYNC | O_RSYNC | O_SYNC) + +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + +/* adapted from libc/stdio/mktemp.c */ +static int +mktemp_internalat(int pfd, char *path, int slen, int mode, int flags, + char *link) +{ + char *start, *cp, *ep; + const char tempchars[] = TEMPCHARS; + unsigned int tries; + struct stat sb; + size_t len; + int fd; + + len = strlen(path); + if (len < MIN_X || slen < 0 || (size_t)slen > len - MIN_X) { + errno = EINVAL; + return(-1); + } + ep = path + len - slen; + + for (start = ep; start > path && start[-1] == 'X'; start--) + ; + if (ep - start < MIN_X) { + errno = EINVAL; + return(-1); + } + + if (flags & ~MKOTEMP_FLAGS) { + errno = EINVAL; + return(-1); + } + flags |= O_CREAT | O_EXCL | O_RDWR; + + tries = INT_MAX; + do { + cp = start; + do { + unsigned short rbuf[16]; + unsigned int i; + + /* + * Avoid lots of arc4random() calls by using + * a buffer sized for up to 16 Xs at a time. + */ + arc4random_buf(rbuf, sizeof(rbuf)); + for (i = 0; i < nitems(rbuf) && cp != ep; i++) + *cp++ = tempchars[rbuf[i] % NUM_CHARS]; + } while (cp != ep); + + switch (mode) { + case MKTEMP_NAME: + if (fstatat(pfd, path, &sb, AT_SYMLINK_NOFOLLOW) != 0) + return(errno == ENOENT ? 0 : -1); + break; + case MKTEMP_FILE: + fd = openat(pfd, path, flags, S_IRUSR|S_IWUSR); + if (fd != -1 || errno != EEXIST) + return(fd); + break; + case MKTEMP_DIR: + if (mkdirat(pfd, path, S_IRUSR|S_IWUSR|S_IXUSR) == 0) + return(0); + if (errno != EEXIST) + return(-1); + break; + case MKTEMP_LINK: + if (symlinkat(link, pfd, path) == 0) + return(0); + else if (errno != EEXIST) + return(-1); + break; + } + } while (--tries); + + errno = EEXIST; + return(-1); +} + +/* + * A combination of mkstemp(3) and openat(2). + * On success returns a file descriptor and trailing Xs are overwritten in + * path to create a unique file name. + * Returns -1 on failure. + */ +int +mkstempat(int fd, char *path) +{ + return(mktemp_internalat(fd, path, 0, MKTEMP_FILE, 0, NULL)); +} + +/* + * A combination of mkstemp(3) and symlinkat(2). + * On success returns path with trailing Xs overwritten to create a unique + * file name. + * Returns NULL on failure. + */ +char* +mkstemplinkat(char *link, int fd, char *path) +{ + if (mktemp_internalat(fd, path, 0, MKTEMP_LINK, 0, link) == -1) + return(NULL); + return(path); +} + +/* + * Turn path into a suitable template for mkstemp*at functions and + * place it into the newly allocated string returned in ret. + * The caller must free ret. + * Returns -1 on failure or number of characters output to ret + * (excluding the final '\0'). + */ +int +mktemplate(char **ret, const char *path, int recursive) +{ + int n, dirlen; + const char *cp; + + if (recursive && (cp = strrchr(path, '/')) != NULL) { + dirlen = cp - path; + if ((n = asprintf(ret, "%.*s/.%s.XXXXXXXXXX", dirlen, path, + path + dirlen + 1)) == -1) + *ret = NULL; + } else { + if ((n = asprintf(ret, ".%s.XXXXXXXXXX", path)) == -1) + *ret = NULL; + } + return(n); +} diff --git a/usr.bin/rsync/uploader.c b/usr.bin/rsync/uploader.c index 7f2d52e3c17..c7e6b460816 100644 --- a/usr.bin/rsync/uploader.c +++ b/usr.bin/rsync/uploader.c @@ -1,4 +1,4 @@ -/* $Id: uploader.c,v 1.8 2019/02/16 05:30:28 deraadt Exp $ */ +/* $Id: uploader.c,v 1.9 2019/02/16 10:46:22 florian Exp $ */ /* * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv> * @@ -167,11 +167,12 @@ init_blk(struct blk *p, const struct blkset *set, off_t offs, static int pre_link(struct upload *p, struct sess *sess) { - int rc, newlink = 0; - char *b; - struct stat st; - struct timespec tv[2]; - const struct flist *f; + struct stat st; + struct timespec tv[2]; + const struct flist *f; + int rc, newlink = 0, updatelink = 0; + mode_t mode; + char *b; f = &p->fl[p->idx]; assert(S_ISLNK(f->st.mode)); @@ -189,8 +190,13 @@ pre_link(struct upload *p, struct sess *sess) assert(p->rootfd != -1); rc = fstatat(p->rootfd, f->path, &st, AT_SYMLINK_NOFOLLOW); if (rc != -1 && !S_ISLNK(st.st_mode)) { - WARNX(sess, "%s: not a symlink", f->path); - return -1; + if (S_ISDIR(st.st_mode)) { + if (unlinkat(p->rootfd, f->path, AT_REMOVEDIR) == -1) { + WARN(sess, "%s", f->path); + return -1; + } + } + rc = -1; /* overwrite object with symlink */ } else if (rc == -1 && errno != ENOENT) { WARN(sess, "%s: fstatat", f->path); return -1; @@ -199,20 +205,9 @@ pre_link(struct upload *p, struct sess *sess) /* * If the symbolic link already exists, then make sure that it * points to the correct place. - * FIXME: does symlinkat() set permissions on the link using the - * destination file or the default umask? - * Do we need a fchmod in here as well? */ - if (rc == -1) { - LOG3(sess, "%s: creating " - "symlink: %s", f->path, f->link); - if (symlinkat(f->link, p->rootfd, f->path) == -1) { - WARN(sess, "%s: symlinkat", f->path); - return -1; - } - newlink = 1; - } else { + if (rc != -1) { b = symlinkat_read(sess, p->rootfd, f->path); if (b == NULL) { ERRX1(sess, "%s: symlinkat_read", f->path); @@ -223,17 +218,26 @@ pre_link(struct upload *p, struct sess *sess) b = NULL; LOG3(sess, "%s: updating " "symlink: %s", f->path, f->link); - if (unlinkat(p->rootfd, f->path, 0) == -1) { - WARN(sess, "%s: unlinkat", f->path); - return -1; - } - if (symlinkat(f->link, p->rootfd, f->path) == -1) { - WARN(sess, "%s: symlinkat", f->path); - return -1; - } - newlink = 1; + updatelink = 1; } free(b); + b = NULL; + } + + if (rc == -1 || updatelink) { + LOG3(sess, "%s: creating " + "symlink: %s", f->path, f->link); + + if (mktemplate(&b, f->path, sess->opts->recursive) == -1) { + ERR(sess, "asprintf"); + return -1; + } + if (mkstemplinkat(f->link, p->rootfd, b) == NULL) { + WARN(sess, "%s: symlinkat", b); + free(b); + return -1; + } + newlink = 1; } /* @@ -248,28 +252,49 @@ pre_link(struct upload *p, struct sess *sess) TIMEVAL_TO_TIMESPEC(&now, &tv[0]); tv[1].tv_sec = f->st.mtime; tv[1].tv_nsec = 0; - rc = utimensat(p->rootfd, f->path, tv, AT_SYMLINK_NOFOLLOW); + rc = utimensat(p->rootfd, newlink ? b : f->path, tv, + AT_SYMLINK_NOFOLLOW); if (rc == -1) { - ERR(sess, "%s: utimensat", f->path); + ERR(sess, "%s: futimes", f->path); + if (newlink) { + (void)unlinkat(p->rootfd, b, 0); + free(b); + } return -1; } LOG4(sess, "%s: updated symlink date", f->path); } - /* - * FIXME: if newlink is set because we updated the symlink, we - * want to carry over the permissions from the last. - */ - if (newlink || sess->opts->preserve_perms) { - rc = fchmodat(p->rootfd, f->path, f->st.mode, AT_SYMLINK_NOFOLLOW); + if (updatelink && !sess->opts->preserve_perms) + /* carry over permissions from replaced symlink */ + mode = st.st_mode; + else + mode = f->st.mode; + + rc = fchmodat(p->rootfd, newlink ? b : f->path, mode, + AT_SYMLINK_NOFOLLOW); if (rc == -1) { - ERR(sess, "%s: fchmodat", f->path); + ERR(sess, "%s: fchmodat", newlink ? b : f->path); + if (newlink) { + (void)unlinkat(p->rootfd, b, 0); + free(b); + } return -1; } LOG4(sess, "%s: updated symlink mode", f->path); } + if (newlink) { + if (renameat(p->rootfd, b, p->rootfd, f->path) == -1) { + ERR(sess, "%s: renameat %s", b, f->path); + (void)unlinkat(p->rootfd, b, 0); + free(b); + return -1; + } + free(b); + } + log_link(sess, f); return 0; } |