freebsd-dev/cddl/contrib/opensolaris/lib/libzfs/common/libzfs_graph.c
Martin Matuska 8fc257994d Merge ZFS version 15 and almost all OpenSolaris bugfixes referenced
in Solaris 10 updates 141445-09 and 142901-14.

Detailed information:
(OpenSolaris revisions and Bug IDs, Solaris 10 patch numbers)

7844:effed23820ae
6755435	zfs_open() and zfs_close() needs to use ZFS_ENTER/ZFS_VERIFY_ZP (141445-01)

7897:e520d8258820
6748436	inconsistent zpool.cache in boot_archive could panic a zfs root filesystem upon boot-up (141445-01)

7965:b795da521357
6740164	zpool attach can create an illegal root pool (141909-02)

8084:b811cc60d650
6769612	zpool_import() will continue to write to cachefile even if altroot is set (N/A)

8121:7fd09d4ebd9c
6757430	want an option for zdb to disable space map loading and leak tracking (141445-01)

8129:e4f45a0bfbb0
6542860	ASSERT: reason != VDEV_LABEL_REMOVE||vdev_inuse(vd, crtxg, reason, 0) (141445-01)

8188:fd00c0a81e80
6761100	want zdb option to select older uberblocks (141445-01)

8190:6eeea43ced42
6774886	zfs_setattr() won't allow ndmp to restore SUNWattr_rw (141445-01)

8225:59a9961c2aeb
6737463	panic while trying to write out config file if root pool import fails (141445-01)

8227:f7d7be9b1f56
6765294	Refactor replay (141445-01)

8228:51e9ca9ee3a5
6572357	libzfs should do more to avoid mnttab lookups (141909-01)
6572376	zfs_iter_filesystems and zfs_iter_snapshots get objset stats twice (141909-01)

8241:5a60f16123ba
6328632	zpool offline is a bit too conservative (141445-01)
6739487	ASSERT: txg <= spa_final_txg due to scrub/export race (141445-01)
6767129	ASSERT: cvd->vdev_isspare, in spa_vdev_detach() (141445-01)
6747698	checksum failures after offline -t / export / import / scrub (141445-01)
6745863	ZFS writes to disk after it has been offlined (141445-01)
6722540	50% slowdown on scrub/resilver with certain vdev configurations (141445-01)
6759999	resilver logic rewrites ditto blocks on both source and destination (141445-01)
6758107	I/O should never suspend during spa_load() (141445-01)
6776548	codereview(1) runs off the page when faced with multi-line comments (N/A)
6761406	AMD errata 91 workaround doesn't work on 64-bit systems (141445-01)

8242:e46e4b2f0a03
6770866	GRUB/ZFS should require physical path or devid, but not both (141445-01)

8269:03a7e9050cfd
6674216	"zfs share" doesn't work, but "zfs set sharenfs=on" does (141445-01)
6621164	$SRC/cmd/zfs/zfs_main.c seems to have a syntax error in the translation note (141445-01)
6635482	i18n problems in libzfs_dataset.c and zfs_main.c (141445-01)
6595194	"zfs get" VALUE column is as wide as NAME (141445-01)
6722991	vdev_disk.c: error checking for ddi_pathname_to_dev_t() must test for NODEV (141445-01)
6396518	ASSERT strings shouldn't be pre-processed (141445-01)

8274:846b39508aff
6713916	scrub/resilver needlessly decompress data (141445-01)

8343:655db2375fed
6739553	libzfs_status msgid table is out of sync (141445-01)
6784104	libzfs unfairly rejects numerical values greater than 2^63 (141445-01)
6784108	zfs_realloc() should not free original memory on failure (141445-01)

8525:e0e0e525d0f8
6788830	set large value to reservation cause core dump (141445-01)
6791064	want sysevents for ZFS scrub (141445-01)
6791066	need to be able to set cachefile on faulted pools (141445-01)
6791071	zpool_do_import() should not enable datasets on faulted pools (141445-01)
6792134	getting multiple properties on a faulted pool leads to confusion (141445-01)

8547:bcc7b46e5ff7
6792884	Vista clients cannot access .zfs (141445-01)

8632:36ef517870a3
6798384	It can take a village to raise a zio (141445-01)

8636:7e4ce9158df3
6551866	deadlock between zfs_write(), zfs_freesp(), and zfs_putapage() (141909-01)
6504953	zfs_getpage() misunderstands VOP_GETPAGE() interface (141909-01)
6702206	ZFS read/writer lock contention throttles sendfile() benchmark (141445-01)
6780491	Zone on a ZFS filesystem has poor fork/exec performance (141445-01)
6747596	assertion failed: DVA_EQUAL(BP_IDENTITY(&zio->io_bp_orig), BP_IDENTITY(zio->io_bp))); (141445-01)

8692:692d4668b40d
6801507	ZFS read aggregation should not mind the gap (141445-01)

8697:e62d2612c14d
6633095	creating a filesystem with many properties set is slow (141445-01)

8768:dfecfdbb27ed
6775697	oracle crashes when overwriting after hitting quota on zfs (141909-01)

8811:f8deccf701cf
6790687	libzfs mnttab caching ignores external changes (141445-01)
6791101	memory leak from libzfs_mnttab_init (141445-01)

8845:91af0d9c0790
6800942	smb_session_create() incorrectly stores IP addresses (N/A)
6582163	Access Control List (ACL) for shares (141445-01)
6804954	smb_search - shortname field should be space padded following the NULL terminator (N/A)
6800184	Panic at smb_oplock_conflict+0x35() (N/A)

8876:59d2e67b4b65
6803822	Reboot after replacement of system disk in a ZFS mirror drops to grub> prompt (141445-01)

8924:5af812f84759
6789318	coredump when issue zdb -uuuu poolname/ (141445-01)
6790345 zdb -dddd -e poolname coredump (141445-01)
6797109 zdb: 'zdb -dddddd pool_name/fs_name inode' coredump if the file with inode was deleted (141445-01)
6797118 zdb: 'zdb -dddddd poolname inum' coredump if I miss the fs name (141445-01)
6803343 shareiscsi=on failed, iscsitgtd failed request to share (141445-01)

9030:243fd360d81f
6815893	hang mounting a dataset after booting into a new boot environment (141445-01)

9056:826e1858a846
6809691	'zpool create -f' no longer overwrites ufs infomation (141445-01)

9179:d8fbd96b79b3
6790064	zfs needs to determine uid and gid earlier in create process (141445-01)

9214:8d350e5d04aa
6604992	forced unmount + being in .zfs/snapshot/<snap1> = not happy (141909-01)
6810367	assertion failed: dvp->v_flag & VROOT, file: ../../common/fs/gfs.c, line: 426 (141909-01)

9229:e3f8b41e5db4
6807765	ztest_dsl_dataset_promote_busy needs to clean up after ENOSPC (141445-01)

9230:e4561e3eb1ef
6821169	offlining a device results in checksum errors (141445-01)
6821170	ZFS should not increment error stats for unavailable devices (141445-01)
6824006	need to increase issue and interrupt taskqs threads in zfs (141445-01)

9234:bffdc4fc05c4
6792139	recovering from a suspended pool needs some work (141445-01)
6794830	reboot command hangs on a failed zfs pool (141445-01)

9246:67c03c93c071
6824062	System panicked in zfs_mount due to NULL pointer dereference when running btts and svvs tests (141909-01)

9276:a8a7fc849933
6816124	System crash running zpool destroy on broken zpool (141445-03)

9355:09928982c591
6818183	zfs snapshot -r is slow due to set_snap_props() doing txg_wait_synced() for each new snapshot (141445-03)

9391:413d0661ef33
6710376	log device can show incorrect status when other parts of pool are degraded (141445-03)

9396:f41cf682d0d3 (part already merged)
6501037	want user/group quotas on ZFS (141445-03)
6827260	assertion failed in arc_read(): hdr == pbuf->b_hdr (141445-03)
6815592	panic: No such hold X on refcount Y from zfs_znode_move (141445-03)
6759986	zfs list shows temporary %clone when doing online zfs recv (141445-03)

9404:319573cd93f8
6774713	zfs ignores canmount=noauto when sharenfs property != off (141445-03)

9412:4aefd8704ce0
6717022	ZFS DMU needs zero-copy support (141445-03)

9425:e7ffacaec3a8
6799895	spa_add_spares() needs to be protected by config lock (141445-03)
6826466	want to post sysevents on hot spare activation (141445-03)
6826468	spa 'allowfaulted' needs some work (141445-03)
6826469	kernel support for storing vdev FRU information (141445-03)
6826470	skip posting checksum errors from DTL regions of leaf vdevs (141445-03)
6826471	I/O errors after device remove probe can confuse FMA (141445-03)
6826472	spares should enjoy some of the benefits of cache devices (141445-03)

9443:2a96d8478e95
6833711	gang leaders shouldn't have to be logical (141445-03)

9463:d0bd231c7518
6764124	want zdb to be able to checksum metadata blocks only (141445-03)

9465:8372081b8019
6830237	zfs panic in zfs_groupmember() (141445-03)

9466:1fdfd1fed9c4
6833162	phantom log device in zpool status (141445-03)

9469:4f68f041ddcd
6824968	add ZFS userquota support to rquotad (141445-03)

9470:6d827468d7b5
6834217	godfather I/O should reexecute (141445-03)

9480:fcff33da767f
6596237	Stop looking and start ganging (141909-02)

9493:9933d599bc93
6623978	lwb->lwb_buf != NULL, file ../../../uts/common/fs/zfs/zil.c, line 787, function zil_lwb_commit (141445-06)

9512:64cafcbcc337
6801810	Commit of aligned streaming rewrites to ZIL device causes unwanted disk reads (N/A)

9515:d3b739d9d043
6586537	async zio taskqs can block out userland commands (142901-09)

9554:787363635b6a
6836768	zfs_userspace() callback has no way to indicate failure (N/A)

9574:1eb6a6ab2c57
6838062	zfs panics when an error is encountered in space_map_load() (141909-02)

9583:b0696cd037cc
6794136	Panic BAD TRAP: type=e when importing degraded zraid pool. (141909-03)

9630:e25a03f552e0
6776104	"zfs import" deadlock between spa_unload() and spa_async_thread() (141445-06)

9653:a70048a304d1
6664765	Unable to remove files when using fat-zap and quota exceeded on ZFS filesystem (141445-06)

9688:127be1845343
6841321	zfs userspace / zfs get userused@ doesn't work on mounted snapshot (N/A)
6843069	zfs get userused@S-1-... doesn't work (N/A)

9873:8ddc892eca6e
6847229	assertion failed: refcount_count(&tx->tx_space_written) + delta <= tx->tx_space_towrite in dmu_tx.c (141445-06)

9904:d260bd3fd47c
6838344	kernel heap corruption detected on zil while stress testing (141445-06)

9951:a4895b3dd543
6844900	zfs_ioc_userspace_upgrade leaks (N/A)

10040:38b25aeeaf7a
6857012	zfs panics on zpool import (141445-06)

10000:241a51d8720c
6848242	zdb -e no longer works as expected (N/A)

10100:4a6965f6bef8
6856634	snv_117 not booting: zfs_parse_bootfs: error2 (141445-07)

10160:a45b03783d44
6861983	zfs should use new name <-> SID interfaces (N/A)
6862984	userquota commands can hang (141445-06)

10299:80845694147f
6696858	zfs receive of incremental replication stream can dereference NULL pointer and crash (N/A)

10302:a9e3d1987706
6696858	zfs receive of incremental replication stream can dereference NULL pointer and crash (fix lint) (N/A)

10575:2a8816c5173b (partial merge)
6882227 spa_async_remove() shouldn't do a full clear (142901-14)

10800:469478b180d9
6880764	fsync on zfs is broken if writes are greater than 32kb on a hard crash and no log attached (142901-09)
6793430 zdb -ivvvv assertion failure: bp->blk_cksum.zc_word[2] == dmu_objset_id(zilog->zl_os) (N/A)

10801:e0bf032e8673 (partial merge)
6822816 assertion failed: zap_remove_int(ds_next_clones_obj) returns ENOENT (142901-09)

10810:b6b161a6ae4a
6892298 buf->b_hdr->b_state != arc_anon, file: ../../common/fs/zfs/arc.c, line: 2849 (142901-09)

10890:499786962772
6807339	spurious checksum errors when replacing a vdev (142901-13)

11249:6c30f7dfc97b
6906110 bad trap panic in zil_replay_log_record (142901-13)
6906946 zfs replay isn't handling uid/gid correctly (142901-13)

11454:6e69bacc1a5a
6898245 suspended zpool should not cause rest of the zfs/zpool commands to hang (142901-10)

11546:42ea6be8961b (partial merge)
6833999 3-way deadlock in dsl_dataset_hold_ref() and dsl_sync_task_group_sync() (142901-09)

Discussed with:	pjd
Approved by:	delphij (mentor)
Obtained from:	OpenSolaris (multiple Bug IDs)
MFC after:	2 months
2010-07-12 23:49:04 +00:00

654 lines
17 KiB
C

/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Iterate over all children of the current object. This includes the normal
* dataset hierarchy, but also arbitrary hierarchies due to clones. We want to
* walk all datasets in the pool, and construct a directed graph of the form:
*
* home
* |
* +----+----+
* | |
* v v ws
* bar baz |
* | |
* v v
* @yesterday ----> foo
*
* In order to construct this graph, we have to walk every dataset in the pool,
* because the clone parent is stored as a property of the child, not the
* parent. The parent only keeps track of the number of clones.
*
* In the normal case (without clones) this would be rather expensive. To avoid
* unnecessary computation, we first try a walk of the subtree hierarchy
* starting from the initial node. At each dataset, we construct a node in the
* graph and an edge leading from its parent. If we don't see any snapshots
* with a non-zero clone count, then we are finished.
*
* If we do find a cloned snapshot, then we finish the walk of the current
* subtree, but indicate that we need to do a complete walk. We then perform a
* global walk of all datasets, avoiding the subtree we already processed.
*
* At the end of this, we'll end up with a directed graph of all relevant (and
* possible some irrelevant) datasets in the system. We need to both find our
* limiting subgraph and determine a safe ordering in which to destroy the
* 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
* vertex's edges sorted according to this value. The topological sort will
* automatically walk the snapshots in the correct order.
*/
#include <assert.h>
#include <libintl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <libzfs.h>
#include "libzfs_impl.h"
#include "zfs_namecheck.h"
#define MIN_EDGECOUNT 4
/*
* Vertex structure. Indexed by dataset name, this structure maintains a list
* of edges to other vertices.
*/
struct zfs_edge;
typedef struct zfs_vertex {
char zv_dataset[ZFS_MAXNAMELEN];
struct zfs_vertex *zv_next;
int zv_visited;
uint64_t zv_txg;
struct zfs_edge **zv_edges;
int zv_edgecount;
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
* of the source vertex.
*/
typedef struct zfs_edge {
zfs_vertex_t *ze_dest;
struct zfs_edge *ze_next;
} zfs_edge_t;
#define ZFS_GRAPH_SIZE 1027 /* this could be dynamic some day */
/*
* Graph structure. Vertices are maintained in a hash indexed by dataset name.
*/
typedef struct zfs_graph {
zfs_vertex_t **zg_hash;
size_t zg_size;
size_t zg_nvertex;
const char *zg_root;
int zg_clone_count;
} zfs_graph_t;
/*
* Allocate a new edge pointing to the target vertex.
*/
static zfs_edge_t *
zfs_edge_create(libzfs_handle_t *hdl, zfs_vertex_t *dest)
{
zfs_edge_t *zep = zfs_alloc(hdl, sizeof (zfs_edge_t));
if (zep == NULL)
return (NULL);
zep->ze_dest = dest;
return (zep);
}
/*
* Destroy an edge.
*/
static void
zfs_edge_destroy(zfs_edge_t *zep)
{
free(zep);
}
/*
* Allocate a new vertex with the given name.
*/
static zfs_vertex_t *
zfs_vertex_create(libzfs_handle_t *hdl, const char *dataset)
{
zfs_vertex_t *zvp = zfs_alloc(hdl, sizeof (zfs_vertex_t));
if (zvp == NULL)
return (NULL);
assert(strlen(dataset) < ZFS_MAXNAMELEN);
(void) strlcpy(zvp->zv_dataset, dataset, sizeof (zvp->zv_dataset));
if ((zvp->zv_edges = zfs_alloc(hdl,
MIN_EDGECOUNT * sizeof (void *))) == NULL) {
free(zvp);
return (NULL);
}
zvp->zv_edgealloc = MIN_EDGECOUNT;
return (zvp);
}
/*
* Destroy a vertex. Frees up any associated edges.
*/
static void
zfs_vertex_destroy(zfs_vertex_t *zvp)
{
int i;
for (i = 0; i < zvp->zv_edgecount; i++)
zfs_edge_destroy(zvp->zv_edges[i]);
free(zvp->zv_edges);
free(zvp);
}
/*
* Given a vertex, add an edge to the destination vertex.
*/
static int
zfs_vertex_add_edge(libzfs_handle_t *hdl, zfs_vertex_t *zvp,
zfs_vertex_t *dest)
{
zfs_edge_t *zep = zfs_edge_create(hdl, dest);
if (zep == NULL)
return (-1);
if (zvp->zv_edgecount == zvp->zv_edgealloc) {
void *ptr;
if ((ptr = zfs_realloc(hdl, zvp->zv_edges,
zvp->zv_edgealloc * sizeof (void *),
zvp->zv_edgealloc * 2 * sizeof (void *))) == NULL)
return (-1);
zvp->zv_edges = ptr;
zvp->zv_edgealloc *= 2;
}
zvp->zv_edges[zvp->zv_edgecount++] = zep;
return (0);
}
static int
zfs_edge_compare(const void *a, const void *b)
{
const zfs_edge_t *ea = *((zfs_edge_t **)a);
const zfs_edge_t *eb = *((zfs_edge_t **)b);
if (ea->ze_dest->zv_txg < eb->ze_dest->zv_txg)
return (-1);
if (ea->ze_dest->zv_txg > eb->ze_dest->zv_txg)
return (1);
return (0);
}
/*
* Sort the given vertex edges according to the creation txg of each vertex.
*/
static void
zfs_vertex_sort_edges(zfs_vertex_t *zvp)
{
if (zvp->zv_edgecount == 0)
return;
qsort(zvp->zv_edges, zvp->zv_edgecount, sizeof (void *),
zfs_edge_compare);
}
/*
* Construct a new graph object. We allow the size to be specified as a
* parameter so in the future we can size the hash according to the number of
* datasets in the pool.
*/
static zfs_graph_t *
zfs_graph_create(libzfs_handle_t *hdl, const char *dataset, size_t size)
{
zfs_graph_t *zgp = zfs_alloc(hdl, sizeof (zfs_graph_t));
if (zgp == NULL)
return (NULL);
zgp->zg_size = size;
if ((zgp->zg_hash = zfs_alloc(hdl,
size * sizeof (zfs_vertex_t *))) == NULL) {
free(zgp);
return (NULL);
}
zgp->zg_root = dataset;
zgp->zg_clone_count = 0;
return (zgp);
}
/*
* Destroy a graph object. We have to iterate over all the hash chains,
* destroying each vertex in the process.
*/
static void
zfs_graph_destroy(zfs_graph_t *zgp)
{
int i;
zfs_vertex_t *current, *next;
for (i = 0; i < zgp->zg_size; i++) {
current = zgp->zg_hash[i];
while (current != NULL) {
next = current->zv_next;
zfs_vertex_destroy(current);
current = next;
}
}
free(zgp->zg_hash);
free(zgp);
}
/*
* Graph hash function. Classic bernstein k=33 hash function, taken from
* usr/src/cmd/sgs/tools/common/strhash.c
*/
static size_t
zfs_graph_hash(zfs_graph_t *zgp, const char *str)
{
size_t hash = 5381;
int c;
while ((c = *str++) != 0)
hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
return (hash % zgp->zg_size);
}
/*
* Given a dataset name, finds the associated vertex, creating it if necessary.
*/
static zfs_vertex_t *
zfs_graph_lookup(libzfs_handle_t *hdl, zfs_graph_t *zgp, const char *dataset,
uint64_t txg)
{
size_t idx = zfs_graph_hash(zgp, dataset);
zfs_vertex_t *zvp;
for (zvp = zgp->zg_hash[idx]; zvp != NULL; zvp = zvp->zv_next) {
if (strcmp(zvp->zv_dataset, dataset) == 0) {
if (zvp->zv_txg == 0)
zvp->zv_txg = txg;
return (zvp);
}
}
if ((zvp = zfs_vertex_create(hdl, dataset)) == NULL)
return (NULL);
zvp->zv_next = zgp->zg_hash[idx];
zvp->zv_txg = txg;
zgp->zg_hash[idx] = zvp;
zgp->zg_nvertex++;
return (zvp);
}
/*
* 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);
}
/*
* 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))) {
/*
* 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);
}
/*
* Returns false if there are no snapshots with dependent clones in this
* subtree or if all of those clones are also in this subtree. Returns
* true if there is an error or there are external dependents.
*/
static boolean_t
external_dependents(libzfs_handle_t *hdl, zfs_graph_t *zgp, const char *dataset)
{
zfs_cmd_t zc = { 0 };
/*
* Check whether this dataset is a clone or has clones since
* iterate_children() only checks the children.
*/
(void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name));
if (ioctl(hdl->libzfs_fd, ZFS_IOC_OBJSET_STATS, &zc) != 0)
return (B_TRUE);
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 (B_TRUE);
if (isa_child_of(zc.zc_objset_stats.dds_origin, dataset))
zgp->zg_clone_count--;
}
if ((zc.zc_objset_stats.dds_num_clones) ||
iterate_children(hdl, zgp, dataset))
return (B_TRUE);
return (zgp->zg_clone_count != 0);
}
/*
* Construct a complete graph of all necessary vertices. First, iterate over
* only our object's children. If no cloned snapshots are found, or all of
* the cloned snapshots are in this subtree then return a graph of the subtree.
* Otherwise, start at the root of the pool and iterate over all datasets.
*/
static zfs_graph_t *
construct_graph(libzfs_handle_t *hdl, const char *dataset)
{
zfs_graph_t *zgp = zfs_graph_create(hdl, dataset, ZFS_GRAPH_SIZE);
int ret = 0;
if (zgp == NULL)
return (zgp);
if ((strchr(dataset, '/') == NULL) ||
(external_dependents(hdl, zgp, dataset))) {
/*
* Determine pool name and try again.
*/
int len = strcspn(dataset, "/@") + 1;
char *pool = zfs_alloc(hdl, len);
if (pool == NULL) {
zfs_graph_destroy(zgp);
return (NULL);
}
(void) strlcpy(pool, dataset, len);
if (iterate_children(hdl, zgp, pool) == -1 ||
zfs_graph_add(hdl, zgp, pool, NULL, 0) != 0) {
free(pool);
zfs_graph_destroy(zgp);
return (NULL);
}
free(pool);
}
if (ret == -1 || zfs_graph_add(hdl, zgp, dataset, NULL, 0) != 0) {
zfs_graph_destroy(zgp);
return (NULL);
}
return (zgp);
}
/*
* Given a graph, do a recursive topological sort into the given array. This is
* really just a depth first search, so that the deepest nodes appear first.
* hijack the 'zv_visited' marker to avoid visiting the same vertex twice.
*/
static int
topo_sort(libzfs_handle_t *hdl, boolean_t allowrecursion, char **result,
size_t *idx, zfs_vertex_t *zgv)
{
int i;
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, 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 == VISIT_SORT_POST)
return (0);
if ((result[*idx] = zfs_alloc(hdl,
strlen(zgv->zv_dataset) + 1)) == NULL)
return (-1);
(void) strcpy(result[*idx], zgv->zv_dataset);
*idx += 1;
zgv->zv_visited = VISIT_SORT_POST;
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);
}