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
librename_atomic_fdinit(int fd,const char * file,const char * prefix,int mode,int flags,librename_atomic_t ** outp)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
librename_atomic_init(const char * dir,const char * file,const char * prefix,int mode,int flags,librename_atomic_t ** outp)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
librename_atomic_fd(librename_atomic_t * lrap)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
librename_atomic_commit(librename_atomic_t * lrap)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
librename_atomic_fini(librename_atomic_t * lrap)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