xref: /freebsd-src/sys/contrib/openzfs/lib/libshare/os/freebsd/nfs.c (revision 2eb4d8dc723da3cf7d735a3226ae49da4c8c5dbc)
1 /*
2  * Copyright (c) 2007 Pawel Jakub Dawidek <pjd@FreeBSD.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  * Copyright (c) 2020 by Delphix. All rights reserved.
27  */
28 
29 #include <sys/cdefs.h>
30 __FBSDID("$FreeBSD$");
31 
32 #include <sys/param.h>
33 #include <sys/vfs.h>
34 
35 #include <assert.h>
36 #include <errno.h>
37 #include <fcntl.h>
38 #include <libutil.h>
39 #include <signal.h>
40 #include <stdio.h>
41 #include <string.h>
42 #include <unistd.h>
43 #include <libintl.h>
44 
45 #include <libshare.h>
46 #include "libshare_impl.h"
47 #include "nfs.h"
48 
49 #define	_PATH_MOUNTDPID	"/var/run/mountd.pid"
50 #define	OPTSSIZE	1024
51 #define	MAXLINESIZE	(PATH_MAX + OPTSSIZE)
52 #define	ZFS_EXPORTS_FILE	"/etc/zfs/exports"
53 #define	ZFS_EXPORTS_LOCK	ZFS_EXPORTS_FILE".lock"
54 
55 static sa_fstype_t *nfs_fstype;
56 
57 /*
58  * Read one line from a file. Skip comments, empty lines and a line with a
59  * mountpoint specified in the 'skip' argument.
60  *
61  * NOTE: This function returns a static buffer and thus is not thread-safe.
62  */
63 static char *
64 zgetline(FILE *fd, const char *skip)
65 {
66 	static char line[MAXLINESIZE];
67 	size_t len, skiplen = 0;
68 	char *s, last;
69 
70 	if (skip != NULL)
71 		skiplen = strlen(skip);
72 	for (;;) {
73 		s = fgets(line, sizeof (line), fd);
74 		if (s == NULL)
75 			return (NULL);
76 		/* Skip empty lines and comments. */
77 		if (line[0] == '\n' || line[0] == '#')
78 			continue;
79 		len = strlen(line);
80 		if (line[len - 1] == '\n')
81 			line[len - 1] = '\0';
82 		last = line[skiplen];
83 		/* Skip the given mountpoint. */
84 		if (skip != NULL && strncmp(skip, line, skiplen) == 0 &&
85 		    (last == '\t' || last == ' ' || last == '\0')) {
86 			continue;
87 		}
88 		break;
89 	}
90 	return (line);
91 }
92 
93 /*
94  * This function translate options to a format acceptable by exports(5), eg.
95  *
96  *	-ro -network=192.168.0.0 -mask=255.255.255.0 -maproot=0 \
97  *	zfs.freebsd.org 69.147.83.54
98  *
99  * Accepted input formats:
100  *
101  *	ro,network=192.168.0.0,mask=255.255.255.0,maproot=0,zfs.freebsd.org
102  *	ro network=192.168.0.0 mask=255.255.255.0 maproot=0 zfs.freebsd.org
103  *	-ro,-network=192.168.0.0,-mask=255.255.255.0,-maproot=0,zfs.freebsd.org
104  *	-ro -network=192.168.0.0 -mask=255.255.255.0 -maproot=0 \
105  *	zfs.freebsd.org
106  *
107  * Recognized keywords:
108  *
109  *	ro, maproot, mapall, mask, network, sec, alldirs, public, webnfs,
110  *	index, quiet
111  *
112  * NOTE: This function returns a static buffer and thus is not thread-safe.
113  */
114 static char *
115 translate_opts(const char *shareopts)
116 {
117 	static const char *known_opts[] = { "ro", "maproot", "mapall", "mask",
118 	    "network", "sec", "alldirs", "public", "webnfs", "index", "quiet",
119 	    NULL };
120 	static char newopts[OPTSSIZE];
121 	char oldopts[OPTSSIZE];
122 	char *o, *s = NULL;
123 	unsigned int i;
124 	size_t len;
125 
126 	strlcpy(oldopts, shareopts, sizeof (oldopts));
127 	newopts[0] = '\0';
128 	s = oldopts;
129 	while ((o = strsep(&s, "-, ")) != NULL) {
130 		if (o[0] == '\0')
131 			continue;
132 		for (i = 0; known_opts[i] != NULL; i++) {
133 			len = strlen(known_opts[i]);
134 			if (strncmp(known_opts[i], o, len) == 0 &&
135 			    (o[len] == '\0' || o[len] == '=')) {
136 				strlcat(newopts, "-", sizeof (newopts));
137 				break;
138 			}
139 		}
140 		strlcat(newopts, o, sizeof (newopts));
141 		strlcat(newopts, " ", sizeof (newopts));
142 	}
143 	return (newopts);
144 }
145 
146 /*
147  * This function copies all entries from the exports file to "filename",
148  * omitting any entries for the specified mountpoint.
149  */
150 int
151 nfs_copy_entries(char *filename, const char *mountpoint)
152 {
153 	int error = SA_OK;
154 	char *line;
155 
156 	FILE *oldfp = fopen(ZFS_EXPORTS_FILE, "re");
157 	FILE *newfp = fopen(filename, "w+e");
158 	if (newfp == NULL) {
159 		fprintf(stderr, "failed to open %s file: %s", filename,
160 		    strerror(errno));
161 		fclose(oldfp);
162 		return (SA_SYSTEM_ERR);
163 	}
164 	fputs(FILE_HEADER, newfp);
165 
166 	/*
167 	 * The ZFS_EXPORTS_FILE may not exist yet. If that's the
168 	 * case then just write out the new file.
169 	 */
170 	if (oldfp != NULL) {
171 		while ((line = zgetline(oldfp, mountpoint)) != NULL)
172 			fprintf(newfp, "%s\n", line);
173 		if (ferror(oldfp) != 0) {
174 			error = ferror(oldfp);
175 		}
176 		if (fclose(oldfp) != 0) {
177 			fprintf(stderr, "Unable to close file %s: %s\n",
178 			    filename, strerror(errno));
179 			error = error != 0 ? error : SA_SYSTEM_ERR;
180 		}
181 	}
182 
183 	if (error == 0 && ferror(newfp) != 0) {
184 		error = ferror(newfp);
185 	}
186 
187 	if (fclose(newfp) != 0) {
188 		fprintf(stderr, "Unable to close file %s: %s\n",
189 		    filename, strerror(errno));
190 		error = error != 0 ? error : SA_SYSTEM_ERR;
191 	}
192 	return (error);
193 }
194 
195 static int
196 nfs_enable_share_impl(sa_share_impl_t impl_share, char *filename)
197 {
198 	FILE *fp = fopen(filename, "a+e");
199 	if (fp == NULL) {
200 		fprintf(stderr, "failed to open %s file: %s", filename,
201 		    strerror(errno));
202 		return (SA_SYSTEM_ERR);
203 	}
204 
205 	char *shareopts = FSINFO(impl_share, nfs_fstype)->shareopts;
206 	if (strcmp(shareopts, "on") == 0)
207 		shareopts = "";
208 
209 	if (fprintf(fp, "%s\t%s\n", impl_share->sa_mountpoint,
210 	    translate_opts(shareopts)) < 0) {
211 		fprintf(stderr, "failed to write to %s\n", filename);
212 		fclose(fp);
213 		return (SA_SYSTEM_ERR);
214 	}
215 
216 	if (fclose(fp) != 0) {
217 		fprintf(stderr, "Unable to close file %s: %s\n",
218 		    filename, strerror(errno));
219 		return (SA_SYSTEM_ERR);
220 	}
221 
222 	return (SA_OK);
223 }
224 
225 static int
226 nfs_enable_share(sa_share_impl_t impl_share)
227 {
228 	return (nfs_toggle_share(
229 	    ZFS_EXPORTS_LOCK, ZFS_EXPORTS_FILE, NULL, impl_share,
230 	    nfs_enable_share_impl));
231 }
232 
233 static int
234 nfs_disable_share_impl(sa_share_impl_t impl_share, char *filename)
235 {
236 	return (SA_OK);
237 }
238 
239 static int
240 nfs_disable_share(sa_share_impl_t impl_share)
241 {
242 	return (nfs_toggle_share(
243 	    ZFS_EXPORTS_LOCK, ZFS_EXPORTS_FILE, NULL, impl_share,
244 	    nfs_disable_share_impl));
245 }
246 
247 static boolean_t
248 nfs_is_shared(sa_share_impl_t impl_share)
249 {
250 	char *s, last, line[MAXLINESIZE];
251 	size_t len;
252 	char *mntpoint = impl_share->sa_mountpoint;
253 	size_t mntlen = strlen(mntpoint);
254 
255 	FILE *fp = fopen(ZFS_EXPORTS_FILE, "re");
256 	if (fp == NULL)
257 		return (B_FALSE);
258 
259 	for (;;) {
260 		s = fgets(line, sizeof (line), fp);
261 		if (s == NULL)
262 			return (B_FALSE);
263 		/* Skip empty lines and comments. */
264 		if (line[0] == '\n' || line[0] == '#')
265 			continue;
266 		len = strlen(line);
267 		if (line[len - 1] == '\n')
268 			line[len - 1] = '\0';
269 		last = line[mntlen];
270 		/* Skip the given mountpoint. */
271 		if (strncmp(mntpoint, line, mntlen) == 0 &&
272 		    (last == '\t' || last == ' ' || last == '\0')) {
273 			fclose(fp);
274 			return (B_TRUE);
275 		}
276 	}
277 	fclose(fp);
278 	return (B_FALSE);
279 }
280 
281 static int
282 nfs_validate_shareopts(const char *shareopts)
283 {
284 	return (SA_OK);
285 }
286 
287 static int
288 nfs_update_shareopts(sa_share_impl_t impl_share, const char *shareopts)
289 {
290 	FSINFO(impl_share, nfs_fstype)->shareopts = (char *)shareopts;
291 	return (SA_OK);
292 }
293 
294 static void
295 nfs_clear_shareopts(sa_share_impl_t impl_share)
296 {
297 	FSINFO(impl_share, nfs_fstype)->shareopts = NULL;
298 }
299 
300 /*
301  * Commit the shares by restarting mountd.
302  */
303 static int
304 nfs_commit_shares(void)
305 {
306 	struct pidfh *pfh;
307 	pid_t mountdpid;
308 
309 	pfh = pidfile_open(_PATH_MOUNTDPID, 0600, &mountdpid);
310 	if (pfh != NULL) {
311 		/* Mountd is not running. */
312 		pidfile_remove(pfh);
313 		return (SA_OK);
314 	}
315 	if (errno != EEXIST) {
316 		/* Cannot open pidfile for some reason. */
317 		return (SA_SYSTEM_ERR);
318 	}
319 	/* We have mountd(8) PID in mountdpid variable. */
320 	kill(mountdpid, SIGHUP);
321 	return (SA_OK);
322 }
323 
324 static const sa_share_ops_t nfs_shareops = {
325 	.enable_share = nfs_enable_share,
326 	.disable_share = nfs_disable_share,
327 	.is_shared = nfs_is_shared,
328 
329 	.validate_shareopts = nfs_validate_shareopts,
330 	.update_shareopts = nfs_update_shareopts,
331 	.clear_shareopts = nfs_clear_shareopts,
332 	.commit_shares = nfs_commit_shares,
333 };
334 
335 /*
336  * Initializes the NFS functionality of libshare.
337  */
338 void
339 libshare_nfs_init(void)
340 {
341 	nfs_fstype = register_fstype("nfs", &nfs_shareops);
342 }
343