xref: /illumos-gate/usr/src/lib/libzfs/common/libzfs_mount.c (revision 99653d4ee642c6528e88224f12409a5f23060994)
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 http://www.opensolaris.org/os/licensing.
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  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*
29  * Routines to manage ZFS mounts.  We separate all the nasty routines that have
30  * to deal with the OS.  The main entry points are:
31  *
32  * 	zfs_is_mounted()
33  * 	zfs_mount()
34  * 	zfs_unmount()
35  * 	zfs_unmountall()
36  *
37  * These functions are used by mount and unmount, and when changing a
38  * filesystem's mountpoint.  This file also contains the functions used to
39  * manage sharing filesystems via NFS:
40  *
41  * 	zfs_is_shared()
42  * 	zfs_share()
43  * 	zfs_unshare()
44  * 	zfs_unshareall()
45  */
46 
47 #include <dirent.h>
48 #include <errno.h>
49 #include <libgen.h>
50 #include <libintl.h>
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <strings.h>
54 #include <unistd.h>
55 #include <zone.h>
56 #include <sys/mntent.h>
57 #include <sys/mnttab.h>
58 #include <sys/mount.h>
59 #include <sys/stat.h>
60 
61 #include <libzfs.h>
62 
63 #include "libzfs_impl.h"
64 
65 /*
66  * Search the sharetab for the given mountpoint, returning true if it is found.
67  */
68 static boolean_t
69 is_shared(libzfs_handle_t *hdl, const char *mountpoint)
70 {
71 	char buf[MAXPATHLEN], *tab;
72 
73 	if (hdl->libzfs_sharetab == NULL)
74 		return (0);
75 
76 	(void) fseek(hdl->libzfs_sharetab, 0, SEEK_SET);
77 
78 	while (fgets(buf, sizeof (buf), hdl->libzfs_sharetab) != NULL) {
79 
80 		/* the mountpoint is the first entry on each line */
81 		if ((tab = strchr(buf, '\t')) != NULL) {
82 			*tab = '\0';
83 			if (strcmp(buf, mountpoint) == 0)
84 				return (B_TRUE);
85 		}
86 	}
87 
88 	return (B_FALSE);
89 }
90 
91 /*
92  * Returns true if the specified directory is empty.  If we can't open the
93  * directory at all, return true so that the mount can fail with a more
94  * informative error message.
95  */
96 static boolean_t
97 dir_is_empty(const char *dirname)
98 {
99 	DIR *dirp;
100 	struct dirent64 *dp;
101 
102 	if ((dirp = opendir(dirname)) == NULL)
103 		return (B_TRUE);
104 
105 	while ((dp = readdir64(dirp)) != NULL) {
106 
107 		if (strcmp(dp->d_name, ".") == 0 ||
108 		    strcmp(dp->d_name, "..") == 0)
109 			continue;
110 
111 		(void) closedir(dirp);
112 		return (B_FALSE);
113 	}
114 
115 	(void) closedir(dirp);
116 	return (B_TRUE);
117 }
118 
119 /*
120  * Checks to see if the mount is active.  If the filesystem is mounted, we fill
121  * in 'where' with the current mountpoint, and return 1.  Otherwise, we return
122  * 0.
123  */
124 boolean_t
125 zfs_is_mounted(zfs_handle_t *zhp, char **where)
126 {
127 	struct mnttab search = { 0 }, entry;
128 
129 	/*
130 	 * Search for the entry in /etc/mnttab.  We don't bother getting the
131 	 * mountpoint, as we can just search for the special device.  This will
132 	 * also let us find mounts when the mountpoint is 'legacy'.
133 	 */
134 	search.mnt_special = (char *)zfs_get_name(zhp);
135 	search.mnt_fstype = MNTTYPE_ZFS;
136 
137 	rewind(zhp->zfs_hdl->libzfs_mnttab);
138 	if (getmntany(zhp->zfs_hdl->libzfs_mnttab, &entry, &search) != 0)
139 		return (B_FALSE);
140 
141 	if (where != NULL)
142 		*where = zfs_strdup(zhp->zfs_hdl, entry.mnt_mountp);
143 
144 	return (B_TRUE);
145 }
146 
147 /*
148  * Mount the given filesystem.
149  */
150 int
151 zfs_mount(zfs_handle_t *zhp, const char *options, int flags)
152 {
153 	struct stat buf;
154 	char mountpoint[ZFS_MAXPROPLEN];
155 	char mntopts[MNT_LINE_MAX];
156 	libzfs_handle_t *hdl = zhp->zfs_hdl;
157 
158 	if (options == NULL)
159 		mntopts[0] = '\0';
160 	else
161 		(void) strlcpy(mntopts, options, sizeof (mntopts));
162 
163 	/* ignore non-filesystems */
164 	if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, mountpoint,
165 	    sizeof (mountpoint), NULL, NULL, 0, B_FALSE) != 0)
166 		return (0);
167 
168 	/* return success if there is no mountpoint set */
169 	if (strcmp(mountpoint, ZFS_MOUNTPOINT_NONE) == 0 ||
170 	    strcmp(mountpoint, ZFS_MOUNTPOINT_LEGACY) == 0)
171 		return (0);
172 
173 	/*
174 	 * If the 'zoned' property is set, and we're in the global zone, simply
175 	 * return success.
176 	 */
177 	if (zfs_prop_get_int(zhp, ZFS_PROP_ZONED) &&
178 	    getzoneid() == GLOBAL_ZONEID)
179 		return (0);
180 
181 	/* Create the directory if it doesn't already exist */
182 	if (lstat(mountpoint, &buf) != 0) {
183 		if (mkdirp(mountpoint, 0755) != 0) {
184 			zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
185 			    "failed to create mountpoint"));
186 			return (zfs_error(hdl, EZFS_MOUNTFAILED,
187 			    dgettext(TEXT_DOMAIN, "cannot mount '%s'"),
188 			    mountpoint));
189 		}
190 	}
191 
192 	/*
193 	 * Determine if the mountpoint is empty.  If so, refuse to perform the
194 	 * mount.  We don't perform this check if MS_OVERLAY is specified, which
195 	 * would defeat the point.  We also avoid this check if 'remount' is
196 	 * specified.
197 	 */
198 	if ((flags & MS_OVERLAY) == 0 &&
199 	    strstr(mntopts, MNTOPT_REMOUNT) == NULL &&
200 	    !dir_is_empty(mountpoint)) {
201 		zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
202 		    "directory is not empty"));
203 		return (zfs_error(hdl, EZFS_MOUNTFAILED,
204 		    dgettext(TEXT_DOMAIN, "cannot mount '%s'"), mountpoint));
205 	}
206 
207 	/* perform the mount */
208 	if (mount(zfs_get_name(zhp), mountpoint, MS_OPTIONSTR | flags,
209 	    MNTTYPE_ZFS, NULL, 0, mntopts, sizeof (mntopts)) != 0) {
210 		/*
211 		 * Generic errors are nasty, but there are just way too many
212 		 * from mount(), and they're well-understood.  We pick a few
213 		 * common ones to improve upon.
214 		 */
215 		if (errno == EBUSY)
216 			zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
217 			    "mountpoint or dataset is busy"));
218 		else
219 			zfs_error_aux(hdl, strerror(errno));
220 
221 		return (zfs_error(hdl, EZFS_MOUNTFAILED,
222 		    dgettext(TEXT_DOMAIN, "cannot mount '%s'"),
223 		    zhp->zfs_name));
224 	}
225 
226 	return (0);
227 }
228 
229 /*
230  * Unmount the given filesystem.
231  */
232 int
233 zfs_unmount(zfs_handle_t *zhp, const char *mountpoint, int flags)
234 {
235 	struct mnttab search = { 0 }, entry;
236 
237 	/* check to see if need to unmount the filesystem */
238 	search.mnt_special = (char *)zfs_get_name(zhp);
239 	search.mnt_fstype = MNTTYPE_ZFS;
240 	rewind(zhp->zfs_hdl->libzfs_mnttab);
241 	if (mountpoint != NULL || ((zfs_get_type(zhp) == ZFS_TYPE_FILESYSTEM) &&
242 	    getmntany(zhp->zfs_hdl->libzfs_mnttab, &entry, &search) == 0)) {
243 
244 		if (mountpoint == NULL)
245 			mountpoint = entry.mnt_mountp;
246 
247 		/*
248 		 * Always unshare the filesystem first.
249 		 */
250 		if (zfs_unshare(zhp, mountpoint) != 0)
251 			return (-1);
252 
253 		/*
254 		 * Try to unmount the filesystem.  There is no reason to try a
255 		 * forced unmount because the vnodes will still carry a
256 		 * reference to the underlying dataset, so we can't destroy it
257 		 * anyway.
258 		 *
259 		 * In the unmount case, we print out a slightly more informative
260 		 * error message, though we'll be relying on the poor error
261 		 * semantics from the kernel.
262 		 */
263 		if (umount2(mountpoint, flags) != 0) {
264 			zfs_error_aux(zhp->zfs_hdl, strerror(errno));
265 			return (zfs_error(zhp->zfs_hdl, EZFS_UMOUNTFAILED,
266 			    dgettext(TEXT_DOMAIN, "cannot unmount '%s'"),
267 			    mountpoint));
268 		}
269 
270 		/*
271 		 * Don't actually destroy the underlying directory
272 		 */
273 	}
274 
275 	return (0);
276 }
277 
278 /*
279  * Unmount this filesystem and any children inheriting the mountpoint property.
280  * To do this, just act like we're changing the mountpoint property, but don't
281  * remount the filesystems afterwards.
282  */
283 int
284 zfs_unmountall(zfs_handle_t *zhp, int flags)
285 {
286 	prop_changelist_t *clp;
287 	int ret;
288 
289 	clp = changelist_gather(zhp, ZFS_PROP_MOUNTPOINT, flags);
290 	if (clp == NULL)
291 		return (-1);
292 
293 	ret = changelist_prefix(clp);
294 	changelist_free(clp);
295 
296 	return (ret);
297 }
298 
299 /*
300  * Check to see if the filesystem is currently shared.
301  */
302 boolean_t
303 zfs_is_shared(zfs_handle_t *zhp, char **where)
304 {
305 	char *mountpoint;
306 
307 	if (!zfs_is_mounted(zhp, &mountpoint))
308 		return (B_FALSE);
309 
310 	if (is_shared(zhp->zfs_hdl, mountpoint)) {
311 		if (where != NULL)
312 			*where = mountpoint;
313 		else
314 			free(mountpoint);
315 		return (B_TRUE);
316 	} else {
317 		free(mountpoint);
318 		return (B_FALSE);
319 	}
320 }
321 
322 /*
323  * Share the given filesystem according to the options in 'sharenfs'.  We rely
324  * on share(1M) to the dirty work for us.
325  */
326 int
327 zfs_share(zfs_handle_t *zhp)
328 {
329 	char mountpoint[ZFS_MAXPROPLEN];
330 	char shareopts[ZFS_MAXPROPLEN];
331 	char buf[MAXPATHLEN];
332 	FILE *fp;
333 	libzfs_handle_t *hdl = zhp->zfs_hdl;
334 
335 	/* ignore non-filesystems */
336 	if (zfs_get_type(zhp) != ZFS_TYPE_FILESYSTEM)
337 		return (0);
338 
339 	/* return success if there is no mountpoint set */
340 	if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT,
341 	    mountpoint, sizeof (mountpoint), NULL, NULL, 0, B_FALSE) != 0 ||
342 	    strcmp(mountpoint, ZFS_MOUNTPOINT_NONE) == 0 ||
343 	    strcmp(mountpoint, ZFS_MOUNTPOINT_LEGACY) == 0)
344 		return (0);
345 
346 	/* return success if there are no share options */
347 	if (zfs_prop_get(zhp, ZFS_PROP_SHARENFS, shareopts, sizeof (shareopts),
348 	    NULL, NULL, 0, B_FALSE) != 0 ||
349 	    strcmp(shareopts, "off") == 0)
350 		return (0);
351 
352 	/*
353 	 * If the 'zoned' property is set, simply return success since:
354 	 * 1. in a global zone, a dataset should not be shared if it's
355 	 *    managed in a local zone.
356 	 * 2. in a local zone, NFS server is not available.
357 	 */
358 	if (zfs_prop_get_int(zhp, ZFS_PROP_ZONED)) {
359 		return (0);
360 	}
361 
362 	/*
363 	 * Invoke the share(1M) command.  We always do this, even if it's
364 	 * currently shared, as the options may have changed.
365 	 */
366 	if (strcmp(shareopts, "on") == 0)
367 		(void) snprintf(buf, sizeof (buf), "/usr/sbin/share "
368 		    "-F nfs \"%s\" 2>&1", mountpoint);
369 	else
370 		(void) snprintf(buf, sizeof (buf), "/usr/sbin/share "
371 		    "-F nfs -o \"%s\" \"%s\" 2>&1", shareopts,
372 		    mountpoint);
373 
374 	if ((fp = popen(buf, "r")) == NULL)
375 		return (zfs_error(hdl, EZFS_SHAREFAILED,
376 		    dgettext(TEXT_DOMAIN, "cannot share '%s'"),
377 		    zfs_get_name(zhp)));
378 
379 	/*
380 	 * share(1M) should only produce output if there is some kind
381 	 * of error.  All output begins with "share_nfs: ", so we trim
382 	 * this off to get to the real error.
383 	 */
384 	if (fgets(buf, sizeof (buf), fp) != NULL) {
385 		char *colon = strchr(buf, ':');
386 
387 		while (buf[strlen(buf) - 1] == '\n')
388 			buf[strlen(buf) - 1] = '\0';
389 
390 		if (colon != NULL)
391 			zfs_error_aux(hdl, colon + 2);
392 
393 		(void) zfs_error(hdl, EZFS_SHAREFAILED,
394 		    dgettext(TEXT_DOMAIN, "cannot share '%s'"));
395 
396 		verify(pclose(fp) != 0);
397 		return (-1);
398 	}
399 
400 	verify(pclose(fp) == 0);
401 
402 	return (0);
403 }
404 
405 /*
406  * Unshare the given filesystem.
407  */
408 int
409 zfs_unshare(zfs_handle_t *zhp, const char *mountpoint)
410 {
411 	char buf[MAXPATHLEN];
412 	struct mnttab search = { 0 }, entry;
413 	libzfs_handle_t *hdl = zhp->zfs_hdl;
414 
415 	/* check to see if need to unmount the filesystem */
416 	search.mnt_special = (char *)zfs_get_name(zhp);
417 	search.mnt_fstype = MNTTYPE_ZFS;
418 	rewind(zhp->zfs_hdl->libzfs_mnttab);
419 	if (mountpoint != NULL || ((zfs_get_type(zhp) == ZFS_TYPE_FILESYSTEM) &&
420 	    getmntany(zhp->zfs_hdl->libzfs_mnttab, &entry, &search) == 0)) {
421 
422 		if (mountpoint == NULL)
423 			mountpoint = entry.mnt_mountp;
424 
425 		if (is_shared(zhp->zfs_hdl, mountpoint)) {
426 			FILE *fp;
427 
428 			(void) snprintf(buf, sizeof (buf),
429 			    "/usr/sbin/unshare  \"%s\" 2>&1",
430 			    mountpoint);
431 
432 			if ((fp = popen(buf, "r")) == NULL)
433 				return (zfs_error(hdl, EZFS_UNSHAREFAILED,
434 				    dgettext(TEXT_DOMAIN,
435 				    "cannot unshare '%s'"), zfs_get_name(zhp)));
436 
437 			/*
438 			 * unshare(1M) should only produce output if there is
439 			 * some kind of error.  All output begins with "unshare
440 			 * nfs: ", so we trim this off to get to the real error.
441 			 */
442 			if (fgets(buf, sizeof (buf), fp) != NULL) {
443 				char *colon = strchr(buf, ':');
444 
445 				while (buf[strlen(buf) - 1] == '\n')
446 					buf[strlen(buf) - 1] = '\0';
447 
448 				if (colon != NULL)
449 					zfs_error_aux(hdl, colon + 2);
450 
451 				verify(pclose(fp) != 0);
452 
453 				return (zfs_error(hdl, EZFS_UNSHAREFAILED,
454 				    dgettext(TEXT_DOMAIN,
455 				    "cannot unshare '%s'"), zfs_get_name(zhp)));
456 			}
457 
458 			verify(pclose(fp) == 0);
459 		}
460 	}
461 
462 	return (0);
463 }
464 
465 /*
466  * Same as zfs_unmountall(), but for unshares.
467  */
468 int
469 zfs_unshareall(zfs_handle_t *zhp)
470 {
471 	prop_changelist_t *clp;
472 	int ret;
473 
474 	clp = changelist_gather(zhp, ZFS_PROP_SHARENFS, 0);
475 	if (clp == NULL)
476 		return (-1);
477 
478 	ret = changelist_unshare(clp);
479 	changelist_free(clp);
480 
481 	return (ret);
482 }
483 
484 /*
485  * Remove the mountpoint associated with the current dataset, if necessary.
486  * We only remove the underlying directory if:
487  *
488  *	- The mountpoint is not 'none' or 'legacy'
489  *	- The mountpoint is non-empty
490  *	- The mountpoint is the default or inherited
491  *	- The 'zoned' property is set, or we're in a local zone
492  *
493  * Any other directories we leave alone.
494  */
495 void
496 remove_mountpoint(zfs_handle_t *zhp)
497 {
498 	char mountpoint[ZFS_MAXPROPLEN];
499 	char source[ZFS_MAXNAMELEN];
500 	zfs_source_t sourcetype;
501 	int zoneid = getzoneid();
502 
503 	/* ignore non-filesystems */
504 	if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, mountpoint,
505 	    sizeof (mountpoint), &sourcetype, source, sizeof (source),
506 	    B_FALSE) != 0)
507 		return;
508 
509 	if (strcmp(mountpoint, ZFS_MOUNTPOINT_NONE) != 0 &&
510 	    strcmp(mountpoint, ZFS_MOUNTPOINT_LEGACY) != 0 &&
511 	    (sourcetype == ZFS_SRC_DEFAULT ||
512 	    sourcetype == ZFS_SRC_INHERITED) &&
513 	    (!zfs_prop_get_int(zhp, ZFS_PROP_ZONED) ||
514 	    zoneid != GLOBAL_ZONEID)) {
515 
516 		/*
517 		 * Try to remove the directory, silently ignoring any errors.
518 		 * The filesystem may have since been removed or moved around,
519 		 * and this isn't really useful to the administrator in any
520 		 * way.
521 		 */
522 		(void) rmdir(mountpoint);
523 	}
524 }
525