1247580Smm/*
2247580Smm * CDDL HEADER START
3247580Smm *
4247580Smm * The contents of this file are subject to the terms of the
5247580Smm * Common Development and Distribution License (the "License").
6247580Smm * You may not use this file except in compliance with the License.
7247580Smm *
8247580Smm * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9247580Smm * or http://www.opensolaris.org/os/licensing.
10247580Smm * See the License for the specific language governing permissions
11247580Smm * and limitations under the License.
12247580Smm *
13247580Smm * When distributing Covered Code, include this CDDL HEADER in each
14247580Smm * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15247580Smm * If applicable, add the following below this CDDL HEADER, with the
16247580Smm * fields enclosed by brackets "[]" replaced with your own identifying
17247580Smm * information: Portions Copyright [yyyy] [name of copyright owner]
18247580Smm *
19247580Smm * CDDL HEADER END
20247580Smm */
21247580Smm/*
22247580Smm * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
23249643Smm * Copyright (c) 2013 by Delphix. All rights reserved.
24252764Sdelphij * Copyright (c) 2013 Steven Hartland. All rights reserved.
25247580Smm */
26247580Smm
27247580Smm#include <sys/zfs_context.h>
28247580Smm#include <sys/dsl_userhold.h>
29247580Smm#include <sys/dsl_dataset.h>
30247580Smm#include <sys/dsl_destroy.h>
31247580Smm#include <sys/dsl_synctask.h>
32247580Smm#include <sys/dmu_tx.h>
33247580Smm#include <sys/zfs_onexit.h>
34247580Smm#include <sys/dsl_pool.h>
35247580Smm#include <sys/dsl_dir.h>
36247580Smm#include <sys/zfs_ioctl.h>
37247580Smm#include <sys/zap.h>
38247580Smm
39247580Smmtypedef struct dsl_dataset_user_hold_arg {
40247580Smm	nvlist_t *dduha_holds;
41252764Sdelphij	nvlist_t *dduha_chkholds;
42247580Smm	nvlist_t *dduha_errlist;
43247580Smm	minor_t dduha_minor;
44247580Smm} dsl_dataset_user_hold_arg_t;
45247580Smm
46247580Smm/*
47247580Smm * If you add new checks here, you may need to add additional checks to the
48247580Smm * "temporary" case in snapshot_check() in dmu_objset.c.
49247580Smm */
50247580Smmint
51247580Smmdsl_dataset_user_hold_check_one(dsl_dataset_t *ds, const char *htag,
52247580Smm    boolean_t temphold, dmu_tx_t *tx)
53247580Smm{
54247580Smm	dsl_pool_t *dp = dmu_tx_pool(tx);
55247580Smm	objset_t *mos = dp->dp_meta_objset;
56247580Smm	int error = 0;
57247580Smm
58252764Sdelphij	ASSERT(dsl_pool_config_held(dp));
59252764Sdelphij
60247580Smm	if (strlen(htag) > MAXNAMELEN)
61252764Sdelphij		return (SET_ERROR(E2BIG));
62247580Smm	/* Tempholds have a more restricted length */
63247580Smm	if (temphold && strlen(htag) + MAX_TAG_PREFIX_LEN >= MAXNAMELEN)
64252764Sdelphij		return (SET_ERROR(E2BIG));
65247580Smm
66247580Smm	/* tags must be unique (if ds already exists) */
67252764Sdelphij	if (ds != NULL && ds->ds_phys->ds_userrefs_obj != 0) {
68252764Sdelphij		uint64_t value;
69252764Sdelphij
70252764Sdelphij		error = zap_lookup(mos, ds->ds_phys->ds_userrefs_obj,
71252764Sdelphij		    htag, 8, 1, &value);
72252764Sdelphij		if (error == 0)
73252764Sdelphij			error = SET_ERROR(EEXIST);
74252764Sdelphij		else if (error == ENOENT)
75252764Sdelphij			error = 0;
76247580Smm	}
77247580Smm
78247580Smm	return (error);
79247580Smm}
80247580Smm
81247580Smmstatic int
82247580Smmdsl_dataset_user_hold_check(void *arg, dmu_tx_t *tx)
83247580Smm{
84247580Smm	dsl_dataset_user_hold_arg_t *dduha = arg;
85247580Smm	dsl_pool_t *dp = dmu_tx_pool(tx);
86247580Smm
87247580Smm	if (spa_version(dp->dp_spa) < SPA_VERSION_USERREFS)
88249643Smm		return (SET_ERROR(ENOTSUP));
89247580Smm
90252764Sdelphij	if (!dmu_tx_is_syncing(tx))
91252764Sdelphij		return (0);
92252764Sdelphij
93252764Sdelphij	for (nvpair_t *pair = nvlist_next_nvpair(dduha->dduha_holds, NULL);
94252764Sdelphij	    pair != NULL; pair = nvlist_next_nvpair(dduha->dduha_holds, pair)) {
95252764Sdelphij		dsl_dataset_t *ds;
96247580Smm		int error = 0;
97252764Sdelphij		char *htag, *name;
98247580Smm
99247580Smm		/* must be a snapshot */
100252764Sdelphij		name = nvpair_name(pair);
101252764Sdelphij		if (strchr(name, '@') == NULL)
102249643Smm			error = SET_ERROR(EINVAL);
103247580Smm
104247580Smm		if (error == 0)
105247580Smm			error = nvpair_value_string(pair, &htag);
106252764Sdelphij
107252764Sdelphij		if (error == 0)
108252764Sdelphij			error = dsl_dataset_hold(dp, name, FTAG, &ds);
109252764Sdelphij
110247580Smm		if (error == 0) {
111247580Smm			error = dsl_dataset_user_hold_check_one(ds, htag,
112247580Smm			    dduha->dduha_minor != 0, tx);
113247580Smm			dsl_dataset_rele(ds, FTAG);
114247580Smm		}
115247580Smm
116252764Sdelphij		if (error == 0) {
117252764Sdelphij			fnvlist_add_string(dduha->dduha_chkholds, name, htag);
118252764Sdelphij		} else {
119252764Sdelphij			/*
120252764Sdelphij			 * We register ENOENT errors so they can be correctly
121252764Sdelphij			 * reported if needed, such as when all holds fail.
122252764Sdelphij			 */
123252764Sdelphij			fnvlist_add_int32(dduha->dduha_errlist, name, error);
124252764Sdelphij			if (error != ENOENT)
125252764Sdelphij				return (error);
126247580Smm		}
127247580Smm	}
128252764Sdelphij
129252764Sdelphij	return (0);
130247580Smm}
131247580Smm
132252764Sdelphij
133252764Sdelphijstatic void
134252764Sdelphijdsl_dataset_user_hold_sync_one_impl(nvlist_t *tmpholds, dsl_dataset_t *ds,
135252764Sdelphij    const char *htag, minor_t minor, uint64_t now, dmu_tx_t *tx)
136247580Smm{
137247580Smm	dsl_pool_t *dp = ds->ds_dir->dd_pool;
138247580Smm	objset_t *mos = dp->dp_meta_objset;
139247580Smm	uint64_t zapobj;
140247580Smm
141252764Sdelphij	ASSERT(RRW_WRITE_HELD(&dp->dp_config_rwlock));
142252764Sdelphij
143247580Smm	if (ds->ds_phys->ds_userrefs_obj == 0) {
144247580Smm		/*
145247580Smm		 * This is the first user hold for this dataset.  Create
146247580Smm		 * the userrefs zap object.
147247580Smm		 */
148247580Smm		dmu_buf_will_dirty(ds->ds_dbuf, tx);
149247580Smm		zapobj = ds->ds_phys->ds_userrefs_obj =
150247580Smm		    zap_create(mos, DMU_OT_USERREFS, DMU_OT_NONE, 0, tx);
151247580Smm	} else {
152247580Smm		zapobj = ds->ds_phys->ds_userrefs_obj;
153247580Smm	}
154247580Smm	ds->ds_userrefs++;
155247580Smm
156247580Smm	VERIFY0(zap_add(mos, zapobj, htag, 8, 1, &now, tx));
157247580Smm
158247580Smm	if (minor != 0) {
159252764Sdelphij		char name[MAXNAMELEN];
160252764Sdelphij		nvlist_t *tags;
161252764Sdelphij
162247580Smm		VERIFY0(dsl_pool_user_hold(dp, ds->ds_object,
163247580Smm		    htag, now, tx));
164252764Sdelphij		(void) snprintf(name, sizeof (name), "%llx",
165252764Sdelphij		    (u_longlong_t)ds->ds_object);
166252764Sdelphij
167252764Sdelphij		if (nvlist_lookup_nvlist(tmpholds, name, &tags) != 0) {
168252764Sdelphij			tags = fnvlist_alloc();
169252764Sdelphij			fnvlist_add_boolean(tags, htag);
170252764Sdelphij			fnvlist_add_nvlist(tmpholds, name, tags);
171252764Sdelphij			fnvlist_free(tags);
172252764Sdelphij		} else {
173252764Sdelphij			fnvlist_add_boolean(tags, htag);
174252764Sdelphij		}
175247580Smm	}
176247580Smm
177247580Smm	spa_history_log_internal_ds(ds, "hold", tx,
178247580Smm	    "tag=%s temp=%d refs=%llu",
179247580Smm	    htag, minor != 0, ds->ds_userrefs);
180247580Smm}
181247580Smm
182252764Sdelphijtypedef struct zfs_hold_cleanup_arg {
183252764Sdelphij	char zhca_spaname[MAXNAMELEN];
184252764Sdelphij	uint64_t zhca_spa_load_guid;
185252764Sdelphij	nvlist_t *zhca_holds;
186252764Sdelphij} zfs_hold_cleanup_arg_t;
187252764Sdelphij
188247580Smmstatic void
189252764Sdelphijdsl_dataset_user_release_onexit(void *arg)
190252764Sdelphij{
191252764Sdelphij	zfs_hold_cleanup_arg_t *ca = arg;
192252764Sdelphij	spa_t *spa;
193252764Sdelphij	int error;
194252764Sdelphij
195252764Sdelphij	error = spa_open(ca->zhca_spaname, &spa, FTAG);
196252764Sdelphij	if (error != 0) {
197252764Sdelphij		zfs_dbgmsg("couldn't release holds on pool=%s "
198252764Sdelphij		    "because pool is no longer loaded",
199252764Sdelphij		    ca->zhca_spaname);
200252764Sdelphij		return;
201252764Sdelphij	}
202252764Sdelphij	if (spa_load_guid(spa) != ca->zhca_spa_load_guid) {
203252764Sdelphij		zfs_dbgmsg("couldn't release holds on pool=%s "
204252764Sdelphij		    "because pool is no longer loaded (guid doesn't match)",
205252764Sdelphij		    ca->zhca_spaname);
206252764Sdelphij		spa_close(spa, FTAG);
207252764Sdelphij		return;
208252764Sdelphij	}
209252764Sdelphij
210252764Sdelphij	(void) dsl_dataset_user_release_tmp(spa_get_dsl(spa), ca->zhca_holds);
211252764Sdelphij	fnvlist_free(ca->zhca_holds);
212252764Sdelphij	kmem_free(ca, sizeof (zfs_hold_cleanup_arg_t));
213252764Sdelphij	spa_close(spa, FTAG);
214252764Sdelphij}
215252764Sdelphij
216252764Sdelphijstatic void
217252764Sdelphijdsl_onexit_hold_cleanup(spa_t *spa, nvlist_t *holds, minor_t minor)
218252764Sdelphij{
219252764Sdelphij	zfs_hold_cleanup_arg_t *ca;
220252764Sdelphij
221252764Sdelphij	if (minor == 0 || nvlist_empty(holds)) {
222252764Sdelphij		fnvlist_free(holds);
223252764Sdelphij		return;
224252764Sdelphij	}
225252764Sdelphij
226252764Sdelphij	ASSERT(spa != NULL);
227252764Sdelphij	ca = kmem_alloc(sizeof (*ca), KM_SLEEP);
228252764Sdelphij
229252764Sdelphij	(void) strlcpy(ca->zhca_spaname, spa_name(spa),
230252764Sdelphij	    sizeof (ca->zhca_spaname));
231252764Sdelphij	ca->zhca_spa_load_guid = spa_load_guid(spa);
232252764Sdelphij	ca->zhca_holds = holds;
233252764Sdelphij	VERIFY0(zfs_onexit_add_cb(minor,
234252764Sdelphij	    dsl_dataset_user_release_onexit, ca, NULL));
235252764Sdelphij}
236252764Sdelphij
237252764Sdelphijvoid
238252764Sdelphijdsl_dataset_user_hold_sync_one(dsl_dataset_t *ds, const char *htag,
239252764Sdelphij    minor_t minor, uint64_t now, dmu_tx_t *tx)
240252764Sdelphij{
241252764Sdelphij	nvlist_t *tmpholds;
242252764Sdelphij
243252764Sdelphij	if (minor != 0)
244252764Sdelphij		tmpholds = fnvlist_alloc();
245252764Sdelphij	else
246252764Sdelphij		tmpholds = NULL;
247252764Sdelphij	dsl_dataset_user_hold_sync_one_impl(tmpholds, ds, htag, minor, now, tx);
248252764Sdelphij	dsl_onexit_hold_cleanup(dsl_dataset_get_spa(ds), tmpholds, minor);
249252764Sdelphij}
250252764Sdelphij
251252764Sdelphijstatic void
252247580Smmdsl_dataset_user_hold_sync(void *arg, dmu_tx_t *tx)
253247580Smm{
254247580Smm	dsl_dataset_user_hold_arg_t *dduha = arg;
255247580Smm	dsl_pool_t *dp = dmu_tx_pool(tx);
256252764Sdelphij	nvlist_t *tmpholds;
257247580Smm	uint64_t now = gethrestime_sec();
258247580Smm
259252764Sdelphij	if (dduha->dduha_minor != 0)
260252764Sdelphij		tmpholds = fnvlist_alloc();
261252764Sdelphij	else
262252764Sdelphij		tmpholds = NULL;
263252764Sdelphij	for (nvpair_t *pair = nvlist_next_nvpair(dduha->dduha_chkholds, NULL);
264252764Sdelphij	    pair != NULL;
265252764Sdelphij	    pair = nvlist_next_nvpair(dduha->dduha_chkholds, pair)) {
266247580Smm		dsl_dataset_t *ds;
267252764Sdelphij
268247580Smm		VERIFY0(dsl_dataset_hold(dp, nvpair_name(pair), FTAG, &ds));
269252764Sdelphij		dsl_dataset_user_hold_sync_one_impl(tmpholds, ds,
270252764Sdelphij		    fnvpair_value_string(pair), dduha->dduha_minor, now, tx);
271247580Smm		dsl_dataset_rele(ds, FTAG);
272247580Smm	}
273252764Sdelphij	dsl_onexit_hold_cleanup(dp->dp_spa, tmpholds, dduha->dduha_minor);
274247580Smm}
275247580Smm
276247580Smm/*
277252764Sdelphij * The full semantics of this function are described in the comment above
278252764Sdelphij * lzc_hold().
279252764Sdelphij *
280252764Sdelphij * To summarize:
281247580Smm * holds is nvl of snapname -> holdname
282247580Smm * errlist will be filled in with snapname -> error
283247580Smm *
284252764Sdelphij * The snaphosts must all be in the same pool.
285252764Sdelphij *
286252764Sdelphij * Holds for snapshots that don't exist will be skipped.
287252764Sdelphij *
288252764Sdelphij * If none of the snapshots for requested holds exist then ENOENT will be
289252764Sdelphij * returned.
290252764Sdelphij *
291252764Sdelphij * If cleanup_minor is not 0, the holds will be temporary, which will be cleaned
292252764Sdelphij * up when the process exits.
293252764Sdelphij *
294252764Sdelphij * On success all the holds, for snapshots that existed, will be created and 0
295252764Sdelphij * will be returned.
296252764Sdelphij *
297252764Sdelphij * On failure no holds will be created, the errlist will be filled in,
298252764Sdelphij * and an errno will returned.
299252764Sdelphij *
300252764Sdelphij * In all cases the errlist will contain entries for holds where the snapshot
301252764Sdelphij * didn't exist.
302247580Smm */
303247580Smmint
304247580Smmdsl_dataset_user_hold(nvlist_t *holds, minor_t cleanup_minor, nvlist_t *errlist)
305247580Smm{
306247580Smm	dsl_dataset_user_hold_arg_t dduha;
307247580Smm	nvpair_t *pair;
308252764Sdelphij	int ret;
309247580Smm
310247580Smm	pair = nvlist_next_nvpair(holds, NULL);
311247580Smm	if (pair == NULL)
312247580Smm		return (0);
313247580Smm
314247580Smm	dduha.dduha_holds = holds;
315252764Sdelphij	dduha.dduha_chkholds = fnvlist_alloc();
316247580Smm	dduha.dduha_errlist = errlist;
317247580Smm	dduha.dduha_minor = cleanup_minor;
318247580Smm
319252764Sdelphij	ret = dsl_sync_task(nvpair_name(pair), dsl_dataset_user_hold_check,
320252764Sdelphij	    dsl_dataset_user_hold_sync, &dduha, fnvlist_num_pairs(holds));
321252764Sdelphij	fnvlist_free(dduha.dduha_chkholds);
322252764Sdelphij
323252764Sdelphij	return (ret);
324247580Smm}
325247580Smm
326252764Sdelphijtypedef int (dsl_holdfunc_t)(dsl_pool_t *dp, const char *name, void *tag,
327252764Sdelphij    dsl_dataset_t **dsp);
328252764Sdelphij
329247580Smmtypedef struct dsl_dataset_user_release_arg {
330252764Sdelphij	dsl_holdfunc_t *ddura_holdfunc;
331247580Smm	nvlist_t *ddura_holds;
332247580Smm	nvlist_t *ddura_todelete;
333247580Smm	nvlist_t *ddura_errlist;
334252764Sdelphij	nvlist_t *ddura_chkholds;
335247580Smm} dsl_dataset_user_release_arg_t;
336247580Smm
337252764Sdelphij/* Place a dataset hold on the snapshot identified by passed dsobj string */
338247580Smmstatic int
339252764Sdelphijdsl_dataset_hold_obj_string(dsl_pool_t *dp, const char *dsobj, void *tag,
340252764Sdelphij    dsl_dataset_t **dsp)
341247580Smm{
342252764Sdelphij	return (dsl_dataset_hold_obj(dp, strtonum(dsobj, NULL), tag, dsp));
343252764Sdelphij}
344252764Sdelphij
345252764Sdelphijstatic int
346252764Sdelphijdsl_dataset_user_release_check_one(dsl_dataset_user_release_arg_t *ddura,
347252764Sdelphij    dsl_dataset_t *ds, nvlist_t *holds, const char *snapname)
348252764Sdelphij{
349247580Smm	uint64_t zapobj;
350252764Sdelphij	nvlist_t *holds_found;
351252764Sdelphij	objset_t *mos;
352252764Sdelphij	int numholds;
353247580Smm
354247580Smm	if (!dsl_dataset_is_snapshot(ds))
355249643Smm		return (SET_ERROR(EINVAL));
356247580Smm
357252764Sdelphij	if (nvlist_empty(holds))
358252764Sdelphij		return (0);
359252764Sdelphij
360252764Sdelphij	numholds = 0;
361252764Sdelphij	mos = ds->ds_dir->dd_pool->dp_meta_objset;
362247580Smm	zapobj = ds->ds_phys->ds_userrefs_obj;
363252764Sdelphij	holds_found = fnvlist_alloc();
364247580Smm
365252764Sdelphij	for (nvpair_t *pair = nvlist_next_nvpair(holds, NULL); pair != NULL;
366247580Smm	    pair = nvlist_next_nvpair(holds, pair)) {
367247580Smm		uint64_t tmp;
368252764Sdelphij		int error;
369252764Sdelphij		const char *holdname = nvpair_name(pair);
370252764Sdelphij
371252764Sdelphij		if (zapobj != 0)
372252764Sdelphij			error = zap_lookup(mos, zapobj, holdname, 8, 1, &tmp);
373252764Sdelphij		else
374252764Sdelphij			error = SET_ERROR(ENOENT);
375252764Sdelphij
376252764Sdelphij		/*
377252764Sdelphij		 * Non-existent holds are put on the errlist, but don't
378252764Sdelphij		 * cause an overall failure.
379252764Sdelphij		 */
380252764Sdelphij		if (error == ENOENT) {
381252764Sdelphij			if (ddura->ddura_errlist != NULL) {
382252764Sdelphij				char *errtag = kmem_asprintf("%s#%s",
383252764Sdelphij				    snapname, holdname);
384252764Sdelphij				fnvlist_add_int32(ddura->ddura_errlist, errtag,
385252764Sdelphij				    ENOENT);
386252764Sdelphij				strfree(errtag);
387252764Sdelphij			}
388252764Sdelphij			continue;
389252764Sdelphij		}
390252764Sdelphij
391252764Sdelphij		if (error != 0) {
392252764Sdelphij			fnvlist_free(holds_found);
393247580Smm			return (error);
394252764Sdelphij		}
395252764Sdelphij
396252764Sdelphij		fnvlist_add_boolean(holds_found, holdname);
397247580Smm		numholds++;
398247580Smm	}
399247580Smm
400247580Smm	if (DS_IS_DEFER_DESTROY(ds) && ds->ds_phys->ds_num_children == 1 &&
401247580Smm	    ds->ds_userrefs == numholds) {
402247580Smm		/* we need to destroy the snapshot as well */
403252764Sdelphij		if (dsl_dataset_long_held(ds)) {
404252764Sdelphij			fnvlist_free(holds_found);
405249643Smm			return (SET_ERROR(EBUSY));
406252764Sdelphij		}
407252764Sdelphij		fnvlist_add_boolean(ddura->ddura_todelete, snapname);
408247580Smm	}
409252764Sdelphij
410252764Sdelphij	if (numholds != 0) {
411252764Sdelphij		fnvlist_add_nvlist(ddura->ddura_chkholds, snapname,
412252764Sdelphij		    holds_found);
413252764Sdelphij	}
414252764Sdelphij	fnvlist_free(holds_found);
415252764Sdelphij
416247580Smm	return (0);
417247580Smm}
418247580Smm
419247580Smmstatic int
420247580Smmdsl_dataset_user_release_check(void *arg, dmu_tx_t *tx)
421247580Smm{
422252764Sdelphij	dsl_dataset_user_release_arg_t *ddura;
423252764Sdelphij	dsl_holdfunc_t *holdfunc;
424252764Sdelphij	dsl_pool_t *dp;
425247580Smm
426247580Smm	if (!dmu_tx_is_syncing(tx))
427247580Smm		return (0);
428247580Smm
429252764Sdelphij	dp = dmu_tx_pool(tx);
430252764Sdelphij
431252764Sdelphij	ASSERT(RRW_WRITE_HELD(&dp->dp_config_rwlock));
432252764Sdelphij
433252764Sdelphij	ddura = arg;
434252764Sdelphij	holdfunc = ddura->ddura_holdfunc;
435252764Sdelphij
436252764Sdelphij	for (nvpair_t *pair = nvlist_next_nvpair(ddura->ddura_holds, NULL);
437252764Sdelphij	    pair != NULL; pair = nvlist_next_nvpair(ddura->ddura_holds, pair)) {
438247580Smm		int error;
439247580Smm		dsl_dataset_t *ds;
440247580Smm		nvlist_t *holds;
441252764Sdelphij		const char *snapname = nvpair_name(pair);
442247580Smm
443247580Smm		error = nvpair_value_nvlist(pair, &holds);
444247580Smm		if (error != 0)
445252764Sdelphij			error = (SET_ERROR(EINVAL));
446252764Sdelphij		else
447252764Sdelphij			error = holdfunc(dp, snapname, FTAG, &ds);
448247580Smm		if (error == 0) {
449252764Sdelphij			error = dsl_dataset_user_release_check_one(ddura, ds,
450252764Sdelphij			    holds, snapname);
451247580Smm			dsl_dataset_rele(ds, FTAG);
452247580Smm		}
453247580Smm		if (error != 0) {
454247580Smm			if (ddura->ddura_errlist != NULL) {
455247580Smm				fnvlist_add_int32(ddura->ddura_errlist,
456252764Sdelphij				    snapname, error);
457247580Smm			}
458252764Sdelphij			/*
459252764Sdelphij			 * Non-existent snapshots are put on the errlist,
460252764Sdelphij			 * but don't cause an overall failure.
461252764Sdelphij			 */
462252764Sdelphij			if (error != ENOENT)
463252764Sdelphij				return (error);
464247580Smm		}
465247580Smm	}
466252764Sdelphij
467252764Sdelphij	return (0);
468247580Smm}
469247580Smm
470247580Smmstatic void
471247580Smmdsl_dataset_user_release_sync_one(dsl_dataset_t *ds, nvlist_t *holds,
472247580Smm    dmu_tx_t *tx)
473247580Smm{
474247580Smm	dsl_pool_t *dp = ds->ds_dir->dd_pool;
475247580Smm	objset_t *mos = dp->dp_meta_objset;
476247580Smm
477252764Sdelphij	for (nvpair_t *pair = nvlist_next_nvpair(holds, NULL); pair != NULL;
478247580Smm	    pair = nvlist_next_nvpair(holds, pair)) {
479252764Sdelphij		int error;
480252764Sdelphij		const char *holdname = nvpair_name(pair);
481252764Sdelphij
482252764Sdelphij		/* Remove temporary hold if one exists. */
483252764Sdelphij		error = dsl_pool_user_release(dp, ds->ds_object, holdname, tx);
484247580Smm		VERIFY(error == 0 || error == ENOENT);
485247580Smm
486252764Sdelphij		VERIFY0(zap_remove(mos, ds->ds_phys->ds_userrefs_obj, holdname,
487252764Sdelphij		    tx));
488252764Sdelphij		ds->ds_userrefs--;
489252764Sdelphij
490247580Smm		spa_history_log_internal_ds(ds, "release", tx,
491252764Sdelphij		    "tag=%s refs=%lld", holdname, (longlong_t)ds->ds_userrefs);
492247580Smm	}
493247580Smm}
494247580Smm
495247580Smmstatic void
496247580Smmdsl_dataset_user_release_sync(void *arg, dmu_tx_t *tx)
497247580Smm{
498247580Smm	dsl_dataset_user_release_arg_t *ddura = arg;
499252764Sdelphij	dsl_holdfunc_t *holdfunc = ddura->ddura_holdfunc;
500247580Smm	dsl_pool_t *dp = dmu_tx_pool(tx);
501247580Smm
502252764Sdelphij	ASSERT(RRW_WRITE_HELD(&dp->dp_config_rwlock));
503252764Sdelphij
504252764Sdelphij	for (nvpair_t *pair = nvlist_next_nvpair(ddura->ddura_chkholds, NULL);
505252764Sdelphij	    pair != NULL; pair = nvlist_next_nvpair(ddura->ddura_chkholds,
506252764Sdelphij	    pair)) {
507247580Smm		dsl_dataset_t *ds;
508252764Sdelphij		const char *name = nvpair_name(pair);
509247580Smm
510252764Sdelphij		VERIFY0(holdfunc(dp, name, FTAG, &ds));
511252764Sdelphij
512247580Smm		dsl_dataset_user_release_sync_one(ds,
513247580Smm		    fnvpair_value_nvlist(pair), tx);
514252764Sdelphij		if (nvlist_exists(ddura->ddura_todelete, name)) {
515247580Smm			ASSERT(ds->ds_userrefs == 0 &&
516247580Smm			    ds->ds_phys->ds_num_children == 1 &&
517247580Smm			    DS_IS_DEFER_DESTROY(ds));
518247580Smm			dsl_destroy_snapshot_sync_impl(ds, B_FALSE, tx);
519247580Smm		}
520247580Smm		dsl_dataset_rele(ds, FTAG);
521247580Smm	}
522247580Smm}
523247580Smm
524247580Smm/*
525252764Sdelphij * The full semantics of this function are described in the comment above
526252764Sdelphij * lzc_release().
527252764Sdelphij *
528252764Sdelphij * To summarize:
529252764Sdelphij * Releases holds specified in the nvl holds.
530252764Sdelphij *
531247580Smm * holds is nvl of snapname -> { holdname, ... }
532247580Smm * errlist will be filled in with snapname -> error
533247580Smm *
534252764Sdelphij * If tmpdp is not NULL the names for holds should be the dsobj's of snapshots,
535252764Sdelphij * otherwise they should be the names of shapshots.
536252764Sdelphij *
537252764Sdelphij * As a release may cause snapshots to be destroyed this trys to ensure they
538252764Sdelphij * aren't mounted.
539252764Sdelphij *
540252764Sdelphij * The release of non-existent holds are skipped.
541252764Sdelphij *
542252764Sdelphij * At least one hold must have been released for the this function to succeed
543252764Sdelphij * and return 0.
544247580Smm */
545252764Sdelphijstatic int
546252764Sdelphijdsl_dataset_user_release_impl(nvlist_t *holds, nvlist_t *errlist,
547252764Sdelphij    dsl_pool_t *tmpdp)
548247580Smm{
549247580Smm	dsl_dataset_user_release_arg_t ddura;
550247580Smm	nvpair_t *pair;
551252764Sdelphij	char *pool;
552247580Smm	int error;
553247580Smm
554247580Smm	pair = nvlist_next_nvpair(holds, NULL);
555247580Smm	if (pair == NULL)
556247580Smm		return (0);
557247580Smm
558252764Sdelphij	/*
559252764Sdelphij	 * The release may cause snapshots to be destroyed; make sure they
560252764Sdelphij	 * are not mounted.
561252764Sdelphij	 */
562252764Sdelphij	if (tmpdp != NULL) {
563252764Sdelphij		/* Temporary holds are specified by dsobj string. */
564252764Sdelphij		ddura.ddura_holdfunc = dsl_dataset_hold_obj_string;
565252764Sdelphij		pool = spa_name(tmpdp->dp_spa);
566252764Sdelphij#ifdef _KERNEL
567252764Sdelphij		for (pair = nvlist_next_nvpair(holds, NULL); pair != NULL;
568252764Sdelphij		    pair = nvlist_next_nvpair(holds, pair)) {
569252764Sdelphij			dsl_dataset_t *ds;
570252764Sdelphij
571262175Savg			dsl_pool_config_enter(tmpdp, FTAG);
572252764Sdelphij			error = dsl_dataset_hold_obj_string(tmpdp,
573252764Sdelphij			    nvpair_name(pair), FTAG, &ds);
574252764Sdelphij			if (error == 0) {
575252764Sdelphij				char name[MAXNAMELEN];
576252764Sdelphij				dsl_dataset_name(ds, name);
577262175Savg				dsl_pool_config_exit(tmpdp, FTAG);
578252764Sdelphij				dsl_dataset_rele(ds, FTAG);
579252764Sdelphij				(void) zfs_unmount_snap(name);
580262175Savg			} else {
581262175Savg				dsl_pool_config_exit(tmpdp, FTAG);
582252764Sdelphij			}
583252764Sdelphij		}
584252764Sdelphij#endif
585252764Sdelphij	} else {
586252764Sdelphij		/* Non-temporary holds are specified by name. */
587252764Sdelphij		ddura.ddura_holdfunc = dsl_dataset_hold;
588252764Sdelphij		pool = nvpair_name(pair);
589252764Sdelphij#ifdef _KERNEL
590252764Sdelphij		for (pair = nvlist_next_nvpair(holds, NULL); pair != NULL;
591252764Sdelphij		    pair = nvlist_next_nvpair(holds, pair)) {
592252764Sdelphij			(void) zfs_unmount_snap(nvpair_name(pair));
593252764Sdelphij		}
594252764Sdelphij#endif
595252764Sdelphij	}
596252764Sdelphij
597247580Smm	ddura.ddura_holds = holds;
598247580Smm	ddura.ddura_errlist = errlist;
599247580Smm	ddura.ddura_todelete = fnvlist_alloc();
600252764Sdelphij	ddura.ddura_chkholds = fnvlist_alloc();
601247580Smm
602252764Sdelphij	error = dsl_sync_task(pool, dsl_dataset_user_release_check,
603252764Sdelphij	    dsl_dataset_user_release_sync, &ddura,
604252764Sdelphij	    fnvlist_num_pairs(holds));
605247580Smm	fnvlist_free(ddura.ddura_todelete);
606252764Sdelphij	fnvlist_free(ddura.ddura_chkholds);
607247580Smm
608247580Smm	return (error);
609247580Smm}
610247580Smm
611252764Sdelphij/*
612252764Sdelphij * holds is nvl of snapname -> { holdname, ... }
613252764Sdelphij * errlist will be filled in with snapname -> error
614252764Sdelphij */
615252764Sdelphijint
616252764Sdelphijdsl_dataset_user_release(nvlist_t *holds, nvlist_t *errlist)
617247580Smm{
618252764Sdelphij	return (dsl_dataset_user_release_impl(holds, errlist, NULL));
619247580Smm}
620247580Smm
621247580Smm/*
622252764Sdelphij * holds is nvl of snapdsobj -> { holdname, ... }
623247580Smm */
624247580Smmvoid
625252764Sdelphijdsl_dataset_user_release_tmp(struct dsl_pool *dp, nvlist_t *holds)
626247580Smm{
627252764Sdelphij	ASSERT(dp != NULL);
628252764Sdelphij	(void) dsl_dataset_user_release_impl(holds, NULL, dp);
629247580Smm}
630247580Smm
631247580Smmint
632247580Smmdsl_dataset_get_holds(const char *dsname, nvlist_t *nvl)
633247580Smm{
634247580Smm	dsl_pool_t *dp;
635247580Smm	dsl_dataset_t *ds;
636247580Smm	int err;
637247580Smm
638247580Smm	err = dsl_pool_hold(dsname, FTAG, &dp);
639247580Smm	if (err != 0)
640247580Smm		return (err);
641247580Smm	err = dsl_dataset_hold(dp, dsname, FTAG, &ds);
642247580Smm	if (err != 0) {
643247580Smm		dsl_pool_rele(dp, FTAG);
644247580Smm		return (err);
645247580Smm	}
646247580Smm
647247580Smm	if (ds->ds_phys->ds_userrefs_obj != 0) {
648247580Smm		zap_attribute_t *za;
649247580Smm		zap_cursor_t zc;
650247580Smm
651247580Smm		za = kmem_alloc(sizeof (zap_attribute_t), KM_SLEEP);
652247580Smm		for (zap_cursor_init(&zc, ds->ds_dir->dd_pool->dp_meta_objset,
653247580Smm		    ds->ds_phys->ds_userrefs_obj);
654247580Smm		    zap_cursor_retrieve(&zc, za) == 0;
655247580Smm		    zap_cursor_advance(&zc)) {
656247580Smm			fnvlist_add_uint64(nvl, za->za_name,
657247580Smm			    za->za_first_integer);
658247580Smm		}
659247580Smm		zap_cursor_fini(&zc);
660247580Smm		kmem_free(za, sizeof (zap_attribute_t));
661247580Smm	}
662247580Smm	dsl_dataset_rele(ds, FTAG);
663247580Smm	dsl_pool_rele(dp, FTAG);
664247580Smm	return (0);
665247580Smm}
666