usr/src/uts/common/fs/zfs/zfs_ioctl.c
changeset 5331 3047ad28a67b
parent 5329 33cb98223b2d
child 5367 c40abbe796be
--- a/usr/src/uts/common/fs/zfs/zfs_ioctl.c	Thu Oct 25 15:58:10 2007 -0700
+++ b/usr/src/uts/common/fs/zfs/zfs_ioctl.c	Thu Oct 25 16:34:29 2007 -0700
@@ -38,6 +38,8 @@
 #include <sys/cmn_err.h>
 #include <sys/stat.h>
 #include <sys/zfs_ioctl.h>
+#include <sys/zfs_i18n.h>
+#include <sys/zfs_znode.h>
 #include <sys/zap.h>
 #include <sys/spa.h>
 #include <sys/spa_impl.h>
@@ -60,6 +62,7 @@
 #include <sys/sdt.h>
 #include <sys/fs/zfs.h>
 #include <sys/zfs_ctldir.h>
+#include <sys/zfs_dir.h>
 #include <sys/zvol.h>
 #include <sharefs/share.h>
 #include <sys/zfs_znode.h>
@@ -154,6 +157,22 @@
 	return (buf);
 }
 
+static int
+zfs_check_version(const char *name, int version)
+{
+
+	spa_t *spa;
+
+	if (spa_open(name, &spa, FTAG) == 0) {
+		if (spa_version(spa) < version) {
+			spa_close(spa, FTAG);
+			return (1);
+		}
+		spa_close(spa, FTAG);
+	}
+	return (0);
+}
+
 static void
 zfs_log_history(zfs_cmd_t *zc)
 {
@@ -1280,9 +1299,8 @@
 			    nvpair_type(elem) != DATA_TYPE_STRING)
 				return (EINVAL);
 
-			error = zfs_secpolicy_write_perms(name,
-			    ZFS_DELEG_PERM_USERPROP, CRED());
-			if (error)
+			if (error = zfs_secpolicy_write_perms(name,
+			    ZFS_DELEG_PERM_USERPROP, CRED()))
 				return (error);
 			continue;
 		}
@@ -1304,35 +1322,25 @@
 			    nvpair_value_uint64(elem, &intval) == 0 &&
 			    intval >= ZIO_COMPRESS_GZIP_1 &&
 			    intval <= ZIO_COMPRESS_GZIP_9) {
-				spa_t *spa;
-
-				if (spa_open(name, &spa, FTAG) == 0) {
-					if (spa_version(spa) <
-					    SPA_VERSION_GZIP_COMPRESSION) {
-						spa_close(spa, FTAG);
-						return (ENOTSUP);
-					}
-
-					spa_close(spa, FTAG);
-				}
+				if (zfs_check_version(name,
+				    SPA_VERSION_GZIP_COMPRESSION))
+					return (ENOTSUP);
 			}
 			break;
 
 		case ZFS_PROP_COPIES:
-		{
-			spa_t *spa;
-
-			if (spa_open(name, &spa, FTAG) == 0) {
-				if (spa_version(spa) <
-				    SPA_VERSION_DITTO_BLOCKS) {
-					spa_close(spa, FTAG);
-					return (ENOTSUP);
-				}
-				spa_close(spa, FTAG);
-			}
+			if (zfs_check_version(name, SPA_VERSION_DITTO_BLOCKS))
+				return (ENOTSUP);
 			break;
+		case ZFS_PROP_NORMALIZE:
+		case ZFS_PROP_UTF8ONLY:
+		case ZFS_PROP_CASE:
+			if (zfs_check_version(name, SPA_VERSION_NORMALIZATION))
+				return (ENOTSUP);
+
 		}
-		}
+		if ((error = zfs_secpolicy_setprop(name, prop, CRED())) != 0)
+			return (error);
 	}
 
 	elem = NULL;
@@ -1642,13 +1650,163 @@
 static void
 zfs_create_cb(objset_t *os, void *arg, cred_t *cr, dmu_tx_t *tx)
 {
-	nvlist_t *nvprops = arg;
-	uint64_t version = ZPL_VERSION;
-
-	(void) nvlist_lookup_uint64(nvprops,
+	zfs_creat_t *zct = arg;
+	uint64_t version;
+
+	if (spa_version(dmu_objset_spa(os)) >= SPA_VERSION_FUID)
+		version = ZPL_VERSION;
+	else
+		version = ZPL_VERSION_FUID - 1;
+
+	(void) nvlist_lookup_uint64(zct->zct_props,
 	    zfs_prop_to_name(ZFS_PROP_VERSION), &version);
 
-	zfs_create_fs(os, cr, version, tx);
+	zfs_create_fs(os, cr, version, zct->zct_norm, tx);
+}
+
+/*
+ * zfs_prop_lookup()
+ *
+ * Look for the property first in the existing property nvlist.  If
+ * it's already present, you're done.  If it's not there, attempt to
+ * find the property value from a parent dataset.  If that fails, fall
+ * back to the property's default value.  In either of these two
+ * cases, if update is TRUE, add a value for the property to the
+ * property nvlist.
+ *
+ * If the rval pointer is non-NULL, copy the discovered value to rval.
+ *
+ * If we get any unexpected errors, bail and return the error number
+ * to the caller.
+ *
+ * If we succeed, return 0.
+ */
+static int
+zfs_prop_lookup(const char *parentname, zfs_prop_t propnum,
+    nvlist_t *proplist, uint64_t *rval, boolean_t update)
+{
+	const char *propname;
+	uint64_t value;
+	int error = ENOENT;
+
+	propname = zfs_prop_to_name(propnum);
+	if (proplist != NULL)
+		error = nvlist_lookup_uint64(proplist, propname, &value);
+	if (error == ENOENT) {
+		error = dsl_prop_get_integer(parentname, propname,
+		    &value, NULL);
+		if (error == ENOENT)
+			value = zfs_prop_default_numeric(propnum);
+		else if (error != 0)
+			return (error);
+		if (update) {
+			ASSERT(proplist != NULL);
+			error = nvlist_add_uint64(proplist, propname, value);
+		}
+	}
+	if (error == 0 && rval)
+		*rval = value;
+	return (error);
+}
+
+/*
+ * zfs_normalization_get
+ *
+ * Get the normalization flag value.  If the properties have
+ * non-default values, make sure the pool version is recent enough to
+ * support these choices.
+ */
+static int
+zfs_normalization_get(const char *dataset, nvlist_t *proplist, int *norm,
+    boolean_t update)
+{
+	char parentname[MAXNAMELEN];
+	char poolname[MAXNAMELEN];
+	char *cp;
+	uint64_t value;
+	int check = 0;
+	int error;
+
+	ASSERT(norm != NULL);
+	*norm = 0;
+
+	(void) strncpy(parentname, dataset, sizeof (parentname));
+	cp = strrchr(parentname, '@');
+	if (cp != NULL) {
+		cp[0] = '\0';
+	} else {
+		cp = strrchr(parentname, '/');
+		if (cp == NULL)
+			return (ENOENT);
+		cp[0] = '\0';
+	}
+
+	(void) strncpy(poolname, dataset, sizeof (poolname));
+	cp = strchr(poolname, '/');
+	if (cp != NULL)
+		cp[0] = '\0';
+
+	error = zfs_prop_lookup(parentname, ZFS_PROP_UTF8ONLY,
+	    proplist, &value, update);
+	if (error != 0)
+		return (error);
+	if (value != zfs_prop_default_numeric(ZFS_PROP_UTF8ONLY))
+		check = 1;
+
+	error = zfs_prop_lookup(parentname, ZFS_PROP_NORMALIZE,
+	    proplist, &value, update);
+	if (error != 0)
+		return (error);
+	if (value != zfs_prop_default_numeric(ZFS_PROP_NORMALIZE)) {
+		check = 1;
+		switch ((int)value) {
+		case ZFS_NORMALIZE_NONE:
+			break;
+		case ZFS_NORMALIZE_C:
+			*norm |= U8_TEXTPREP_NFC;
+			break;
+		case ZFS_NORMALIZE_D:
+			*norm |= U8_TEXTPREP_NFD;
+			break;
+		case ZFS_NORMALIZE_KC:
+			*norm |= U8_TEXTPREP_NFKC;
+			break;
+		case ZFS_NORMALIZE_KD:
+			*norm |= U8_TEXTPREP_NFKD;
+			break;
+		default:
+			ASSERT((int)value >= ZFS_NORMALIZE_NONE);
+			ASSERT((int)value <= ZFS_NORMALIZE_KD);
+			break;
+		}
+	}
+
+	error = zfs_prop_lookup(parentname, ZFS_PROP_CASE,
+	    proplist, &value, update);
+	if (error != 0)
+		return (error);
+	if (value != zfs_prop_default_numeric(ZFS_PROP_CASE)) {
+		check = 1;
+		switch ((int)value) {
+		case ZFS_CASE_SENSITIVE:
+			break;
+		case ZFS_CASE_INSENSITIVE:
+			*norm |= U8_TEXTPREP_TOUPPER;
+			break;
+		case ZFS_CASE_MIXED:
+			*norm |= U8_TEXTPREP_TOUPPER;
+			break;
+		default:
+			ASSERT((int)value >= ZFS_CASE_SENSITIVE);
+			ASSERT((int)value <= ZFS_CASE_MIXED);
+			break;
+		}
+	}
+
+	if (check == 1)
+		if (zfs_check_version(poolname, SPA_VERSION_NORMALIZATION))
+			return (ENOTSUP);
+	return (0);
 }
 
 static int
@@ -1656,6 +1814,7 @@
 {
 	objset_t *clone;
 	int error = 0;
+	zfs_creat_t zct;
 	nvlist_t *nvprops = NULL;
 	void (*cbfunc)(objset_t *os, void *arg, cred_t *cr, dmu_tx_t *tx);
 	dmu_objset_type_t type = zc->zc_objset_type;
@@ -1682,6 +1841,9 @@
 	    &nvprops)) != 0)
 		return (error);
 
+	zct.zct_norm = 0;
+	zct.zct_props = nvprops;
+
 	if (zc->zc_value[0] != '\0') {
 		/*
 		 * We're creating a clone of an existing snapshot.
@@ -1699,6 +1861,34 @@
 			return (error);
 		}
 		error = dmu_objset_create(zc->zc_name, type, clone, NULL, NULL);
+		if (error) {
+			dmu_objset_close(clone);
+			nvlist_free(nvprops);
+			return (error);
+		}
+		/*
+		 * If caller did not provide any properties, allocate
+		 * an nvlist for properties, as we will be adding our set-once
+		 * properties to it.  This carries the choices made on the
+		 * original file system into the clone.
+		 */
+		if (nvprops == NULL)
+			VERIFY(nvlist_alloc(&nvprops,
+			    NV_UNIQUE_NAME, KM_SLEEP) == 0);
+
+		/*
+		 * We have to have normalization and case-folding
+		 * flags correct when we do the file system creation,
+		 * so go figure them out now.  All we really care about
+		 * here is getting these values into the property list.
+		 */
+		error = zfs_normalization_get(zc->zc_value, nvprops,
+		    &zct.zct_norm, B_TRUE);
+		if (error != 0) {
+			dmu_objset_close(clone);
+			nvlist_free(nvprops);
+			return (error);
+		}
 		dmu_objset_close(clone);
 	} else {
 		if (cbfunc == NULL) {
@@ -1737,18 +1927,38 @@
 			}
 		} else if (type == DMU_OST_ZFS) {
 			uint64_t version;
-
-			if (0 == nvlist_lookup_uint64(nvprops,
-			    zfs_prop_to_name(ZFS_PROP_VERSION), &version) &&
-			    (version < ZPL_VERSION_INITIAL ||
+			int error;
+
+			error = nvlist_lookup_uint64(nvprops,
+			    zfs_prop_to_name(ZFS_PROP_VERSION), &version);
+
+			if (error == 0 && (version < ZPL_VERSION_INITIAL ||
 			    version > ZPL_VERSION)) {
 				nvlist_free(nvprops);
-				return (EINVAL);
+				return (ENOTSUP);
+			} else if (error == 0 && version >= ZPL_VERSION_FUID &&
+			    zfs_check_version(zc->zc_name, SPA_VERSION_FUID)) {
+				nvlist_free(nvprops);
+				return (ENOTSUP);
+			}
+
+			/*
+			 * We have to have normalization and
+			 * case-folding flags correct when we do the
+			 * file system creation, so go figure them out
+			 * now.  The final argument to zfs_normalization_get()
+			 * tells that routine not to update the nvprops
+			 * list.
+			 */
+			error = zfs_normalization_get(zc->zc_name, nvprops,
+			    &zct.zct_norm, B_FALSE);
+			if (error != 0) {
+				nvlist_free(nvprops);
+				return (error);
 			}
 		}
-
 		error = dmu_objset_create(zc->zc_name, type, NULL, cbfunc,
-		    nvprops);
+		    &zct);
 	}
 
 	/*
@@ -1952,7 +2162,7 @@
 	if (zfsvfs != NULL)
 		VFS_RELE(zfsvfs->z_vfs);
 	new_off = fp->f_offset + zc->zc_cookie;
-	if (VOP_SEEK(fp->f_vnode, fp->f_offset, &new_off) == 0)
+	if (VOP_SEEK(fp->f_vnode, fp->f_offset, &new_off, NULL) == 0)
 		fp->f_offset = new_off;
 
 	releasef(fd);
@@ -2123,55 +2333,129 @@
 /*
  * We don't want to have a hard dependency
  * against some special symbols in sharefs
- * and nfs.  Determine them if needed when
+ * nfs, and smbsrv.  Determine them if needed when
  * the first file system is shared.
- * Neither sharefs or nfs are unloadable modules.
+ * Neither sharefs, nfs or smbsrv are unloadable modules.
  */
-int (*zexport_fs)(void *arg);
+int (*znfsexport_fs)(void *arg);
 int (*zshare_fs)(enum sharefs_sys_op, share_t *, uint32_t);
-
-int zfs_share_inited;
+int (*zsmbexport_fs)(void *arg, boolean_t add_share);
+
+int zfs_nfsshare_inited;
+int zfs_smbshare_inited;
+
 ddi_modhandle_t nfs_mod;
 ddi_modhandle_t sharefs_mod;
+ddi_modhandle_t smbsrv_mod;
 kmutex_t zfs_share_lock;
 
 static int
+zfs_init_sharefs()
+{
+	int error;
+
+	ASSERT(MUTEX_HELD(&zfs_share_lock));
+	/* Both NFS and SMB shares also require sharetab support. */
+	if (sharefs_mod == NULL && ((sharefs_mod =
+	    ddi_modopen("fs/sharefs",
+	    KRTLD_MODE_FIRST, &error)) == NULL)) {
+		return (ENOSYS);
+	}
+	if (zshare_fs == NULL && ((zshare_fs =
+	    (int (*)(enum sharefs_sys_op, share_t *, uint32_t))
+	    ddi_modsym(sharefs_mod, "sharefs_impl", &error)) == NULL)) {
+		return (ENOSYS);
+	}
+	return (0);
+}
+
+static int
 zfs_ioc_share(zfs_cmd_t *zc)
 {
 	int error;
 	int opcode;
 
-	if (zfs_share_inited == 0) {
-		mutex_enter(&zfs_share_lock);
-		nfs_mod = ddi_modopen("fs/nfs", KRTLD_MODE_FIRST, &error);
-		sharefs_mod = ddi_modopen("fs/sharefs",
-		    KRTLD_MODE_FIRST, &error);
-		if (nfs_mod == NULL || sharefs_mod == NULL) {
+	switch (zc->zc_share.z_sharetype) {
+	case ZFS_SHARE_NFS:
+	case ZFS_UNSHARE_NFS:
+		if (zfs_nfsshare_inited == 0) {
+			mutex_enter(&zfs_share_lock);
+			if (nfs_mod == NULL && ((nfs_mod = ddi_modopen("fs/nfs",
+			    KRTLD_MODE_FIRST, &error)) == NULL)) {
+				mutex_exit(&zfs_share_lock);
+				return (ENOSYS);
+			}
+			if (znfsexport_fs == NULL &&
+			    ((znfsexport_fs = (int (*)(void *))
+			    ddi_modsym(nfs_mod,
+			    "nfs_export", &error)) == NULL)) {
+				mutex_exit(&zfs_share_lock);
+				return (ENOSYS);
+			}
+			error = zfs_init_sharefs();
+			if (error) {
+				mutex_exit(&zfs_share_lock);
+				return (ENOSYS);
+			}
+			zfs_nfsshare_inited = 1;
 			mutex_exit(&zfs_share_lock);
-			return (ENOSYS);
-		}
-		if (zexport_fs == NULL && ((zexport_fs = (int (*)(void *))
-		    ddi_modsym(nfs_mod, "nfs_export", &error)) == NULL)) {
-			mutex_exit(&zfs_share_lock);
-			return (ENOSYS);
 		}
-
-		if (zshare_fs == NULL && ((zshare_fs =
-		    (int (*)(enum sharefs_sys_op, share_t *, uint32_t))
-		    ddi_modsym(sharefs_mod, "sharefs_impl", &error)) == NULL)) {
+		break;
+	case ZFS_SHARE_SMB:
+	case ZFS_UNSHARE_SMB:
+		if (zfs_smbshare_inited == 0) {
+			mutex_enter(&zfs_share_lock);
+			if (smbsrv_mod == NULL && ((smbsrv_mod =
+			    ddi_modopen("drv/smbsrv",
+			    KRTLD_MODE_FIRST, &error)) == NULL)) {
+				mutex_exit(&zfs_share_lock);
+				return (ENOSYS);
+			}
+			if (zsmbexport_fs == NULL && ((zsmbexport_fs =
+			    (int (*)(void *, boolean_t))ddi_modsym(smbsrv_mod,
+			    "lmshrd_share_upcall", &error)) == NULL)) {
+				mutex_exit(&zfs_share_lock);
+				return (ENOSYS);
+			}
+			error = zfs_init_sharefs();
+			if (error) {
+				mutex_exit(&zfs_share_lock);
+				return (ENOSYS);
+			}
+			zfs_smbshare_inited = 1;
 			mutex_exit(&zfs_share_lock);
-			return (ENOSYS);
 		}
-		zfs_share_inited = 1;
-		mutex_exit(&zfs_share_lock);
+		break;
+	default:
+		return (EINVAL);
 	}
 
-	if (error = zexport_fs((void *)(uintptr_t)zc->zc_share.z_exportdata))
-		return (error);
-
-	opcode = (zc->zc_share.z_sharetype == B_TRUE) ?
+	switch (zc->zc_share.z_sharetype) {
+	case ZFS_SHARE_NFS:
+	case ZFS_UNSHARE_NFS:
+		if (error =
+		    znfsexport_fs((void *)
+		    (uintptr_t)zc->zc_share.z_exportdata))
+			return (error);
+		break;
+	case ZFS_SHARE_SMB:
+	case ZFS_UNSHARE_SMB:
+		if (error = zsmbexport_fs((void *)
+		    (uintptr_t)zc->zc_share.z_exportdata,
+		    zc->zc_share.z_sharetype == ZFS_SHARE_SMB ?
+		    B_TRUE : B_FALSE)) {
+			return (error);
+		}
+		break;
+	}
+
+	opcode = (zc->zc_share.z_sharetype == ZFS_SHARE_NFS ||
+	    zc->zc_share.z_sharetype == ZFS_SHARE_SMB) ?
 	    SHAREFS_ADD : SHAREFS_REMOVE;
 
+	/*
+	 * Add or remove share from sharetab
+	 */
 	error = zshare_fs(opcode,
 	    (void *)(uintptr_t)zc->zc_share.z_sharedata,
 	    zc->zc_share.z_sharemax);
@@ -2447,10 +2731,12 @@
 	zvol_fini();
 	zfs_fini();
 	spa_fini();
-	if (zfs_share_inited) {
+	if (zfs_nfsshare_inited)
 		(void) ddi_modclose(nfs_mod);
+	if (zfs_smbshare_inited)
+		(void) ddi_modclose(smbsrv_mod);
+	if (zfs_nfsshare_inited || zfs_smbshare_inited)
 		(void) ddi_modclose(sharefs_mod);
-	}
 
 	tsd_destroy(&zfs_fsyncer_key);
 	ldi_ident_release(zfs_li);