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