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