Illumos #3006
[zfs.git] / module / zfs / dsl_dir.c
index e5e18f4..69f68c2 100644 (file)
@@ -19,8 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
- * Use is subject to license terms.
+ * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
  */
 
 #include <sys/dmu.h>
@@ -32,6 +31,7 @@
 #include <sys/dsl_synctask.h>
 #include <sys/dsl_deleg.h>
 #include <sys/spa.h>
+#include <sys/metaslab.h>
 #include <sys/zap.h>
 #include <sys/zio.h>
 #include <sys/arc.h>
@@ -39,8 +39,7 @@
 #include "zfs_namecheck.h"
 
 static uint64_t dsl_dir_space_towrite(dsl_dir_t *dd);
-static void dsl_dir_set_reservation_sync(void *arg1, void *arg2,
-    cred_t *cr, dmu_tx_t *tx);
+static void dsl_dir_set_reservation_sync(void *arg1, void *arg2, dmu_tx_t *tx);
 
 
 /* ARGSUSED */
@@ -48,7 +47,7 @@ static void
 dsl_dir_evict(dmu_buf_t *db, void *arg)
 {
        dsl_dir_t *dd = arg;
-       dsl_pool_t *dp = dd->dd_pool;
+       ASSERTV(dsl_pool_t *dp = dd->dd_pool;)
        int t;
 
        for (t = 0; t < TXG_SIZE; t++) {
@@ -63,8 +62,8 @@ dsl_dir_evict(dmu_buf_t *db, void *arg)
        spa_close(dd->dd_pool->dp_spa, dd);
 
        /*
-        * The props callback list should be empty since they hold the
-        * dir open.
+        * The props callback list should have been cleaned up by
+        * objset_evict().
         */
        list_destroy(&dd->dd_prop_cbs);
        mutex_destroy(&dd->dd_lock);
@@ -96,9 +95,8 @@ dsl_dir_open_obj(dsl_pool_t *dp, uint64_t ddobj,
 #endif
        if (dd == NULL) {
                dsl_dir_t *winner;
-               int err;
 
-               dd = kmem_zalloc(sizeof (dsl_dir_t), KM_SLEEP);
+               dd = kmem_zalloc(sizeof (dsl_dir_t), KM_PUSHPAGE);
                dd->dd_object = ddobj;
                dd->dd_dbuf = dbuf;
                dd->dd_pool = dp;
@@ -108,6 +106,8 @@ dsl_dir_open_obj(dsl_pool_t *dp, uint64_t ddobj,
                list_create(&dd->dd_prop_cbs, sizeof (dsl_prop_cb_record_t),
                    offsetof(dsl_prop_cb_record_t, cbr_node));
 
+               dsl_dir_snap_cmtime_update(dd);
+
                if (dd->dd_phys->dd_parent_obj) {
                        err = dsl_dir_open_obj(dp, dd->dd_phys->dd_parent_obj,
                            NULL, dd, &dd->dd_parent);
@@ -134,6 +134,25 @@ dsl_dir_open_obj(dsl_pool_t *dp, uint64_t ddobj,
                        (void) strcpy(dd->dd_myname, spa_name(dp->dp_spa));
                }
 
+               if (dsl_dir_is_clone(dd)) {
+                       dmu_buf_t *origin_bonus;
+                       dsl_dataset_phys_t *origin_phys;
+
+                       /*
+                        * We can't open the origin dataset, because
+                        * that would require opening this dsl_dir.
+                        * Just look at its phys directly instead.
+                        */
+                       err = dmu_bonus_hold(dp->dp_meta_objset,
+                           dd->dd_phys->dd_origin_obj, FTAG, &origin_bonus);
+                       if (err)
+                               goto errout;
+                       origin_phys = origin_bonus->db_data;
+                       dd->dd_origin_txg =
+                           origin_phys->ds_creation_txg;
+                       dmu_buf_rele(origin_bonus, FTAG);
+               }
+
                winner = dmu_buf_set_user_ie(dbuf, dd, &dd->dd_phys,
                    dsl_dir_evict);
                if (winner) {
@@ -170,7 +189,6 @@ errout:
        kmem_free(dd, sizeof (dsl_dir_t));
        dmu_buf_rele(dbuf, tag);
        return (err);
-
 }
 
 void
@@ -204,7 +222,7 @@ dsl_dir_name(dsl_dir_t *dd, char *buf)
        }
 }
 
-/* Calculate name legnth, avoiding all the strcat calls of dsl_dir_name */
+/* Calculate name length, avoiding all the strcat calls of dsl_dir_name */
 int
 dsl_dir_namelen(dsl_dir_t *dd)
 {
@@ -227,24 +245,11 @@ dsl_dir_namelen(dsl_dir_t *dd)
        return (result);
 }
 
-int
-dsl_dir_is_private(dsl_dir_t *dd)
-{
-       int rv = FALSE;
-
-       if (dd->dd_parent && dsl_dir_is_private(dd->dd_parent))
-               rv = TRUE;
-       if (dataset_name_hidden(dd->dd_myname))
-               rv = TRUE;
-       return (rv);
-}
-
-
 static int
 getcomponent(const char *path, char *component, const char **nextp)
 {
        char *p;
-       if (path == NULL)
+       if ((path == NULL) || (path[0] == '\0'))
                return (ENOENT);
        /* This would be a good place to reserve some namespace... */
        p = strpbrk(path, "/@");
@@ -290,14 +295,14 @@ getcomponent(const char *path, char *component, const char **nextp)
 }
 
 /*
- * same as dsl_open_dir, ignore the first component of name and use the
+ * same as dsl_dir_open, ignore the first component of name and use the
  * spa instead
  */
 int
 dsl_dir_open_spa(spa_t *spa, const char *name, void *tag,
     dsl_dir_t **ddp, const char **tailp)
 {
-       char buf[MAXNAMELEN];
+       char *buf;
        const char *next, *nextnext = NULL;
        int err;
        dsl_dir_t *dd;
@@ -307,14 +312,15 @@ dsl_dir_open_spa(spa_t *spa, const char *name, void *tag,
 
        dprintf("%s\n", name);
 
+       buf = kmem_alloc(MAXNAMELEN, KM_PUSHPAGE);
        err = getcomponent(name, buf, &next);
        if (err)
-               return (err);
+               goto error;
        if (spa == NULL) {
                err = spa_open(buf, &spa, FTAG);
                if (err) {
                        dprintf("spa_open(%s) failed\n", buf);
-                       return (err);
+                       goto error;
                }
                openedspa = TRUE;
 
@@ -330,7 +336,7 @@ dsl_dir_open_spa(spa_t *spa, const char *name, void *tag,
                rw_exit(&dp->dp_config_rwlock);
                if (openedspa)
                        spa_close(spa, FTAG);
-               return (err);
+               goto error;
        }
 
        while (next != NULL) {
@@ -366,7 +372,7 @@ dsl_dir_open_spa(spa_t *spa, const char *name, void *tag,
                dsl_dir_close(dd, tag);
                if (openedspa)
                        spa_close(spa, FTAG);
-               return (err);
+               goto error;
        }
 
        /*
@@ -385,6 +391,8 @@ dsl_dir_open_spa(spa_t *spa, const char *name, void *tag,
        if (openedspa)
                spa_close(spa, FTAG);
        *ddp = dd;
+error:
+       kmem_free(buf, MAXNAMELEN);
        return (err);
 }
 
@@ -406,7 +414,7 @@ dsl_dir_create_sync(dsl_pool_t *dp, dsl_dir_t *pds, const char *name,
 {
        objset_t *mos = dp->dp_meta_objset;
        uint64_t ddobj;
-       dsl_dir_phys_t *dsphys;
+       dsl_dir_phys_t *ddphys;
        dmu_buf_t *dbuf;
 
        ddobj = dmu_object_alloc(mos, DMU_OT_DSL_DIR, 0,
@@ -421,17 +429,17 @@ dsl_dir_create_sync(dsl_pool_t *dp, dsl_dir_t *pds, const char *name,
        }
        VERIFY(0 == dmu_bonus_hold(mos, ddobj, FTAG, &dbuf));
        dmu_buf_will_dirty(dbuf, tx);
-       dsphys = dbuf->db_data;
+       ddphys = dbuf->db_data;
 
-       dsphys->dd_creation_time = gethrestime_sec();
+       ddphys->dd_creation_time = gethrestime_sec();
        if (pds)
-               dsphys->dd_parent_obj = pds->dd_object;
-       dsphys->dd_props_zapobj = zap_create(mos,
+               ddphys->dd_parent_obj = pds->dd_object;
+       ddphys->dd_props_zapobj = zap_create(mos,
            DMU_OT_DSL_PROPS, DMU_OT_NONE, 0, tx);
-       dsphys->dd_child_dir_zapobj = zap_create(mos,
+       ddphys->dd_child_dir_zapobj = zap_create(mos,
            DMU_OT_DSL_DIR_CHILD_MAP, DMU_OT_NONE, 0, tx);
        if (spa_version(dp->dp_spa) >= SPA_VERSION_USED_BREAKDOWN)
-               dsphys->dd_flags |= DD_FLAG_USED_BREAKDOWN;
+               ddphys->dd_flags |= DD_FLAG_USED_BREAKDOWN;
        dmu_buf_rele(dbuf, FTAG);
 
        return (ddobj);
@@ -441,7 +449,8 @@ dsl_dir_create_sync(dsl_pool_t *dp, dsl_dir_t *pds, const char *name,
 int
 dsl_dir_destroy_check(void *arg1, void *arg2, dmu_tx_t *tx)
 {
-       dsl_dir_t *dd = arg1;
+       dsl_dataset_t *ds = arg1;
+       dsl_dir_t *dd = ds->ds_dir;
        dsl_pool_t *dp = dd->dd_pool;
        objset_t *mos = dp->dp_meta_objset;
        int err;
@@ -450,12 +459,14 @@ dsl_dir_destroy_check(void *arg1, void *arg2, dmu_tx_t *tx)
        /*
         * There should be exactly two holds, both from
         * dsl_dataset_destroy: one on the dd directory, and one on its
-        * head ds.  Otherwise, someone is trying to lookup something
-        * inside this dir while we want to destroy it.  The
-        * config_rwlock ensures that nobody else opens it after we
-        * check.
+        * head ds.  If there are more holds, then a concurrent thread is
+        * performing a lookup inside this dir while we're trying to destroy
+        * it.  To minimize this possibility, we perform this check only
+        * in syncing context and fail the operation if we encounter
+        * additional holds.  The dp_config_rwlock ensures that nobody else
+        * opens it after we check.
         */
-       if (dmu_buf_refcount(dd->dd_dbuf) > 2)
+       if (dmu_tx_is_syncing(tx) && dmu_buf_refcount(dd->dd_dbuf) > 2)
                return (EBUSY);
 
        err = zap_count(mos, dd->dd_phys->dd_child_dir_zapobj, &count);
@@ -468,23 +479,31 @@ dsl_dir_destroy_check(void *arg1, void *arg2, dmu_tx_t *tx)
 }
 
 void
-dsl_dir_destroy_sync(void *arg1, void *tag, cred_t *cr, dmu_tx_t *tx)
+dsl_dir_destroy_sync(void *arg1, void *tag, dmu_tx_t *tx)
 {
-       dsl_dir_t *dd = arg1;
+       dsl_dataset_t *ds = arg1;
+       dsl_dir_t *dd = ds->ds_dir;
        objset_t *mos = dd->dd_pool->dp_meta_objset;
-       uint64_t val, obj;
+       dsl_prop_setarg_t psa;
+       uint64_t value = 0;
+       uint64_t obj;
        dd_used_t t;
 
        ASSERT(RW_WRITE_HELD(&dd->dd_pool->dp_config_rwlock));
        ASSERT(dd->dd_phys->dd_head_dataset_obj == 0);
 
        /* Remove our reservation. */
-       val = 0;
-       dsl_dir_set_reservation_sync(dd, &val, cr, tx);
-       ASSERT3U(dd->dd_phys->dd_used_bytes, ==, 0);
-       ASSERT3U(dd->dd_phys->dd_reserved, ==, 0);
+       dsl_prop_setarg_init_uint64(&psa, "reservation",
+           (ZPROP_SRC_NONE | ZPROP_SRC_LOCAL | ZPROP_SRC_RECEIVED),
+           &value);
+       psa.psa_effective_value = 0;    /* predict default value */
+
+       dsl_dir_set_reservation_sync(ds, &psa, tx);
+
+       ASSERT0(dd->dd_phys->dd_used_bytes);
+       ASSERT0(dd->dd_phys->dd_reserved);
        for (t = 0; t < DD_USED_NUM; t++)
-               ASSERT3U(dd->dd_phys->dd_used_breakdown[t], ==, 0);
+               ASSERT0(dd->dd_phys->dd_used_breakdown[t]);
 
        VERIFY(0 == zap_destroy(mos, dd->dd_phys->dd_child_dir_zapobj, tx));
        VERIFY(0 == zap_destroy(mos, dd->dd_phys->dd_props_zapobj, tx));
@@ -572,10 +591,8 @@ dsl_dir_sync(dsl_dir_t *dd, dmu_tx_t *tx)
 {
        ASSERT(dmu_tx_is_syncing(tx));
 
-       dmu_buf_will_dirty(dd->dd_dbuf, tx);
-
        mutex_enter(&dd->dd_lock);
-       ASSERT3U(dd->dd_tempreserved[tx->tx_txg&TXG_MASK], ==, 0);
+       ASSERT0(dd->dd_tempreserved[tx->tx_txg&TXG_MASK]);
        dprintf_dd(dd, "txg=%llu towrite=%lluK\n", tx->tx_txg,
            dd->dd_space_towrite[tx->tx_txg&TXG_MASK] / 1024);
        dd->dd_space_towrite[tx->tx_txg&TXG_MASK] = 0;
@@ -654,15 +671,6 @@ dsl_dir_space_available(dsl_dir_t *dd,
        if (used > quota) {
                /* over quota */
                myspace = 0;
-
-               /*
-                * While it's OK to be a little over quota, if
-                * we think we are using more space than there
-                * is in the pool (which is already 1.6% more than
-                * dsl_pool_adjustedsize()), something is very
-                * wrong.
-                */
-               ASSERT3U(used, <=, spa_get_space(dd->dd_pool->dp_spa));
        } else {
                /*
                 * the lesser of the space provided by our parent and
@@ -690,8 +698,9 @@ dsl_dir_tempreserve_impl(dsl_dir_t *dd, uint64_t asize, boolean_t netfree,
 {
        uint64_t txg = tx->tx_txg;
        uint64_t est_inflight, used_on_disk, quota, parent_rsrv;
+       uint64_t deferred = 0;
        struct tempreserve *tr;
-       int enospc = EDQUOT;
+       int retval = EDQUOT;
        int txgidx = txg & TXG_MASK;
        int i;
        uint64_t ref_rsrv = 0;
@@ -717,7 +726,7 @@ dsl_dir_tempreserve_impl(dsl_dir_t *dd, uint64_t asize, boolean_t netfree,
         */
        if (first && tx->tx_objset) {
                int error;
-               dsl_dataset_t *ds = tx->tx_objset->os->os_dsl_dataset;
+               dsl_dataset_t *ds = tx->tx_objset->os_dsl_dataset;
 
                error = dsl_dataset_check_quota(ds, checkrefquota,
                    asize, est_inflight, &used_on_disk, &ref_rsrv);
@@ -737,7 +746,8 @@ dsl_dir_tempreserve_impl(dsl_dir_t *dd, uint64_t asize, boolean_t netfree,
                quota = dd->dd_phys->dd_quota;
 
        /*
-        * Adjust the quota against the actual pool size at the root.
+        * Adjust the quota against the actual pool size at the root
+        * minus any outstanding deferred frees.
         * To ensure that it's possible to remove files from a full
         * pool without inducing transient overcommits, we throttle
         * netfree transactions against a quota that is slightly larger,
@@ -746,10 +756,12 @@ dsl_dir_tempreserve_impl(dsl_dir_t *dd, uint64_t asize, boolean_t netfree,
         * removes to get through.
         */
        if (dd->dd_parent == NULL) {
+               spa_t *spa = dd->dd_pool->dp_spa;
                uint64_t poolsize = dsl_pool_adjustedsize(dd->dd_pool, netfree);
-               if (poolsize < quota) {
-                       quota = poolsize;
-                       enospc = ENOSPC;
+               deferred = metaslab_class_get_deferred(spa_normal_class(spa));
+               if (poolsize - deferred < quota) {
+                       quota = poolsize - deferred;
+                       retval = ENOSPC;
                }
        }
 
@@ -759,15 +771,16 @@ dsl_dir_tempreserve_impl(dsl_dir_t *dd, uint64_t asize, boolean_t netfree,
         * on-disk is over quota and there are no pending changes (which
         * may free up space for us).
         */
-       if (used_on_disk + est_inflight > quota) {
-               if (est_inflight > 0 || used_on_disk < quota)
-                       enospc = ERESTART;
+       if (used_on_disk + est_inflight >= quota) {
+               if (est_inflight > 0 || used_on_disk < quota ||
+                   (retval == ENOSPC && used_on_disk < quota + deferred))
+                       retval = ERESTART;
                dprintf_dd(dd, "failing: used=%lluK inflight = %lluK "
                    "quota=%lluK tr=%lluK err=%d\n",
                    used_on_disk>>10, est_inflight>>10,
-                   quota>>10, asize>>10, enospc);
+                   quota>>10, asize>>10, retval);
                mutex_exit(&dd->dd_lock);
-               return (enospc);
+               return (retval);
        }
 
        /* We need to up our estimated delta before dropping dd_lock */
@@ -777,7 +790,7 @@ dsl_dir_tempreserve_impl(dsl_dir_t *dd, uint64_t asize, boolean_t netfree,
            asize - ref_rsrv);
        mutex_exit(&dd->dd_lock);
 
-       tr = kmem_zalloc(sizeof (struct tempreserve), KM_SLEEP);
+       tr = kmem_zalloc(sizeof (struct tempreserve), KM_PUSHPAGE);
        tr->tr_ds = dd;
        tr->tr_size = asize;
        list_insert_tail(tr_list, tr);
@@ -811,7 +824,7 @@ dsl_dir_tempreserve_space(dsl_dir_t *dd, uint64_t lsize, uint64_t asize,
                return (0);
        }
 
-       tr_list = kmem_alloc(sizeof (list_t), KM_SLEEP);
+       tr_list = kmem_alloc(sizeof (list_t), KM_PUSHPAGE);
        list_create(tr_list, sizeof (struct tempreserve),
            offsetof(struct tempreserve, tr_node));
        ASSERT3S(asize, >, 0);
@@ -821,7 +834,7 @@ dsl_dir_tempreserve_space(dsl_dir_t *dd, uint64_t lsize, uint64_t asize,
        if (err == 0) {
                struct tempreserve *tr;
 
-               tr = kmem_zalloc(sizeof (struct tempreserve), KM_SLEEP);
+               tr = kmem_zalloc(sizeof (struct tempreserve), KM_PUSHPAGE);
                tr->tr_size = lsize;
                list_insert_tail(tr_list, tr);
 
@@ -837,7 +850,7 @@ dsl_dir_tempreserve_space(dsl_dir_t *dd, uint64_t lsize, uint64_t asize,
        if (err == 0) {
                struct tempreserve *tr;
 
-               tr = kmem_zalloc(sizeof (struct tempreserve), KM_SLEEP);
+               tr = kmem_zalloc(sizeof (struct tempreserve), KM_PUSHPAGE);
                tr->tr_dp = dd->dd_pool;
                tr->tr_size = asize;
                list_insert_tail(tr_list, tr);
@@ -870,7 +883,7 @@ dsl_dir_tempreserve_clear(void *tr_cookie, dmu_tx_t *tx)
        if (tr_cookie == NULL)
                return;
 
-       while (tr = list_head(tr_list)) {
+       while ((tr = list_head(tr_list))) {
                if (tr->tr_dp) {
                        dsl_pool_tempreserve_clear(tr->tr_dp, tr->tr_size, tx);
                } else if (tr->tr_ds) {
@@ -934,8 +947,6 @@ dsl_dir_diduse_space(dsl_dir_t *dd, dd_used_t type,
        ASSERT(dmu_tx_is_syncing(tx));
        ASSERT(type < DD_USED_NUM);
 
-       dsl_dir_dirty(dd, tx);
-
        if (needlock)
                mutex_enter(&dd->dd_lock);
        accounted_delta = parent_delta(dd, dd->dd_phys->dd_used_bytes, used);
@@ -944,6 +955,7 @@ dsl_dir_diduse_space(dsl_dir_t *dd, dd_used_t type,
            dd->dd_phys->dd_compressed_bytes >= -compressed);
        ASSERT(uncompressed >= 0 ||
            dd->dd_phys->dd_uncompressed_bytes >= -uncompressed);
+       dmu_buf_will_dirty(dd->dd_dbuf, tx);
        dd->dd_phys->dd_used_bytes += used;
        dd->dd_phys->dd_uncompressed_bytes += uncompressed;
        dd->dd_phys->dd_compressed_bytes += compressed;
@@ -953,11 +965,13 @@ dsl_dir_diduse_space(dsl_dir_t *dd, dd_used_t type,
                    dd->dd_phys->dd_used_breakdown[type] >= -used);
                dd->dd_phys->dd_used_breakdown[type] += used;
 #ifdef DEBUG
-               dd_used_t t;
-               uint64_t u = 0;
-               for (t = 0; t < DD_USED_NUM; t++)
-                       u += dd->dd_phys->dd_used_breakdown[t];
-               ASSERT3U(u, ==, dd->dd_phys->dd_used_bytes);
+               {
+                       dd_used_t t;
+                       uint64_t u = 0;
+                       for (t = 0; t < DD_USED_NUM; t++)
+                               u += dd->dd_phys->dd_used_breakdown[t];
+                       ASSERT3U(u, ==, dd->dd_phys->dd_used_bytes);
+               }
 #endif
        }
        if (needlock)
@@ -985,13 +999,13 @@ dsl_dir_transfer_space(dsl_dir_t *dd, int64_t delta,
        if (delta == 0 || !(dd->dd_phys->dd_flags & DD_FLAG_USED_BREAKDOWN))
                return;
 
-       dsl_dir_dirty(dd, tx);
        if (needlock)
                mutex_enter(&dd->dd_lock);
        ASSERT(delta > 0 ?
            dd->dd_phys->dd_used_breakdown[oldtype] >= delta :
            dd->dd_phys->dd_used_breakdown[newtype] >= -delta);
        ASSERT(dd->dd_phys->dd_used_bytes >= ABS(delta));
+       dmu_buf_will_dirty(dd->dd_dbuf, tx);
        dd->dd_phys->dd_used_breakdown[oldtype] -= delta;
        dd->dd_phys->dd_used_breakdown[newtype] += delta;
        if (needlock)
@@ -1001,13 +1015,16 @@ dsl_dir_transfer_space(dsl_dir_t *dd, int64_t delta,
 static int
 dsl_dir_set_quota_check(void *arg1, void *arg2, dmu_tx_t *tx)
 {
-       dsl_dir_t *dd = arg1;
-       uint64_t *quotap = arg2;
-       uint64_t new_quota = *quotap;
-       int err = 0;
+       dsl_dataset_t *ds = arg1;
+       dsl_dir_t *dd = ds->ds_dir;
+       dsl_prop_setarg_t *psa = arg2;
+       int err;
        uint64_t towrite;
 
-       if (new_quota == 0)
+       if ((err = dsl_prop_predict_sync(ds->ds_dir, psa)) != 0)
+               return (err);
+
+       if (psa->psa_effective_value == 0)
                return (0);
 
        mutex_enter(&dd->dd_lock);
@@ -1019,64 +1036,84 @@ dsl_dir_set_quota_check(void *arg1, void *arg2, dmu_tx_t *tx)
         */
        towrite = dsl_dir_space_towrite(dd);
        if ((dmu_tx_is_syncing(tx) || towrite == 0) &&
-           (new_quota < dd->dd_phys->dd_reserved ||
-           new_quota < dd->dd_phys->dd_used_bytes + towrite)) {
+           (psa->psa_effective_value < dd->dd_phys->dd_reserved ||
+           psa->psa_effective_value < dd->dd_phys->dd_used_bytes + towrite)) {
                err = ENOSPC;
        }
        mutex_exit(&dd->dd_lock);
        return (err);
 }
 
-/* ARGSUSED */
+extern dsl_syncfunc_t dsl_prop_set_sync;
+
 static void
-dsl_dir_set_quota_sync(void *arg1, void *arg2, cred_t *cr, dmu_tx_t *tx)
+dsl_dir_set_quota_sync(void *arg1, void *arg2, dmu_tx_t *tx)
 {
-       dsl_dir_t *dd = arg1;
-       uint64_t *quotap = arg2;
-       uint64_t new_quota = *quotap;
+       dsl_dataset_t *ds = arg1;
+       dsl_dir_t *dd = ds->ds_dir;
+       dsl_prop_setarg_t *psa = arg2;
+       uint64_t effective_value = psa->psa_effective_value;
+
+       dsl_prop_set_sync(ds, psa, tx);
+       DSL_PROP_CHECK_PREDICTION(dd, psa);
 
        dmu_buf_will_dirty(dd->dd_dbuf, tx);
 
        mutex_enter(&dd->dd_lock);
-       dd->dd_phys->dd_quota = new_quota;
+       dd->dd_phys->dd_quota = effective_value;
        mutex_exit(&dd->dd_lock);
-
-       spa_history_internal_log(LOG_DS_QUOTA, dd->dd_pool->dp_spa,
-           tx, cr, "%lld dataset = %llu ",
-           (longlong_t)new_quota, dd->dd_phys->dd_head_dataset_obj);
 }
 
 int
-dsl_dir_set_quota(const char *ddname, uint64_t quota)
+dsl_dir_set_quota(const char *ddname, zprop_source_t source, uint64_t quota)
 {
        dsl_dir_t *dd;
+       dsl_dataset_t *ds;
+       dsl_prop_setarg_t psa;
        int err;
 
-       err = dsl_dir_open(ddname, FTAG, &dd, NULL);
+       dsl_prop_setarg_init_uint64(&psa, "quota", source, &quota);
+
+       err = dsl_dataset_hold(ddname, FTAG, &ds);
        if (err)
                return (err);
 
-       if (quota != dd->dd_phys->dd_quota) {
-               /*
-                * If someone removes a file, then tries to set the quota, we
-                * want to make sure the file freeing takes effect.
-                */
-               txg_wait_open(dd->dd_pool, 0);
-
-               err = dsl_sync_task_do(dd->dd_pool, dsl_dir_set_quota_check,
-                   dsl_dir_set_quota_sync, dd, &quota, 0);
+       err = dsl_dir_open(ddname, FTAG, &dd, NULL);
+       if (err) {
+               dsl_dataset_rele(ds, FTAG);
+               return (err);
        }
+
+       ASSERT(ds->ds_dir == dd);
+
+       /*
+        * If someone removes a file, then tries to set the quota, we want to
+        * make sure the file freeing takes effect.
+        */
+       txg_wait_open(dd->dd_pool, 0);
+
+       err = dsl_sync_task_do(dd->dd_pool, dsl_dir_set_quota_check,
+           dsl_dir_set_quota_sync, ds, &psa, 0);
+
        dsl_dir_close(dd, FTAG);
+       dsl_dataset_rele(ds, FTAG);
        return (err);
 }
 
 int
 dsl_dir_set_reservation_check(void *arg1, void *arg2, dmu_tx_t *tx)
 {
-       dsl_dir_t *dd = arg1;
-       uint64_t *reservationp = arg2;
-       uint64_t new_reservation = *reservationp;
+       dsl_dataset_t *ds = arg1;
+       dsl_dir_t *dd = ds->ds_dir;
+       dsl_prop_setarg_t *psa = arg2;
+       uint64_t effective_value;
        uint64_t used, avail;
+       int err;
+
+       if ((err = dsl_prop_predict_sync(ds->ds_dir, psa)) != 0)
+               return (err);
+
+       effective_value = psa->psa_effective_value;
 
        /*
         * If we are doing the preliminary check in open context, the
@@ -1096,37 +1133,40 @@ dsl_dir_set_reservation_check(void *arg1, void *arg2, dmu_tx_t *tx)
                avail = dsl_pool_adjustedsize(dd->dd_pool, B_FALSE) - used;
        }
 
-       if (MAX(used, new_reservation) > MAX(used, dd->dd_phys->dd_reserved)) {
-               uint64_t delta = MAX(used, new_reservation) -
+       if (MAX(used, effective_value) > MAX(used, dd->dd_phys->dd_reserved)) {
+               uint64_t delta = MAX(used, effective_value) -
                    MAX(used, dd->dd_phys->dd_reserved);
 
                if (delta > avail)
                        return (ENOSPC);
                if (dd->dd_phys->dd_quota > 0 &&
-                   new_reservation > dd->dd_phys->dd_quota)
+                   effective_value > dd->dd_phys->dd_quota)
                        return (ENOSPC);
        }
 
        return (0);
 }
 
-/* ARGSUSED */
 static void
-dsl_dir_set_reservation_sync(void *arg1, void *arg2, cred_t *cr, dmu_tx_t *tx)
+dsl_dir_set_reservation_sync(void *arg1, void *arg2, dmu_tx_t *tx)
 {
-       dsl_dir_t *dd = arg1;
-       uint64_t *reservationp = arg2;
-       uint64_t new_reservation = *reservationp;
+       dsl_dataset_t *ds = arg1;
+       dsl_dir_t *dd = ds->ds_dir;
+       dsl_prop_setarg_t *psa = arg2;
+       uint64_t effective_value = psa->psa_effective_value;
        uint64_t used;
        int64_t delta;
 
+       dsl_prop_set_sync(ds, psa, tx);
+       DSL_PROP_CHECK_PREDICTION(dd, psa);
+
        dmu_buf_will_dirty(dd->dd_dbuf, tx);
 
        mutex_enter(&dd->dd_lock);
        used = dd->dd_phys->dd_used_bytes;
-       delta = MAX(used, new_reservation) -
+       delta = MAX(used, effective_value) -
            MAX(used, dd->dd_phys->dd_reserved);
-       dd->dd_phys->dd_reserved = new_reservation;
+       dd->dd_phys->dd_reserved = effective_value;
 
        if (dd->dd_parent != NULL) {
                /* Roll up this additional usage into our ancestors */
@@ -1134,24 +1174,36 @@ dsl_dir_set_reservation_sync(void *arg1, void *arg2, cred_t *cr, dmu_tx_t *tx)
                    delta, 0, 0, tx);
        }
        mutex_exit(&dd->dd_lock);
-
-       spa_history_internal_log(LOG_DS_RESERVATION, dd->dd_pool->dp_spa,
-           tx, cr, "%lld dataset = %llu",
-           (longlong_t)new_reservation, dd->dd_phys->dd_head_dataset_obj);
 }
 
 int
-dsl_dir_set_reservation(const char *ddname, uint64_t reservation)
+dsl_dir_set_reservation(const char *ddname, zprop_source_t source,
+    uint64_t reservation)
 {
        dsl_dir_t *dd;
+       dsl_dataset_t *ds;
+       dsl_prop_setarg_t psa;
        int err;
 
-       err = dsl_dir_open(ddname, FTAG, &dd, NULL);
+       dsl_prop_setarg_init_uint64(&psa, "reservation", source, &reservation);
+
+       err = dsl_dataset_hold(ddname, FTAG, &ds);
        if (err)
                return (err);
+
+       err = dsl_dir_open(ddname, FTAG, &dd, NULL);
+       if (err) {
+               dsl_dataset_rele(ds, FTAG);
+               return (err);
+       }
+
+       ASSERT(ds->ds_dir == dd);
+
        err = dsl_sync_task_do(dd->dd_pool, dsl_dir_set_reservation_check,
-           dsl_dir_set_reservation_sync, dd, &reservation, 0);
+           dsl_dir_set_reservation_sync, ds, &psa, 0);
+
        dsl_dir_close(dd, FTAG);
+       dsl_dataset_rele(ds, FTAG);
        return (err);
 }
 
@@ -1189,7 +1241,6 @@ struct renamearg {
        const char *mynewname;
 };
 
-/*ARGSUSED*/
 static int
 dsl_dir_rename_check(void *arg1, void *arg2, dmu_tx_t *tx)
 {
@@ -1200,8 +1251,14 @@ dsl_dir_rename_check(void *arg1, void *arg2, dmu_tx_t *tx)
        int err;
        uint64_t val;
 
-       /* There should be 2 references: the open and the dirty */
-       if (dmu_buf_refcount(dd->dd_dbuf) > 2)
+       /*
+        * There should only be one reference, from dmu_objset_rename().
+        * Fleeting holds are also possible (eg, from "zfs list" getting
+        * stats), but any that are present in open context will likely
+        * be gone by syncing context, so only fail from syncing
+        * context.
+        */
+       if (dmu_tx_is_syncing(tx) && dmu_buf_refcount(dd->dd_dbuf) > 1)
                return (EBUSY);
 
        /* check for existing name */
@@ -1221,8 +1278,8 @@ dsl_dir_rename_check(void *arg1, void *arg2, dmu_tx_t *tx)
                if (closest_common_ancestor(dd, ra->newparent) == dd)
                        return (EINVAL);
 
-               if (err = dsl_dir_transfer_possible(dd->dd_parent,
-                   ra->newparent, myspace))
+               if ((err = dsl_dir_transfer_possible(dd->dd_parent,
+                   ra->newparent, myspace)))
                        return (err);
        }
 
@@ -1230,7 +1287,7 @@ dsl_dir_rename_check(void *arg1, void *arg2, dmu_tx_t *tx)
 }
 
 static void
-dsl_dir_rename_sync(void *arg1, void *arg2, cred_t *cr, dmu_tx_t *tx)
+dsl_dir_rename_sync(void *arg1, void *arg2, dmu_tx_t *tx)
 {
        dsl_dir_t *dd = arg1;
        struct renamearg *ra = arg2;
@@ -1266,7 +1323,7 @@ dsl_dir_rename_sync(void *arg1, void *arg2, cred_t *cr, dmu_tx_t *tx)
        /* remove from old parent zapobj */
        err = zap_remove(mos, dd->dd_parent->dd_phys->dd_child_dir_zapobj,
            dd->dd_myname, tx);
-       ASSERT3U(err, ==, 0);
+       ASSERT0(err);
 
        (void) strcpy(dd->dd_myname, ra->mynewname);
        dsl_dir_close(dd->dd_parent, dd);
@@ -1277,10 +1334,10 @@ dsl_dir_rename_sync(void *arg1, void *arg2, cred_t *cr, dmu_tx_t *tx)
        /* add to new parent zapobj */
        err = zap_add(mos, ra->newparent->dd_phys->dd_child_dir_zapobj,
            dd->dd_myname, 8, 1, &dd->dd_object, tx);
-       ASSERT3U(err, ==, 0);
+       ASSERT0(err);
 
-       spa_history_internal_log(LOG_DS_RENAME, dd->dd_pool->dp_spa,
-           tx, cr, "dataset = %llu", dd->dd_phys->dd_head_dataset_obj);
+       spa_history_log_internal(LOG_DS_RENAME, dd->dd_pool->dp_spa,
+           tx, "dataset = %llu", dd->dd_phys->dd_head_dataset_obj);
 }
 
 int
@@ -1329,3 +1386,33 @@ dsl_dir_transfer_possible(dsl_dir_t *sdd, dsl_dir_t *tdd, uint64_t space)
 
        return (0);
 }
+
+timestruc_t
+dsl_dir_snap_cmtime(dsl_dir_t *dd)
+{
+       timestruc_t t;
+
+       mutex_enter(&dd->dd_lock);
+       t = dd->dd_snap_cmtime;
+       mutex_exit(&dd->dd_lock);
+
+       return (t);
+}
+
+void
+dsl_dir_snap_cmtime_update(dsl_dir_t *dd)
+{
+       timestruc_t t;
+
+       gethrestime(&t);
+       mutex_enter(&dd->dd_lock);
+       dd->dd_snap_cmtime = t;
+       mutex_exit(&dd->dd_lock);
+}
+
+#if defined(_KERNEL) && defined(HAVE_SPL)
+EXPORT_SYMBOL(dsl_dir_set_quota);
+EXPORT_SYMBOL(dsl_dir_set_reservation);
+EXPORT_SYMBOL(dsl_dir_open);
+EXPORT_SYMBOL(dsl_dir_close);
+#endif