]>
Commit | Line | Data |
---|---|---|
13fe0198 MA |
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. | |
2e528b49 | 23 | * Copyright (c) 2013 by Delphix. All rights reserved. |
13fe0198 MA |
24 | */ |
25 | ||
26 | #include <sys/zfs_context.h> | |
27 | #include <sys/dsl_userhold.h> | |
28 | #include <sys/dsl_dataset.h> | |
29 | #include <sys/dsl_destroy.h> | |
30 | #include <sys/dsl_synctask.h> | |
31 | #include <sys/dmu_tx.h> | |
32 | #include <sys/zfs_onexit.h> | |
33 | #include <sys/dsl_pool.h> | |
34 | #include <sys/dsl_dir.h> | |
35 | #include <sys/zfs_ioctl.h> | |
36 | #include <sys/zap.h> | |
37 | ||
38 | typedef struct dsl_dataset_user_hold_arg { | |
39 | nvlist_t *dduha_holds; | |
40 | nvlist_t *dduha_errlist; | |
41 | minor_t dduha_minor; | |
42 | } dsl_dataset_user_hold_arg_t; | |
43 | ||
44 | /* | |
45 | * If you add new checks here, you may need to add additional checks to the | |
46 | * "temporary" case in snapshot_check() in dmu_objset.c. | |
47 | */ | |
48 | int | |
49 | dsl_dataset_user_hold_check_one(dsl_dataset_t *ds, const char *htag, | |
50 | boolean_t temphold, dmu_tx_t *tx) | |
51 | { | |
52 | dsl_pool_t *dp = dmu_tx_pool(tx); | |
53 | objset_t *mos = dp->dp_meta_objset; | |
54 | int error = 0; | |
55 | ||
56 | if (strlen(htag) > MAXNAMELEN) | |
57 | return (E2BIG); | |
58 | /* Tempholds have a more restricted length */ | |
59 | if (temphold && strlen(htag) + MAX_TAG_PREFIX_LEN >= MAXNAMELEN) | |
60 | return (E2BIG); | |
61 | ||
62 | /* tags must be unique (if ds already exists) */ | |
63 | if (ds != NULL) { | |
64 | mutex_enter(&ds->ds_lock); | |
65 | if (ds->ds_phys->ds_userrefs_obj != 0) { | |
66 | uint64_t value; | |
67 | error = zap_lookup(mos, ds->ds_phys->ds_userrefs_obj, | |
68 | htag, 8, 1, &value); | |
69 | if (error == 0) | |
2e528b49 | 70 | error = SET_ERROR(EEXIST); |
13fe0198 MA |
71 | else if (error == ENOENT) |
72 | error = 0; | |
73 | } | |
74 | mutex_exit(&ds->ds_lock); | |
75 | } | |
76 | ||
77 | return (error); | |
78 | } | |
79 | ||
80 | static int | |
81 | dsl_dataset_user_hold_check(void *arg, dmu_tx_t *tx) | |
82 | { | |
83 | dsl_dataset_user_hold_arg_t *dduha = arg; | |
84 | dsl_pool_t *dp = dmu_tx_pool(tx); | |
85 | nvpair_t *pair; | |
86 | int rv = 0; | |
87 | ||
88 | if (spa_version(dp->dp_spa) < SPA_VERSION_USERREFS) | |
2e528b49 | 89 | return (SET_ERROR(ENOTSUP)); |
13fe0198 MA |
90 | |
91 | for (pair = nvlist_next_nvpair(dduha->dduha_holds, NULL); pair != NULL; | |
92 | pair = nvlist_next_nvpair(dduha->dduha_holds, pair)) { | |
93 | int error = 0; | |
94 | dsl_dataset_t *ds; | |
95 | char *htag; | |
96 | ||
97 | /* must be a snapshot */ | |
98 | if (strchr(nvpair_name(pair), '@') == NULL) | |
2e528b49 | 99 | error = SET_ERROR(EINVAL); |
13fe0198 MA |
100 | |
101 | if (error == 0) | |
102 | error = nvpair_value_string(pair, &htag); | |
103 | if (error == 0) { | |
104 | error = dsl_dataset_hold(dp, | |
105 | nvpair_name(pair), FTAG, &ds); | |
106 | } | |
107 | if (error == 0) { | |
108 | error = dsl_dataset_user_hold_check_one(ds, htag, | |
109 | dduha->dduha_minor != 0, tx); | |
110 | dsl_dataset_rele(ds, FTAG); | |
111 | } | |
112 | ||
113 | if (error != 0) { | |
114 | rv = error; | |
115 | fnvlist_add_int32(dduha->dduha_errlist, | |
116 | nvpair_name(pair), error); | |
117 | } | |
118 | } | |
119 | return (rv); | |
120 | } | |
121 | ||
122 | void | |
123 | dsl_dataset_user_hold_sync_one(dsl_dataset_t *ds, const char *htag, | |
124 | minor_t minor, uint64_t now, dmu_tx_t *tx) | |
125 | { | |
126 | dsl_pool_t *dp = ds->ds_dir->dd_pool; | |
127 | objset_t *mos = dp->dp_meta_objset; | |
128 | uint64_t zapobj; | |
129 | ||
130 | mutex_enter(&ds->ds_lock); | |
131 | if (ds->ds_phys->ds_userrefs_obj == 0) { | |
132 | /* | |
133 | * This is the first user hold for this dataset. Create | |
134 | * the userrefs zap object. | |
135 | */ | |
136 | dmu_buf_will_dirty(ds->ds_dbuf, tx); | |
137 | zapobj = ds->ds_phys->ds_userrefs_obj = | |
138 | zap_create(mos, DMU_OT_USERREFS, DMU_OT_NONE, 0, tx); | |
139 | } else { | |
140 | zapobj = ds->ds_phys->ds_userrefs_obj; | |
141 | } | |
142 | ds->ds_userrefs++; | |
143 | mutex_exit(&ds->ds_lock); | |
144 | ||
145 | VERIFY0(zap_add(mos, zapobj, htag, 8, 1, &now, tx)); | |
146 | ||
147 | if (minor != 0) { | |
148 | VERIFY0(dsl_pool_user_hold(dp, ds->ds_object, | |
149 | htag, now, tx)); | |
150 | dsl_register_onexit_hold_cleanup(ds, htag, minor); | |
151 | } | |
152 | ||
153 | spa_history_log_internal_ds(ds, "hold", tx, | |
154 | "tag=%s temp=%d refs=%llu", | |
155 | htag, minor != 0, ds->ds_userrefs); | |
156 | } | |
157 | ||
158 | static void | |
159 | dsl_dataset_user_hold_sync(void *arg, dmu_tx_t *tx) | |
160 | { | |
161 | dsl_dataset_user_hold_arg_t *dduha = arg; | |
162 | dsl_pool_t *dp = dmu_tx_pool(tx); | |
163 | nvpair_t *pair; | |
164 | uint64_t now = gethrestime_sec(); | |
165 | ||
166 | for (pair = nvlist_next_nvpair(dduha->dduha_holds, NULL); pair != NULL; | |
167 | pair = nvlist_next_nvpair(dduha->dduha_holds, pair)) { | |
168 | dsl_dataset_t *ds; | |
169 | VERIFY0(dsl_dataset_hold(dp, nvpair_name(pair), FTAG, &ds)); | |
170 | dsl_dataset_user_hold_sync_one(ds, fnvpair_value_string(pair), | |
171 | dduha->dduha_minor, now, tx); | |
172 | dsl_dataset_rele(ds, FTAG); | |
173 | } | |
174 | } | |
175 | ||
176 | /* | |
177 | * holds is nvl of snapname -> holdname | |
178 | * errlist will be filled in with snapname -> error | |
179 | * if cleanup_minor is not 0, the holds will be temporary, cleaned up | |
180 | * when the process exits. | |
181 | * | |
182 | * if any fails, all will fail. | |
183 | */ | |
184 | int | |
185 | dsl_dataset_user_hold(nvlist_t *holds, minor_t cleanup_minor, nvlist_t *errlist) | |
186 | { | |
187 | dsl_dataset_user_hold_arg_t dduha; | |
188 | nvpair_t *pair; | |
189 | ||
190 | pair = nvlist_next_nvpair(holds, NULL); | |
191 | if (pair == NULL) | |
192 | return (0); | |
193 | ||
194 | dduha.dduha_holds = holds; | |
195 | dduha.dduha_errlist = errlist; | |
196 | dduha.dduha_minor = cleanup_minor; | |
197 | ||
198 | return (dsl_sync_task(nvpair_name(pair), dsl_dataset_user_hold_check, | |
199 | dsl_dataset_user_hold_sync, &dduha, fnvlist_num_pairs(holds))); | |
200 | } | |
201 | ||
202 | typedef struct dsl_dataset_user_release_arg { | |
203 | nvlist_t *ddura_holds; | |
204 | nvlist_t *ddura_todelete; | |
205 | nvlist_t *ddura_errlist; | |
206 | } dsl_dataset_user_release_arg_t; | |
207 | ||
208 | static int | |
209 | dsl_dataset_user_release_check_one(dsl_dataset_t *ds, | |
210 | nvlist_t *holds, boolean_t *todelete) | |
211 | { | |
212 | uint64_t zapobj; | |
213 | nvpair_t *pair; | |
214 | objset_t *mos = ds->ds_dir->dd_pool->dp_meta_objset; | |
215 | int error; | |
216 | int numholds = 0; | |
217 | ||
218 | *todelete = B_FALSE; | |
219 | ||
220 | if (!dsl_dataset_is_snapshot(ds)) | |
2e528b49 | 221 | return (SET_ERROR(EINVAL)); |
13fe0198 MA |
222 | |
223 | zapobj = ds->ds_phys->ds_userrefs_obj; | |
224 | if (zapobj == 0) | |
2e528b49 | 225 | return (SET_ERROR(ESRCH)); |
13fe0198 MA |
226 | |
227 | for (pair = nvlist_next_nvpair(holds, NULL); pair != NULL; | |
228 | pair = nvlist_next_nvpair(holds, pair)) { | |
229 | /* Make sure the hold exists */ | |
230 | uint64_t tmp; | |
231 | error = zap_lookup(mos, zapobj, nvpair_name(pair), 8, 1, &tmp); | |
232 | if (error == ENOENT) | |
2e528b49 | 233 | error = SET_ERROR(ESRCH); |
13fe0198 MA |
234 | if (error != 0) |
235 | return (error); | |
236 | numholds++; | |
237 | } | |
238 | ||
239 | if (DS_IS_DEFER_DESTROY(ds) && ds->ds_phys->ds_num_children == 1 && | |
240 | ds->ds_userrefs == numholds) { | |
241 | /* we need to destroy the snapshot as well */ | |
242 | ||
243 | if (dsl_dataset_long_held(ds)) | |
2e528b49 | 244 | return (SET_ERROR(EBUSY)); |
13fe0198 MA |
245 | *todelete = B_TRUE; |
246 | } | |
247 | return (0); | |
248 | } | |
249 | ||
250 | static int | |
251 | dsl_dataset_user_release_check(void *arg, dmu_tx_t *tx) | |
252 | { | |
253 | dsl_dataset_user_release_arg_t *ddura = arg; | |
254 | dsl_pool_t *dp = dmu_tx_pool(tx); | |
255 | nvpair_t *pair; | |
256 | int rv = 0; | |
257 | ||
258 | if (!dmu_tx_is_syncing(tx)) | |
259 | return (0); | |
260 | ||
261 | for (pair = nvlist_next_nvpair(ddura->ddura_holds, NULL); pair != NULL; | |
262 | pair = nvlist_next_nvpair(ddura->ddura_holds, pair)) { | |
263 | const char *name = nvpair_name(pair); | |
264 | int error; | |
265 | dsl_dataset_t *ds; | |
266 | nvlist_t *holds; | |
267 | ||
268 | error = nvpair_value_nvlist(pair, &holds); | |
269 | if (error != 0) | |
2e528b49 | 270 | return (SET_ERROR(EINVAL)); |
13fe0198 MA |
271 | |
272 | error = dsl_dataset_hold(dp, name, FTAG, &ds); | |
273 | if (error == 0) { | |
274 | boolean_t deleteme; | |
275 | error = dsl_dataset_user_release_check_one(ds, | |
276 | holds, &deleteme); | |
277 | if (error == 0 && deleteme) { | |
278 | fnvlist_add_boolean(ddura->ddura_todelete, | |
279 | name); | |
280 | } | |
281 | dsl_dataset_rele(ds, FTAG); | |
282 | } | |
283 | if (error != 0) { | |
284 | if (ddura->ddura_errlist != NULL) { | |
285 | fnvlist_add_int32(ddura->ddura_errlist, | |
286 | name, error); | |
287 | } | |
288 | rv = error; | |
289 | } | |
290 | } | |
291 | return (rv); | |
292 | } | |
293 | ||
294 | static void | |
295 | dsl_dataset_user_release_sync_one(dsl_dataset_t *ds, nvlist_t *holds, | |
296 | dmu_tx_t *tx) | |
297 | { | |
298 | dsl_pool_t *dp = ds->ds_dir->dd_pool; | |
299 | objset_t *mos = dp->dp_meta_objset; | |
300 | uint64_t zapobj; | |
301 | int error; | |
302 | nvpair_t *pair; | |
303 | ||
304 | for (pair = nvlist_next_nvpair(holds, NULL); pair != NULL; | |
305 | pair = nvlist_next_nvpair(holds, pair)) { | |
306 | ds->ds_userrefs--; | |
307 | error = dsl_pool_user_release(dp, ds->ds_object, | |
308 | nvpair_name(pair), tx); | |
309 | VERIFY(error == 0 || error == ENOENT); | |
310 | zapobj = ds->ds_phys->ds_userrefs_obj; | |
311 | VERIFY0(zap_remove(mos, zapobj, nvpair_name(pair), tx)); | |
312 | ||
313 | spa_history_log_internal_ds(ds, "release", tx, | |
314 | "tag=%s refs=%lld", nvpair_name(pair), | |
315 | (longlong_t)ds->ds_userrefs); | |
316 | } | |
317 | } | |
318 | ||
319 | static void | |
320 | dsl_dataset_user_release_sync(void *arg, dmu_tx_t *tx) | |
321 | { | |
322 | dsl_dataset_user_release_arg_t *ddura = arg; | |
323 | dsl_pool_t *dp = dmu_tx_pool(tx); | |
324 | nvpair_t *pair; | |
325 | ||
326 | for (pair = nvlist_next_nvpair(ddura->ddura_holds, NULL); pair != NULL; | |
327 | pair = nvlist_next_nvpair(ddura->ddura_holds, pair)) { | |
328 | dsl_dataset_t *ds; | |
329 | ||
330 | VERIFY0(dsl_dataset_hold(dp, nvpair_name(pair), FTAG, &ds)); | |
331 | dsl_dataset_user_release_sync_one(ds, | |
332 | fnvpair_value_nvlist(pair), tx); | |
333 | if (nvlist_exists(ddura->ddura_todelete, | |
334 | nvpair_name(pair))) { | |
335 | ASSERT(ds->ds_userrefs == 0 && | |
336 | ds->ds_phys->ds_num_children == 1 && | |
337 | DS_IS_DEFER_DESTROY(ds)); | |
338 | dsl_destroy_snapshot_sync_impl(ds, B_FALSE, tx); | |
339 | } | |
340 | dsl_dataset_rele(ds, FTAG); | |
341 | } | |
342 | } | |
343 | ||
344 | /* | |
345 | * holds is nvl of snapname -> { holdname, ... } | |
346 | * errlist will be filled in with snapname -> error | |
347 | * | |
348 | * if any fails, all will fail. | |
349 | */ | |
350 | int | |
351 | dsl_dataset_user_release(nvlist_t *holds, nvlist_t *errlist) | |
352 | { | |
353 | dsl_dataset_user_release_arg_t ddura; | |
354 | nvpair_t *pair; | |
355 | int error; | |
356 | ||
357 | pair = nvlist_next_nvpair(holds, NULL); | |
358 | if (pair == NULL) | |
359 | return (0); | |
360 | ||
361 | ddura.ddura_holds = holds; | |
362 | ddura.ddura_errlist = errlist; | |
363 | ddura.ddura_todelete = fnvlist_alloc(); | |
364 | ||
365 | error = dsl_sync_task(nvpair_name(pair), dsl_dataset_user_release_check, | |
366 | dsl_dataset_user_release_sync, &ddura, fnvlist_num_pairs(holds)); | |
367 | fnvlist_free(ddura.ddura_todelete); | |
368 | return (error); | |
369 | } | |
370 | ||
371 | typedef struct dsl_dataset_user_release_tmp_arg { | |
372 | uint64_t ddurta_dsobj; | |
373 | nvlist_t *ddurta_holds; | |
374 | boolean_t ddurta_deleteme; | |
375 | } dsl_dataset_user_release_tmp_arg_t; | |
376 | ||
377 | static int | |
378 | dsl_dataset_user_release_tmp_check(void *arg, dmu_tx_t *tx) | |
379 | { | |
380 | dsl_dataset_user_release_tmp_arg_t *ddurta = arg; | |
381 | dsl_pool_t *dp = dmu_tx_pool(tx); | |
382 | dsl_dataset_t *ds; | |
383 | int error; | |
384 | ||
385 | if (!dmu_tx_is_syncing(tx)) | |
386 | return (0); | |
387 | ||
388 | error = dsl_dataset_hold_obj(dp, ddurta->ddurta_dsobj, FTAG, &ds); | |
389 | if (error) | |
390 | return (error); | |
391 | ||
392 | error = dsl_dataset_user_release_check_one(ds, | |
393 | ddurta->ddurta_holds, &ddurta->ddurta_deleteme); | |
394 | dsl_dataset_rele(ds, FTAG); | |
395 | return (error); | |
396 | } | |
397 | ||
398 | static void | |
399 | dsl_dataset_user_release_tmp_sync(void *arg, dmu_tx_t *tx) | |
400 | { | |
401 | dsl_dataset_user_release_tmp_arg_t *ddurta = arg; | |
402 | dsl_pool_t *dp = dmu_tx_pool(tx); | |
403 | dsl_dataset_t *ds; | |
404 | ||
405 | VERIFY0(dsl_dataset_hold_obj(dp, ddurta->ddurta_dsobj, FTAG, &ds)); | |
406 | dsl_dataset_user_release_sync_one(ds, ddurta->ddurta_holds, tx); | |
407 | if (ddurta->ddurta_deleteme) { | |
408 | ASSERT(ds->ds_userrefs == 0 && | |
409 | ds->ds_phys->ds_num_children == 1 && | |
410 | DS_IS_DEFER_DESTROY(ds)); | |
411 | dsl_destroy_snapshot_sync_impl(ds, B_FALSE, tx); | |
412 | } | |
413 | dsl_dataset_rele(ds, FTAG); | |
414 | } | |
415 | ||
416 | /* | |
417 | * Called at spa_load time to release a stale temporary user hold. | |
418 | * Also called by the onexit code. | |
419 | */ | |
420 | void | |
421 | dsl_dataset_user_release_tmp(dsl_pool_t *dp, uint64_t dsobj, const char *htag) | |
422 | { | |
423 | dsl_dataset_user_release_tmp_arg_t ddurta; | |
424 | ||
425 | #ifdef _KERNEL | |
426 | dsl_dataset_t *ds; | |
427 | int error; | |
428 | ||
429 | /* Make sure it is not mounted. */ | |
430 | dsl_pool_config_enter(dp, FTAG); | |
431 | error = dsl_dataset_hold_obj(dp, dsobj, FTAG, &ds); | |
432 | if (error == 0) { | |
433 | char name[MAXNAMELEN]; | |
434 | dsl_dataset_name(ds, name); | |
435 | dsl_dataset_rele(ds, FTAG); | |
436 | dsl_pool_config_exit(dp, FTAG); | |
d09f25dc | 437 | (void) zfs_unmount_snap(name); |
13fe0198 MA |
438 | } else { |
439 | dsl_pool_config_exit(dp, FTAG); | |
440 | } | |
441 | #endif | |
442 | ||
443 | ddurta.ddurta_dsobj = dsobj; | |
444 | ddurta.ddurta_holds = fnvlist_alloc(); | |
445 | fnvlist_add_boolean(ddurta.ddurta_holds, htag); | |
446 | ||
447 | (void) dsl_sync_task(spa_name(dp->dp_spa), | |
448 | dsl_dataset_user_release_tmp_check, | |
449 | dsl_dataset_user_release_tmp_sync, &ddurta, 1); | |
450 | fnvlist_free(ddurta.ddurta_holds); | |
451 | } | |
452 | ||
453 | typedef struct zfs_hold_cleanup_arg { | |
454 | char zhca_spaname[MAXNAMELEN]; | |
455 | uint64_t zhca_spa_load_guid; | |
456 | uint64_t zhca_dsobj; | |
457 | char zhca_htag[MAXNAMELEN]; | |
458 | } zfs_hold_cleanup_arg_t; | |
459 | ||
460 | static void | |
461 | dsl_dataset_user_release_onexit(void *arg) | |
462 | { | |
463 | zfs_hold_cleanup_arg_t *ca = arg; | |
464 | spa_t *spa; | |
465 | int error; | |
466 | ||
467 | error = spa_open(ca->zhca_spaname, &spa, FTAG); | |
468 | if (error != 0) { | |
469 | zfs_dbgmsg("couldn't release hold on pool=%s ds=%llu tag=%s " | |
470 | "because pool is no longer loaded", | |
471 | ca->zhca_spaname, ca->zhca_dsobj, ca->zhca_htag); | |
472 | return; | |
473 | } | |
474 | if (spa_load_guid(spa) != ca->zhca_spa_load_guid) { | |
475 | zfs_dbgmsg("couldn't release hold on pool=%s ds=%llu tag=%s " | |
476 | "because pool is no longer loaded (guid doesn't match)", | |
477 | ca->zhca_spaname, ca->zhca_dsobj, ca->zhca_htag); | |
478 | spa_close(spa, FTAG); | |
479 | return; | |
480 | } | |
481 | ||
482 | dsl_dataset_user_release_tmp(spa_get_dsl(spa), | |
483 | ca->zhca_dsobj, ca->zhca_htag); | |
484 | kmem_free(ca, sizeof (zfs_hold_cleanup_arg_t)); | |
485 | spa_close(spa, FTAG); | |
486 | } | |
487 | ||
488 | void | |
489 | dsl_register_onexit_hold_cleanup(dsl_dataset_t *ds, const char *htag, | |
490 | minor_t minor) | |
491 | { | |
c5322236 | 492 | zfs_hold_cleanup_arg_t *ca = kmem_alloc(sizeof (*ca), KM_PUSHPAGE); |
13fe0198 MA |
493 | spa_t *spa = dsl_dataset_get_spa(ds); |
494 | (void) strlcpy(ca->zhca_spaname, spa_name(spa), | |
495 | sizeof (ca->zhca_spaname)); | |
496 | ca->zhca_spa_load_guid = spa_load_guid(spa); | |
497 | ca->zhca_dsobj = ds->ds_object; | |
498 | (void) strlcpy(ca->zhca_htag, htag, sizeof (ca->zhca_htag)); | |
499 | VERIFY0(zfs_onexit_add_cb(minor, | |
500 | dsl_dataset_user_release_onexit, ca, NULL)); | |
501 | } | |
502 | ||
503 | int | |
504 | dsl_dataset_get_holds(const char *dsname, nvlist_t *nvl) | |
505 | { | |
506 | dsl_pool_t *dp; | |
507 | dsl_dataset_t *ds; | |
508 | int err; | |
509 | ||
510 | err = dsl_pool_hold(dsname, FTAG, &dp); | |
511 | if (err != 0) | |
512 | return (err); | |
513 | err = dsl_dataset_hold(dp, dsname, FTAG, &ds); | |
514 | if (err != 0) { | |
515 | dsl_pool_rele(dp, FTAG); | |
516 | return (err); | |
517 | } | |
518 | ||
519 | if (ds->ds_phys->ds_userrefs_obj != 0) { | |
520 | zap_attribute_t *za; | |
521 | zap_cursor_t zc; | |
522 | ||
c5322236 | 523 | za = kmem_alloc(sizeof (zap_attribute_t), KM_PUSHPAGE); |
13fe0198 MA |
524 | for (zap_cursor_init(&zc, ds->ds_dir->dd_pool->dp_meta_objset, |
525 | ds->ds_phys->ds_userrefs_obj); | |
526 | zap_cursor_retrieve(&zc, za) == 0; | |
527 | zap_cursor_advance(&zc)) { | |
528 | fnvlist_add_uint64(nvl, za->za_name, | |
529 | za->za_first_integer); | |
530 | } | |
531 | zap_cursor_fini(&zc); | |
532 | kmem_free(za, sizeof (zap_attribute_t)); | |
533 | } | |
534 | dsl_dataset_rele(ds, FTAG); | |
535 | dsl_pool_rele(dp, FTAG); | |
536 | return (0); | |
537 | } |