/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright (c) 2014 Joyent, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include typedef enum librename_atomic_state { LIBRENAME_ATOMIC_INITIAL = 0x0, LIBRENAME_ATOMIC_FSYNC, LIBRENAME_ATOMIC_RENAME, LIBRENAME_ATOMIC_POSTSYNC, LIBRENAME_ATOMIC_COMPLETED } librename_atomic_state_t; struct librename_atomic { char *lra_fname; /* RO */ char *lra_altname; /* RO */ int lra_dirfd; /* RO */ int lra_tmpfd; /* RO */ mutex_t lra_lock; librename_atomic_state_t lra_state; /* lra_lock */ }; int librename_atomic_fdinit(int fd, const char *file, const char *prefix, int mode, int flags, librename_atomic_t **outp) { int ret; int oflags; librename_atomic_t *lrap; struct stat st; if (fd < 0 || file == NULL || outp == NULL) return (EINVAL); if (flags & ~(LIBRENAME_ATOMIC_NOUNLINK | LIBRENAME_ATOMIC_CLOEXEC)) return (EINVAL); if (strchr(file, '/') != NULL) return (EINVAL); if (prefix != NULL && strchr(prefix, '/') != NULL) return (EINVAL); *outp = NULL; lrap = malloc(sizeof (librename_atomic_t)); if (lrap == NULL) return (errno); if (fstat(fd, &st) != 0) { ret = errno; free(lrap); return (ret); } if (!S_ISDIR(st.st_mode)) { free(lrap); return (ENOTDIR); } if ((lrap->lra_dirfd = dup(fd)) == -1) { ret = errno; free(lrap); return (ret); } lrap->lra_fname = strdup(file); if (lrap->lra_fname == NULL) { ret = errno; VERIFY0(close(lrap->lra_dirfd)); free(lrap); return (ret); } if (prefix == NULL) { ret = asprintf(&lrap->lra_altname, ".%d.%s", (int)getpid(), file); } else { ret = asprintf(&lrap->lra_altname, "%s%s", prefix, file); } if (ret == -1) { ret = errno; free(lrap->lra_fname); VERIFY0(close(lrap->lra_dirfd)); free(lrap); return (errno); } oflags = O_CREAT | O_TRUNC | O_RDWR | O_NOFOLLOW; if (flags & LIBRENAME_ATOMIC_NOUNLINK) oflags |= O_EXCL; if (flags & LIBRENAME_ATOMIC_CLOEXEC) oflags |= O_CLOEXEC; lrap->lra_tmpfd = openat(lrap->lra_dirfd, lrap->lra_altname, oflags, mode); if (lrap->lra_tmpfd < 0) { ret = errno; free(lrap->lra_altname); free(lrap->lra_fname); VERIFY0(close(lrap->lra_dirfd)); free(lrap); return (ret); } VERIFY0(mutex_init(&lrap->lra_lock, USYNC_THREAD, NULL)); lrap->lra_state = LIBRENAME_ATOMIC_INITIAL; *outp = lrap; return (0); } int librename_atomic_init(const char *dir, const char *file, const char *prefix, int mode, int flags, librename_atomic_t **outp) { int fd, ret; if ((fd = open(dir, O_RDONLY)) < 0) return (errno); ret = librename_atomic_fdinit(fd, file, prefix, mode, flags, outp); VERIFY0(close(fd)); return (ret); } int librename_atomic_fd(librename_atomic_t *lrap) { return (lrap->lra_tmpfd); } /* * To atomically commit a file, we need to go through and do the following: * * o fsync the source * o run rename * o fsync the source again and the directory. */ int librename_atomic_commit(librename_atomic_t *lrap) { int ret = 0; VERIFY0(mutex_lock(&lrap->lra_lock)); if (lrap->lra_state == LIBRENAME_ATOMIC_COMPLETED) { ret = EINVAL; goto out; } if (fsync(lrap->lra_tmpfd) != 0) { ret = errno; goto out; } lrap->lra_state = LIBRENAME_ATOMIC_FSYNC; if (renameat(lrap->lra_dirfd, lrap->lra_altname, lrap->lra_dirfd, lrap->lra_fname) != 0) { ret = errno; goto out; } lrap->lra_state = LIBRENAME_ATOMIC_RENAME; if (fsync(lrap->lra_tmpfd) != 0) { ret = errno; goto out; } lrap->lra_state = LIBRENAME_ATOMIC_POSTSYNC; if (fsync(lrap->lra_dirfd) != 0) { ret = errno; goto out; } lrap->lra_state = LIBRENAME_ATOMIC_COMPLETED; out: VERIFY0(mutex_unlock(&lrap->lra_lock)); return (ret); } void librename_atomic_fini(librename_atomic_t *lrap) { free(lrap->lra_altname); free(lrap->lra_fname); VERIFY0(close(lrap->lra_tmpfd)); VERIFY0(close(lrap->lra_dirfd)); VERIFY0(mutex_destroy(&lrap->lra_lock)); free(lrap); }