dsl_userhold.c revision 269006
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, 2014 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 && ds->ds_phys->ds_userrefs_obj != 0) {
68		uint64_t value;
69
70		error = zap_lookup(mos, ds->ds_phys->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 (ds->ds_phys->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 = ds->ds_phys->ds_userrefs_obj =
150		    zap_create(mos, DMU_OT_USERREFS, DMU_OT_NONE, 0, tx);
151	} else {
152		zapobj = ds->ds_phys->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[MAXNAMELEN];
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, 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 (!dsl_dataset_is_snapshot(ds))
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 = ds->ds_phys->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) && ds->ds_phys->ds_num_children == 1 &&
402	    ds->ds_userrefs == numholds) {
403		/* we need to destroy the snapshot as well */
404		if (dsl_dataset_long_held(ds)) {
405			fnvlist_free(holds_found);
406			return (SET_ERROR(EBUSY));
407		}
408		fnvlist_add_boolean(ddura->ddura_todelete, snapname);
409	}
410
411	if (numholds != 0) {
412		fnvlist_add_nvlist(ddura->ddura_chkholds, snapname,
413		    holds_found);
414	}
415	fnvlist_free(holds_found);
416
417	return (0);
418}
419
420static int
421dsl_dataset_user_release_check(void *arg, dmu_tx_t *tx)
422{
423	dsl_dataset_user_release_arg_t *ddura;
424	dsl_holdfunc_t *holdfunc;
425	dsl_pool_t *dp;
426
427	if (!dmu_tx_is_syncing(tx))
428		return (0);
429
430	dp = dmu_tx_pool(tx);
431
432	ASSERT(RRW_WRITE_HELD(&dp->dp_config_rwlock));
433
434	ddura = arg;
435	holdfunc = ddura->ddura_holdfunc;
436
437	for (nvpair_t *pair = nvlist_next_nvpair(ddura->ddura_holds, NULL);
438	    pair != NULL; pair = nvlist_next_nvpair(ddura->ddura_holds, pair)) {
439		int error;
440		dsl_dataset_t *ds;
441		nvlist_t *holds;
442		const char *snapname = nvpair_name(pair);
443
444		error = nvpair_value_nvlist(pair, &holds);
445		if (error != 0)
446			error = (SET_ERROR(EINVAL));
447		else
448			error = holdfunc(dp, snapname, FTAG, &ds);
449		if (error == 0) {
450			error = dsl_dataset_user_release_check_one(ddura, ds,
451			    holds, snapname);
452			dsl_dataset_rele(ds, FTAG);
453		}
454		if (error != 0) {
455			if (ddura->ddura_errlist != NULL) {
456				fnvlist_add_int32(ddura->ddura_errlist,
457				    snapname, error);
458			}
459			/*
460			 * Non-existent snapshots are put on the errlist,
461			 * but don't cause an overall failure.
462			 */
463			if (error != ENOENT)
464				return (error);
465		}
466	}
467
468	return (0);
469}
470
471static void
472dsl_dataset_user_release_sync_one(dsl_dataset_t *ds, nvlist_t *holds,
473    dmu_tx_t *tx)
474{
475	dsl_pool_t *dp = ds->ds_dir->dd_pool;
476	objset_t *mos = dp->dp_meta_objset;
477
478	for (nvpair_t *pair = nvlist_next_nvpair(holds, NULL); pair != NULL;
479	    pair = nvlist_next_nvpair(holds, pair)) {
480		int error;
481		const char *holdname = nvpair_name(pair);
482
483		/* Remove temporary hold if one exists. */
484		error = dsl_pool_user_release(dp, ds->ds_object, holdname, tx);
485		VERIFY(error == 0 || error == ENOENT);
486
487		VERIFY0(zap_remove(mos, ds->ds_phys->ds_userrefs_obj, holdname,
488		    tx));
489		ds->ds_userrefs--;
490
491		spa_history_log_internal_ds(ds, "release", tx,
492		    "tag=%s refs=%lld", holdname, (longlong_t)ds->ds_userrefs);
493	}
494}
495
496static void
497dsl_dataset_user_release_sync(void *arg, dmu_tx_t *tx)
498{
499	dsl_dataset_user_release_arg_t *ddura = arg;
500	dsl_holdfunc_t *holdfunc = ddura->ddura_holdfunc;
501	dsl_pool_t *dp = dmu_tx_pool(tx);
502
503	ASSERT(RRW_WRITE_HELD(&dp->dp_config_rwlock));
504
505	for (nvpair_t *pair = nvlist_next_nvpair(ddura->ddura_chkholds, NULL);
506	    pair != NULL; pair = nvlist_next_nvpair(ddura->ddura_chkholds,
507	    pair)) {
508		dsl_dataset_t *ds;
509		const char *name = nvpair_name(pair);
510
511		VERIFY0(holdfunc(dp, name, FTAG, &ds));
512
513		dsl_dataset_user_release_sync_one(ds,
514		    fnvpair_value_nvlist(pair), tx);
515		if (nvlist_exists(ddura->ddura_todelete, name)) {
516			ASSERT(ds->ds_userrefs == 0 &&
517			    ds->ds_phys->ds_num_children == 1 &&
518			    DS_IS_DEFER_DESTROY(ds));
519			dsl_destroy_snapshot_sync_impl(ds, B_FALSE, tx);
520		}
521		dsl_dataset_rele(ds, FTAG);
522	}
523}
524
525/*
526 * The full semantics of this function are described in the comment above
527 * lzc_release().
528 *
529 * To summarize:
530 * Releases holds specified in the nvl holds.
531 *
532 * holds is nvl of snapname -> { holdname, ... }
533 * errlist will be filled in with snapname -> error
534 *
535 * If tmpdp is not NULL the names for holds should be the dsobj's of snapshots,
536 * otherwise they should be the names of shapshots.
537 *
538 * As a release may cause snapshots to be destroyed this trys to ensure they
539 * aren't mounted.
540 *
541 * The release of non-existent holds are skipped.
542 *
543 * At least one hold must have been released for the this function to succeed
544 * and return 0.
545 */
546static int
547dsl_dataset_user_release_impl(nvlist_t *holds, nvlist_t *errlist,
548    dsl_pool_t *tmpdp)
549{
550	dsl_dataset_user_release_arg_t ddura;
551	nvpair_t *pair;
552	char *pool;
553	int error;
554
555	pair = nvlist_next_nvpair(holds, NULL);
556	if (pair == NULL)
557		return (0);
558
559	/*
560	 * The release may cause snapshots to be destroyed; make sure they
561	 * are not mounted.
562	 */
563	if (tmpdp != NULL) {
564		/* Temporary holds are specified by dsobj string. */
565		ddura.ddura_holdfunc = dsl_dataset_hold_obj_string;
566		pool = spa_name(tmpdp->dp_spa);
567#ifdef _KERNEL
568		for (pair = nvlist_next_nvpair(holds, NULL); pair != NULL;
569		    pair = nvlist_next_nvpair(holds, pair)) {
570			dsl_dataset_t *ds;
571
572			dsl_pool_config_enter(tmpdp, FTAG);
573			error = dsl_dataset_hold_obj_string(tmpdp,
574			    nvpair_name(pair), FTAG, &ds);
575			if (error == 0) {
576				char name[MAXNAMELEN];
577				dsl_dataset_name(ds, name);
578				dsl_pool_config_exit(tmpdp, FTAG);
579				dsl_dataset_rele(ds, FTAG);
580				(void) zfs_unmount_snap(name);
581			} else {
582				dsl_pool_config_exit(tmpdp, FTAG);
583			}
584		}
585#endif
586	} else {
587		/* Non-temporary holds are specified by name. */
588		ddura.ddura_holdfunc = dsl_dataset_hold;
589		pool = nvpair_name(pair);
590#ifdef _KERNEL
591		for (pair = nvlist_next_nvpair(holds, NULL); pair != NULL;
592		    pair = nvlist_next_nvpair(holds, pair)) {
593			(void) zfs_unmount_snap(nvpair_name(pair));
594		}
595#endif
596	}
597
598	ddura.ddura_holds = holds;
599	ddura.ddura_errlist = errlist;
600	ddura.ddura_todelete = fnvlist_alloc();
601	ddura.ddura_chkholds = fnvlist_alloc();
602
603	error = dsl_sync_task(pool, dsl_dataset_user_release_check,
604	    dsl_dataset_user_release_sync, &ddura, 0, ZFS_SPACE_CHECK_NONE);
605	fnvlist_free(ddura.ddura_todelete);
606	fnvlist_free(ddura.ddura_chkholds);
607
608	return (error);
609}
610
611/*
612 * holds is nvl of snapname -> { holdname, ... }
613 * errlist will be filled in with snapname -> error
614 */
615int
616dsl_dataset_user_release(nvlist_t *holds, nvlist_t *errlist)
617{
618	return (dsl_dataset_user_release_impl(holds, errlist, NULL));
619}
620
621/*
622 * holds is nvl of snapdsobj -> { holdname, ... }
623 */
624void
625dsl_dataset_user_release_tmp(struct dsl_pool *dp, nvlist_t *holds)
626{
627	ASSERT(dp != NULL);
628	(void) dsl_dataset_user_release_impl(holds, NULL, dp);
629}
630
631int
632dsl_dataset_get_holds(const char *dsname, nvlist_t *nvl)
633{
634	dsl_pool_t *dp;
635	dsl_dataset_t *ds;
636	int err;
637
638	err = dsl_pool_hold(dsname, FTAG, &dp);
639	if (err != 0)
640		return (err);
641	err = dsl_dataset_hold(dp, dsname, FTAG, &ds);
642	if (err != 0) {
643		dsl_pool_rele(dp, FTAG);
644		return (err);
645	}
646
647	if (ds->ds_phys->ds_userrefs_obj != 0) {
648		zap_attribute_t *za;
649		zap_cursor_t zc;
650
651		za = kmem_alloc(sizeof (zap_attribute_t), KM_SLEEP);
652		for (zap_cursor_init(&zc, ds->ds_dir->dd_pool->dp_meta_objset,
653		    ds->ds_phys->ds_userrefs_obj);
654		    zap_cursor_retrieve(&zc, za) == 0;
655		    zap_cursor_advance(&zc)) {
656			fnvlist_add_uint64(nvl, za->za_name,
657			    za->za_first_integer);
658		}
659		zap_cursor_fini(&zc);
660		kmem_free(za, sizeof (zap_attribute_t));
661	}
662	dsl_dataset_rele(ds, FTAG);
663	dsl_pool_rele(dp, FTAG);
664	return (0);
665}
666