--- old/usr/src/common/zfs/zfeature_common.c	Mon Oct 29 18:19:31 2012
+++ new/usr/src/common/zfs/zfeature_common.c	Mon Oct 29 18:19:31 2012
@@ -21,6 +21,7 @@
 
 /*
  * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2012 Joyent, Inc. All rights reserved.
  */
 
 #ifdef _KERNEL
@@ -156,4 +157,7 @@
 	zfeature_register(SPA_FEATURE_EMPTY_BPOBJ,
 	    "com.delphix:empty_bpobj", "empty_bpobj",
 	    "Snapshots use less space.", B_TRUE, B_FALSE, NULL);
+	zfeature_register(SPA_FEATURE_DS_SS_QUOTA,
+	    "com.joyent:ds_ss_quota", "dataset_quotas",
+	    "Dataset and snapshot quotas.", B_TRUE, B_FALSE, NULL);
 }
--- old/usr/src/common/zfs/zfeature_common.h	Mon Oct 29 18:19:31 2012
+++ new/usr/src/common/zfs/zfeature_common.h	Mon Oct 29 18:19:31 2012
@@ -21,6 +21,7 @@
 
 /*
  * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2012 Joyent, Inc. All rights reserved.
  */
 
 #ifndef _ZFEATURE_COMMON_H
@@ -52,6 +53,7 @@
 enum spa_feature {
 	SPA_FEATURE_ASYNC_DESTROY,
 	SPA_FEATURE_EMPTY_BPOBJ,
+	SPA_FEATURE_DS_SS_QUOTA,
 	SPA_FEATURES
 } spa_feature_t;
 
--- old/usr/src/common/zfs/zfs_prop.c	Mon Oct 29 18:19:31 2012
+++ new/usr/src/common/zfs/zfs_prop.c	Mon Oct 29 18:19:31 2012
@@ -362,6 +362,12 @@
 	zprop_register_number(ZFS_PROP_REFRESERVATION, "refreservation", 0,
 	    PROP_DEFAULT, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME,
 	    "<size> | none", "REFRESERV");
+	zprop_register_number(ZFS_PROP_DATASET_QUOTA, "dataset_quota", 0,
+	    PROP_DEFAULT, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME,
+	    "<count> | none", "DSQUOTA");
+	zprop_register_number(ZFS_PROP_SNAPSHOT_QUOTA, "snapshot_quota", 0,
+	    PROP_DEFAULT, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME,
+	    "<count> | none", "SSQUOTA");
 
 	/* inherit number properties */
 	zprop_register_number(ZFS_PROP_RECORDSIZE, "recordsize",
--- old/usr/src/lib/libzfs/common/libzfs_dataset.c	Mon Oct 29 18:19:32 2012
+++ new/usr/src/lib/libzfs/common/libzfs_dataset.c	Mon Oct 29 18:19:32 2012
@@ -2193,6 +2193,8 @@
 	case ZFS_PROP_REFQUOTA:
 	case ZFS_PROP_RESERVATION:
 	case ZFS_PROP_REFRESERVATION:
+	case ZFS_PROP_DATASET_QUOTA:
+	case ZFS_PROP_SNAPSHOT_QUOTA:
 
 		if (get_numeric_property(zhp, prop, src, &source, &val) != 0)
 			return (-1);
--- old/usr/src/man/man1m/zfs.1m	Mon Oct 29 18:19:32 2012
+++ new/usr/src/man/man1m/zfs.1m	Mon Oct 29 18:19:32 2012
@@ -401,8 +401,9 @@
 A dataset can also be delegated to a non-global zone by using the \fBzonecfg\fR
 \fBadd dataset\fR subcommand. You cannot delegate a dataset to one zone and the
 children of the same dataset to another zone. The zone administrator can change
-properties of the dataset or any of its children. However, the \fBquota\fR
-property is controlled by the global administrator.
+properties of the dataset or any of its children. However, the \fBquota\fR,
+\fBdataset_quota\fR and \fBsnapshot_quota\fR properties are controlled by the
+global administrator.
 .sp
 .LP
 A \fBZFS\fR volume can be added as a device to a non-global zone by using the
@@ -946,6 +947,21 @@
 .sp
 .ne 2
 .na
+\fB\fBdataset_quota\fR=\fIcount\fR | \fBnone\fR\fR
+.ad
+.sp .6
+.RS 4n
+Limits the number of datasets that can be created within a dataset and its
+descendents. This property enforces a hard limit on the number of datasets
+below the given dataset.  Setting a dataset_quota on a descendent of a dataset
+that already has a dataset_quota does not override the ancestor's dataset_quota,
+but rather imposes an additional limit. This feature must be enabled to be
+used (see \fBzpool-features\fR(5)).
+.RE
+
+.sp
+.ne 2
+.na
 \fB\fBdevices\fR=\fBon\fR | \fBoff\fR\fR
 .ad
 .sp .6
@@ -1031,6 +1047,24 @@
 .sp
 .ne 2
 .na
+\fB\fBsnapshot_quota\fR=\fIcount\fR | \fBnone\fR\fR
+.ad
+.sp .6
+.RS 4n
+Limits the number of snapshots that can be created on a dataset and its
+descendents. This property enforces a hard limit on the number of snapshots
+below the given dataset.  Setting a snapshot_quota on a descendent of a dataset
+that already has a snapshot_quota does not override the ancestor's
+snapshot_quota, but rather imposes an additional limit. The quota is not
+enforced for recursive snapshots taken from above the dataset with the quota,
+but that snapshot will be counted against the quota if the dataset is
+snapshotted directly.  This feature must be enabled to be used (see
+\fBzpool-features\fR(5)).
+.RE
+
+.sp
+.ne 2
+.na
 \fB\fBuserquota@\fR\fIuser\fR=\fIsize\fR | \fBnone\fR\fR
 .ad
 .sp .6
@@ -3125,6 +3159,7 @@
 checksum         property
 compression      property
 copies           property
+dataset_quota    property
 devices          property
 exec             property
 mountpoint       property
@@ -3143,6 +3178,7 @@
 sharenfs         property
 sharesmb         property
 snapdir          property
+snapshot_quota   property
 utf8only         property
 version          property
 volblocksize     property
--- old/usr/src/man/man5/zpool-features.5	Mon Oct 29 18:19:32 2012
+++ new/usr/src/man/man5/zpool-features.5	Mon Oct 29 18:19:32 2012
@@ -1,5 +1,6 @@
 '\" te
 .\" Copyright (c) 2012 by Delphix. All rights reserved.
+.\" Copyright (c) 2012 Joyent, Inc. All rights reserved.
 .\" The contents of this file are subject to the terms of the Common Development
 .\" and Distribution License (the "License").  You may not use this file except
 .\" in compliance with the License. You can obtain a copy of the license at
@@ -12,7 +13,7 @@
 .\" CDDL HEADER, with the fields enclosed by brackets "[]" replaced with your
 .\" own identifying information:
 .\" Portions Copyright [yyyy] [name of copyright owner]
-.TH ZPOOL-FEATURES 5 "Mar 16, 2012"
+.TH ZPOOL-FEATURES 5 "Oct 24, 2012"
 .SH NAME
 zpool\-features \- ZFS pool feature descriptions
 .SH DESCRIPTION
@@ -170,6 +171,27 @@
 This feature is only \fBactive\fR while \fBfreeing\fR is non\-zero.
 .RE
 
+.sp
+.ne 2
+.na
+\fB\fBdataset_quota\fR\fR
+.ad
+.RS 4n
+.TS
+l l .
+GUID	com.joyent:dataset_quota
+READ\-ONLY COMPATIBLE	yes
+DEPENDENCIES	none
+.TE
+
+This feature enables dataset quotas and snapshot quotas. These quotas
+can be used to limit how many datasets and/or snapshots can be created
+under the dataset on which the quotas are set.
+
+This feature is \fBactive\fR once either of the quota properties has been
+set on a dataset.
+.RE
+
 .sp
 .ne 2
 .na
--- old/usr/src/uts/common/fs/zfs/dmu_objset.c	Mon Oct 29 18:19:32 2012
+++ new/usr/src/uts/common/fs/zfs/dmu_objset.c	Mon Oct 29 18:19:32 2012
@@ -21,6 +21,7 @@
 /*
  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2012, Joyent, Inc. All rights reserved.
  */
 
 /* Portions Copyright 2010 Robert Milkowski */
@@ -690,7 +691,7 @@
 			return (EINVAL);
 	}
 
-	return (0);
+	return (dsl_dir_dscount_check(dd, tx, 1, NULL));
 }
 
 static void
@@ -705,6 +706,8 @@
 
 	ASSERT(dmu_tx_is_syncing(tx));
 
+	dsl_dir_dscount_adjust(dd, tx, 1, B_TRUE, B_TRUE);
+
 	obj = dsl_dataset_create_sync(dd, oa->lastname,
 	    oa->clone_origin, oa->flags, oa->cr, tx);
 
@@ -807,6 +810,7 @@
 	dsl_sync_task_group_t *saa_dstg;
 	boolean_t saa_needsuspend;
 	nvlist_t *saa_props;
+	uint64_t saa_tot_cnt;
 
 	/* the following are used only if 'temporary' is set: */
 	boolean_t saa_temporary;
@@ -831,8 +835,44 @@
 
 	/* The props have already been checked by zfs_check_userprops(). */
 
+	/*
+	 * The saa_tot_cnt is used to track how many snapshots there are going
+	 * to be at the highest level of the snapshot tree. This is necessary
+	 * because the counts are not actually adjusted when we are checking,
+	 * only when we finally sync. For a single snapshot, this is easy, the
+	 * count is 1, but it gets more complicated for recursive snapshots.
+	 *
+	 * We only enforce the snapshot quota at the level where the snapshot
+	 * is being taken. This is to prevent datasets with a full snapshot
+	 * count at a lower level from blocking recursive snapshots being taken
+	 * at a higher level. For example, the quota is only enforced on 'a'
+	 * and 'b' when taking a recursive snapshot of a/b@x with the following
+	 * existing state:
+	 *	a/b 	(0 snaps, snap quota is 5)
+	 *	a/b/c	(0 snaps, snap quota is none)
+	 *	a/b/d	(1 snaps, snap quota is 1)
+	 * A recursive snapshot of a/b will be allowed since it results in
+	 * 3 new snapshots (a/b@x, a/b/c@x, a/b/d@x), even though a/b/d already
+	 * has 1 snapshot and has hit its quota (note that the existing
+	 * snapshot on a/b/d is being counted against the quota on a/b). When
+	 * the snapshot completes, a/b will have a snapshot count of 4 and
+	 * a/b/d will have a count of 2. As can be seen, this means that
+	 * datasets can have a snapshot count > their quota.
+	 *
+	 * In order to properly handle recursive snapshots, we increment the
+	 * total count in open context, but this count is not validated in open
+	 * context. This gives us the maximum count to validate at the
+	 * top-level dataset when we're in syncing context. We then use a count
+	 * of 0 in syncing conext as we descend the tree past the top-level
+	 * snapshot so that lower levels are not being validated against their
+	 * quota.
+	 */
+	if (!dmu_tx_is_syncing(tx))
+		saa->saa_tot_cnt++;
 	error = dsl_dataset_snapshot_check(os->os_dsl_dataset,
-	    soa->soa_snapname, tx);
+	    soa->soa_snapname, saa->saa_tot_cnt, tx);
+	if (dmu_tx_is_syncing(tx))
+		saa->saa_tot_cnt = 0;
 	if (error)
 		return (error);
 
--- old/usr/src/uts/common/fs/zfs/dmu_send.c	Mon Oct 29 18:19:33 2012
+++ new/usr/src/uts/common/fs/zfs/dmu_send.c	Mon Oct 29 18:19:33 2012
@@ -51,6 +51,7 @@
 int zfs_send_corrupt_data = B_FALSE;
 
 static char *dmu_recv_tag = "dmu_recv_tag";
+char *tmp_dmu_recv_tag = "tmp_dmu_recv_tag";
 
 static int
 dump_bytes(dmu_sendarg_t *dsp, void *buf, int len)
@@ -643,6 +644,22 @@
 			return (ENODEV);
 	}
 
+	/*
+	 * Check dataset and snapshot quotas before receiving. We'll recheck
+	 * again at the end, but might as well abort before receiving if we're
+	 * already over quota.
+	 */
+	if (dd->dd_parent != NULL) {
+		err = dsl_dir_dscount_check(dd->dd_parent, NULL, 1, NULL);
+		if (err != 0)
+			return (err);
+	}
+
+	err = dsl_snapcount_check(dd, tx, 1, NULL);
+	if (err != 0)
+		return (err);
+
+
 	return (0);
 }
 
@@ -725,6 +742,11 @@
 		/* if full, most recent snapshot must be $ORIGIN */
 		if (ds->ds_phys->ds_prev_snap_txg >= TXG_INITIAL)
 			return (ENODEV);
+
+		/* Check snapshot quota before receiving */
+		err = dsl_snapcount_check(ds->ds_dir, tx, 1, NULL);
+		if (err != 0)
+			return (err);
 	}
 
 	/* temporary clone name must not exist */
@@ -1547,6 +1569,7 @@
 	char *tosnap;
 	uint64_t creation_time;
 	uint64_t toguid;
+	boolean_t is_new;
 };
 
 static int
@@ -1555,7 +1578,21 @@
 	dsl_dataset_t *ds = arg1;
 	struct recvendsyncarg *resa = arg2;
 
-	return (dsl_dataset_snapshot_check(ds, resa->tosnap, tx));
+	if (resa->is_new) {
+		/* re-check the dataset quota now that recv is complete */
+		dsl_dir_t *dd;
+		int err;
+
+		dd = ds->ds_dir;
+		if (dd->dd_parent != NULL) {
+			err = dsl_dir_dscount_check(dd->dd_parent, NULL, 1,
+			    NULL);
+			if (err != 0)
+				return (err);
+		}
+	}
+
+	return (dsl_dataset_snapshot_check(ds, resa->tosnap, 1, tx));
 }
 
 static void
@@ -1564,6 +1601,11 @@
 	dsl_dataset_t *ds = arg1;
 	struct recvendsyncarg *resa = arg2;
 
+	if (resa->is_new)
+		/* update the dataset counts */
+		dsl_dir_dscount_adjust(ds->ds_dir->dd_parent, tx, 1, B_FALSE,
+		    B_TRUE);
+
 	dsl_dataset_snapshot_sync(ds, resa->tosnap, tx);
 
 	/* set snapshot's creation time and guid */
@@ -1616,7 +1658,8 @@
 	} else {
 		mutex_exit(&ds->ds_recvlock);
 		dsl_dataset_rele(ds, dmu_recv_tag);
-		(void) dsl_dataset_destroy(drc->drc_real_ds, dmu_recv_tag,
+		/* tag indicates temporary ds to dsl_dir_destroy_sync */
+		(void) dsl_dataset_destroy(drc->drc_real_ds, tmp_dmu_recv_tag,
 		    B_FALSE);
 		return (EBUSY);
 	}
@@ -1624,6 +1667,7 @@
 	resa.creation_time = drc->drc_drrb->drr_creation_time;
 	resa.toguid = drc->drc_drrb->drr_toguid;
 	resa.tosnap = drc->drc_tosnap;
+	resa.is_new = B_FALSE;
 
 	err = dsl_sync_task_do(ds->ds_dir->dd_pool,
 	    recv_end_check, recv_end_sync, ds, &resa, 3);
@@ -1637,7 +1681,9 @@
 	if (err == 0 && drc->drc_guid_to_ds_map != NULL)
 		(void) add_ds_to_guidmap(drc->drc_guid_to_ds_map, ds);
 	dsl_dataset_disown(ds, dmu_recv_tag);
-	myerr = dsl_dataset_destroy(drc->drc_real_ds, dmu_recv_tag, B_FALSE);
+	/* tag indicates temporary ds to dsl_dir_destroy_sync */
+	myerr = dsl_dataset_destroy(drc->drc_real_ds, tmp_dmu_recv_tag,
+	    B_FALSE);
 	ASSERT0(myerr);
 	return (err);
 }
@@ -1659,6 +1705,7 @@
 	resa.creation_time = drc->drc_drrb->drr_creation_time;
 	resa.toguid = drc->drc_drrb->drr_toguid;
 	resa.tosnap = drc->drc_tosnap;
+	resa.is_new = B_TRUE;
 
 	err = dsl_sync_task_do(ds->ds_dir->dd_pool,
 	    recv_end_check, recv_end_sync, ds, &resa, 3);
--- old/usr/src/uts/common/fs/zfs/dsl_dataset.c	Mon Oct 29 18:19:33 2012
+++ new/usr/src/uts/common/fs/zfs/dsl_dataset.c	Mon Oct 29 18:19:33 2012
@@ -348,6 +348,10 @@
 	err = zap_remove_norm(mos, snapobj, name, mt, tx);
 	if (err == ENOTSUP && mt == MT_FIRST)
 		err = zap_remove(mos, snapobj, name, tx);
+
+	if (err == 0)
+		dsl_snapcount_adjust(ds->ds_dir, tx, -1, B_TRUE);
+
 	return (err);
 }
 
@@ -1136,7 +1140,7 @@
 		dsl_sync_task_create(dstg, dsl_dataset_destroy_check,
 		    dsl_dataset_destroy_sync, &dsda, tag, 0);
 		dsl_sync_task_create(dstg, dsl_dir_destroy_check,
-		    dsl_dir_destroy_sync, dd, FTAG, 0);
+		    dsl_dir_destroy_sync, dd, tag, 0);
 		err = dsl_sync_task_group_wait(dstg);
 		dsl_sync_task_group_destroy(dstg);
 
@@ -2012,9 +2016,142 @@
 	return (0);
 }
 
+/*
+ * Check if adding additional snapshot(s) would exceed any snapshot quotas.
+ * Note that all snapshot quotas up to the root dataset (i.e. the pool itself)
+ * or the given ancestor must be satisfied. Note that it is valid for the
+ * count to exceed the quota. This can happen if a recursive snapshot is taken
+ * from a dataset above this one.
+ */
 int
+dsl_snapcount_check(dsl_dir_t *dd, dmu_tx_t *tx, uint64_t cnt,
+    dsl_dir_t *ancestor)
+{
+	uint64_t quota;
+	int err = 0;
+
+	/*
+	 * As with dsl_dataset_set_reservation_check(), don't run this check in
+	 * open context.
+	 */
+	if (!dmu_tx_is_syncing(tx))
+		return (0);
+
+	/*
+	 * If renaming a dataset with no snapshots, count adjustment is 0.
+	 * Likewise when taking a recursive snapshot below the top-level (see
+	 * the comment in snapshot_check() for more details).
+	 */
+	if (cnt == 0)
+		return (0);
+
+	/*
+	 * If an ancestor has been provided, stop checking the quota once we
+	 * hit that dir. We need this during rename so that we don't overcount
+	 * the check once we recurse up to the common ancestor.
+	 */
+	if (ancestor == dd)
+		return (0);
+
+	/*
+	 * If there's no value for this property, there's no need to enforce a
+	 * snapshot quota.
+	 */
+	err = dsl_prop_get_dd(dd, zfs_prop_to_name(ZFS_PROP_SNAPSHOT_QUOTA),
+	    8, 1, &quota, NULL, B_FALSE);
+	if (err == ENOENT)
+		return (0);
+	else if (err != 0)
+		return (err);
+
+#ifdef _KERNEL
+	extern void __dtrace_probe_zfs__ss__quota(uint64_t, uint64_t, char *);
+	__dtrace_probe_zfs__ss__quota(
+	    (uint64_t)dd->dd_phys->dd_snapshot_count, (uint64_t)quota,
+	    dd->dd_myname);
+#endif
+
+	if (quota > 0 && (dd->dd_phys->dd_snapshot_count + cnt) > quota)
+		return (EDQUOT);
+
+	if (dd->dd_parent != NULL)
+		err = dsl_snapcount_check(dd->dd_parent, tx, cnt, ancestor);
+
+	return (err);
+}
+
+/*
+ * Adjust the snapshot count for the specified dsl_dir_t and all parents.
+ * When a new snapshot is created, increment the count on all parents, and when
+ * a snapshot is destroyed, decrement the count.
+ */
+void
+dsl_snapcount_adjust(dsl_dir_t *dd, dmu_tx_t *tx, int64_t delta,
+    boolean_t first)
+{
+	/*
+	 * On initial entry we need to check if this feature is active, but
+	 * we don't want to re-check this on each recursive call. Note: the
+	 * feature cannot be active if its not enabled. If the feature is not
+	 * active, don't touch the on-disk count fields.
+	 */
+	if (first) {
+		dsl_dataset_t *ds = NULL;
+		spa_t *spa;
+		zfeature_info_t *quota_feat =
+		    &spa_feature_table[SPA_FEATURE_DS_SS_QUOTA];
+
+		VERIFY(0 == dsl_dataset_hold_obj(dd->dd_pool,
+		    dd->dd_phys->dd_head_dataset_obj, FTAG, &ds));
+		spa = dsl_dataset_get_spa(ds);
+		dsl_dataset_rele(ds, FTAG);
+		if (!spa_feature_is_active(spa, quota_feat))
+			return;
+	}
+
+	/*
+	 * As with dsl_dataset_set_reservation_check(), wdon't want to run
+	 * this check in open context.
+	 */
+	if (!dmu_tx_is_syncing(tx))
+		return;
+
+	/* if renaming a dataset with no snapshots, count adjustment is 0 */
+	if (delta == 0)
+		return;
+
+	/* Increment count for parent */
+	dmu_buf_will_dirty(dd->dd_dbuf, tx);
+
+	mutex_enter(&dd->dd_lock);
+
+	/*
+	 * Counts may be incorrect if dealing with an existing pool and
+	 * there has never been a quota set in the dataset hierarchy.
+	 * This is not an error.
+	 */
+	if (delta < 0 && dd->dd_phys->dd_snapshot_count < (delta * -1)) {
+#ifdef _KERNEL
+		extern void __dtrace_probe_zfs__sscnt__adj__neg(char *);
+		__dtrace_probe_zfs__sscnt__adj__neg(dd->dd_myname);
+#endif
+		mutex_exit(&dd->dd_lock);
+		return;
+	}
+
+	dd->dd_phys->dd_snapshot_count += delta;
+
+	/* Roll up this additional count into our ancestors */
+
+	if (dd->dd_parent != NULL)
+		dsl_snapcount_adjust(dd->dd_parent, tx, delta, B_FALSE);
+
+	mutex_exit(&dd->dd_lock);
+}
+
+int
 dsl_dataset_snapshot_check(dsl_dataset_t *ds, const char *snapname,
-    dmu_tx_t *tx)
+    uint64_t cnt, dmu_tx_t *tx)
 {
 	int err;
 	uint64_t value;
@@ -2042,6 +2179,10 @@
 	if (dsl_dataset_namelen(ds) + 1 + strlen(snapname) >= MAXNAMELEN)
 		return (ENAMETOOLONG);
 
+	err = dsl_snapcount_check(ds->ds_dir, tx, cnt, NULL);
+	if (err)
+		return (err);
+
 	err = dsl_dataset_snapshot_reserve_space(ds, tx);
 	if (err)
 		return (err);
@@ -2063,6 +2204,8 @@
 
 	ASSERT(RW_WRITE_HELD(&dp->dp_config_rwlock));
 
+	dsl_snapcount_adjust(ds->ds_dir, tx, 1, B_TRUE);
+
 	/*
 	 * The origin's ds_creation_txg has to be < TXG_INITIAL
 	 */
@@ -2718,9 +2861,9 @@
 		pa->uncomp -= pa->origin_origin->ds_phys->ds_uncompressed_bytes;
 	}
 
-	/* Check that there is enough space here */
+	/* Check that there is enough space and quota headroom here */
 	err = dsl_dir_transfer_possible(origin_ds->ds_dir, hds->ds_dir,
-	    pa->used);
+	    origin_ds->ds_dir, pa->used, tx);
 	if (err)
 		return (err);
 
@@ -2853,6 +2996,7 @@
 		VERIFY(0 == zap_add(dp->dp_meta_objset,
 		    hds->ds_phys->ds_snapnames_zapobj, ds->ds_snapname,
 		    8, 1, &ds->ds_object, tx));
+		dsl_snapcount_adjust(hds->ds_dir, tx, 1, B_TRUE);
 
 		/* change containing dsl_dir */
 		dmu_buf_will_dirty(ds->ds_dbuf, tx);
--- old/usr/src/uts/common/fs/zfs/dsl_dir.c	Mon Oct 29 18:19:33 2012
+++ new/usr/src/uts/common/fs/zfs/dsl_dir.c	Mon Oct 29 18:19:33 2012
@@ -21,6 +21,7 @@
 /*
  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2012 Joyent, Inc. All rights reserved.
  */
 
 #include <sys/dmu.h>
@@ -38,12 +39,73 @@
 #include <sys/arc.h>
 #include <sys/sunddi.h>
 #include <sys/zfs_zone.h>
+#include <sys/zfeature.h>
 #include "zfs_namecheck.h"
 
+/*
+ * Dataset and Snapshot Quotas
+ * ---------------------------
+ *
+ * These quotas are used to limit the number of datasets and/or snapshots
+ * that can be created at a given level in the tree or below. A common use-case
+ * is with a delegated dataset where the administrator wants to ensure that
+ * a user within the zone is not creating too many datasets or snapshots, even
+ * though they're not exceeding their space quota.
+ *
+ * The count of datasets and snapshots is stored in the dsl_dir_phys_t which
+ * impacts the on-disk format. As such, this capability is controlled by a
+ * feature flag and must be enabled to be used. Once enabled, the feature is
+ * not active until the first quota is set. At that point, future operations to
+ * create/destroy datasets or snapshots will validate and update the counts.
+ *
+ * Because the on-disk counts will be incorrect (garbage) before the feature is
+ * active, the counts are updated when the quota is first set. Starting at the
+ * dataset with the new quota, the code descends into all sub-datasets and
+ * updates the counts to be accurate. In practice this is lightweight since
+ * a quota is typically set when the dataset is created and thus has no
+ * children. Once set, changing the quota value won't require a traversal since
+ * the counts are already valid. The counts in datasets above the one with the
+ * new quota will still be incorrect, unless a quota is eventually set on one
+ * of those datasets. If a dataset with a quota is encountered during the
+ * descent, the counts are known to be valid and there is no need to descend
+ * into that dataset's children. When a new quota value is set on a dataset
+ * with an existing quota, the new value must not be less than the current
+ * count at that level or an error is returned and the quota is not changed.
+ *
+ * Once the feature is active, then whenever a dataset or snapshot is created,
+ * the code recurses up the tree, validating the new count against the quota
+ * at each level. In practice, most levels will not have a quota set. If there
+ * is a quota at any level up the tree, the check must pass or the creation
+ * will fail. Likewise, when a dataset or snapshot is destroyed, the counts
+ * are recursively adjusted all the way up the tree. Renaming a dataset into
+ * different point in the tree will first validate, then update the counts on
+ * each branch up to the common ancestor. A receive will also validate the
+ * counts and then update them.
+ *
+ * Recursive snapshots behave a bit differently. The quota is only validated
+ * against the top-level dataset at which the snapshot is being taken. This
+ * is to prevent a denial-of-service in which a lower level dataset could
+ * max out its quota and thus block snapshots from being taken at a higher
+ * level (in addition, the complexity to address this is not worth the cost).
+ * Because of this, it is possible for the snapshot count to be over the quota
+ * and snapshots taken at a high level could cause a lower level dataset to hit
+ * or exceed its quota. The administrator taking the high-level recursive
+ * snapshot should be aware of this side-effect and behave accordingly.
+ *
+ * The dataset quota is validated by dsl_dir_dscount_check() and updated by
+ * dsl_dir_dscount_adjust(). The snapshot quota is validated by
+ * dsl_snapcount_check() and updated by dsl_snapcount_adjust().
+ * A new quota value is validated in dsl_dir_validate_ds_ss_quota() and the
+ * dataset counts are adjusted, if necessary, by dsl_dir_set_ds_ss_count().
+ */
+
 static uint64_t dsl_dir_space_towrite(dsl_dir_t *dd);
 static void dsl_dir_set_reservation_sync_impl(dsl_dir_t *dd,
     uint64_t value, dmu_tx_t *tx);
 
+extern dsl_syncfunc_t dsl_prop_set_sync;
+extern char *tmp_dmu_recv_tag;
+
 /* ARGSUSED */
 static void
 dsl_dir_evict(dmu_buf_t *db, void *arg)
@@ -407,6 +469,315 @@
 	return (dsl_dir_open_spa(NULL, name, tag, ddp, tailp));
 }
 
+/*
+ * Check if there is already a dataset/snapshot quota set for the dataset. If
+ * not, then the counts on this dataset, and those below, may be incorrect due
+ * to the use of a pre-existing pool which did not support the dataset/snapshot
+ * quota feature.
+ *
+ * Recursively descend the dataset tree and update the dataset/snapshot counts
+ * on each dataset below, then update the cumulative count on the current
+ * dataset. If the dataset already has a quota set on it, then we know that
+ * its counts, and the counts on the datasets below it, have been updated to
+ * be correct, so we can skip that dataset.
+ */
+static void
+dsl_dir_set_ds_ss_count(const char *nm, dsl_dir_t *dd, dmu_tx_t *tx,
+    uint64_t *dscnt, uint64_t *sscnt)
+{
+	uint64_t my_ds_cnt = 0;
+	uint64_t my_ss_cnt = 0;
+	objset_t *os = dd->dd_pool->dp_meta_objset;
+	zap_cursor_t *zc;
+	zap_attribute_t *za;
+	char *namebuf;
+	int err;
+	boolean_t quota_set = B_FALSE;
+	uint64_t dsquota, ssquota;
+	dsl_dataset_t *ds;
+
+	err = dsl_prop_get_dd(dd, zfs_prop_to_name(ZFS_PROP_DATASET_QUOTA),
+	    8, 1, &dsquota, NULL, B_FALSE);
+	if (err == 0 && dsquota != 0)
+		quota_set = B_TRUE;
+
+	if (!quota_set) {
+		err = dsl_prop_get_dd(dd,
+		    zfs_prop_to_name(ZFS_PROP_SNAPSHOT_QUOTA), 8, 1, &ssquota,
+		    NULL, B_FALSE);
+		if (err == 0 && ssquota != 0)
+			quota_set = B_TRUE;
+	}
+
+	/*
+	 * If the dd has a quota, we know its count is already good and we
+	 * don't need to recurse down any further.
+	 */
+	if (quota_set) {
+		/* Return dataset count plus 1 for self */
+		*dscnt = dd->dd_phys->dd_dataset_count + 1;
+		*sscnt = dd->dd_phys->dd_snapshot_count;
+
+		return;
+	}
+
+	zc = kmem_alloc(sizeof (zap_cursor_t), KM_SLEEP);
+	za = kmem_alloc(sizeof (zap_attribute_t), KM_SLEEP);
+	namebuf = kmem_alloc(MAXPATHLEN, KM_SLEEP);
+
+	mutex_enter(&dd->dd_lock);
+
+	/* Iterate datasets */
+	for (zap_cursor_init(zc, os, dd->dd_phys->dd_child_dir_zapobj);
+	    zap_cursor_retrieve(zc, za) == 0;
+	    zap_cursor_advance(zc)) {
+		dsl_dir_t *chld_dd;
+		uint64_t chld_ds_cnt = 0;
+		uint64_t chld_ss_cnt = 0;
+
+		(void) snprintf(namebuf, MAXPATHLEN, "%s/%s", nm, za->za_name);
+
+		if (dsl_dir_open(namebuf, FTAG, &chld_dd, NULL))
+			continue;
+
+		dsl_dir_set_ds_ss_count(namebuf, chld_dd, tx, &chld_ds_cnt,
+		    &chld_ss_cnt);
+
+		dsl_dir_close(chld_dd, FTAG);
+
+		my_ds_cnt += chld_ds_cnt;
+		my_ss_cnt += chld_ss_cnt;
+	}
+	zap_cursor_fini(zc);
+
+	kmem_free(namebuf, MAXPATHLEN);
+
+	/* Iterate snapshots */
+	if (dsl_dataset_hold(nm, FTAG, &ds) == 0) {
+		for (zap_cursor_init(zc, os, ds->ds_phys->ds_snapnames_zapobj);
+		    zap_cursor_retrieve(zc, za) == 0;
+		    zap_cursor_advance(zc)) {
+			my_ss_cnt++;
+		}
+		zap_cursor_fini(zc);
+		dsl_dataset_rele(ds, FTAG);
+	}
+
+	kmem_free(zc, sizeof (zap_cursor_t));
+	kmem_free(za, sizeof (zap_attribute_t));
+
+#ifdef _KERNEL
+	extern void __dtrace_probe_zfs__ds__fix__count(char *, uint64_t,
+	    uint64_t);
+	__dtrace_probe_zfs__ds__fix__count((char *)nm, my_ds_cnt, my_ss_cnt);
+#endif
+
+	/* save updated counts */
+	dmu_buf_will_dirty(dd->dd_dbuf, tx);
+	dd->dd_phys->dd_dataset_count = my_ds_cnt;
+	dd->dd_phys->dd_snapshot_count = my_ss_cnt;
+
+	mutex_exit(&dd->dd_lock);
+
+	/* Return child dataset count plus 1 for self */
+	*dscnt = my_ds_cnt + 1;
+	*sscnt = my_ss_cnt;
+}
+
+/*
+ * Return ENOSPC if new quota is less than the existing count, otherwise return
+ * -1 to force the zfs_set_prop_nvlist code down the default path to set the
+ * value in the nvlist.
+ */
+int
+dsl_dir_validate_ds_ss_quota(const char *ddname, uint64_t quota,
+    zfs_prop_t ptype)
+{
+	dsl_dir_t *dd;
+	dsl_dataset_t *ds;
+	int err = -1;
+	uint64_t count;
+	dmu_tx_t *tx;
+	uint64_t my_ds_cnt = 0;
+	uint64_t my_ss_cnt = 0;
+	spa_t *spa;
+	zfeature_info_t *quota_feat =
+	    &spa_feature_table[SPA_FEATURE_DS_SS_QUOTA];
+
+	if (dsl_dataset_hold(ddname, FTAG, &ds))
+		return (EACCES);
+
+	spa = dsl_dataset_get_spa(ds);
+	if (!spa_feature_is_enabled(spa,
+	    &spa_feature_table[SPA_FEATURE_DS_SS_QUOTA])) {
+		dsl_dataset_rele(ds, FTAG);
+		return (ENOTSUP);
+	}
+
+	/* 0 means no quota */
+	if (quota == 0) {
+		dsl_dataset_rele(ds, FTAG);
+		return (-1);
+	}
+
+	if (dsl_dir_open(ddname, FTAG, &dd, NULL)) {
+		dsl_dataset_rele(ds, FTAG);
+		return (EACCES);
+	}
+
+	ASSERT(ds->ds_dir == dd);
+
+	tx = dmu_tx_create_dd(dd);
+	if (dmu_tx_assign(tx, TXG_WAIT)) {
+		dmu_tx_abort(tx);
+		return (ENOSPC);
+	}
+
+	/* set the feature active flag now */
+	if (!spa_feature_is_active(spa, quota_feat))
+		spa_feature_incr(spa, quota_feat, tx);
+
+	/*
+	 * Since we are now setting a non-0 quota on the dataset, we need to
+	 * ensure the counts are correct. Descend down the tree from this
+	 * point and update all of the counts to be accurate.
+	 */
+	rw_enter(&dd->dd_pool->dp_config_rwlock, RW_READER);
+	dsl_dir_set_ds_ss_count(ddname, dd, tx, &my_ds_cnt, &my_ss_cnt);
+	rw_exit(&dd->dd_pool->dp_config_rwlock);
+
+	dmu_tx_commit(tx);
+
+	if (ptype == ZFS_PROP_DATASET_QUOTA)
+		count = dd->dd_phys->dd_dataset_count;
+	else
+		count = dd->dd_phys->dd_snapshot_count;
+
+	if (quota < count)
+		err = ENOSPC;
+
+	dsl_dir_close(dd, FTAG);
+	dsl_dataset_rele(ds, FTAG);
+
+	return (err);
+}
+
+/*
+ * Check if adding additional child dataset(s) would exceed any dataset
+ * quotas.  Note that all dataset quotas up to the root dataset (i.e. the pool
+ * itself) or the given ancestor must be satisfied. When receiving we don't
+ * check if the tx is syncing. In this case, the tx is passed as NULL.
+ */
+int
+dsl_dir_dscount_check(dsl_dir_t *dd, dmu_tx_t *tx, uint64_t cnt,
+    dsl_dir_t *ancestor)
+{
+	uint64_t quota;
+	int err = 0;
+
+	VERIFY(RW_LOCK_HELD(&dd->dd_pool->dp_config_rwlock));
+
+	/*
+	 * As with dsl_dataset_set_reservation_check(), don't run this check in
+	 * open context.
+	 */
+	if (tx != NULL && !dmu_tx_is_syncing(tx))
+		return (0);
+
+	/*
+	 * If an ancestor has been provided, stop checking the quota once we
+	 * hit that dir. We need this during rename so that we don't overcount
+	 * the check once we recurse up to the common ancestor.
+	 */
+	if (ancestor == dd)
+		return (0);
+
+	/*
+	 * If there's no value for this property, there's no need to enforce a
+	 * dataset quota.
+	 */
+	err = dsl_prop_get_dd(dd, zfs_prop_to_name(ZFS_PROP_DATASET_QUOTA),
+	    8, 1, &quota, NULL, B_FALSE);
+	if (err == ENOENT)
+		return (0);
+	else if (err != 0)
+		return (err);
+
+#ifdef _KERNEL
+	extern void __dtrace_probe_zfs__ds__quota(uint64_t, uint64_t, char *);
+	__dtrace_probe_zfs__ds__quota((uint64_t)dd->dd_phys->dd_dataset_count,
+	    (uint64_t)quota, dd->dd_myname);
+#endif
+
+	if (quota > 0 && (dd->dd_phys->dd_dataset_count + cnt) > quota)
+		return (EDQUOT);
+
+	if (dd->dd_parent != NULL)
+		err = dsl_dir_dscount_check(dd->dd_parent, tx, cnt, ancestor);
+
+	return (err);
+}
+
+/*
+ * Adjust the dataset count for the specified dsl_dir_t and all parent datasets.
+ * When a new dataset is created, increment the count on all parents, and when a
+ * dataset is destroyed, decrement the count.
+ */
+void
+dsl_dir_dscount_adjust(dsl_dir_t *dd, dmu_tx_t *tx, int64_t delta,
+    boolean_t syncing, boolean_t first)
+{
+	/*
+	 * On initial entry we need to check if this feature is active, but
+	 * we don't want to re-check this on each recursive call. Note: the
+	 * feature cannot be active if its not enabled. If the feature is not
+	 * active, don't touch the on-disk count fields.
+	 */
+	if (first) {
+		dsl_dataset_t *ds = NULL;
+		spa_t *spa;
+		zfeature_info_t *quota_feat =
+		    &spa_feature_table[SPA_FEATURE_DS_SS_QUOTA];
+
+		VERIFY(0 == dsl_dataset_hold_obj(dd->dd_pool,
+		    dd->dd_phys->dd_head_dataset_obj, FTAG, &ds));
+		spa = dsl_dataset_get_spa(ds);
+		dsl_dataset_rele(ds, FTAG);
+		if (!spa_feature_is_active(spa, quota_feat))
+			return;
+	}
+
+	VERIFY(RW_LOCK_HELD(&dd->dd_pool->dp_config_rwlock));
+	if (syncing)
+		VERIFY(dmu_tx_is_syncing(tx));
+
+	dmu_buf_will_dirty(dd->dd_dbuf, tx);
+
+	mutex_enter(&dd->dd_lock);
+
+	/*
+	 * Counts may be incorrect if dealing with an existing pool and
+	 * there has never been a quota set in the dataset hierarchy.
+	 * This is not an error.
+	 */
+	if (delta < 0 && dd->dd_phys->dd_dataset_count < (delta * -1)) {
+#ifdef _KERNEL
+		extern void __dtrace_probe_zfs__dscnt__adj__neg(char *);
+		__dtrace_probe_zfs__dscnt__adj__neg(dd->dd_myname);
+#endif
+		mutex_exit(&dd->dd_lock);
+		return;
+	}
+
+	dd->dd_phys->dd_dataset_count += delta;
+
+	if (dd->dd_parent != NULL)
+		dsl_dir_dscount_adjust(dd->dd_parent, tx, delta, syncing,
+		    B_FALSE);
+
+	mutex_exit(&dd->dd_lock);
+}
+
 uint64_t
 dsl_dir_create_sync(dsl_pool_t *dp, dsl_dir_t *pds, const char *name,
     dmu_tx_t *tx)
@@ -488,6 +859,20 @@
 	ASSERT(dd->dd_phys->dd_head_dataset_obj == 0);
 
 	/*
+	 * Decrement the dataset count for all parent datasets.
+	 *
+	 * We have to worry about a special case where we are receiving a
+	 * dataset that already exists. In this case a temporary clone name
+	 * of %X is created (see dmu_recv_begin). In dmu_recv_existing_end we
+	 * destroy this temporary clone which leads to here. We don't want to
+	 * decrement the dataset counters in this case, since we never
+	 * incremented them. To detect this case we check the tag for
+	 * "tmp_dmu_recv_tag" to see if we're in that code path.
+	 */
+	if (dd->dd_parent != NULL && strcmp(tag, tmp_dmu_recv_tag) != 0)
+		dsl_dir_dscount_adjust(dd->dd_parent, tx, -1, B_TRUE, B_TRUE);
+
+	/*
 	 * Remove our reservation. The impl() routine avoids setting the
 	 * actual property, which would require the (already destroyed) ds.
 	 */
@@ -1036,8 +1421,6 @@
 	return (err);
 }
 
-extern dsl_syncfunc_t dsl_prop_set_sync;
-
 static void
 dsl_dir_set_quota_sync(void *arg1, void *arg2, dmu_tx_t *tx)
 {
@@ -1278,7 +1661,7 @@
 			return (EINVAL);
 
 		if (err = dsl_dir_transfer_possible(dd->dd_parent,
-		    ra->newparent, myspace))
+		    ra->newparent, dd, myspace, tx))
 			return (err);
 	}
 
@@ -1303,6 +1686,20 @@
 	    "-> %s/%s", namebuf, ra->mynewname);
 
 	if (ra->newparent != dd->dd_parent) {
+		int cnt;
+
+		mutex_enter(&dd->dd_lock);
+
+		cnt = dd->dd_phys->dd_dataset_count + 1;
+		dsl_dir_dscount_adjust(dd->dd_parent, tx, -cnt, B_TRUE, B_TRUE);
+		dsl_dir_dscount_adjust(ra->newparent, tx, cnt, B_TRUE, B_TRUE);
+
+		cnt = dd->dd_phys->dd_snapshot_count;
+		dsl_snapcount_adjust(dd->dd_parent, tx, -cnt, B_TRUE);
+		dsl_snapcount_adjust(ra->newparent, tx, cnt, B_TRUE);
+
+		mutex_exit(&dd->dd_lock);
+
 		dsl_dir_diduse_space(dd->dd_parent, DD_USED_CHILD,
 		    -dd->dd_phys->dd_used_bytes,
 		    -dd->dd_phys->dd_compressed_bytes,
@@ -1375,11 +1772,13 @@
 }
 
 int
-dsl_dir_transfer_possible(dsl_dir_t *sdd, dsl_dir_t *tdd, uint64_t space)
+dsl_dir_transfer_possible(dsl_dir_t *sdd, dsl_dir_t *tdd, dsl_dir_t *moving_dd,
+    uint64_t space, dmu_tx_t *tx)
 {
 	dsl_dir_t *ancestor;
 	int64_t adelta;
 	uint64_t avail;
+	int err;
 
 	ancestor = closest_common_ancestor(sdd, tdd);
 	adelta = would_change(sdd, -space, ancestor);
@@ -1387,6 +1786,17 @@
 	if (avail < space)
 		return (ENOSPC);
 
+	if (sdd != moving_dd) {
+		err = dsl_dir_dscount_check(tdd, tx,
+		    moving_dd->dd_phys->dd_dataset_count + 1, ancestor);
+		if (err != 0)
+			return (err);
+	}
+	err = dsl_snapcount_check(tdd, tx,
+	    moving_dd->dd_phys->dd_snapshot_count, ancestor);
+	if (err != 0)
+		return (err);
+
 	return (0);
 }
 
--- old/usr/src/uts/common/fs/zfs/sys/dsl_dataset.h	Mon Oct 29 18:19:33 2012
+++ new/usr/src/uts/common/fs/zfs/sys/dsl_dataset.h	Mon Oct 29 18:19:33 2012
@@ -218,7 +218,8 @@
 dsl_checkfunc_t dsl_dataset_destroy_check;
 dsl_syncfunc_t dsl_dataset_destroy_sync;
 dsl_syncfunc_t dsl_dataset_user_hold_sync;
-int dsl_dataset_snapshot_check(dsl_dataset_t *ds, const char *, dmu_tx_t *tx);
+int dsl_dataset_snapshot_check(dsl_dataset_t *ds, const char *, uint64_t,
+    dmu_tx_t *tx);
 void dsl_dataset_snapshot_sync(dsl_dataset_t *ds, const char *, dmu_tx_t *tx);
 int dsl_dataset_rename(char *name, const char *newname, boolean_t recursive);
 int dsl_dataset_promote(const char *name, char *conflsnap);
--- old/usr/src/uts/common/fs/zfs/sys/dsl_dir.h	Mon Oct 29 18:19:34 2012
+++ new/usr/src/uts/common/fs/zfs/sys/dsl_dir.h	Mon Oct 29 18:19:34 2012
@@ -20,6 +20,7 @@
  */
 /*
  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, Joyent, Inc. All rights reserved.
  */
 
 #ifndef	_SYS_DSL_DIR_H
@@ -70,7 +71,11 @@
 	uint64_t dd_flags;
 	uint64_t dd_used_breakdown[DD_USED_NUM];
 	uint64_t dd_clones; /* dsl_dir objects */
-	uint64_t dd_pad[13]; /* pad out to 256 bytes for good measure */
+
+	uint64_t dd_dataset_count;
+	uint64_t dd_snapshot_count;
+
+	uint64_t dd_pad[11]; /* pad out to 256 bytes for good measure */
 } dsl_dir_phys_t;
 
 struct dsl_dir {
@@ -131,8 +136,15 @@
     uint64_t quota);
 int dsl_dir_set_reservation(const char *ddname, zprop_source_t source,
     uint64_t reservation);
+int dsl_dir_validate_ds_ss_quota(const char *, uint64_t, zfs_prop_t);
+int dsl_dir_dscount_check(dsl_dir_t *, dmu_tx_t *, uint64_t, dsl_dir_t *);
+void dsl_dir_dscount_adjust(dsl_dir_t *, dmu_tx_t *, int64_t, boolean_t,
+    boolean_t);
+int dsl_snapcount_check(dsl_dir_t *, dmu_tx_t *, uint64_t, dsl_dir_t *);
+void dsl_snapcount_adjust(dsl_dir_t *, dmu_tx_t *, int64_t, boolean_t);
 int dsl_dir_rename(dsl_dir_t *dd, const char *newname);
-int dsl_dir_transfer_possible(dsl_dir_t *sdd, dsl_dir_t *tdd, uint64_t space);
+int dsl_dir_transfer_possible(dsl_dir_t *sdd, dsl_dir_t *tdd,
+    dsl_dir_t *moving_dd, uint64_t space, dmu_tx_t *tx);
 int dsl_dir_set_reservation_check(void *arg1, void *arg2, dmu_tx_t *tx);
 boolean_t dsl_dir_is_clone(dsl_dir_t *dd);
 void dsl_dir_new_refreservation(dsl_dir_t *dd, struct dsl_dataset *ds,
--- old/usr/src/uts/common/fs/zfs/zfs_ioctl.c	Mon Oct 29 18:19:34 2012
+++ new/usr/src/uts/common/fs/zfs/zfs_ioctl.c	Mon Oct 29 18:19:34 2012
@@ -617,6 +617,8 @@
 		break;
 
 	case ZFS_PROP_QUOTA:
+	case ZFS_PROP_DATASET_QUOTA:
+	case ZFS_PROP_SNAPSHOT_QUOTA:
 		if (!INGLOBALZONE(curproc)) {
 			uint64_t zoned;
 			char setpoint[MAXNAMELEN];
@@ -2384,6 +2386,14 @@
 	case ZFS_PROP_REFQUOTA:
 		err = dsl_dataset_set_quota(dsname, source, intval);
 		break;
+	case ZFS_PROP_DATASET_QUOTA:
+		err = dsl_dir_validate_ds_ss_quota(dsname, intval,
+		    ZFS_PROP_DATASET_QUOTA);
+		break;
+	case ZFS_PROP_SNAPSHOT_QUOTA:
+		err = dsl_dir_validate_ds_ss_quota(dsname, intval,
+		    ZFS_PROP_SNAPSHOT_QUOTA);
+		break;
 	case ZFS_PROP_RESERVATION:
 		err = dsl_dir_set_reservation(dsname, source, intval);
 		break;
--- old/usr/src/uts/common/sys/fs/zfs.h	Mon Oct 29 18:19:34 2012
+++ new/usr/src/uts/common/sys/fs/zfs.h	Mon Oct 29 18:19:34 2012
@@ -138,6 +138,8 @@
 	ZFS_PROP_REFRATIO,
 	ZFS_PROP_WRITTEN,
 	ZFS_PROP_CLONES,
+	ZFS_PROP_DATASET_QUOTA,
+	ZFS_PROP_SNAPSHOT_QUOTA,
 	ZFS_NUM_PROPS
 } zfs_prop_t;
 
