summaryrefslogtreecommitdiff
path: root/usr.bin/rsync
diff options
context:
space:
mode:
authorFlorian Obser <florian@cvs.openbsd.org>2019-02-16 10:46:23 +0000
committerFlorian Obser <florian@cvs.openbsd.org>2019-02-16 10:46:23 +0000
commit603c710d890537089cde0824b4af8935d3960296 (patch)
tree46fb35b290c8cca86b11770bd809fe27266a8acd /usr.bin/rsync
parentffb81903474e9f6ce0fe506577905da85f07f179 (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/Makefile4
-rw-r--r--usr.bin/rsync/downloader.c43
-rw-r--r--usr.bin/rsync/extern.h6
-rw-r--r--usr.bin/rsync/mktemp.c171
-rw-r--r--usr.bin/rsync/uploader.c101
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;
}