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 }