1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
23 * Copyright (c) 2012, 2017 by Delphix. All rights reserved.
24 * Copyright (c) 2013 Steven Hartland. All rights reserved.
25 */
26
27#include <sys/zfs_context.h>
28#include <sys/dsl_userhold.h>
29#include <sys/dsl_dataset.h>
30#include <sys/dsl_destroy.h>
31#include <sys/dsl_synctask.h>
32#include <sys/dmu_tx.h>
33#include <sys/zfs_onexit.h>
34#include <sys/dsl_pool.h>
35#include <sys/dsl_dir.h>
36#include <sys/zfs_ioctl.h>
37#include <sys/zap.h>
38
39typedef struct dsl_dataset_user_hold_arg {
40	nvlist_t *dduha_holds;
41	nvlist_t *dduha_chkholds;
42	nvlist_t *dduha_errlist;
43	minor_t dduha_minor;
44} dsl_dataset_user_hold_arg_t;
45
46/*
47 * If you add new checks here, you may need to add additional checks to the
48 * "temporary" case in snapshot_check() in dmu_objset.c.
49 */
50int
51dsl_dataset_user_hold_check_one(dsl_dataset_t *ds, const char *htag,
52    boolean_t temphold, dmu_tx_t *tx)
53{
54	dsl_pool_t *dp = dmu_tx_pool(tx);
55	objset_t *mos = dp->dp_meta_objset;
56	int error = 0;
57
58	ASSERT(dsl_pool_config_held(dp));
59
60	if (strlen(htag) > MAXNAMELEN)
61		return (SET_ERROR(E2BIG));
62	/* Tempholds have a more restricted length */
63	if (temphold && strlen(htag) + MAX_TAG_PREFIX_LEN >= MAXNAMELEN)
64		return (SET_ERROR(E2BIG));
65
66	/* tags must be unique (if ds already exists) */
67	if (ds != NULL && dsl_dataset_phys(ds)->ds_userrefs_obj != 0) {
68		uint64_t value;
69
70		error = zap_lookup(mos, dsl_dataset_phys(ds)->ds_userrefs_obj,
71		    htag, 8, 1, &value);
72		if (error == 0)
73			error = SET_ERROR(EEXIST);
74		else if (error == ENOENT)
75			error = 0;
76	}
77
78	return (error);
79}
80
81static int
82dsl_dataset_user_hold_check(void *arg, dmu_tx_t *tx)
83{
84	dsl_dataset_user_hold_arg_t *dduha = arg;
85	dsl_pool_t *dp = dmu_tx_pool(tx);
86
87	if (spa_version(dp->dp_spa) < SPA_VERSION_USERREFS)
88		return (SET_ERROR(ENOTSUP));
89
90	if (!dmu_tx_is_syncing(tx))
91		return (0);
92
93	for (nvpair_t *pair = nvlist_next_nvpair(dduha->dduha_holds, NULL);
94	    pair != NULL; pair = nvlist_next_nvpair(dduha->dduha_holds, pair)) {
95		dsl_dataset_t *ds;
96		int error = 0;
97		char *htag, *name;
98
99		/* must be a snapshot */
100		name = nvpair_name(pair);
101		if (strchr(name, '@') == NULL)
102			error = SET_ERROR(EINVAL);
103
104		if (error == 0)
105			error = nvpair_value_string(pair, &htag);
106
107		if (error == 0)
108			error = dsl_dataset_hold(dp, name, FTAG, &ds);
109
110		if (error == 0) {
111			error = dsl_dataset_user_hold_check_one(ds, htag,
112			    dduha->dduha_minor != 0, tx);
113			dsl_dataset_rele(ds, FTAG);
114		}
115
116		if (error == 0) {
117			fnvlist_add_string(dduha->dduha_chkholds, name, htag);
118		} else {
119			/*
120			 * We register ENOENT errors so they can be correctly
121			 * reported if needed, such as when all holds fail.
122			 */
123			fnvlist_add_int32(dduha->dduha_errlist, name, error);
124			if (error != ENOENT)
125				return (error);
126		}
127	}
128
129	return (0);
130}
131
132
133static void
134dsl_dataset_user_hold_sync_one_impl(nvlist_t *tmpholds, dsl_dataset_t *ds,
135    const char *htag, minor_t minor, uint64_t now, dmu_tx_t *tx)
136{
137	dsl_pool_t *dp = ds->ds_dir->dd_pool;
138	objset_t *mos = dp->dp_meta_objset;
139	uint64_t zapobj;
140
141	ASSERT(RRW_WRITE_HELD(&dp->dp_config_rwlock));
142
143	if (dsl_dataset_phys(ds)->ds_userrefs_obj == 0) {
144		/*
145		 * This is the first user hold for this dataset.  Create
146		 * the userrefs zap object.
147		 */
148		dmu_buf_will_dirty(ds->ds_dbuf, tx);
149		zapobj = dsl_dataset_phys(ds)->ds_userrefs_obj =
150		    zap_create(mos, DMU_OT_USERREFS, DMU_OT_NONE, 0, tx);
151	} else {
152		zapobj = dsl_dataset_phys(ds)->ds_userrefs_obj;
153	}
154	ds->ds_userrefs++;
155
156	VERIFY0(zap_add(mos, zapobj, htag, 8, 1, &now, tx));
157
158	if (minor != 0) {
159		char name[MAXNAMELEN];
160		nvlist_t *tags;
161
162		VERIFY0(dsl_pool_user_hold(dp, ds->ds_object,
163		    htag, now, tx));
164		(void) snprintf(name, sizeof (name), "%llx",
165		    (u_longlong_t)ds->ds_object);
166
167		if (nvlist_lookup_nvlist(tmpholds, name, &tags) != 0) {
168			tags = fnvlist_alloc();
169			fnvlist_add_boolean(tags, htag);
170			fnvlist_add_nvlist(tmpholds, name, tags);
171			fnvlist_free(tags);
172		} else {
173			fnvlist_add_boolean(tags, htag);
174		}
175	}
176
177	spa_history_log_internal_ds(ds, "hold", tx,
178	    "tag=%s temp=%d refs=%llu",
179	    htag, minor != 0, ds->ds_userrefs);
180}
181
182typedef struct zfs_hold_cleanup_arg {
183	char zhca_spaname[ZFS_MAX_DATASET_NAME_LEN];
184	uint64_t zhca_spa_load_guid;
185	nvlist_t *zhca_holds;
186} zfs_hold_cleanup_arg_t;
187
188static void
189dsl_dataset_user_release_onexit(void *arg)
190{
191	zfs_hold_cleanup_arg_t *ca = arg;
192	spa_t *spa;
193	int error;
194
195	error = spa_open(ca->zhca_spaname, &spa, FTAG);
196	if (error != 0) {
197		zfs_dbgmsg("couldn't release holds on pool=%s "
198		    "because pool is no longer loaded",
199		    ca->zhca_spaname);
200		return;
201	}
202	if (spa_load_guid(spa) != ca->zhca_spa_load_guid) {
203		zfs_dbgmsg("couldn't release holds on pool=%s "
204		    "because pool is no longer loaded (guid doesn't match)",
205		    ca->zhca_spaname);
206		spa_close(spa, FTAG);
207		return;
208	}
209
210	(void) dsl_dataset_user_release_tmp(spa_get_dsl(spa), ca->zhca_holds);
211	fnvlist_free(ca->zhca_holds);
212	kmem_free(ca, sizeof (zfs_hold_cleanup_arg_t));
213	spa_close(spa, FTAG);
214}
215
216static void
217dsl_onexit_hold_cleanup(spa_t *spa, nvlist_t *holds, minor_t minor)
218{
219	zfs_hold_cleanup_arg_t *ca;
220
221	if (minor == 0 || nvlist_empty(holds)) {
222		fnvlist_free(holds);
223		return;
224	}
225
226	ASSERT(spa != NULL);
227	ca = kmem_alloc(sizeof (*ca), KM_SLEEP);
228
229	(void) strlcpy(ca->zhca_spaname, spa_name(spa),
230	    sizeof (ca->zhca_spaname));
231	ca->zhca_spa_load_guid = spa_load_guid(spa);
232	ca->zhca_holds = holds;
233	VERIFY0(zfs_onexit_add_cb(minor,
234	    dsl_dataset_user_release_onexit, ca, NULL));
235}
236
237void
238dsl_dataset_user_hold_sync_one(dsl_dataset_t *ds, const char *htag,
239    minor_t minor, uint64_t now, dmu_tx_t *tx)
240{
241	nvlist_t *tmpholds;
242
243	if (minor != 0)
244		tmpholds = fnvlist_alloc();
245	else
246		tmpholds = NULL;
247	dsl_dataset_user_hold_sync_one_impl(tmpholds, ds, htag, minor, now, tx);
248	dsl_onexit_hold_cleanup(dsl_dataset_get_spa(ds), tmpholds, minor);
249}
250
251static void
252dsl_dataset_user_hold_sync(void *arg, dmu_tx_t *tx)
253{
254	dsl_dataset_user_hold_arg_t *dduha = arg;
255	dsl_pool_t *dp = dmu_tx_pool(tx);
256	nvlist_t *tmpholds;
257	uint64_t now = gethrestime_sec();
258
259	if (dduha->dduha_minor != 0)
260		tmpholds = fnvlist_alloc();
261	else
262		tmpholds = NULL;
263	for (nvpair_t *pair = nvlist_next_nvpair(dduha->dduha_chkholds, NULL);
264	    pair != NULL;
265	    pair = nvlist_next_nvpair(dduha->dduha_chkholds, pair)) {
266		dsl_dataset_t *ds;
267
268		VERIFY0(dsl_dataset_hold(dp, nvpair_name(pair), FTAG, &ds));
269		dsl_dataset_user_hold_sync_one_impl(tmpholds, ds,
270		    fnvpair_value_string(pair), dduha->dduha_minor, now, tx);
271		dsl_dataset_rele(ds, FTAG);
272	}
273	dsl_onexit_hold_cleanup(dp->dp_spa, tmpholds, dduha->dduha_minor);
274}
275
276/*
277 * The full semantics of this function are described in the comment above
278 * lzc_hold().
279 *
280 * To summarize:
281 * holds is nvl of snapname -> holdname
282 * errlist will be filled in with snapname -> error
283 *
284 * The snaphosts must all be in the same pool.
285 *
286 * Holds for snapshots that don't exist will be skipped.
287 *
288 * If none of the snapshots for requested holds exist then ENOENT will be
289 * returned.
290 *
291 * If cleanup_minor is not 0, the holds will be temporary, which will be cleaned
292 * up when the process exits.
293 *
294 * On success all the holds, for snapshots that existed, will be created and 0
295 * will be returned.
296 *
297 * On failure no holds will be created, the errlist will be filled in,
298 * and an errno will returned.
299 *
300 * In all cases the errlist will contain entries for holds where the snapshot
301 * didn't exist.
302 */
303int
304dsl_dataset_user_hold(nvlist_t *holds, minor_t cleanup_minor, nvlist_t *errlist)
305{
306	dsl_dataset_user_hold_arg_t dduha;
307	nvpair_t *pair;
308	int ret;
309
310	pair = nvlist_next_nvpair(holds, NULL);
311	if (pair == NULL)
312		return (0);
313
314	dduha.dduha_holds = holds;
315	dduha.dduha_chkholds = fnvlist_alloc();
316	dduha.dduha_errlist = errlist;
317	dduha.dduha_minor = cleanup_minor;
318
319	ret = dsl_sync_task(nvpair_name(pair), dsl_dataset_user_hold_check,
320	    dsl_dataset_user_hold_sync, &dduha,
321	    fnvlist_num_pairs(holds), ZFS_SPACE_CHECK_RESERVED);
322	fnvlist_free(dduha.dduha_chkholds);
323
324	return (ret);
325}
326
327typedef int (dsl_holdfunc_t)(dsl_pool_t *dp, const char *name, void *tag,
328    dsl_dataset_t **dsp);
329
330typedef struct dsl_dataset_user_release_arg {
331	dsl_holdfunc_t *ddura_holdfunc;
332	nvlist_t *ddura_holds;
333	nvlist_t *ddura_todelete;
334	nvlist_t *ddura_errlist;
335	nvlist_t *ddura_chkholds;
336} dsl_dataset_user_release_arg_t;
337
338/* Place a dataset hold on the snapshot identified by passed dsobj string */
339static int
340dsl_dataset_hold_obj_string(dsl_pool_t *dp, const char *dsobj, void *tag,
341    dsl_dataset_t **dsp)
342{
343	return (dsl_dataset_hold_obj(dp, zfs_strtonum(dsobj, NULL), tag, dsp));
344}
345
346static int
347dsl_dataset_user_release_check_one(dsl_dataset_user_release_arg_t *ddura,
348    dsl_dataset_t *ds, nvlist_t *holds, const char *snapname)
349{
350	uint64_t zapobj;
351	nvlist_t *holds_found;
352	objset_t *mos;
353	int numholds;
354
355	if (!ds->ds_is_snapshot)
356		return (SET_ERROR(EINVAL));
357
358	if (nvlist_empty(holds))
359		return (0);
360
361	numholds = 0;
362	mos = ds->ds_dir->dd_pool->dp_meta_objset;
363	zapobj = dsl_dataset_phys(ds)->ds_userrefs_obj;
364	holds_found = fnvlist_alloc();
365
366	for (nvpair_t *pair = nvlist_next_nvpair(holds, NULL); pair != NULL;
367	    pair = nvlist_next_nvpair(holds, pair)) {
368		uint64_t tmp;
369		int error;
370		const char *holdname = nvpair_name(pair);
371
372		if (zapobj != 0)
373			error = zap_lookup(mos, zapobj, holdname, 8, 1, &tmp);
374		else
375			error = SET_ERROR(ENOENT);
376
377		/*
378		 * Non-existent holds are put on the errlist, but don't
379		 * cause an overall failure.
380		 */
381		if (error == ENOENT) {
382			if (ddura->ddura_errlist != NULL) {
383				char *errtag = kmem_asprintf("%s#%s",
384				    snapname, holdname);
385				fnvlist_add_int32(ddura->ddura_errlist, errtag,
386				    ENOENT);
387				strfree(errtag);
388			}
389			continue;
390		}
391
392		if (error != 0) {
393			fnvlist_free(holds_found);
394			return (error);
395		}
396
397		fnvlist_add_boolean(holds_found, holdname);
398		numholds++;
399	}
400
401	if (DS_IS_DEFER_DESTROY(ds) &&
402	    dsl_dataset_phys(ds)->ds_num_children == 1 &&
403	    ds->ds_userrefs == numholds) {
404		/* we need to destroy the snapshot as well */
405		if (dsl_dataset_long_held(ds)) {
406			fnvlist_free(holds_found);
407			return (SET_ERROR(EBUSY));
408		}
409		fnvlist_add_boolean(ddura->ddura_todelete, snapname);
410	}
411
412	if (numholds != 0) {
413		fnvlist_add_nvlist(ddura->ddura_chkholds, snapname,
414		    holds_found);
415	}
416	fnvlist_free(holds_found);
417
418	return (0);
419}
420
421static int
422dsl_dataset_user_release_check(void *arg, dmu_tx_t *tx)
423{
424	dsl_dataset_user_release_arg_t *ddura;
425	dsl_holdfunc_t *holdfunc;
426	dsl_pool_t *dp;
427
428	if (!dmu_tx_is_syncing(tx))
429		return (0);
430
431	dp = dmu_tx_pool(tx);
432
433	ASSERT(RRW_WRITE_HELD(&dp->dp_config_rwlock));
434
435	ddura = arg;
436	holdfunc = ddura->ddura_holdfunc;
437
438	for (nvpair_t *pair = nvlist_next_nvpair(ddura->ddura_holds, NULL);
439	    pair != NULL; pair = nvlist_next_nvpair(ddura->ddura_holds, pair)) {
440		int error;
441		dsl_dataset_t *ds;
442		nvlist_t *holds;
443		const char *snapname = nvpair_name(pair);
444
445		error = nvpair_value_nvlist(pair, &holds);
446		if (error != 0)
447			error = (SET_ERROR(EINVAL));
448		else
449			error = holdfunc(dp, snapname, FTAG, &ds);
450		if (error == 0) {
451			error = dsl_dataset_user_release_check_one(ddura, ds,
452			    holds, snapname);
453			dsl_dataset_rele(ds, FTAG);
454		}
455		if (error != 0) {
456			if (ddura->ddura_errlist != NULL) {
457				fnvlist_add_int32(ddura->ddura_errlist,
458				    snapname, error);
459			}
460			/*
461			 * Non-existent snapshots are put on the errlist,
462			 * but don't cause an overall failure.
463			 */
464			if (error != ENOENT)
465				return (error);
466		}
467	}
468
469	return (0);
470}
471
472static void
473dsl_dataset_user_release_sync_one(dsl_dataset_t *ds, nvlist_t *holds,
474    dmu_tx_t *tx)
475{
476	dsl_pool_t *dp = ds->ds_dir->dd_pool;
477	objset_t *mos = dp->dp_meta_objset;
478
479	for (nvpair_t *pair = nvlist_next_nvpair(holds, NULL); pair != NULL;
480	    pair = nvlist_next_nvpair(holds, pair)) {
481		int error;
482		const char *holdname = nvpair_name(pair);
483
484		/* Remove temporary hold if one exists. */
485		error = dsl_pool_user_release(dp, ds->ds_object, holdname, tx);
486		VERIFY(error == 0 || error == ENOENT);
487
488		VERIFY0(zap_remove(mos, dsl_dataset_phys(ds)->ds_userrefs_obj,
489		    holdname, tx));
490		ds->ds_userrefs--;
491
492		spa_history_log_internal_ds(ds, "release", tx,
493		    "tag=%s refs=%lld", holdname, (longlong_t)ds->ds_userrefs);
494	}
495}
496
497static void
498dsl_dataset_user_release_sync(void *arg, dmu_tx_t *tx)
499{
500	dsl_dataset_user_release_arg_t *ddura = arg;
501	dsl_holdfunc_t *holdfunc = ddura->ddura_holdfunc;
502	dsl_pool_t *dp = dmu_tx_pool(tx);
503
504	ASSERT(RRW_WRITE_HELD(&dp->dp_config_rwlock));
505
506	for (nvpair_t *pair = nvlist_next_nvpair(ddura->ddura_chkholds, NULL);
507	    pair != NULL; pair = nvlist_next_nvpair(ddura->ddura_chkholds,
508	    pair)) {
509		dsl_dataset_t *ds;
510		const char *name = nvpair_name(pair);
511
512		VERIFY0(holdfunc(dp, name, FTAG, &ds));
513
514		dsl_dataset_user_release_sync_one(ds,
515		    fnvpair_value_nvlist(pair), tx);
516		if (nvlist_exists(ddura->ddura_todelete, name)) {
517			ASSERT(ds->ds_userrefs == 0 &&
518			    dsl_dataset_phys(ds)->ds_num_children == 1 &&
519			    DS_IS_DEFER_DESTROY(ds));
520			dsl_destroy_snapshot_sync_impl(ds, B_FALSE, tx);
521		}
522		dsl_dataset_rele(ds, FTAG);
523	}
524}
525
526/*
527 * The full semantics of this function are described in the comment above
528 * lzc_release().
529 *
530 * To summarize:
531 * Releases holds specified in the nvl holds.
532 *
533 * holds is nvl of snapname -> { holdname, ... }
534 * errlist will be filled in with snapname -> error
535 *
536 * If tmpdp is not NULL the names for holds should be the dsobj's of snapshots,
537 * otherwise they should be the names of shapshots.
538 *
539 * As a release may cause snapshots to be destroyed this trys to ensure they
540 * aren't mounted.
541 *
542 * The release of non-existent holds are skipped.
543 *
544 * At least one hold must have been released for the this function to succeed
545 * and return 0.
546 */
547static int
548dsl_dataset_user_release_impl(nvlist_t *holds, nvlist_t *errlist,
549    dsl_pool_t *tmpdp)
550{
551	dsl_dataset_user_release_arg_t ddura;
552	nvpair_t *pair;
553	char *pool;
554	int error;
555
556	pair = nvlist_next_nvpair(holds, NULL);
557	if (pair == NULL)
558		return (0);
559
560	/*
561	 * The release may cause snapshots to be destroyed; make sure they
562	 * are not mounted.
563	 */
564	if (tmpdp != NULL) {
565		/* Temporary holds are specified by dsobj string. */
566		ddura.ddura_holdfunc = dsl_dataset_hold_obj_string;
567		pool = spa_name(tmpdp->dp_spa);
568#ifdef _KERNEL
569		for (pair = nvlist_next_nvpair(holds, NULL); pair != NULL;
570		    pair = nvlist_next_nvpair(holds, pair)) {
571			dsl_dataset_t *ds;
572
573			dsl_pool_config_enter(tmpdp, FTAG);
574			error = dsl_dataset_hold_obj_string(tmpdp,
575			    nvpair_name(pair), FTAG, &ds);
576			if (error == 0) {
577				char name[ZFS_MAX_DATASET_NAME_LEN];
578				dsl_dataset_name(ds, name);
579				dsl_pool_config_exit(tmpdp, FTAG);
580				dsl_dataset_rele(ds, FTAG);
581				(void) zfs_unmount_snap(name);
582			} else {
583				dsl_pool_config_exit(tmpdp, FTAG);
584			}
585		}
586#endif
587	} else {
588		/* Non-temporary holds are specified by name. */
589		ddura.ddura_holdfunc = dsl_dataset_hold;
590		pool = nvpair_name(pair);
591#ifdef _KERNEL
592		for (pair = nvlist_next_nvpair(holds, NULL); pair != NULL;
593		    pair = nvlist_next_nvpair(holds, pair)) {
594			(void) zfs_unmount_snap(nvpair_name(pair));
595		}
596#endif
597	}
598
599	ddura.ddura_holds = holds;
600	ddura.ddura_errlist = errlist;
601	ddura.ddura_todelete = fnvlist_alloc();
602	ddura.ddura_chkholds = fnvlist_alloc();
603
604	error = dsl_sync_task(pool, dsl_dataset_user_release_check,
605	    dsl_dataset_user_release_sync, &ddura, 0,
606	    ZFS_SPACE_CHECK_EXTRA_RESERVED);
607	fnvlist_free(ddura.ddura_todelete);
608	fnvlist_free(ddura.ddura_chkholds);
609
610	return (error);
611}
612
613/*
614 * holds is nvl of snapname -> { holdname, ... }
615 * errlist will be filled in with snapname -> error
616 */
617int
618dsl_dataset_user_release(nvlist_t *holds, nvlist_t *errlist)
619{
620	return (dsl_dataset_user_release_impl(holds, errlist, NULL));
621}
622
623/*
624 * holds is nvl of snapdsobj -> { holdname, ... }
625 */
626void
627dsl_dataset_user_release_tmp(struct dsl_pool *dp, nvlist_t *holds)
628{
629	ASSERT(dp != NULL);
630	(void) dsl_dataset_user_release_impl(holds, NULL, dp);
631}
632
633int
634dsl_dataset_get_holds(const char *dsname, nvlist_t *nvl)
635{
636	dsl_pool_t *dp;
637	dsl_dataset_t *ds;
638	int err;
639
640	err = dsl_pool_hold(dsname, FTAG, &dp);
641	if (err != 0)
642		return (err);
643	err = dsl_dataset_hold(dp, dsname, FTAG, &ds);
644	if (err != 0) {
645		dsl_pool_rele(dp, FTAG);
646		return (err);
647	}
648
649	if (dsl_dataset_phys(ds)->ds_userrefs_obj != 0) {
650		zap_attribute_t *za;
651		zap_cursor_t zc;
652
653		za = kmem_alloc(sizeof (zap_attribute_t), KM_SLEEP);
654		for (zap_cursor_init(&zc, ds->ds_dir->dd_pool->dp_meta_objset,
655		    dsl_dataset_phys(ds)->ds_userrefs_obj);
656		    zap_cursor_retrieve(&zc, za) == 0;
657		    zap_cursor_advance(&zc)) {
658			fnvlist_add_uint64(nvl, za->za_name,
659			    za->za_first_integer);
660		}
661		zap_cursor_fini(&zc);
662		kmem_free(za, sizeof (zap_attribute_t));
663	}
664	dsl_dataset_rele(ds, FTAG);
665	dsl_pool_rele(dp, FTAG);
666	return (0);
667}
668