xref: /freebsd/sys/contrib/openzfs/lib/libshare/nfs.c (revision d30a1689f5b37e78ea189232a8b94a7011dc0dc8)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or https://opensource.org/licenses/CDDL-1.0.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <sys/file.h>
26 #include <fcntl.h>
27 #include <ctype.h>
28 #include <stdio.h>
29 #include <errno.h>
30 #include <libshare.h>
31 #include <unistd.h>
32 #include "nfs.h"
33 
34 
35 /*
36  * nfs_exports_[lock|unlock] are used to guard against conconcurrent
37  * updates to the exports file. Each protocol is responsible for
38  * providing the necessary locking to ensure consistency.
39  */
40 static int
41 nfs_exports_lock(const char *name, int *nfs_lock_fd)
42 {
43 	int err;
44 
45 	*nfs_lock_fd = open(name, O_RDWR | O_CREAT | O_CLOEXEC, 0600);
46 	if (*nfs_lock_fd == -1) {
47 		err = errno;
48 		fprintf(stderr, "failed to lock %s: %s\n", name, strerror(err));
49 		return (err);
50 	}
51 
52 	while ((err = flock(*nfs_lock_fd, LOCK_EX)) != 0 && errno == EINTR)
53 		;
54 	if (err != 0) {
55 		err = errno;
56 		fprintf(stderr, "failed to lock %s: %s\n", name, strerror(err));
57 		(void) close(*nfs_lock_fd);
58 		*nfs_lock_fd = -1;
59 		return (err);
60 	}
61 
62 	return (0);
63 }
64 
65 static void
66 nfs_exports_unlock(const char *name, int *nfs_lock_fd)
67 {
68 	verify(*nfs_lock_fd > 0);
69 
70 	if (flock(*nfs_lock_fd, LOCK_UN) != 0)
71 		fprintf(stderr, "failed to unlock %s: %s\n",
72 		    name, strerror(errno));
73 
74 	(void) close(*nfs_lock_fd);
75 	*nfs_lock_fd = -1;
76 }
77 
78 struct tmpfile {
79 	/*
80 	 * This only needs to be as wide as ZFS_EXPORTS_FILE and mktemp suffix,
81 	 * 64 is more than enough.
82 	 */
83 	char name[64];
84 	FILE *fp;
85 };
86 
87 static boolean_t
88 nfs_init_tmpfile(const char *prefix, const char *mdir, struct tmpfile *tmpf)
89 {
90 	if (mdir != NULL &&
91 	    mkdir(mdir, 0755) < 0 &&
92 	    errno != EEXIST) {
93 		fprintf(stderr, "failed to create %s: %s\n",
94 		    mdir, strerror(errno));
95 		return (B_FALSE);
96 	}
97 
98 	strcpy(tmpf->name, prefix);
99 	strcat(tmpf->name, ".XXXXXXXX");
100 
101 	int fd = mkostemp(tmpf->name, O_CLOEXEC);
102 	if (fd == -1) {
103 		fprintf(stderr, "Unable to create temporary file: %s",
104 		    strerror(errno));
105 		return (B_FALSE);
106 	}
107 
108 	tmpf->fp = fdopen(fd, "w+");
109 	if (tmpf->fp == NULL) {
110 		fprintf(stderr, "Unable to reopen temporary file: %s",
111 		    strerror(errno));
112 		close(fd);
113 		return (B_FALSE);
114 	}
115 
116 	return (B_TRUE);
117 }
118 
119 static void
120 nfs_abort_tmpfile(struct tmpfile *tmpf)
121 {
122 	unlink(tmpf->name);
123 	fclose(tmpf->fp);
124 }
125 
126 static int
127 nfs_fini_tmpfile(const char *exports, struct tmpfile *tmpf)
128 {
129 	if (fflush(tmpf->fp) != 0) {
130 		fprintf(stderr, "Failed to write to temporary file: %s\n",
131 		    strerror(errno));
132 		nfs_abort_tmpfile(tmpf);
133 		return (SA_SYSTEM_ERR);
134 	}
135 
136 	if (rename(tmpf->name, exports) == -1) {
137 		fprintf(stderr, "Unable to rename %s -> %s: %s\n",
138 		    tmpf->name, exports, strerror(errno));
139 		nfs_abort_tmpfile(tmpf);
140 		return (SA_SYSTEM_ERR);
141 	}
142 
143 	(void) fchmod(fileno(tmpf->fp), 0644);
144 	fclose(tmpf->fp);
145 	return (SA_OK);
146 }
147 
148 int
149 nfs_escape_mountpoint(const char *mp, char **out, boolean_t *need_free)
150 {
151 	if (strpbrk(mp, "\t\n\v\f\r \\") == NULL) {
152 		*out = (char *)mp;
153 		*need_free = B_FALSE;
154 		return (SA_OK);
155 	} else {
156 		size_t len = strlen(mp);
157 		*out = malloc(len * 4 + 1);
158 		if (!*out)
159 			return (SA_NO_MEMORY);
160 		*need_free = B_TRUE;
161 
162 		char *oc = *out;
163 		for (const char *c = mp; c < mp + len; ++c)
164 			if (memchr("\t\n\v\f\r \\", *c,
165 			    strlen("\t\n\v\f\r \\"))) {
166 				sprintf(oc, "\\%03hho", *c);
167 				oc += 4;
168 			} else
169 				*oc++ = *c;
170 		*oc = '\0';
171 	}
172 
173 	return (SA_OK);
174 }
175 
176 static int
177 nfs_process_exports(const char *exports, const char *mountpoint,
178     boolean_t (*cbk)(void *userdata, char *line, boolean_t found_mountpoint),
179     void *userdata)
180 {
181 	int error = SA_OK;
182 	boolean_t cont = B_TRUE;
183 
184 	FILE *oldfp = fopen(exports, "re");
185 	if (oldfp != NULL) {
186 		boolean_t need_mp_free;
187 		char *mp;
188 		if ((error = nfs_escape_mountpoint(mountpoint,
189 		    &mp, &need_mp_free)) != SA_OK) {
190 			(void) fclose(oldfp);
191 			return (error);
192 		}
193 
194 		char *buf = NULL, *sep;
195 		size_t buflen = 0, mplen = strlen(mp);
196 
197 		while (cont && getline(&buf, &buflen, oldfp) != -1) {
198 			if (buf[0] == '\n' || buf[0] == '#')
199 				continue;
200 
201 			cont = cbk(userdata, buf,
202 			    (sep = strpbrk(buf, "\t \n")) != NULL &&
203 			    sep - buf == mplen &&
204 			    strncmp(buf, mp, mplen) == 0);
205 		}
206 		free(buf);
207 		if (need_mp_free)
208 			free(mp);
209 
210 		if (ferror(oldfp) != 0)
211 			error = ferror(oldfp);
212 
213 		if (fclose(oldfp) != 0) {
214 			fprintf(stderr, "Unable to close file %s: %s\n",
215 			    exports, strerror(errno));
216 			error = error != SA_OK ? error : SA_SYSTEM_ERR;
217 		}
218 	}
219 
220 	return (error);
221 }
222 
223 static boolean_t
224 nfs_copy_entries_cb(void *userdata, char *line, boolean_t found_mountpoint)
225 {
226 	FILE *newfp = userdata;
227 	if (!found_mountpoint)
228 		fputs(line, newfp);
229 	return (B_TRUE);
230 }
231 
232 /*
233  * Copy all entries from the exports file (if it exists) to newfp,
234  * omitting any entries for the specified mountpoint.
235  */
236 static int
237 nfs_copy_entries(FILE *newfp, const char *exports, const char *mountpoint)
238 {
239 	fputs(FILE_HEADER, newfp);
240 
241 	int error = nfs_process_exports(
242 	    exports, mountpoint, nfs_copy_entries_cb, newfp);
243 
244 	if (error == SA_OK && ferror(newfp) != 0)
245 		error = ferror(newfp);
246 
247 	return (error);
248 }
249 
250 int
251 nfs_toggle_share(const char *lockfile, const char *exports,
252     const char *expdir, sa_share_impl_t impl_share,
253     int(*cbk)(sa_share_impl_t impl_share, FILE *tmpfile))
254 {
255 	int error, nfs_lock_fd = -1;
256 	struct tmpfile tmpf;
257 
258 	if (!nfs_init_tmpfile(exports, expdir, &tmpf))
259 		return (SA_SYSTEM_ERR);
260 
261 	error = nfs_exports_lock(lockfile, &nfs_lock_fd);
262 	if (error != 0) {
263 		nfs_abort_tmpfile(&tmpf);
264 		return (error);
265 	}
266 
267 	error = nfs_copy_entries(tmpf.fp, exports, impl_share->sa_mountpoint);
268 	if (error != SA_OK)
269 		goto fullerr;
270 
271 	error = cbk(impl_share, tmpf.fp);
272 	if (error != SA_OK)
273 		goto fullerr;
274 
275 	error = nfs_fini_tmpfile(exports, &tmpf);
276 	nfs_exports_unlock(lockfile, &nfs_lock_fd);
277 	return (error);
278 
279 fullerr:
280 	nfs_abort_tmpfile(&tmpf);
281 	nfs_exports_unlock(lockfile, &nfs_lock_fd);
282 	return (error);
283 }
284 
285 void
286 nfs_reset_shares(const char *lockfile, const char *exports)
287 {
288 	int nfs_lock_fd = -1;
289 
290 	if (nfs_exports_lock(lockfile, &nfs_lock_fd) == 0) {
291 		(void) ! truncate(exports, 0);
292 		nfs_exports_unlock(lockfile, &nfs_lock_fd);
293 	}
294 }
295 
296 static boolean_t
297 nfs_is_shared_cb(void *userdata, char *line, boolean_t found_mountpoint)
298 {
299 	(void) line;
300 
301 	boolean_t *found = userdata;
302 	*found = found_mountpoint;
303 	return (!found_mountpoint);
304 }
305 
306 boolean_t
307 nfs_is_shared_impl(const char *exports, sa_share_impl_t impl_share)
308 {
309 	boolean_t found = B_FALSE;
310 	nfs_process_exports(exports, impl_share->sa_mountpoint,
311 	    nfs_is_shared_cb, &found);
312 	return (found);
313 }
314