summaryrefslogtreecommitdiff
path: root/usr/src/lib/libzfs/common/libzfs_mount.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/lib/libzfs/common/libzfs_mount.c')
-rw-r--r--usr/src/lib/libzfs/common/libzfs_mount.c384
1 files changed, 322 insertions, 62 deletions
diff --git a/usr/src/lib/libzfs/common/libzfs_mount.c b/usr/src/lib/libzfs/common/libzfs_mount.c
index 894bcc0d03..5eab867e36 100644
--- a/usr/src/lib/libzfs/common/libzfs_mount.c
+++ b/usr/src/lib/libzfs/common/libzfs_mount.c
@@ -42,6 +42,12 @@
* zfs_share()
* zfs_unshare()
* zfs_unshareall()
+ *
+ * The following functions are available for pool consumers, and will
+ * mount/unmount (and share/unshare) all datasets within pool:
+ *
+ * zpool_mount_datasets()
+ * zpool_unmount_datasets()
*/
#include <dirent.h>
@@ -227,6 +233,22 @@ zfs_mount(zfs_handle_t *zhp, const char *options, int flags)
}
/*
+ * Unmount a single filesystem.
+ */
+static int
+unmount_one(libzfs_handle_t *hdl, const char *mountpoint, int flags)
+{
+ if (umount2(mountpoint, flags) != 0) {
+ zfs_error_aux(hdl, strerror(errno));
+ return (zfs_error(hdl, EZFS_UMOUNTFAILED,
+ dgettext(TEXT_DOMAIN, "cannot unmount '%s'"),
+ mountpoint));
+ }
+
+ return (0);
+}
+
+/*
* Unmount the given filesystem.
*/
int
@@ -235,7 +257,7 @@ zfs_unmount(zfs_handle_t *zhp, const char *mountpoint, int flags)
struct mnttab search = { 0 }, entry;
/* check to see if need to unmount the filesystem */
- search.mnt_special = (char *)zfs_get_name(zhp);
+ search.mnt_special = zhp->zfs_name;
search.mnt_fstype = MNTTYPE_ZFS;
rewind(zhp->zfs_hdl->libzfs_mnttab);
if (mountpoint != NULL || ((zfs_get_type(zhp) == ZFS_TYPE_FILESYSTEM) &&
@@ -245,31 +267,11 @@ zfs_unmount(zfs_handle_t *zhp, const char *mountpoint, int flags)
mountpoint = entry.mnt_mountp;
/*
- * Always unshare the filesystem first.
+ * Unshare and unmount the filesystem
*/
- if (zfs_unshare(zhp, mountpoint) != 0)
+ if (zfs_unshare(zhp, mountpoint) != 0 ||
+ unmount_one(zhp->zfs_hdl, mountpoint, flags) != 0)
return (-1);
-
- /*
- * Try to unmount the filesystem. There is no reason to try a
- * forced unmount because the vnodes will still carry a
- * reference to the underlying dataset, so we can't destroy it
- * anyway.
- *
- * In the unmount case, we print out a slightly more informative
- * error message, though we'll be relying on the poor error
- * semantics from the kernel.
- */
- if (umount2(mountpoint, flags) != 0) {
- zfs_error_aux(zhp->zfs_hdl, strerror(errno));
- return (zfs_error(zhp->zfs_hdl, EZFS_UMOUNTFAILED,
- dgettext(TEXT_DOMAIN, "cannot unmount '%s'"),
- mountpoint));
- }
-
- /*
- * Don't actually destroy the underlying directory
- */
}
return (0);
@@ -391,7 +393,8 @@ zfs_share(zfs_handle_t *zhp)
zfs_error_aux(hdl, colon + 2);
(void) zfs_error(hdl, EZFS_SHAREFAILED,
- dgettext(TEXT_DOMAIN, "cannot share '%s'"));
+ dgettext(TEXT_DOMAIN, "cannot share '%s'"),
+ zfs_get_name(zhp));
verify(pclose(fp) != 0);
return (-1);
@@ -403,14 +406,56 @@ zfs_share(zfs_handle_t *zhp)
}
/*
+ * Unshare a filesystem by mountpoint.
+ */
+static int
+unshare_one(libzfs_handle_t *hdl, const char *name, const char *mountpoint)
+{
+ char buf[MAXPATHLEN];
+ FILE *fp;
+
+ (void) snprintf(buf, sizeof (buf),
+ "/usr/sbin/unshare \"%s\" 2>&1",
+ mountpoint);
+
+ if ((fp = popen(buf, "r")) == NULL)
+ return (zfs_error(hdl, EZFS_UNSHAREFAILED,
+ dgettext(TEXT_DOMAIN,
+ "cannot unshare '%s'"), name));
+
+ /*
+ * unshare(1M) should only produce output if there is
+ * some kind of error. All output begins with "unshare
+ * nfs: ", so we trim this off to get to the real error.
+ */
+ if (fgets(buf, sizeof (buf), fp) != NULL) {
+ char *colon = strchr(buf, ':');
+
+ while (buf[strlen(buf) - 1] == '\n')
+ buf[strlen(buf) - 1] = '\0';
+
+ if (colon != NULL)
+ zfs_error_aux(hdl, colon + 2);
+
+ verify(pclose(fp) != 0);
+
+ return (zfs_error(hdl, EZFS_UNSHAREFAILED,
+ dgettext(TEXT_DOMAIN,
+ "cannot unshare '%s'"), name));
+ }
+
+ verify(pclose(fp) == 0);
+
+ return (0);
+}
+
+/*
* Unshare the given filesystem.
*/
int
zfs_unshare(zfs_handle_t *zhp, const char *mountpoint)
{
- char buf[MAXPATHLEN];
struct mnttab search = { 0 }, entry;
- libzfs_handle_t *hdl = zhp->zfs_hdl;
/* check to see if need to unmount the filesystem */
search.mnt_special = (char *)zfs_get_name(zhp);
@@ -422,41 +467,9 @@ zfs_unshare(zfs_handle_t *zhp, const char *mountpoint)
if (mountpoint == NULL)
mountpoint = entry.mnt_mountp;
- if (is_shared(zhp->zfs_hdl, mountpoint)) {
- FILE *fp;
-
- (void) snprintf(buf, sizeof (buf),
- "/usr/sbin/unshare \"%s\" 2>&1",
- mountpoint);
-
- if ((fp = popen(buf, "r")) == NULL)
- return (zfs_error(hdl, EZFS_UNSHAREFAILED,
- dgettext(TEXT_DOMAIN,
- "cannot unshare '%s'"), zfs_get_name(zhp)));
-
- /*
- * unshare(1M) should only produce output if there is
- * some kind of error. All output begins with "unshare
- * nfs: ", so we trim this off to get to the real error.
- */
- if (fgets(buf, sizeof (buf), fp) != NULL) {
- char *colon = strchr(buf, ':');
-
- while (buf[strlen(buf) - 1] == '\n')
- buf[strlen(buf) - 1] = '\0';
-
- if (colon != NULL)
- zfs_error_aux(hdl, colon + 2);
-
- verify(pclose(fp) != 0);
-
- return (zfs_error(hdl, EZFS_UNSHAREFAILED,
- dgettext(TEXT_DOMAIN,
- "cannot unshare '%s'"), zfs_get_name(zhp)));
- }
-
- verify(pclose(fp) == 0);
- }
+ if (is_shared(zhp->zfs_hdl, mountpoint) &&
+ unshare_one(zhp->zfs_hdl, zhp->zfs_name, mountpoint) != 0)
+ return (-1);
}
return (0);
@@ -522,3 +535,250 @@ remove_mountpoint(zfs_handle_t *zhp)
(void) rmdir(mountpoint);
}
}
+
+/*
+ * Mount and share all datasets within the given pool. This assumes that no
+ * datasets within the pool are currently mounted. Because users can create
+ * complicated nested hierarchies of mountpoints, we first gather all the
+ * datasets and mountpoints within the pool, and sort them by mountpoint. Once
+ * we have the list of all filesystems, we iterate over them in order and mount
+ * and/or share each one.
+ */
+typedef struct mount_cbdata {
+ zfs_handle_t **cb_datasets;
+ int cb_used;
+ int cb_alloc;
+} mount_cbdata_t;
+
+static int
+mount_cb(zfs_handle_t *zhp, void *data)
+{
+ mount_cbdata_t *cbp = data;
+
+ if (zfs_get_type(zhp) != ZFS_TYPE_FILESYSTEM) {
+ zfs_close(zhp);
+ return (0);
+ }
+
+ if (cbp->cb_alloc == cbp->cb_used) {
+ zfs_handle_t **datasets;
+
+ if ((datasets = zfs_alloc(zhp->zfs_hdl, cbp->cb_alloc * 2 *
+ sizeof (void *))) == NULL)
+ return (-1);
+
+ (void) memcpy(cbp->cb_datasets, datasets,
+ cbp->cb_alloc * sizeof (void *));
+ free(cbp->cb_datasets);
+ cbp->cb_datasets = datasets;
+ }
+
+ cbp->cb_datasets[cbp->cb_used++] = zhp;
+ return (0);
+}
+
+static int
+dataset_compare(const void *a, const void *b)
+{
+ zfs_handle_t **za = (zfs_handle_t **)a;
+ zfs_handle_t **zb = (zfs_handle_t **)b;
+ char mounta[MAXPATHLEN];
+ char mountb[MAXPATHLEN];
+
+ verify(zfs_prop_get(*za, ZFS_PROP_MOUNTPOINT, mounta,
+ sizeof (mounta), NULL, NULL, 0, B_FALSE) == 0);
+ verify(zfs_prop_get(*zb, ZFS_PROP_MOUNTPOINT, mountb,
+ sizeof (mountb), NULL, NULL, 0, B_FALSE) == 0);
+
+ return (strcmp(mounta, mountb));
+}
+
+int
+zpool_mount_datasets(zpool_handle_t *zhp, const char *mntopts)
+{
+ mount_cbdata_t cb = { 0 };
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+ zfs_handle_t *zfsp;
+ int i, ret = -1;
+
+ /*
+ * Gather all datasets within the pool.
+ */
+ if ((cb.cb_datasets = zfs_alloc(hdl, 4 * sizeof (void *))) == NULL)
+ return (-1);
+ cb.cb_alloc = 4;
+
+ if ((zfsp = zfs_open(hdl, zhp->zpool_name, ZFS_TYPE_ANY)) == NULL)
+ goto out;
+
+ cb.cb_datasets[0] = zfsp;
+ cb.cb_used = 1;
+
+ if (zfs_iter_children(zfsp, mount_cb, &cb) != 0)
+ goto out;
+
+ /*
+ * Sort the datasets by mountpoint.
+ */
+ qsort(cb.cb_datasets, cb.cb_used, sizeof (void *), dataset_compare);
+
+ /*
+ * And mount all the datasets.
+ */
+ ret = 0;
+ for (i = 0; i < cb.cb_used; i++) {
+ if (zfs_mount(cb.cb_datasets[i], mntopts, 0) != 0 ||
+ zfs_share(cb.cb_datasets[i]) != 0)
+ ret = -1;
+ }
+
+out:
+ for (i = 0; i < cb.cb_used; i++)
+ zfs_close(cb.cb_datasets[i]);
+ free(cb.cb_datasets);
+
+ return (ret);
+}
+
+/*
+ * Unshare and unmount all datasets within the given pool. We don't want to
+ * rely on traversing the DSL to discover the filesystems within the pool,
+ * because this may be expensive (if not all of them are mounted), and can fail
+ * arbitrarily (on I/O error, for example). Instead, we walk /etc/mnttab and
+ * gather all the filesystems that are currently mounted.
+ */
+static int
+mountpoint_compare(const void *a, const void *b)
+{
+ const char *mounta = *((char **)a);
+ const char *mountb = *((char **)b);
+
+ return (strcmp(mountb, mounta));
+}
+
+int
+zpool_unmount_datasets(zpool_handle_t *zhp, boolean_t force)
+{
+ int used, alloc;
+ struct mnttab entry;
+ size_t namelen;
+ char **mountpoints = NULL;
+ zfs_handle_t **datasets = NULL;
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+ int i;
+ int ret = -1;
+ int flags = (force ? MS_FORCE : 0);
+
+ namelen = strlen(zhp->zpool_name);
+
+ rewind(hdl->libzfs_mnttab);
+ used = alloc = 0;
+ while (getmntent(hdl->libzfs_mnttab, &entry) == 0) {
+ /*
+ * Ignore non-ZFS entries.
+ */
+ if (entry.mnt_fstype == NULL ||
+ strcmp(entry.mnt_fstype, MNTTYPE_ZFS) != 0)
+ continue;
+
+ /*
+ * Ignore filesystems not within this pool.
+ */
+ if (entry.mnt_mountp == NULL ||
+ strncmp(entry.mnt_special, zhp->zpool_name, namelen) != 0 ||
+ (entry.mnt_special[namelen] != '/' &&
+ entry.mnt_special[namelen] != '\0'))
+ continue;
+
+ /*
+ * At this point we've found a filesystem within our pool. Add
+ * it to our growing list.
+ */
+ if (used == alloc) {
+ if (alloc == 0) {
+ if ((mountpoints = zfs_alloc(hdl,
+ 8 * sizeof (void *))) == NULL)
+ goto out;
+
+ if ((datasets = zfs_alloc(hdl,
+ 8 * sizeof (void *))) == NULL)
+ goto out;
+
+ alloc = 8;
+ } else {
+ char **dest;
+
+ if ((dest = zfs_alloc(hdl,
+ alloc * 2 * sizeof (void *))) == NULL)
+ goto out;
+ (void) memcpy(dest, mountpoints,
+ alloc * sizeof (void *));
+ free(mountpoints);
+ mountpoints = dest;
+
+ if ((dest = zfs_alloc(hdl,
+ alloc * 2 * sizeof (void *))) == NULL)
+ goto out;
+ (void) memcpy(dest, datasets,
+ alloc * sizeof (void *));
+ free(datasets);
+ datasets = (zfs_handle_t **)dest;
+
+ alloc *= 2;
+ }
+ }
+
+ if ((mountpoints[used] = zfs_strdup(hdl,
+ entry.mnt_mountp)) == NULL)
+ goto out;
+
+ /*
+ * This is allowed to fail, in case there is some I/O error. It
+ * is only used to determine if we need to remove the underlying
+ * mountpoint, so failure is not fatal.
+ */
+ datasets[used] = make_dataset_handle(hdl, entry.mnt_special);
+
+ used++;
+ }
+
+ /*
+ * At this point, we have the entire list of filesystems, so sort it by
+ * mountpoint.
+ */
+ qsort(mountpoints, used, sizeof (char *), mountpoint_compare);
+
+ /*
+ * Walk through and first unshare everything.
+ */
+ for (i = 0; i < used; i++) {
+ if (is_shared(hdl, mountpoints[i]) &&
+ unshare_one(hdl, datasets[i] ? datasets[i]->zfs_name :
+ mountpoints[i], mountpoints[i]) != 0)
+ goto out;
+ }
+
+ /*
+ * Now unmount everything, removing the underlying directories as
+ * appropriate.
+ */
+ for (i = 0; i < used; i++) {
+ if (unmount_one(hdl, mountpoints[i], flags) != 0)
+ goto out;
+
+ if (datasets[i])
+ remove_mountpoint(datasets[i]);
+ }
+
+ ret = 0;
+out:
+ for (i = 0; i < used; i++) {
+ if (datasets[i])
+ zfs_close(datasets[i]);
+ free(mountpoints[i]);
+ }
+ free(datasets);
+ free(mountpoints);
+
+ return (ret);
+}