2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A/*
2N/A * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A
2N/A/*
2N/A * Iterate over all children of the current object. This includes the normal
2N/A * dataset hierarchy, but also arbitrary hierarchies due to clones. We want to
2N/A * walk all datasets in the pool, and construct a directed graph of the form:
2N/A *
2N/A * home
2N/A * |
2N/A * +----+----+
2N/A * | |
2N/A * v v ws
2N/A * bar baz |
2N/A * | |
2N/A * v v
2N/A * @yesterday ----> foo
2N/A *
2N/A * In order to construct this graph, we have to walk every dataset in the pool,
2N/A * because the clone parent is stored as a property of the child, not the
2N/A * parent. The parent only keeps track of the number of clones.
2N/A *
2N/A * In the normal case (without clones) this would be rather expensive. To avoid
2N/A * unnecessary computation, we first try a walk of the subtree hierarchy
2N/A * starting from the initial node. At each dataset, we construct a node in the
2N/A * graph and an edge leading from its parent. If we don't see any snapshots
2N/A * with a non-zero clone count, then we are finished.
2N/A *
2N/A * If we do find a cloned snapshot, then we finish the walk of the current
2N/A * subtree, but indicate that we need to do a complete walk. We then perform a
2N/A * global walk of all datasets, avoiding the subtree we already processed.
2N/A *
2N/A * At the end of this, we'll end up with a directed graph of all relevant (and
2N/A * possible some irrelevant) datasets in the system. We need to both find our
2N/A * limiting subgraph and determine a safe ordering in which to destroy the
2N/A * datasets. We do a topological ordering of our graph starting at our target
2N/A * dataset, and then walk the results in reverse.
2N/A *
2N/A * It's possible for the graph to have cycles if, for example, the user renames
2N/A * a clone to be the parent of its origin snapshot. The user can request to
2N/A * generate an error in this case, or ignore the cycle and continue.
2N/A *
2N/A * When removing datasets, we want to destroy the snapshots in chronological
2N/A * order (because this is the most efficient method). In order to accomplish
2N/A * this, we store the creation transaction group with each vertex and keep each
2N/A * vertex's edges sorted according to this value. The topological sort will
2N/A * automatically walk the snapshots in the correct order.
2N/A */
2N/A
2N/A#include <assert.h>
2N/A#include <libintl.h>
2N/A#include <stdio.h>
2N/A#include <stdlib.h>
2N/A#include <string.h>
2N/A#include <strings.h>
2N/A#include <unistd.h>
2N/A
2N/A#include <libzfs.h>
2N/A
2N/A#include "libzfs_impl.h"
2N/A
2N/A#define MIN_EDGECOUNT 4
2N/A
2N/A/*
2N/A * Vertex structure. Indexed by dataset name, this structure maintains a list
2N/A * of edges to other vertices.
2N/A */
2N/Astruct zfs_edge;
2N/Atypedef struct zfs_vertex {
2N/A char zv_dataset[ZFS_MAXNAMELEN];
2N/A struct zfs_vertex *zv_next;
2N/A int zv_visited;
2N/A uint64_t zv_txg;
2N/A struct zfs_edge **zv_edges;
2N/A int zv_edgecount;
2N/A int zv_edgealloc;
2N/A} zfs_vertex_t;
2N/A
2N/Aenum {
2N/A VISIT_SEEN = 1,
2N/A VISIT_SORT_PRE,
2N/A VISIT_SORT_POST
2N/A};
2N/A
2N/A/*
2N/A * Edge structure. Simply maintains a pointer to the destination vertex. There
2N/A * is no need to store the source vertex, since we only use edges in the context
2N/A * of the source vertex.
2N/A */
2N/Atypedef struct zfs_edge {
2N/A zfs_vertex_t *ze_dest;
2N/A struct zfs_edge *ze_next;
2N/A} zfs_edge_t;
2N/A
2N/A#define ZFS_GRAPH_SIZE 1027 /* this could be dynamic some day */
2N/A
2N/A/*
2N/A * Graph structure. Vertices are maintained in a hash indexed by dataset name.
2N/A */
2N/Atypedef struct zfs_graph {
2N/A zfs_vertex_t **zg_hash;
2N/A size_t zg_size;
2N/A size_t zg_nvertex;
2N/A const char *zg_root;
2N/A int zg_clone_count;
2N/A} zfs_graph_t;
2N/A
2N/A/*
2N/A * Allocate a new edge pointing to the target vertex.
2N/A */
2N/Astatic zfs_edge_t *
2N/Azfs_edge_create(libzfs_handle_t *hdl, zfs_vertex_t *dest)
2N/A{
2N/A zfs_edge_t *zep = zfs_alloc(hdl, sizeof (zfs_edge_t));
2N/A
2N/A if (zep == NULL)
2N/A return (NULL);
2N/A
2N/A zep->ze_dest = dest;
2N/A
2N/A return (zep);
2N/A}
2N/A
2N/A/*
2N/A * Destroy an edge.
2N/A */
2N/Astatic void
2N/Azfs_edge_destroy(zfs_edge_t *zep)
2N/A{
2N/A free(zep);
2N/A}
2N/A
2N/A/*
2N/A * Allocate a new vertex with the given name.
2N/A */
2N/Astatic zfs_vertex_t *
2N/Azfs_vertex_create(libzfs_handle_t *hdl, const char *dataset)
2N/A{
2N/A zfs_vertex_t *zvp = zfs_alloc(hdl, sizeof (zfs_vertex_t));
2N/A
2N/A if (zvp == NULL)
2N/A return (NULL);
2N/A
2N/A assert(strlen(dataset) < ZFS_MAXNAMELEN);
2N/A
2N/A (void) strlcpy(zvp->zv_dataset, dataset, sizeof (zvp->zv_dataset));
2N/A
2N/A if ((zvp->zv_edges = zfs_alloc(hdl,
2N/A MIN_EDGECOUNT * sizeof (void *))) == NULL) {
2N/A free(zvp);
2N/A return (NULL);
2N/A }
2N/A
2N/A zvp->zv_edgealloc = MIN_EDGECOUNT;
2N/A
2N/A return (zvp);
2N/A}
2N/A
2N/A/*
2N/A * Destroy a vertex. Frees up any associated edges.
2N/A */
2N/Astatic void
2N/Azfs_vertex_destroy(zfs_vertex_t *zvp)
2N/A{
2N/A int i;
2N/A
2N/A for (i = 0; i < zvp->zv_edgecount; i++)
2N/A zfs_edge_destroy(zvp->zv_edges[i]);
2N/A
2N/A free(zvp->zv_edges);
2N/A free(zvp);
2N/A}
2N/A
2N/A/*
2N/A * Given a vertex, add an edge to the destination vertex.
2N/A */
2N/Astatic int
2N/Azfs_vertex_add_edge(libzfs_handle_t *hdl, zfs_vertex_t *zvp,
2N/A zfs_vertex_t *dest)
2N/A{
2N/A zfs_edge_t *zep;
2N/A
2N/A if (zvp->zv_edgecount == zvp->zv_edgealloc) {
2N/A void *ptr;
2N/A
2N/A if ((ptr = zfs_realloc(hdl, zvp->zv_edges,
2N/A zvp->zv_edgealloc * sizeof (void *),
2N/A zvp->zv_edgealloc * 2 * sizeof (void *))) == NULL)
2N/A return (-1);
2N/A
2N/A zvp->zv_edges = ptr;
2N/A zvp->zv_edgealloc *= 2;
2N/A }
2N/A
2N/A if ((zep = zfs_edge_create(hdl, dest)) == NULL)
2N/A return (-1);
2N/A
2N/A zvp->zv_edges[zvp->zv_edgecount++] = zep;
2N/A
2N/A return (0);
2N/A}
2N/A
2N/Astatic int
2N/Azfs_edge_compare(const void *a, const void *b)
2N/A{
2N/A const zfs_edge_t *ea = *((zfs_edge_t **)a);
2N/A const zfs_edge_t *eb = *((zfs_edge_t **)b);
2N/A
2N/A if (ea->ze_dest->zv_txg < eb->ze_dest->zv_txg)
2N/A return (-1);
2N/A if (ea->ze_dest->zv_txg > eb->ze_dest->zv_txg)
2N/A return (1);
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * Sort the given vertex edges according to the creation txg of each vertex.
2N/A */
2N/Astatic void
2N/Azfs_vertex_sort_edges(zfs_vertex_t *zvp)
2N/A{
2N/A if (zvp->zv_edgecount == 0)
2N/A return;
2N/A
2N/A qsort(zvp->zv_edges, zvp->zv_edgecount, sizeof (void *),
2N/A zfs_edge_compare);
2N/A}
2N/A
2N/A/*
2N/A * Construct a new graph object. We allow the size to be specified as a
2N/A * parameter so in the future we can size the hash according to the number of
2N/A * datasets in the pool.
2N/A */
2N/Astatic zfs_graph_t *
2N/Azfs_graph_create(libzfs_handle_t *hdl, const char *dataset, size_t size)
2N/A{
2N/A zfs_graph_t *zgp = zfs_alloc(hdl, sizeof (zfs_graph_t));
2N/A
2N/A if (zgp == NULL)
2N/A return (NULL);
2N/A
2N/A zgp->zg_size = size;
2N/A if ((zgp->zg_hash = zfs_alloc(hdl,
2N/A size * sizeof (zfs_vertex_t *))) == NULL) {
2N/A free(zgp);
2N/A return (NULL);
2N/A }
2N/A
2N/A zgp->zg_root = dataset;
2N/A zgp->zg_clone_count = 0;
2N/A
2N/A return (zgp);
2N/A}
2N/A
2N/A/*
2N/A * Destroy a graph object. We have to iterate over all the hash chains,
2N/A * destroying each vertex in the process.
2N/A */
2N/Astatic void
2N/Azfs_graph_destroy(zfs_graph_t *zgp)
2N/A{
2N/A int i;
2N/A zfs_vertex_t *current, *next;
2N/A
2N/A for (i = 0; i < zgp->zg_size; i++) {
2N/A current = zgp->zg_hash[i];
2N/A while (current != NULL) {
2N/A next = current->zv_next;
2N/A zfs_vertex_destroy(current);
2N/A current = next;
2N/A }
2N/A }
2N/A
2N/A free(zgp->zg_hash);
2N/A free(zgp);
2N/A}
2N/A
2N/A/*
2N/A * Graph hash function. Classic bernstein k=33 hash function, taken from
2N/A * usr/src/cmd/sgs/tools/common/strhash.c
2N/A */
2N/Astatic size_t
2N/Azfs_graph_hash(zfs_graph_t *zgp, const char *str)
2N/A{
2N/A size_t hash = 5381;
2N/A int c;
2N/A
2N/A while ((c = *str++) != 0)
2N/A hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
2N/A
2N/A return (hash % zgp->zg_size);
2N/A}
2N/A
2N/A/*
2N/A * Given a dataset name, finds the associated vertex, creating it if necessary.
2N/A */
2N/Astatic zfs_vertex_t *
2N/Azfs_graph_lookup(libzfs_handle_t *hdl, zfs_graph_t *zgp, const char *dataset,
2N/A uint64_t txg)
2N/A{
2N/A size_t idx = zfs_graph_hash(zgp, dataset);
2N/A zfs_vertex_t *zvp;
2N/A
2N/A for (zvp = zgp->zg_hash[idx]; zvp != NULL; zvp = zvp->zv_next) {
2N/A if (strcmp(zvp->zv_dataset, dataset) == 0) {
2N/A if (zvp->zv_txg == 0)
2N/A zvp->zv_txg = txg;
2N/A return (zvp);
2N/A }
2N/A }
2N/A
2N/A if ((zvp = zfs_vertex_create(hdl, dataset)) == NULL)
2N/A return (NULL);
2N/A
2N/A zvp->zv_next = zgp->zg_hash[idx];
2N/A zvp->zv_txg = txg;
2N/A zgp->zg_hash[idx] = zvp;
2N/A zgp->zg_nvertex++;
2N/A
2N/A return (zvp);
2N/A}
2N/A
2N/A/*
2N/A * Given two dataset names, create an edge between them. For the source vertex,
2N/A * mark 'zv_visited' to indicate that we have seen this vertex, and not simply
2N/A * created it as a destination of another edge. If 'dest' is NULL, then this
2N/A * is an individual vertex (i.e. the starting vertex), so don't add an edge.
2N/A */
2N/Astatic int
2N/Azfs_graph_add(libzfs_handle_t *hdl, zfs_graph_t *zgp, const char *source,
2N/A const char *dest, uint64_t txg)
2N/A{
2N/A zfs_vertex_t *svp, *dvp;
2N/A
2N/A if ((svp = zfs_graph_lookup(hdl, zgp, source, 0)) == NULL)
2N/A return (-1);
2N/A svp->zv_visited = VISIT_SEEN;
2N/A if (dest != NULL) {
2N/A dvp = zfs_graph_lookup(hdl, zgp, dest, txg);
2N/A if (dvp == NULL)
2N/A return (-1);
2N/A if (zfs_vertex_add_edge(hdl, svp, dvp) != 0)
2N/A return (-1);
2N/A }
2N/A
2N/A return (0);
2N/A}
2N/A
2N/Astatic int
2N/Alist_ioctl(libzfs_handle_t *hdl, int ioc, zfs_cmd_t *zc)
2N/A{
2N/A int rc;
2N/A
2N/A rc = ioctl(hdl->libzfs_fd, ioc, zc);
2N/A
2N/A if (rc == -1) {
2N/A switch (errno) {
2N/A /*
2N/A * An errno value of ESRCH indicates normal completion.
2N/A * If ENOENT is returned, then the underlying dataset
2N/A * has been removed since we obtained the handle.
2N/A */
2N/A case ESRCH:
2N/A case ENOENT:
2N/A break;
2N/A default:
2N/A (void) zfs_standard_error_fmt(hdl, zc->zc_name,
2N/A ZFS_TYPE_DATASET, errno, dgettext(TEXT_DOMAIN,
2N/A "cannot iterate %s"), zfs_list_ioctl_typestr(ioc));
2N/A break;
2N/A }
2N/A }
2N/A
2N/A return (rc);
2N/A}
2N/A
2N/A/*
2N/A * Iterate over all children of the given dataset, adding any vertices
2N/A * as necessary. Returns -1 if there was an error, or 0 otherwise.
2N/A * This is a simple recursive algorithm - the ZFS namespace typically
2N/A * is very flat. We manually invoke the necessary ioctl() calls to
2N/A * avoid the overhead and additional semantics of zfs_open().
2N/A */
2N/Astatic int
2N/Aiterate_children(libzfs_handle_t *hdl, zfs_graph_t *zgp, const char *dataset)
2N/A{
2N/A zfs_cmd_t zc = { 0 };
2N/A zfs_vertex_t *zvp;
2N/A
2N/A /*
2N/A * Look up the source vertex, and avoid it if we've seen it before.
2N/A */
2N/A zvp = zfs_graph_lookup(hdl, zgp, dataset, 0);
2N/A if (zvp == NULL)
2N/A return (-1);
2N/A if (zvp->zv_visited == VISIT_SEEN)
2N/A return (0);
2N/A
2N/A /*
2N/A * Iterate over all children
2N/A */
2N/A for ((void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name));
2N/A list_ioctl(hdl, ZFS_IOC_DATASET_LIST_NEXT, &zc) == 0;
2N/A (void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name))) {
2N/A
2N/A if (zc.zc_objset_stats.dds_origin[0] != '\0') {
2N/A if (zfs_graph_add(hdl, zgp,
2N/A zc.zc_objset_stats.dds_origin, zc.zc_name,
2N/A zc.zc_objset_stats.dds_creation_txg) != 0)
2N/A return (-1);
2N/A /*
2N/A * Count origins only if they are contained in the graph
2N/A */
2N/A if (isa_child_of(zc.zc_objset_stats.dds_origin,
2N/A zgp->zg_root))
2N/A zgp->zg_clone_count--;
2N/A }
2N/A
2N/A /*
2N/A * Add an edge between the parent and the child.
2N/A */
2N/A if (zfs_graph_add(hdl, zgp, dataset, zc.zc_name,
2N/A zc.zc_objset_stats.dds_creation_txg) != 0)
2N/A return (-1);
2N/A
2N/A /*
2N/A * Recursively visit child
2N/A */
2N/A if (iterate_children(hdl, zgp, zc.zc_name))
2N/A return (-1);
2N/A }
2N/A
2N/A /*
2N/A * Now iterate over all snapshots.
2N/A */
2N/A bzero(&zc, sizeof (zc));
2N/A
2N/A for ((void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name));
2N/A list_ioctl(hdl, ZFS_IOC_SNAPSHOT_LIST_NEXT, &zc) == 0;
2N/A (void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name))) {
2N/A /*
2N/A * Add an edge between the parent and the child.
2N/A */
2N/A if (zfs_graph_add(hdl, zgp, dataset, zc.zc_name,
2N/A zc.zc_objset_stats.dds_creation_txg) != 0)
2N/A return (-1);
2N/A
2N/A zgp->zg_clone_count += zc.zc_objset_stats.dds_num_clones;
2N/A }
2N/A
2N/A /*
2N/A * Now iterate over all shares.
2N/A */
2N/A bzero(&zc, sizeof (zc));
2N/A
2N/A for ((void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name));
2N/A list_ioctl(hdl, ZFS_IOC_SHARE_LIST_NEXT, &zc) == 0;
2N/A (void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name))) {
2N/A /*
2N/A * Add an edge between the parent and the child.
2N/A */
2N/A if (zfs_graph_add(hdl, zgp, dataset, zc.zc_name,
2N/A zc.zc_objset_stats.dds_creation_txg) != 0)
2N/A return (-1);
2N/A }
2N/A
2N/A zvp->zv_visited = VISIT_SEEN;
2N/A
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * Returns false if there are no snapshots with dependent clones in this
2N/A * subtree or if all of those clones are also in this subtree. Returns
2N/A * true if there is an error or there are external dependents.
2N/A */
2N/Astatic boolean_t
2N/Aexternal_dependents(libzfs_handle_t *hdl, zfs_graph_t *zgp, const char *dataset)
2N/A{
2N/A zfs_cmd_t zc = { 0 };
2N/A
2N/A /*
2N/A * Check whether this dataset is a clone or has clones since
2N/A * iterate_children() only checks the children.
2N/A */
2N/A (void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name));
2N/A if (ioctl(hdl->libzfs_fd, ZFS_IOC_OBJSET_STATS, &zc) != 0)
2N/A return (B_TRUE);
2N/A
2N/A if (zc.zc_objset_stats.dds_origin[0] != '\0') {
2N/A if (zfs_graph_add(hdl, zgp,
2N/A zc.zc_objset_stats.dds_origin, zc.zc_name,
2N/A zc.zc_objset_stats.dds_creation_txg) != 0)
2N/A return (B_TRUE);
2N/A if (isa_child_of(zc.zc_objset_stats.dds_origin, dataset))
2N/A zgp->zg_clone_count--;
2N/A }
2N/A
2N/A if ((zc.zc_objset_stats.dds_num_clones) ||
2N/A iterate_children(hdl, zgp, dataset))
2N/A return (B_TRUE);
2N/A
2N/A return (zgp->zg_clone_count != 0);
2N/A}
2N/A
2N/A/*
2N/A * Construct a complete graph of all necessary vertices. First, iterate over
2N/A * only our object's children. If no cloned snapshots are found, or all of
2N/A * the cloned snapshots are in this subtree then return a graph of the subtree.
2N/A * Otherwise, start at the root of the pool and iterate over all datasets.
2N/A */
2N/Astatic zfs_graph_t *
2N/Aconstruct_graph(libzfs_handle_t *hdl, const char *dataset)
2N/A{
2N/A zfs_graph_t *zgp = zfs_graph_create(hdl, dataset, ZFS_GRAPH_SIZE);
2N/A int ret = 0;
2N/A
2N/A if (zgp == NULL)
2N/A return (zgp);
2N/A
2N/A if ((strchr(dataset, '/') == NULL) ||
2N/A (external_dependents(hdl, zgp, dataset))) {
2N/A /*
2N/A * Determine pool name and try again.
2N/A */
2N/A int len = strcspn(dataset, "/@") + 1;
2N/A char *pool = zfs_alloc(hdl, len);
2N/A
2N/A if (pool == NULL) {
2N/A zfs_graph_destroy(zgp);
2N/A return (NULL);
2N/A }
2N/A (void) strlcpy(pool, dataset, len);
2N/A
2N/A if (iterate_children(hdl, zgp, pool) == -1 ||
2N/A zfs_graph_add(hdl, zgp, pool, NULL, 0) != 0) {
2N/A free(pool);
2N/A zfs_graph_destroy(zgp);
2N/A return (NULL);
2N/A }
2N/A free(pool);
2N/A }
2N/A
2N/A if (ret == -1 || zfs_graph_add(hdl, zgp, dataset, NULL, 0) != 0) {
2N/A zfs_graph_destroy(zgp);
2N/A return (NULL);
2N/A }
2N/A
2N/A return (zgp);
2N/A}
2N/A
2N/A/*
2N/A * Given a graph, do a recursive topological sort into the given array. This is
2N/A * really just a depth first search, so that the deepest nodes appear first.
2N/A * hijack the 'zv_visited' marker to avoid visiting the same vertex twice.
2N/A */
2N/Astatic int
2N/Atopo_sort(libzfs_handle_t *hdl, boolean_t allowrecursion, char **result,
2N/A size_t *idx, zfs_vertex_t *zgv)
2N/A{
2N/A int i;
2N/A
2N/A if (zgv->zv_visited == VISIT_SORT_PRE && !allowrecursion) {
2N/A /*
2N/A * If we've already seen this vertex as part of our depth-first
2N/A * search, then we have a cyclic dependency, and we must return
2N/A * an error.
2N/A */
2N/A zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
2N/A "recursive dependency at '%s'"),
2N/A zgv->zv_dataset);
2N/A return (zfs_error(hdl, EZFS_RECURSIVE,
2N/A dgettext(TEXT_DOMAIN,
2N/A "cannot determine dependent datasets")));
2N/A } else if (zgv->zv_visited >= VISIT_SORT_PRE) {
2N/A /*
2N/A * If we've already processed this as part of the topological
2N/A * sort, then don't bother doing so again.
2N/A */
2N/A return (0);
2N/A }
2N/A
2N/A zgv->zv_visited = VISIT_SORT_PRE;
2N/A
2N/A /* avoid doing a search if we don't have to */
2N/A zfs_vertex_sort_edges(zgv);
2N/A for (i = 0; i < zgv->zv_edgecount; i++) {
2N/A if (topo_sort(hdl, allowrecursion, result, idx,
2N/A zgv->zv_edges[i]->ze_dest) != 0)
2N/A return (-1);
2N/A }
2N/A
2N/A /* we may have visited this in the course of the above */
2N/A if (zgv->zv_visited == VISIT_SORT_POST)
2N/A return (0);
2N/A
2N/A if ((result[*idx] = zfs_alloc(hdl,
2N/A strlen(zgv->zv_dataset) + 1)) == NULL)
2N/A return (-1);
2N/A
2N/A (void) strcpy(result[*idx], zgv->zv_dataset);
2N/A *idx += 1;
2N/A zgv->zv_visited = VISIT_SORT_POST;
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * The only public interface for this file. Do the dirty work of constructing a
2N/A * child list for the given object. Construct the graph, do the toplogical
2N/A * sort, and then return the array of strings to the caller.
2N/A *
2N/A * The 'allowrecursion' parameter controls behavior when cycles are found. If
2N/A * it is set, the the cycle is ignored and the results returned as if the cycle
2N/A * did not exist. If it is not set, then the routine will generate an error if
2N/A * a cycle is found.
2N/A */
2N/Aint
2N/Aget_dependents(libzfs_handle_t *hdl, boolean_t allowrecursion,
2N/A const char *dataset, char ***result, size_t *count)
2N/A{
2N/A zfs_graph_t *zgp;
2N/A zfs_vertex_t *zvp;
2N/A
2N/A if ((zgp = construct_graph(hdl, dataset)) == NULL)
2N/A return (-1);
2N/A
2N/A if ((*result = zfs_alloc(hdl,
2N/A zgp->zg_nvertex * sizeof (char *))) == NULL) {
2N/A zfs_graph_destroy(zgp);
2N/A return (-1);
2N/A }
2N/A
2N/A if ((zvp = zfs_graph_lookup(hdl, zgp, dataset, 0)) == NULL) {
2N/A free(*result);
2N/A zfs_graph_destroy(zgp);
2N/A return (-1);
2N/A }
2N/A
2N/A *count = 0;
2N/A if (topo_sort(hdl, allowrecursion, *result, count, zvp) != 0) {
2N/A free(*result);
2N/A zfs_graph_destroy(zgp);
2N/A return (-1);
2N/A }
2N/A
2N/A /*
2N/A * Get rid of the last entry, which is our starting vertex and not
2N/A * strictly a dependent.
2N/A */
2N/A assert(*count > 0);
2N/A free((*result)[*count - 1]);
2N/A (*count)--;
2N/A
2N/A zfs_graph_destroy(zgp);
2N/A
2N/A return (0);
2N/A}