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