/* * Given two dataset names, create an edge between them. For the source vertex, * mark 'zv_visited' to indicate that we have seen this vertex, and not simply * created it as a destination of another edge. If 'dest' is NULL, then this * is an individual vertex (i.e. the starting vertex), so don't add an edge. */ static int zfs_graph_add(libzfs_handle_t *hdl, zfs_graph_t *zgp, const char *source, const char *dest, uint64_t txg) { zfs_vertex_t *svp, *dvp; if ((svp = zfs_graph_lookup(hdl, zgp, source, 0)) == NULL) return (-1); svp->zv_visited = VISIT_SEEN; if (dest != NULL) { dvp = zfs_graph_lookup(hdl, zgp, dest, txg); if (dvp == NULL) return (-1); if (zfs_vertex_add_edge(hdl, svp, dvp) != 0) return (-1); } return (0); }
/* * 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. */ int get_dependents(libzfs_handle_t *hdl, boolean_t allowrecursion, const char *dataset, char ***result, size_t *count) { zfs_graph_t *zgp; zfs_vertex_t *zvp; if ((zgp = construct_graph(hdl, dataset)) == NULL) return (-1); if ((*result = zfs_alloc(hdl, zgp->zg_nvertex * sizeof (char *))) == NULL) { zfs_graph_destroy(zgp); return (-1); } if ((zvp = zfs_graph_lookup(hdl, zgp, dataset, 0)) == NULL) { free(*result); zfs_graph_destroy(zgp); return (-1); } *count = 0; if (topo_sort(hdl, allowrecursion, *result, count, zvp) != 0) { free(*result); zfs_graph_destroy(zgp); return (-1); } /* * Get rid of the last entry, which is our starting vertex and not * strictly a dependent. */ assert(*count > 0); free((*result)[*count - 1]); (*count)--; zfs_graph_destroy(zgp); return (0); }
/* * 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(). */ static int iterate_children(libzfs_handle_t *hdl, zfs_graph_t *zgp, const char *dataset) { zfs_cmd_t zc = { 0 }; int ret = 0, err; zfs_vertex_t *zvp; /* * Look up the source vertex, and avoid it if we've seen it before. */ zvp = zfs_graph_lookup(hdl, zgp, dataset, 0); if (zvp == NULL) return (-1); 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))) { /* * Ignore private dataset names. */ if (dataset_name_hidden(zc.zc_name)) continue; /* * Get statistics for this dataset, to determine the type of the * dataset and clone statistics. If this fails, the dataset has * since been removed, and we're pretty much screwed anyway. */ if (ioctl(hdl->libzfs_fd, ZFS_IOC_OBJSET_STATS, &zc) != 0) continue; /* * Add an edge between the parent and the child. */ if (zfs_graph_add(hdl, zgp, dataset, 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); if (err == -1) return (-1); else if (err == 1) ret = 1; /* * Indicate if we found a dataset with a non-zero clone count. */ if (zc.zc_objset_stats.dds_num_clones != 0) ret = 1; } /* * Now iterate over all snapshots. */ bzero(&zc, sizeof (zc)); for ((void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name)); ioctl(hdl->libzfs_fd, ZFS_IOC_SNAPSHOT_LIST_NEXT, &zc) == 0; (void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name))) { /* * Get statistics for this dataset, to determine the type of the * dataset and clone statistics. If this fails, the dataset has * since been removed, and we're pretty much screwed anyway. */ if (ioctl(hdl->libzfs_fd, ZFS_IOC_OBJSET_STATS, &zc) != 0) continue; /* * Add an edge between the parent and the child. */ if (zfs_graph_add(hdl, zgp, dataset, zc.zc_name, zc.zc_objset_stats.dds_creation_txg) != 0) return (-1); /* * Indicate if we found a dataset with a non-zero clone count. */ if (zc.zc_objset_stats.dds_num_clones != 0) ret = 1; } zvp->zv_visited = VISIT_SEEN; return (ret); }
/* * Iterate over all children of the given dataset, adding any vertices * as necessary. Returns -1 if there was an error, or 0 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) { zfs_cmd_t zc = { 0 }; zfs_vertex_t *zvp; /* * Look up the source vertex, and avoid it if we've seen it before. */ zvp = zfs_graph_lookup(hdl, zgp, dataset, 0); if (zvp == NULL) return (-1); if (zvp->zv_visited == VISIT_SEEN) return (0); /* * Iterate over all children */ 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))) { /* * Ignore private dataset names. */ if (dataset_name_hidden(zc.zc_name)) continue; /* * Get statistics for this dataset, to determine the type of the * dataset and clone statistics. If this fails, the dataset has * since been removed, and we're pretty much screwed anyway. */ zc.zc_objset_stats.dds_origin[0] = '\0'; if (ioctl(hdl->libzfs_fd, ZFS_IOC_OBJSET_STATS, &zc) != 0) continue; if (zc.zc_objset_stats.dds_origin[0] != '\0') { if (zfs_graph_add(hdl, zgp, zc.zc_objset_stats.dds_origin, zc.zc_name, zc.zc_objset_stats.dds_creation_txg) != 0) return (-1); /* * Count origins only if they are contained in the graph */ if (isa_child_of(zc.zc_objset_stats.dds_origin, zgp->zg_root)) zgp->zg_clone_count--; } /* * Add an edge between the parent and the child. */ if (zfs_graph_add(hdl, zgp, dataset, zc.zc_name, zc.zc_objset_stats.dds_creation_txg) != 0) return (-1); /* * Recursively visit child */ if (iterate_children(hdl, zgp, zc.zc_name)) return (-1); } /* * Now iterate over all snapshots. */ bzero(&zc, sizeof (zc)); for ((void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name)); ioctl(hdl->libzfs_fd, ZFS_IOC_SNAPSHOT_LIST_NEXT, &zc) == 0; (void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name))) { /* * Get statistics for this dataset, to determine the type of the * dataset and clone statistics. If this fails, the dataset has * since been removed, and we're pretty much screwed anyway. */ if (ioctl(hdl->libzfs_fd, ZFS_IOC_OBJSET_STATS, &zc) != 0) continue; /* * Add an edge between the parent and the child. */ if (zfs_graph_add(hdl, zgp, dataset, zc.zc_name, zc.zc_objset_stats.dds_creation_txg) != 0) return (-1); zgp->zg_clone_count += zc.zc_objset_stats.dds_num_clones; } zvp->zv_visited = VISIT_SEEN; return (0); }