--- old/usr/src/uts/common/fs/zfs/dsl_prop.c	Mon Aug  5 17:26:52 2019
+++ new/usr/src/uts/common/fs/zfs/dsl_prop.c	Mon Aug  5 17:26:52 2019
@@ -23,6 +23,7 @@
  * Copyright (c) 2012, 2015 by Delphix. All rights reserved.
  * Copyright (c) 2013 Martin Matuska. All rights reserved.
  * Copyright 2015, Joyent, Inc.
+ * Copyright 2016 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #include <sys/zfs_context.h>
@@ -33,15 +34,15 @@
 #include <sys/dsl_dir.h>
 #include <sys/dsl_prop.h>
 #include <sys/dsl_synctask.h>
+#include <sys/zfeature.h>
 #include <sys/spa.h>
 #include <sys/zap.h>
 #include <sys/fs/zfs.h>
+#include <sys/wbc.h>
 
 #include "zfs_prop.h"
+#include "zfs_errno.h"
 
-#define	ZPROP_INHERIT_SUFFIX "$inherit"
-#define	ZPROP_RECVD_SUFFIX "$recvd"
-
 static int
 dodefault(zfs_prop_t prop, int intsz, int numints, void *buf)
 {
@@ -287,7 +288,16 @@
 
 	ASSERT(dsl_pool_config_held(dp));
 
-	err = dsl_prop_get_int_ds(ds, propname, &value);
+	if (zfs_name_to_prop(propname) == ZFS_PROP_WBC_MODE) {
+		wbc_mode_prop_val_t val;
+
+		err = dsl_prop_get_ds(ds, propname, 8,
+		    WBC_MODE_PROP_VAL_SZ, &val, NULL);
+		if (err == 0)
+			value = (uintptr_t)((void *)&val);
+	} else
+		err = dsl_prop_get_int_ds(ds, propname, &value);
+
 	if (err != 0)
 		return (err);
 
@@ -477,6 +487,7 @@
 		for (cbr = list_head(&pr->pr_cbs); cbr;
 		    cbr = list_next(&pr->pr_cbs, cbr)) {
 			uint64_t value;
+			const char *propname;
 
 			/*
 			 * Callback entries do not have holds on their
@@ -504,10 +515,19 @@
 			    !dsl_dataset_try_add_ref(dp, cbr->cbr_ds, FTAG))
 				continue;
 
-			if (dsl_prop_get_ds(cbr->cbr_ds,
-			    cbr->cbr_pr->pr_propname, sizeof (value), 1,
-			    &value, NULL) == 0)
+			propname = cbr->cbr_pr->pr_propname;
+			if (zfs_name_to_prop(propname) == ZFS_PROP_WBC_MODE) {
+				wbc_mode_prop_val_t val;
+
+				if (dsl_prop_get_ds(cbr->cbr_ds, propname, 8,
+				    WBC_MODE_PROP_VAL_SZ, &val, NULL) == 0) {
+					value = (uintptr_t)((void *)&val);
+					cbr->cbr_func(cbr->cbr_arg, value);
+				}
+			} else if (dsl_prop_get_ds(cbr->cbr_ds, propname,
+			    sizeof (value), 1, &value, NULL) == 0) {
 				cbr->cbr_func(cbr->cbr_arg, value);
+			}
 
 			if (ds != cbr->cbr_ds)
 				dsl_dataset_rele(cbr->cbr_ds, FTAG);
@@ -620,7 +640,8 @@
 	char *recvdstr;
 	char *tbuf = NULL;
 	int err;
-	uint64_t version = spa_version(ds->ds_dir->dd_pool->dp_spa);
+	spa_t *spa = ds->ds_dir->dd_pool->dp_spa;
+	uint64_t version = spa_version(spa);
 
 	isint = (dodefault(zfs_name_to_prop(propname), 8, 1, &intval) == 0);
 
@@ -718,7 +739,47 @@
 	strfree(inheritstr);
 	strfree(recvdstr);
 
-	if (isint) {
+	if (zfs_name_to_prop(propname) == ZFS_PROP_WBC_MODE) {
+		wbc_mode_prop_val_t val;
+
+		VERIFY0(dsl_prop_get_ds(ds, propname, 8,
+		    WBC_MODE_PROP_VAL_SZ, &val, NULL));
+
+		dsl_prop_changed_notify(ds->ds_dir->dd_pool,
+		    ds->ds_dir->dd_object, propname,
+		    (uintptr_t)((void *)&val), TRUE);
+
+		/*
+		 * Flow diagram of ZFS_PROP_WBC_MODE states
+		 *
+		 * off (root_ds_object == 0, txg_off == 0)
+		 *	(user sees wbc_mode=off, source=default)
+		 *
+		 *	user operation "set on" ==>>
+		 *
+		 * on (root_ds_object != 0, txg_off == 0)
+		 *	(user sees wbc_mode=on, source=local)
+		 *
+		 *	user operation "set off" ==>>
+		 *
+		 * off_delayed (root_ds_object != 0, txg_off != 0)
+		 *	(user sees wbc_mode=off, source=local)
+		 *
+		 *	internal operation "inherit" ==>>
+		 *
+		 * off (root_ds_object == 0, txg_off == 0)
+		 *	(user sees wbc_mode=off, source=default)
+		 */
+		if (val.root_ds_object == 0)
+			spa_feature_decr(spa, SPA_FEATURE_WBC, tx);
+		else if (val.txg_off == 0)
+			spa_feature_incr(spa, SPA_FEATURE_WBC, tx);
+
+		(void) snprintf(valbuf, sizeof (valbuf),
+		    "%s", (val.root_ds_object != 0 &&
+		    val.txg_off == 0) ? "on" : "off");
+		valstr = valbuf;
+	} else if (isint) {
 		VERIFY0(dsl_prop_get_int_ds(ds, propname, &intval));
 
 		if (ds->ds_is_snapshot) {
@@ -802,6 +863,75 @@
 	return (error);
 }
 
+/* ARGSUSED */
+static int
+dsl_prop_wbc_mode_check_child_cb(dsl_pool_t *dp,
+    dsl_dataset_t *ds, void *arg)
+{
+	int err;
+	zfs_prop_t *prop = arg;
+	objset_t *os = NULL;
+
+	err = dmu_objset_from_ds(ds, &os);
+	if (err != 0)
+		return (err);
+
+	if (*prop == ZFS_PROP_DEDUP) {
+		/*
+		 * User tries to set ZFS_PROP_DEDUP.
+		 * In this case we just check that
+		 * the target DS and its children
+		 * do not use writecache
+		 */
+		if (os->os_wbc_mode != ZFS_WBC_MODE_OFF)
+			return (SET_ERROR(EKZFS_WBCCONFLICT));
+	} else {
+		ASSERT3U(*prop, ==, ZFS_PROP_WBC_MODE);
+
+		/*
+		 * User tries to set ZFS_PROP_WBC_MODE.
+		 * In this case we need check that
+		 * the target DS and its children
+		 * do not use writecache and dedup
+		 */
+		if (os->os_wbc_mode != ZFS_WBC_MODE_OFF)
+			return (SET_ERROR(EKZFS_WBCCHILD));
+
+		if (os->os_dedup_checksum != ZIO_CHECKSUM_OFF)
+			return (SET_ERROR(EKZFS_WBCCONFLICT));
+	}
+
+	return (0);
+}
+
+static int
+dsl_prop_wbc_mode_check(dsl_dataset_t *ds, objset_t *os)
+{
+	int err = 0;
+	zfs_prop_t prop = ZFS_PROP_WBC_MODE;
+
+	if (os->os_wbc_mode != ZFS_WBC_MODE_OFF) {
+		/*
+		 * ZFS_PROP_WBC_MODE cannot be set for
+		 * a child DS if the prop was set for
+		 * the parent
+		 */
+		if (os->os_wbc_root_ds_obj != ds->ds_object)
+			return (SET_ERROR(EKZFS_WBCPARENT));
+	} else {
+		/*
+		 * Is not allowed to change wbc_mode for parent
+		 * if its children already have the changed prop
+		 */
+		err = dmu_objset_find_dp(ds->ds_dir->dd_pool,
+		    ds->ds_dir->dd_object,
+		    dsl_prop_wbc_mode_check_child_cb, &prop,
+		    DS_FIND_CHILDREN);
+	}
+
+	return (err);
+}
+
 typedef struct dsl_props_set_arg {
 	const char *dpsa_dsname;
 	zprop_source_t dpsa_source;
@@ -814,6 +944,7 @@
 	dsl_props_set_arg_t *dpsa = arg;
 	dsl_pool_t *dp = dmu_tx_pool(tx);
 	dsl_dataset_t *ds;
+	objset_t *os = NULL;
 	uint64_t version;
 	nvpair_t *elem = NULL;
 	int err;
@@ -822,12 +953,97 @@
 	if (err != 0)
 		return (err);
 
+	err = dmu_objset_from_ds(ds, &os);
+	if (err != 0) {
+		dsl_dataset_rele(ds, FTAG);
+		return (err);
+	}
+
 	version = spa_version(ds->ds_dir->dd_pool->dp_spa);
 	while ((elem = nvlist_next_nvpair(dpsa->dpsa_props, elem)) != NULL) {
-		if (strlen(nvpair_name(elem)) >= ZAP_MAXNAMELEN) {
+		const char *prop_name = nvpair_name(elem);
+		zfs_prop_t prop = zfs_name_to_prop(prop_name);
+
+		if (strlen(prop_name) >= ZAP_MAXNAMELEN) {
 			dsl_dataset_rele(ds, FTAG);
 			return (SET_ERROR(ENAMETOOLONG));
 		}
+
+		/*
+		 * Deduplication and WBC cannot be used together
+		 * This code returns error also for case when
+		 * WBC is ON, DEDUP is off and a user tries
+		 * to do DEDUP=off, because in this case the code
+		 * will be more complex, but benefit is too small
+		 */
+		if (prop == ZFS_PROP_DEDUP) {
+			if (os->os_wbc_root_ds_obj != 0) {
+				dsl_dataset_rele(ds, FTAG);
+				return (SET_ERROR(EKZFS_WBCCONFLICT));
+			}
+
+			/*
+			 * Need to be sure that children DS
+			 * do not use writecache
+			 */
+			err = dmu_objset_find_dp(ds->ds_dir->dd_pool,
+			    ds->ds_dir->dd_object,
+			    dsl_prop_wbc_mode_check_child_cb, &prop,
+			    DS_FIND_CHILDREN);
+			if (err != 0) {
+				dsl_dataset_rele(ds, FTAG);
+				return (err);
+			}
+		}
+
+		if (prop == ZFS_PROP_WBC_MODE) {
+			uint64_t wbc_mode_new = 0;
+			data_type_t elem_type = nvpair_type(elem);
+
+			if (elem_type == DATA_TYPE_UINT64)
+				wbc_mode_new = fnvpair_value_uint64(elem);
+
+			if (os->os_wbc_root_ds_obj == ds->ds_object &&
+			    elem_type == DATA_TYPE_UINT64) {
+
+				/*
+				 * ZFS_WBC_MODE_OFF_DELAYED means that
+				 * the coresponding wbc_instance is in
+				 * transition state: from ON to OFF
+				 * so ON/OFF is not permitted
+				 */
+				if (os->os_wbc_mode ==
+				    ZFS_WBC_MODE_OFF_DELAYED) {
+					dsl_dataset_rele(ds, FTAG);
+					return (SET_ERROR(EINPROGRESS));
+				}
+
+				ASSERT3U(os->os_wbc_mode, ==, ZFS_WBC_MODE_ON);
+
+				/* Check for double ON */
+				if (wbc_mode_new == ZFS_WBC_MODE_ON) {
+					dsl_dataset_rele(ds, FTAG);
+					return (SET_ERROR(EALREADY));
+				}
+			}
+
+			err = dsl_prop_wbc_mode_check(ds, os);
+			if (err != 0) {
+				dsl_dataset_rele(ds, FTAG);
+				return (err);
+			}
+
+			/* Check for double OFF */
+			if (os->os_wbc_root_ds_obj == 0 &&
+			    elem_type == DATA_TYPE_UINT64 &&
+			    wbc_mode_new == ZFS_WBC_MODE_OFF) {
+				dsl_dataset_rele(ds, FTAG);
+				return (SET_ERROR(EALREADY));
+			}
+
+			continue;
+		}
+
 		if (nvpair_type(elem) == DATA_TYPE_STRING) {
 			char *valstr = fnvpair_value_string(elem);
 			if (strlen(valstr) >= (version <
@@ -834,9 +1050,10 @@
 			    SPA_VERSION_STMF_PROP ?
 			    ZAP_OLDMAXVALUELEN : ZAP_MAXVALUELEN)) {
 				dsl_dataset_rele(ds, FTAG);
-				return (E2BIG);
+				return (SET_ERROR(E2BIG));
 			}
 		}
+
 	}
 
 	if (ds->ds_is_snapshot && version < SPA_VERSION_SNAP_PROPS) {
@@ -855,15 +1072,11 @@
 
 	while ((elem = nvlist_next_nvpair(props, elem)) != NULL) {
 		nvpair_t *pair = elem;
-		const char *name = nvpair_name(pair);
 
 		if (nvpair_type(pair) == DATA_TYPE_NVLIST) {
 			/*
-			 * This usually happens when we reuse the nvlist_t data
-			 * returned by the counterpart dsl_prop_get_all_impl().
-			 * For instance we do this to restore the original
-			 * received properties when an error occurs in the
-			 * zfs_ioc_recv() codepath.
+			 * dsl_prop_get_all_impl() returns properties in this
+			 * format.
 			 */
 			nvlist_t *attrs = fnvpair_value_nvlist(pair);
 			pair = fnvlist_lookup_nvpair(attrs, ZPROP_VALUE);
@@ -871,14 +1084,40 @@
 
 		if (nvpair_type(pair) == DATA_TYPE_STRING) {
 			const char *value = fnvpair_value_string(pair);
-			dsl_prop_set_sync_impl(ds, name,
+			dsl_prop_set_sync_impl(ds, nvpair_name(pair),
 			    source, 1, strlen(value) + 1, value, tx);
 		} else if (nvpair_type(pair) == DATA_TYPE_UINT64) {
+			const char *propname = nvpair_name(pair);
+			zfs_prop_t prop = zfs_name_to_prop(propname);
 			uint64_t intval = fnvpair_value_uint64(pair);
-			dsl_prop_set_sync_impl(ds, name,
-			    source, sizeof (intval), 1, &intval, tx);
+
+			if (prop == ZFS_PROP_WBC_MODE) {
+				wbc_mode_prop_val_t val;
+
+				/*
+				 * Disabling WBC involves the following:
+				 * 1) all the subsequent data writes will
+				 * stop using special vdev
+				 * 2) already WriteBackCached data blocks
+				 * (with TXG <= txg_off below) will gradually
+				 * migrated from special vdev
+				 *
+				 * To handle this need to remember TXG.
+				 * WBC will be completely disabled for this DS,
+				 * after WBC-window cross this TXG
+				 */
+				val.txg_off = (intval == 0) ? tx->tx_txg : 0;
+				val.root_ds_object = ds->ds_object;
+				val.flags = 0;
+
+				dsl_prop_set_sync_impl(ds, nvpair_name(pair),
+				    source, 8, WBC_MODE_PROP_VAL_SZ, &val, tx);
+			} else {
+				dsl_prop_set_sync_impl(ds, nvpair_name(pair),
+				    source, sizeof (intval), 1, &intval, tx);
+			}
 		} else if (nvpair_type(pair) == DATA_TYPE_BOOLEAN) {
-			dsl_prop_set_sync_impl(ds, name,
+			dsl_prop_set_sync_impl(ds, nvpair_name(pair),
 			    source, 0, 0, NULL, tx);
 		} else {
 			panic("invalid nvpair type");
@@ -892,8 +1131,19 @@
 	dsl_props_set_arg_t *dpsa = arg;
 	dsl_pool_t *dp = dmu_tx_pool(tx);
 	dsl_dataset_t *ds;
+	objset_t *os = NULL;
 
 	VERIFY0(dsl_dataset_hold(dp, dpsa->dpsa_dsname, FTAG, &ds));
+	/*
+	 * Need to initialize os, to be sure that non-mounted datasets and
+	 * non-exposed zvols will receive notification about modified
+	 * properties.
+	 * During the initialization a property can register its callback
+	 * that will be called if the property is changed.
+	 * dsl_props_set_sync_impl() calls dsl_prop_changed_notify()
+	 * that calls the required callback if it exists.
+	 */
+	VERIFY0(dmu_objset_from_ds(ds, &os));
 	dsl_props_set_sync_impl(ds, dpsa->dpsa_source, dpsa->dpsa_props, tx);
 	dsl_dataset_rele(ds, FTAG);
 }
@@ -922,6 +1172,93 @@
 	    &dpsa, nblks, ZFS_SPACE_CHECK_RESERVED));
 }
 
+static void
+dsl_props_mds_set_sync(void *arg, dmu_tx_t *tx)
+{
+	dsl_props_set_arg_t *top_dpsa = arg;
+	dsl_pool_t *dp = dmu_tx_pool(tx);
+	nvlist_t *dss_props = top_dpsa->dpsa_props;
+	nvpair_t *pair = NULL;
+
+	while ((pair = nvlist_next_nvpair(dss_props, pair)) != NULL) {
+		dsl_props_set_arg_t dpsa;
+		dsl_dataset_t *ds = NULL;
+		const char *ds_name;
+
+		ds_name = nvpair_name(pair);
+		VERIFY0(dsl_dataset_hold(dp, ds_name, FTAG, &ds));
+
+		dpsa.dpsa_dsname = ds_name;
+		dpsa.dpsa_source = ZPROP_SRC_LOCAL;
+		dpsa.dpsa_props = fnvpair_value_nvlist(pair);
+
+		dsl_props_set_sync(&dpsa, tx);
+		dsl_dataset_rele(ds, FTAG);
+	}
+}
+
+static int
+dsl_props_mds_set_check(void *arg, dmu_tx_t *tx)
+{
+	dsl_props_set_arg_t *top_dpsa = arg;
+	dsl_pool_t *dp = dmu_tx_pool(tx);
+	nvlist_t *dss_props = top_dpsa->dpsa_props;
+	nvpair_t *pair = NULL;
+
+	while ((pair = nvlist_next_nvpair(dss_props, pair)) != NULL) {
+		dsl_props_set_arg_t dpsa;
+		dsl_dataset_t *ds = NULL;
+		const char *ds_name;
+		int err;
+
+		ds_name = nvpair_name(pair);
+		err = dsl_dataset_hold(dp, ds_name, FTAG, &ds);
+		if (err != 0)
+			return (err);
+
+		if (nvpair_type(pair) != DATA_TYPE_NVLIST) {
+			dsl_dataset_rele(ds, FTAG);
+			return (SET_ERROR(EINVAL));
+		}
+
+		dpsa.dpsa_dsname = ds_name;
+		dpsa.dpsa_source = ZPROP_SRC_LOCAL;
+		dpsa.dpsa_props = fnvpair_value_nvlist(pair);
+
+		err = dsl_props_set_check(&dpsa, tx);
+		dsl_dataset_rele(ds, FTAG);
+		if (err != 0)
+			return (err);
+	}
+
+	return (0);
+}
+
+
+/*
+ * The given 'dss_props' nvlist represents the following struct:
+ *  ds1 -> prop1:value
+ *      -> prop3:value
+ *  ds2 -> prop1:value
+ *      -> prop2:value
+ *
+ * All-or-nothing: if any prop can't be set, nothing will be modified.
+ */
+int
+dsl_props_set_mds(const char *pool_name, nvlist_t *dss_props,
+    size_t num_props)
+{
+	dsl_props_set_arg_t dpsa;
+
+	dpsa.dpsa_dsname = pool_name;
+	dpsa.dpsa_source = ZPROP_SRC_LOCAL;
+	dpsa.dpsa_props = dss_props;
+
+	return (dsl_sync_task(pool_name, dsl_props_mds_set_check,
+	    dsl_props_mds_set_sync, &dpsa, 2 * num_props,
+	    ZFS_SPACE_CHECK_RESERVED));
+}
+
 typedef enum dsl_prop_getflags {
 	DSL_PROP_GET_INHERITING = 0x1,	/* searching parent of target ds */
 	DSL_PROP_GET_SNAPSHOT = 0x2,	/* snapshot dataset */
@@ -1029,6 +1366,7 @@
 				kmem_free(tmp, za.za_num_integers);
 				break;
 			}
+
 			VERIFY(nvlist_add_string(propval, ZPROP_VALUE,
 			    tmp) == 0);
 			kmem_free(tmp, za.za_num_integers);
@@ -1037,6 +1375,25 @@
 			 * Integer property
 			 */
 			ASSERT(za.za_integer_length == 8);
+
+			if (prop == ZFS_PROP_WBC_MODE) {
+				wbc_mode_prop_val_t val;
+
+				ASSERT(za.za_num_integers ==
+				    WBC_MODE_PROP_VAL_SZ);
+
+				err = zap_lookup(mos, propobj,
+				    za.za_name, 8, za.za_num_integers, &val);
+				if (err != 0)
+					break;
+
+				if (val.root_ds_object != 0 &&
+				    val.txg_off == 0)
+					za.za_first_integer = ZFS_WBC_MODE_ON;
+				else
+					za.za_first_integer = ZFS_WBC_MODE_OFF;
+			}
+
 			(void) nvlist_add_uint64(propval, ZPROP_VALUE,
 			    za.za_first_integer);
 		}
