diff options
Diffstat (limited to 'usr/src/lib')
-rw-r--r-- | usr/src/lib/libzfs/common/libzfs.h | 9 | ||||
-rw-r--r-- | usr/src/lib/libzfs/common/libzfs_changelist.c | 79 | ||||
-rw-r--r-- | usr/src/lib/libzfs/common/libzfs_config.c | 3 | ||||
-rw-r--r-- | usr/src/lib/libzfs/common/libzfs_dataset.c | 59 | ||||
-rw-r--r-- | usr/src/lib/libzfs/common/libzfs_graph.c | 109 | ||||
-rw-r--r-- | usr/src/lib/libzfs/common/libzfs_impl.h | 4 | ||||
-rw-r--r-- | usr/src/lib/libzfs/common/libzfs_import.c | 7 | ||||
-rw-r--r-- | usr/src/lib/libzfs/common/libzfs_mount.c | 384 | ||||
-rw-r--r-- | usr/src/lib/libzfs/common/libzfs_util.c | 2 | ||||
-rw-r--r-- | usr/src/lib/libzfs/spec/libzfs.spec | 8 | ||||
-rw-r--r-- | usr/src/lib/libzfs_jni/common/libzfs_jni_dataset.c | 2 |
11 files changed, 534 insertions, 132 deletions
diff --git a/usr/src/lib/libzfs/common/libzfs.h b/usr/src/lib/libzfs/common/libzfs.h index dcaccba2d3..a60957df6f 100644 --- a/usr/src/lib/libzfs/common/libzfs.h +++ b/usr/src/lib/libzfs/common/libzfs.h @@ -89,6 +89,7 @@ enum { EZFS_INTR, /* signal received */ EZFS_ISSPARE, /* device is a hot spare */ EZFS_INVALCONFIG, /* invalid vdev configuration */ + EZFS_RECURSIVE, /* recursive dependency */ EZFS_UNKNOWN /* unknown error */ }; @@ -274,7 +275,7 @@ int zfs_get_proplist(char *fields, zfs_prop_t *proplist, int max, int *count, typedef int (*zfs_iter_f)(zfs_handle_t *, void *); extern int zfs_iter_root(libzfs_handle_t *, zfs_iter_f, void *); extern int zfs_iter_children(zfs_handle_t *, zfs_iter_f, void *); -extern int zfs_iter_dependents(zfs_handle_t *, zfs_iter_f, void *); +extern int zfs_iter_dependents(zfs_handle_t *, boolean_t, zfs_iter_f, void *); extern int zfs_iter_filesystems(zfs_handle_t *, zfs_iter_f, void *); extern int zfs_iter_snapshots(zfs_handle_t *, zfs_iter_f, void *); @@ -354,6 +355,12 @@ extern int zpool_read_label(int, nvlist_t **); extern int zpool_create_zvol_links(zpool_handle_t *); extern int zpool_remove_zvol_links(zpool_handle_t *); +/* + * Mount and unmount datasets within a pool + */ +extern int zpool_mount_datasets(zpool_handle_t *, const char *); +extern int zpool_unmount_datasets(zpool_handle_t *, boolean_t); + #ifdef __cplusplus } #endif diff --git a/usr/src/lib/libzfs/common/libzfs_changelist.c b/usr/src/lib/libzfs/common/libzfs_changelist.c index ed1b291ef3..7b7b5cd9d4 100644 --- a/usr/src/lib/libzfs/common/libzfs_changelist.c +++ b/usr/src/lib/libzfs/common/libzfs_changelist.c @@ -77,6 +77,7 @@ struct prop_changelist { boolean_t cl_alldependents; int cl_flags; boolean_t cl_haszonedchild; + boolean_t cl_sorted; }; /* @@ -384,15 +385,20 @@ change_one(zfs_handle_t *zhp, void *data) uu_list_node_init(cn, &cn->cn_listnode, clp->cl_pool); - if (clp->cl_alldependents) { - verify(uu_list_insert_after(clp->cl_list, - uu_list_last(clp->cl_list), cn) == 0); - return (0); + if (clp->cl_sorted) { + uu_list_index_t idx; + + (void) uu_list_find(clp->cl_list, cn, NULL, + &idx); + uu_list_insert(clp->cl_list, cn, idx); } else { + ASSERT(!clp->cl_alldependents); verify(uu_list_insert_before(clp->cl_list, uu_list_first(clp->cl_list), cn) == 0); - return (zfs_iter_children(zhp, change_one, data)); } + + if (!clp->cl_alldependents) + return (zfs_iter_children(zhp, change_one, data)); } else { zfs_close(zhp); } @@ -400,6 +406,40 @@ change_one(zfs_handle_t *zhp, void *data) return (0); } +/*ARGSUSED*/ +static int +compare_mountpoints(const void *a, const void *b, void *unused) +{ + const prop_changenode_t *ca = a; + const prop_changenode_t *cb = b; + + char mounta[MAXPATHLEN]; + char mountb[MAXPATHLEN]; + + boolean_t hasmounta, hasmountb; + + /* + * When unsharing or unmounting filesystems, we need to do it in + * mountpoint order. This allows the user to have a mountpoint + * hierarchy that is different from the dataset hierarchy, and still + * allow it to be changed. However, if either dataset doesn't have a + * mountpoint (because it is a volume or a snapshot), we place it at the + * end of the list, because it doesn't affect our change at all. + */ + hasmounta = (zfs_prop_get(ca->cn_handle, ZFS_PROP_MOUNTPOINT, mounta, + sizeof (mounta), NULL, NULL, 0, B_FALSE) == 0); + hasmountb = (zfs_prop_get(cb->cn_handle, ZFS_PROP_MOUNTPOINT, mountb, + sizeof (mountb), NULL, NULL, 0, B_FALSE) == 0); + + if (!hasmounta && hasmountb) + return (-1); + else if (hasmounta && !hasmountb) + return (1); + else if (!hasmounta && !hasmountb) + return (0); + else + return (strcmp(mountb, mounta)); +} /* * Given a ZFS handle and a property, construct a complete list of datasets that @@ -416,14 +456,26 @@ changelist_gather(zfs_handle_t *zhp, zfs_prop_t prop, int flags) prop_changenode_t *cn; zfs_handle_t *temp; char property[ZFS_MAXPROPLEN]; + uu_compare_fn_t *compare = NULL; if ((clp = zfs_alloc(zhp->zfs_hdl, sizeof (prop_changelist_t))) == NULL) return (NULL); + /* + * For mountpoint-related tasks, we want to sort everything by + * mountpoint, so that we mount and unmount them in the appropriate + * order, regardless of their position in the hierarchy. + */ + if (prop == ZFS_PROP_NAME || prop == ZFS_PROP_ZONED || + prop == ZFS_PROP_MOUNTPOINT || prop == ZFS_PROP_SHARENFS) { + compare = compare_mountpoints; + clp->cl_sorted = B_TRUE; + } + clp->cl_pool = uu_list_pool_create("changelist_pool", sizeof (prop_changenode_t), offsetof(prop_changenode_t, cn_listnode), - NULL, 0); + compare, 0); if (clp->cl_pool == NULL) { assert(uu_error() == UU_ERROR_NO_MEMORY); (void) zfs_error(zhp->zfs_hdl, EZFS_NOMEM, "internal error"); @@ -431,7 +483,8 @@ changelist_gather(zfs_handle_t *zhp, zfs_prop_t prop, int flags) return (NULL); } - clp->cl_list = uu_list_create(clp->cl_pool, NULL, 0); + clp->cl_list = uu_list_create(clp->cl_pool, NULL, + clp->cl_sorted ? UU_LIST_SORTED : 0); clp->cl_flags = flags; if (clp->cl_list == NULL) { @@ -465,7 +518,7 @@ changelist_gather(zfs_handle_t *zhp, zfs_prop_t prop, int flags) return (clp); if (clp->cl_alldependents) { - if (zfs_iter_dependents(zhp, change_one, clp) != 0) { + if (zfs_iter_dependents(zhp, B_TRUE, change_one, clp) != 0) { changelist_free(clp); return (NULL); } @@ -501,8 +554,14 @@ changelist_gather(zfs_handle_t *zhp, zfs_prop_t prop, int flags) cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED); uu_list_node_init(cn, &cn->cn_listnode, clp->cl_pool); - verify(uu_list_insert_after(clp->cl_list, - uu_list_last(clp->cl_list), cn) == 0); + if (clp->cl_sorted) { + uu_list_index_t idx; + (void) uu_list_find(clp->cl_list, cn, NULL, &idx); + uu_list_insert(clp->cl_list, cn, idx); + } else { + verify(uu_list_insert_after(clp->cl_list, + uu_list_last(clp->cl_list), cn) == 0); + } /* * If the property was previously 'legacy' or 'none', record this fact, diff --git a/usr/src/lib/libzfs/common/libzfs_config.c b/usr/src/lib/libzfs/common/libzfs_config.c index 53f6bc2df4..593131cea6 100644 --- a/usr/src/lib/libzfs/common/libzfs_config.c +++ b/usr/src/lib/libzfs/common/libzfs_config.c @@ -211,11 +211,14 @@ namespace_reload(libzfs_handle_t *hdl) if ((cn->cn_name = zfs_strdup(hdl, nvpair_name(elem))) == NULL) { free(cn); + nvlist_free(config); return (-1); } verify(nvpair_value_nvlist(elem, &child) == 0); if (nvlist_dup(child, &cn->cn_config, 0) != 0) { + free(cn->cn_name); + free(cn); nvlist_free(config); return (no_memory(hdl)); } diff --git a/usr/src/lib/libzfs/common/libzfs_dataset.c b/usr/src/lib/libzfs/common/libzfs_dataset.c index 5b766f7818..4d150bbf79 100644 --- a/usr/src/lib/libzfs/common/libzfs_dataset.c +++ b/usr/src/lib/libzfs/common/libzfs_dataset.c @@ -1084,6 +1084,33 @@ get_numeric_property(zfs_handle_t *zhp, zfs_prop_t prop, zfs_source_t *src, *source = NULL; + /* + * Because looking up the mount options is potentially expensive + * (iterating over all of /etc/mnttab), we defer its calculation until + * we're looking up a property which requires its presence. + */ + if (!zhp->zfs_mntcheck && + (prop == ZFS_PROP_ATIME || + prop == ZFS_PROP_DEVICES || + prop == ZFS_PROP_EXEC || + prop == ZFS_PROP_READONLY || + prop == ZFS_PROP_SETUID || + prop == ZFS_PROP_MOUNTED)) { + struct mnttab search = { 0 }, entry; + + search.mnt_special = (char *)zhp->zfs_name; + search.mnt_fstype = MNTTYPE_ZFS; + rewind(zhp->zfs_hdl->libzfs_mnttab); + + if (getmntany(zhp->zfs_hdl->libzfs_mnttab, &entry, + &search) == 0 && (zhp->zfs_mntopts = + zfs_strdup(zhp->zfs_hdl, + entry.mnt_mntopts)) == NULL) + return (-1); + + zhp->zfs_mntcheck = B_TRUE; + } + if (zhp->zfs_mntopts == NULL) mnt.mnt_mntopts = ""; else @@ -1229,26 +1256,6 @@ get_numeric_property(zfs_handle_t *zhp, zfs_prop_t prop, zfs_source_t *src, break; case ZFS_PROP_MOUNTED: - /* - * Unlike other properties, we defer calculation of 'MOUNTED' - * until actually requested. This is because the getmntany() - * call can be extremely expensive on systems with a large - * number of filesystems, and the property isn't needed in - * normal use cases. - */ - if (zhp->zfs_mntopts == NULL) { - struct mnttab search = { 0 }, entry; - - search.mnt_special = (char *)zhp->zfs_name; - search.mnt_fstype = MNTTYPE_ZFS; - rewind(zhp->zfs_hdl->libzfs_mnttab); - - if (getmntany(zhp->zfs_hdl->libzfs_mnttab, &entry, - &search) == 0 && (zhp->zfs_mntopts = - zfs_strdup(zhp->zfs_hdl, - entry.mnt_mntopts)) == NULL) - return (-1); - } *val = (zhp->zfs_mntopts != NULL); break; @@ -2729,7 +2736,9 @@ rollback_destroy(zfs_handle_t *zhp, void *data) cbp->cb_create) { cbp->cb_dependent = B_TRUE; - (void) zfs_iter_dependents(zhp, rollback_destroy, cbp); + if (zfs_iter_dependents(zhp, B_FALSE, rollback_destroy, + cbp) != 0) + cbp->cb_error = 1; cbp->cb_dependent = B_FALSE; if (zfs_destroy(zhp) != 0) @@ -2857,7 +2866,8 @@ out: * libzfs_graph.c. */ int -zfs_iter_dependents(zfs_handle_t *zhp, zfs_iter_f func, void *data) +zfs_iter_dependents(zfs_handle_t *zhp, boolean_t allowrecursion, + zfs_iter_f func, void *data) { char **dependents; size_t count; @@ -2865,7 +2875,10 @@ zfs_iter_dependents(zfs_handle_t *zhp, zfs_iter_f func, void *data) zfs_handle_t *child; int ret = 0; - dependents = get_dependents(zhp->zfs_hdl, zhp->zfs_name, &count); + if (get_dependents(zhp->zfs_hdl, allowrecursion, zhp->zfs_name, + &dependents, &count) != 0) + return (-1); + for (i = 0; i < count; i++) { if ((child = make_dataset_handle(zhp->zfs_hdl, dependents[i])) == NULL) diff --git a/usr/src/lib/libzfs/common/libzfs_graph.c b/usr/src/lib/libzfs/common/libzfs_graph.c index e86a6c9377..8ea76e51a6 100644 --- a/usr/src/lib/libzfs/common/libzfs_graph.c +++ b/usr/src/lib/libzfs/common/libzfs_graph.c @@ -60,6 +60,10 @@ * datasets. We do a topological ordering of our graph starting at our target * dataset, and then walk the results in reverse. * + * It's possible for the graph to have cycles if, for example, the user renames + * a clone to be the parent of its origin snapshot. The user can request to + * generate an error in this case, or ignore the cycle and continue. + * * When removing datasets, we want to destroy the snapshots in chronological * order (because this is the most efficient method). In order to accomplish * this, we store the creation transaction group with each vertex and keep each @@ -68,6 +72,7 @@ */ #include <assert.h> +#include <libintl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -96,6 +101,12 @@ typedef struct zfs_vertex { int zv_edgealloc; } zfs_vertex_t; +enum { + VISIT_SEEN = 1, + VISIT_SORT_PRE, + VISIT_SORT_POST +}; + /* * Edge structure. Simply maintains a pointer to the destination vertex. There * is no need to store the source vertex, since we only use edges in the context @@ -346,7 +357,7 @@ zfs_graph_add(libzfs_handle_t *hdl, zfs_graph_t *zgp, const char *source, if ((svp = zfs_graph_lookup(hdl, zgp, source, 0)) == NULL) return (-1); - svp->zv_visited = 1; + svp->zv_visited = VISIT_SEEN; if (dest != NULL) { dvp = zfs_graph_lookup(hdl, zgp, dest, txg); if (dvp == NULL) @@ -361,10 +372,9 @@ zfs_graph_add(libzfs_handle_t *hdl, zfs_graph_t *zgp, const char *source, /* * Iterate over all children of the given dataset, adding any vertices as * necessary. Returns 0 if no cloned snapshots were seen, -1 if there was an - * error, or 1 otherwise. This is - * a simple recursive algorithm - the ZFS namespace typically is very flat. We - * manually invoke the necessary ioctl() calls to avoid the overhead and - * additional semantics of zfs_open(). + * error, or 1 otherwise. This is a simple recursive algorithm - the ZFS + * namespace typically is very flat. We manually invoke the necessary ioctl() + * calls to avoid the overhead and additional semantics of zfs_open(). */ static int iterate_children(libzfs_handle_t *hdl, zfs_graph_t *zgp, const char *dataset) @@ -379,9 +389,22 @@ iterate_children(libzfs_handle_t *hdl, zfs_graph_t *zgp, const char *dataset) zvp = zfs_graph_lookup(hdl, zgp, dataset, 0); if (zvp == NULL) return (-1); - if (zvp->zv_visited) + if (zvp->zv_visited == VISIT_SEEN) return (0); + /* + * We check the clone parent here instead of within the loop, so that if + * the root dataset has been promoted from a clone, we find its parent + * appropriately. + */ + (void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name)); + if (ioctl(hdl->libzfs_fd, ZFS_IOC_OBJSET_STATS, &zc) == 0 && + zc.zc_objset_stats.dds_clone_of[0] != '\0') { + if (zfs_graph_add(hdl, zgp, zc.zc_objset_stats.dds_clone_of, + zc.zc_name, zc.zc_objset_stats.dds_creation_txg) != 0) + return (-1); + } + for ((void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name)); ioctl(hdl->libzfs_fd, ZFS_IOC_DATASET_LIST_NEXT, &zc) == 0; (void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name))) { @@ -408,14 +431,6 @@ iterate_children(libzfs_handle_t *hdl, zfs_graph_t *zgp, const char *dataset) return (-1); /* - * If this dataset has a clone parent, add an appropriate edge. - */ - if (zc.zc_objset_stats.dds_clone_of[0] != '\0' && - zfs_graph_add(hdl, zgp, zc.zc_objset_stats.dds_clone_of, - zc.zc_name, zc.zc_objset_stats.dds_creation_txg) != 0) - return (-1); - - /* * Iterate over all children */ err = iterate_children(hdl, zgp, zc.zc_name); @@ -462,7 +477,7 @@ iterate_children(libzfs_handle_t *hdl, zfs_graph_t *zgp, const char *dataset) ret = 1; } - zvp->zv_visited = 1; + zvp->zv_visited = VISIT_SEEN; return (ret); } @@ -532,22 +547,43 @@ construct_graph(libzfs_handle_t *hdl, const char *dataset) * hijack the 'zv_visited' marker to avoid visiting the same vertex twice. */ static int -topo_sort(libzfs_handle_t *hdl, char **result, size_t *idx, zfs_vertex_t *zgv) +topo_sort(libzfs_handle_t *hdl, boolean_t allowrecursion, char **result, + size_t *idx, zfs_vertex_t *zgv) { int i; - /* avoid doing a search if we don't have to */ - if (zgv->zv_visited == 2) + if (zgv->zv_visited == VISIT_SORT_PRE && !allowrecursion) { + /* + * If we've already seen this vertex as part of our depth-first + * search, then we have a cyclic dependency, and we must return + * an error. + */ + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "recursive dependency at '%s'"), + zgv->zv_dataset); + return (zfs_error(hdl, EZFS_RECURSIVE, + dgettext(TEXT_DOMAIN, + "cannot determine dependent datasets"))); + } else if (zgv->zv_visited >= VISIT_SORT_PRE) { + /* + * If we've already processed this as part of the topological + * sort, then don't bother doing so again. + */ return (0); + } + zgv->zv_visited = VISIT_SORT_PRE; + + /* avoid doing a search if we don't have to */ zfs_vertex_sort_edges(zgv); for (i = 0; i < zgv->zv_edgecount; i++) { - if (topo_sort(hdl, result, idx, zgv->zv_edges[i]->ze_dest) != 0) + if (topo_sort(hdl, allowrecursion, result, idx, + zgv->zv_edges[i]->ze_dest) != 0) return (-1); } /* we may have visited this in the course of the above */ - if (zgv->zv_visited == 2) + if (zgv->zv_visited == VISIT_SORT_POST) return (0); if ((result[*idx] = zfs_alloc(hdl, @@ -556,7 +592,7 @@ topo_sort(libzfs_handle_t *hdl, char **result, size_t *idx, zfs_vertex_t *zgv) (void) strcpy(result[*idx], zgv->zv_dataset); *idx += 1; - zgv->zv_visited = 2; + zgv->zv_visited = VISIT_SORT_POST; return (0); } @@ -564,34 +600,39 @@ topo_sort(libzfs_handle_t *hdl, char **result, size_t *idx, zfs_vertex_t *zgv) * The only public interface for this file. Do the dirty work of constructing a * child list for the given object. Construct the graph, do the toplogical * sort, and then return the array of strings to the caller. + * + * The 'allowrecursion' parameter controls behavior when cycles are found. If + * it is set, the the cycle is ignored and the results returned as if the cycle + * did not exist. If it is not set, then the routine will generate an error if + * a cycle is found. */ -char ** -get_dependents(libzfs_handle_t *hdl, const char *dataset, size_t *count) +int +get_dependents(libzfs_handle_t *hdl, boolean_t allowrecursion, + const char *dataset, char ***result, size_t *count) { - char **result; zfs_graph_t *zgp; zfs_vertex_t *zvp; if ((zgp = construct_graph(hdl, dataset)) == NULL) - return (NULL); + return (-1); - if ((result = zfs_alloc(hdl, + if ((*result = zfs_alloc(hdl, zgp->zg_nvertex * sizeof (char *))) == NULL) { zfs_graph_destroy(zgp); - return (NULL); + return (-1); } if ((zvp = zfs_graph_lookup(hdl, zgp, dataset, 0)) == NULL) { - free(result); + free(*result); zfs_graph_destroy(zgp); - return (NULL); + return (-1); } *count = 0; - if (topo_sort(hdl, result, count, zvp) != 0) { - free(result); + if (topo_sort(hdl, allowrecursion, *result, count, zvp) != 0) { + free(*result); zfs_graph_destroy(zgp); - return (NULL); + return (-1); } /* @@ -599,10 +640,10 @@ get_dependents(libzfs_handle_t *hdl, const char *dataset, size_t *count) * strictly a dependent. */ assert(*count > 0); - free(result[*count - 1]); + free((*result)[*count - 1]); (*count)--; zfs_graph_destroy(zgp); - return (result); + return (0); } diff --git a/usr/src/lib/libzfs/common/libzfs_impl.h b/usr/src/lib/libzfs/common/libzfs_impl.h index 9341a97546..3e0f737a4b 100644 --- a/usr/src/lib/libzfs/common/libzfs_impl.h +++ b/usr/src/lib/libzfs/common/libzfs_impl.h @@ -63,6 +63,7 @@ struct zfs_handle { nvlist_t *zfs_props; uint64_t zfs_volsize; uint64_t zfs_volblocksize; + boolean_t zfs_mntcheck; char *zfs_mntopts; char zfs_root[MAXPATHLEN]; }; @@ -87,7 +88,8 @@ int no_memory(libzfs_handle_t *); int zfs_standard_error(libzfs_handle_t *, int, const char *, ...); int zpool_standard_error(libzfs_handle_t *, int, const char *, ...); -char **get_dependents(libzfs_handle_t *, const char *, size_t *); +int get_dependents(libzfs_handle_t *, boolean_t, const char *, char ***, + size_t *); typedef struct prop_changelist prop_changelist_t; diff --git a/usr/src/lib/libzfs/common/libzfs_import.c b/usr/src/lib/libzfs/common/libzfs_import.c index dbc3b9dd1a..6b76b2922c 100644 --- a/usr/src/lib/libzfs/common/libzfs_import.c +++ b/usr/src/lib/libzfs/common/libzfs_import.c @@ -788,6 +788,13 @@ zpool_find_import(libzfs_handle_t *hdl, int argc, char **argv) if (S_ISDIR(statbuf.st_mode)) continue; + /* + * Ignore special (non-character or non-block) files. + */ + if (!S_ISREG(statbuf.st_mode) && + !S_ISBLK(statbuf.st_mode)) + continue; + if ((fd = open64(path, O_RDONLY)) < 0) continue; 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); +} diff --git a/usr/src/lib/libzfs/common/libzfs_util.c b/usr/src/lib/libzfs/common/libzfs_util.c index 29e99dc5b1..b09834e57d 100644 --- a/usr/src/lib/libzfs/common/libzfs_util.c +++ b/usr/src/lib/libzfs/common/libzfs_util.c @@ -146,6 +146,8 @@ libzfs_error_description(libzfs_handle_t *hdl) "spare")); case EZFS_INVALCONFIG: return (dgettext(TEXT_DOMAIN, "invalid vdev configuration")); + case EZFS_RECURSIVE: + return (dgettext(TEXT_DOMAIN, "recursive dataset dependency")); case EZFS_UNKNOWN: return (dgettext(TEXT_DOMAIN, "unknown error")); default: diff --git a/usr/src/lib/libzfs/spec/libzfs.spec b/usr/src/lib/libzfs/spec/libzfs.spec index 0ebb03f0b8..e5d33c678a 100644 --- a/usr/src/lib/libzfs/spec/libzfs.spec +++ b/usr/src/lib/libzfs/spec/libzfs.spec @@ -352,6 +352,10 @@ function zpool_iter version SUNWprivate_1.1 end +function zpool_mount_datasets +version SUNWprivate_1.1 +end + function zpool_open version SUNWprivate_1.1 end @@ -372,6 +376,10 @@ function zpool_remove_zvol_links version SUNWprivate_1.1 end +function zpool_unmount_datasets +version SUNWprivate_1.1 +end + function zpool_upgrade version SUNWprivate_1.1 end diff --git a/usr/src/lib/libzfs_jni/common/libzfs_jni_dataset.c b/usr/src/lib/libzfs_jni/common/libzfs_jni_dataset.c index 2daeca32e2..1b580a9078 100644 --- a/usr/src/lib/libzfs_jni/common/libzfs_jni_dataset.c +++ b/usr/src/lib/libzfs_jni/common/libzfs_jni_dataset.c @@ -754,7 +754,7 @@ zjni_get_Datasets_dependents(JNIEnv *env, jobjectArray paths) zfs_handle_t *zhp = zfs_open(g_zfs, path, ZFS_TYPE_ANY); if (zhp != NULL) { /* Add all dependents of this Dataset to list */ - (void) zfs_iter_dependents(zhp, + (void) zfs_iter_dependents(zhp, B_FALSE, zjni_create_add_Dataset, &data); /* Add this Dataset to list (and close zhp) */ |