1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright (c) 2014 Joyent, Inc. All rights reserved. 14 */ 15 16 #include <librename.h> 17 18 #include <errno.h> 19 #include <stdlib.h> 20 #include <string.h> 21 #include <stdio.h> 22 #include <sys/debug.h> 23 #include <sys/types.h> 24 #include <sys/stat.h> 25 #include <fcntl.h> 26 #include <unistd.h> 27 #include <synch.h> 28 29 typedef enum librename_atomic_state { 30 LIBRENAME_ATOMIC_INITIAL = 0x0, 31 LIBRENAME_ATOMIC_FSYNC, 32 LIBRENAME_ATOMIC_RENAME, 33 LIBRENAME_ATOMIC_POSTSYNC, 34 LIBRENAME_ATOMIC_COMPLETED 35 } librename_atomic_state_t; 36 37 struct librename_atomic { 38 char *lra_fname; /* RO */ 39 char *lra_altname; /* RO */ 40 int lra_dirfd; /* RO */ 41 int lra_tmpfd; /* RO */ 42 mutex_t lra_lock; 43 librename_atomic_state_t lra_state; /* lra_lock */ 44 }; 45 46 int 47 librename_atomic_fdinit(int fd, const char *file, const char *prefix, 48 int mode, int flags, librename_atomic_t **outp) 49 { 50 int ret; 51 int oflags; 52 librename_atomic_t *lrap; 53 struct stat st; 54 55 if (fd < 0 || file == NULL || outp == NULL) 56 return (EINVAL); 57 58 if (flags & ~(LIBRENAME_ATOMIC_NOUNLINK | LIBRENAME_ATOMIC_CLOEXEC)) 59 return (EINVAL); 60 61 if (strchr(file, '/') != NULL) 62 return (EINVAL); 63 64 if (prefix != NULL && strchr(prefix, '/') != NULL) 65 return (EINVAL); 66 67 *outp = NULL; 68 lrap = malloc(sizeof (librename_atomic_t)); 69 if (lrap == NULL) 70 return (errno); 71 72 if (fstat(fd, &st) != 0) { 73 ret = errno; 74 free(lrap); 75 return (ret); 76 } 77 78 if (!S_ISDIR(st.st_mode)) { 79 free(lrap); 80 return (ENOTDIR); 81 } 82 83 if ((lrap->lra_dirfd = dup(fd)) == -1) { 84 ret = errno; 85 free(lrap); 86 return (ret); 87 } 88 89 lrap->lra_fname = strdup(file); 90 if (lrap->lra_fname == NULL) { 91 ret = errno; 92 VERIFY0(close(lrap->lra_dirfd)); 93 free(lrap); 94 return (ret); 95 } 96 97 if (prefix == NULL) { 98 ret = asprintf(&lrap->lra_altname, ".%d.%s", (int)getpid(), 99 file); 100 } else { 101 ret = asprintf(&lrap->lra_altname, "%s%s", prefix, file); 102 } 103 if (ret == -1) { 104 ret = errno; 105 free(lrap->lra_fname); 106 VERIFY0(close(lrap->lra_dirfd)); 107 free(lrap); 108 return (errno); 109 } 110 111 oflags = O_CREAT | O_TRUNC | O_RDWR | O_NOFOLLOW; 112 if (flags & LIBRENAME_ATOMIC_NOUNLINK) 113 oflags |= O_EXCL; 114 115 if (flags & LIBRENAME_ATOMIC_CLOEXEC) 116 oflags |= O_CLOEXEC; 117 118 lrap->lra_tmpfd = openat(lrap->lra_dirfd, lrap->lra_altname, 119 oflags, mode); 120 if (lrap->lra_tmpfd < 0) { 121 ret = errno; 122 free(lrap->lra_altname); 123 free(lrap->lra_fname); 124 VERIFY0(close(lrap->lra_dirfd)); 125 free(lrap); 126 return (ret); 127 } 128 129 VERIFY0(mutex_init(&lrap->lra_lock, USYNC_THREAD, NULL)); 130 131 lrap->lra_state = LIBRENAME_ATOMIC_INITIAL; 132 *outp = lrap; 133 return (0); 134 } 135 136 int 137 librename_atomic_init(const char *dir, const char *file, const char *prefix, 138 int mode, int flags, librename_atomic_t **outp) 139 { 140 int fd, ret; 141 142 if ((fd = open(dir, O_RDONLY)) < 0) 143 return (errno); 144 145 ret = librename_atomic_fdinit(fd, file, prefix, mode, flags, outp); 146 VERIFY0(close(fd)); 147 148 return (ret); 149 } 150 151 int 152 librename_atomic_fd(librename_atomic_t *lrap) 153 { 154 return (lrap->lra_tmpfd); 155 } 156 157 /* 158 * To atomically commit a file, we need to go through and do the following: 159 * 160 * o fsync the source 161 * o run rename 162 * o fsync the source again and the directory. 163 */ 164 int 165 librename_atomic_commit(librename_atomic_t *lrap) 166 { 167 int ret = 0; 168 169 VERIFY0(mutex_lock(&lrap->lra_lock)); 170 if (lrap->lra_state == LIBRENAME_ATOMIC_COMPLETED) { 171 ret = EINVAL; 172 goto out; 173 } 174 175 if (fsync(lrap->lra_tmpfd) != 0) { 176 ret = errno; 177 goto out; 178 } 179 lrap->lra_state = LIBRENAME_ATOMIC_FSYNC; 180 181 if (renameat(lrap->lra_dirfd, lrap->lra_altname, lrap->lra_dirfd, 182 lrap->lra_fname) != 0) { 183 ret = errno; 184 goto out; 185 } 186 lrap->lra_state = LIBRENAME_ATOMIC_RENAME; 187 188 if (fsync(lrap->lra_tmpfd) != 0) { 189 ret = errno; 190 goto out; 191 } 192 lrap->lra_state = LIBRENAME_ATOMIC_POSTSYNC; 193 194 if (fsync(lrap->lra_dirfd) != 0) { 195 ret = errno; 196 goto out; 197 } 198 lrap->lra_state = LIBRENAME_ATOMIC_COMPLETED; 199 200 out: 201 VERIFY0(mutex_unlock(&lrap->lra_lock)); 202 return (ret); 203 } 204 205 void 206 librename_atomic_fini(librename_atomic_t *lrap) 207 { 208 209 free(lrap->lra_altname); 210 free(lrap->lra_fname); 211 VERIFY0(close(lrap->lra_tmpfd)); 212 VERIFY0(close(lrap->lra_dirfd)); 213 VERIFY0(mutex_destroy(&lrap->lra_lock)); 214 free(lrap); 215 } 216