diff --git a/include/os/freebsd/zfs/sys/zfs_vfsops_os.h b/include/os/freebsd/zfs/sys/zfs_vfsops_os.h
index a263b48f7517..4dc05581fc8e 100644
--- a/include/os/freebsd/zfs/sys/zfs_vfsops_os.h
+++ b/include/os/freebsd/zfs/sys/zfs_vfsops_os.h
@@ -240,7 +240,9 @@ struct zfsvfs {
RW_WRITE_HELD(&(zfsvfs)->z_teardown_inactive_lock)
#endif
-#define ZSB_XATTR 0x0001 /* Enable user xattrs */
+#define ZSB_XATTR 0x0001 /* Enable user xattrs */
+#define ZSB_XATTR_COMPAT 0x0002 /* Enable cross-platform user xattrs */
+
/*
* Normal filesystems (those not under .zfs/snapshot) have a total
* file ID size limited to 12 bytes (including the length field) due to
diff --git a/include/os/linux/zfs/sys/zfs_vfsops_os.h b/include/os/linux/zfs/sys/zfs_vfsops_os.h
index 7b4a1aac9aad..cc8376c7c15f 100644
--- a/include/os/linux/zfs/sys/zfs_vfsops_os.h
+++ b/include/os/linux/zfs/sys/zfs_vfsops_os.h
@@ -171,7 +171,8 @@ struct zfsvfs {
#define ZFS_TEARDOWN_HELD(zfsvfs) \
RRM_LOCK_HELD(&(zfsvfs)->z_teardown_lock)
-#define ZSB_XATTR 0x0001 /* Enable user xattrs */
+#define ZSB_XATTR 0x0001 /* Enable user xattrs */
+#define ZSB_XATTR_COMPAT 0x0002 /* Enable cross-platform user xattrs */
/*
* Allow a maximum number of links. While ZFS does not internally limit
diff --git a/include/sys/fs/zfs.h b/include/sys/fs/zfs.h
index 71d736d5cc97..27a87a9bbeb8 100644
--- a/include/sys/fs/zfs.h
+++ b/include/sys/fs/zfs.h
@@ -186,6 +186,7 @@ typedef enum {
ZFS_PROP_IVSET_GUID, /* not exposed to the user */
ZFS_PROP_REDACTED,
ZFS_PROP_REDACT_SNAPS,
+ ZFS_PROP_XATTR_COMPAT,
ZFS_NUM_PROPS
} zfs_prop_t;
@@ -457,6 +458,11 @@ typedef enum zfs_key_location {
#define DEFAULT_PBKDF2_ITERATIONS 350000
#define MIN_PBKDF2_ITERATIONS 100000
+typedef enum zfs_xattr_compat {
+ ZFS_XATTR_COMPAT_LINUX = 0,
+ ZFS_XATTR_COMPAT_ALL,
+} zfs_xattr_compat_t;
+
/*
* On-disk version number.
*/
@@ -1613,6 +1619,36 @@ typedef enum {
#define ZFS_EV_HIST_DSID "history_dsid"
#define ZFS_EV_RESILVER_TYPE "resilver_type"
+/*
+ * xattr namespace prefixes. These are forbidden in xattr names.
+ *
+ * For cross-platform compatibility, xattrs in the user namespace should not be
+ * prefixed with the namespace name, but for backwards compatibility with older
+ * ZFS on Linux versions we do prefix the namespace.
+ */
+#define ZFS_XA_NS_FREEBSD_PREFIX "freebsd:"
+#define ZFS_XA_NS_FREEBSD_PREFIX_LEN strlen("freebsd:")
+#define ZFS_XA_NS_LINUX_SECURITY_PREFIX "security."
+#define ZFS_XA_NS_LINUX_SECURITY_PREFIX_LEN strlen("security.")
+#define ZFS_XA_NS_LINUX_SYSTEM_PREFIX "system."
+#define ZFS_XA_NS_LINUX_SYSTEM_PREFIX_LEN strlen("system.")
+#define ZFS_XA_NS_LINUX_TRUSTED_PREFIX "trusted."
+#define ZFS_XA_NS_LINUX_TRUSTED_PREFIX_LEN strlen("trusted.")
+#define ZFS_XA_NS_LINUX_USER_PREFIX "user."
+#define ZFS_XA_NS_LINUX_USER_PREFIX_LEN strlen("user.")
+
+/* BEGIN CSTYLED */
+#define ZFS_XA_NS_PREFIX_MATCH(ns, name) \
+ (strncmp(name, ZFS_XA_NS_##ns##_PREFIX, ZFS_XA_NS_##ns##_PREFIX_LEN) == 0)
+
+#define ZFS_XA_NS_PREFIX_FORBIDDEN(name) \
+ (ZFS_XA_NS_PREFIX_MATCH(FREEBSD, name) || \
+ ZFS_XA_NS_PREFIX_MATCH(LINUX_SECURITY, name) || \
+ ZFS_XA_NS_PREFIX_MATCH(LINUX_SYSTEM, name) || \
+ ZFS_XA_NS_PREFIX_MATCH(LINUX_TRUSTED, name) || \
+ ZFS_XA_NS_PREFIX_MATCH(LINUX_USER, name))
+/* END CSTYLED */
+
#ifdef __cplusplus
}
#endif
diff --git a/include/zfeature_common.h b/include/zfeature_common.h
index 76dd7ed57478..88a28f042d32 100644
--- a/include/zfeature_common.h
+++ b/include/zfeature_common.h
@@ -75,6 +75,7 @@ typedef enum spa_feature {
SPA_FEATURE_DEVICE_REBUILD,
SPA_FEATURE_ZSTD_COMPRESS,
SPA_FEATURE_DRAID,
+ SPA_FEATURE_XATTR_COMPAT,
SPA_FEATURES
} spa_feature_t;
diff --git a/lib/libnvpair/libnvpair.abi b/lib/libnvpair/libnvpair.abi
index 19bda07d287c..777be6ceb34c 100644
--- a/lib/libnvpair/libnvpair.abi
+++ b/lib/libnvpair/libnvpair.abi
@@ -240,7 +240,7 @@
-
+
@@ -303,11 +303,11 @@
-
-
-
-
-
+
+
+
+
+
@@ -365,12 +365,12 @@
-
-
-
-
-
-
+
+
+
+
+
+
@@ -397,41 +397,41 @@
-
-
-
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -552,105 +552,105 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -660,461 +660,461 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
-
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
@@ -1123,37 +1123,37 @@
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
+
+
+
@@ -1745,10 +1745,10 @@
-
-
-
-
+
+
+
+
@@ -1797,7 +1797,7 @@
-
+
@@ -1847,9 +1847,9 @@
-
-
-
+
+
+
@@ -1872,11 +1872,11 @@
-
+
-
+
@@ -2603,7 +2603,7 @@
-
+
@@ -3175,7 +3175,7 @@
-
+
diff --git a/lib/libuutil/libuutil.abi b/lib/libuutil/libuutil.abi
index 9c62e36ad2d1..6aa7332d6419 100644
--- a/lib/libuutil/libuutil.abi
+++ b/lib/libuutil/libuutil.abi
@@ -266,13 +266,13 @@
-
+
-
-
+
+
@@ -280,26 +280,26 @@
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
@@ -324,11 +324,11 @@
-
-
+
+
-
+
@@ -520,95 +520,95 @@
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
-
+
+
+
+
@@ -623,28 +623,28 @@
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
@@ -719,7 +719,7 @@
-
+
@@ -745,25 +745,25 @@
-
-
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
@@ -779,21 +779,21 @@
-
-
-
-
+
+
+
+
-
-
+
+
-
+
@@ -900,103 +900,103 @@
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
@@ -1011,32 +1011,32 @@
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
+
@@ -1145,22 +1145,22 @@
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
+
-
-
+
+
@@ -1200,10 +1200,10 @@
-
-
-
-
+
+
+
+
@@ -1219,20 +1219,20 @@
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
+
+
+
@@ -1251,44 +1251,44 @@
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
+
-
+
-
+
@@ -1313,26 +1313,26 @@
-
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
@@ -1345,7 +1345,7 @@
-
+
@@ -1447,7 +1447,7 @@
-
+
@@ -1470,100 +1470,100 @@
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
+
-
-
-
+
+
+
@@ -1579,34 +1579,34 @@
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
@@ -1628,7 +1628,7 @@
-
+
@@ -1636,8 +1636,8 @@
-
-
+
+
@@ -1659,7 +1659,7 @@
-
+
@@ -1808,13 +1808,13 @@
-
+
-
+
diff --git a/lib/libzfs/libzfs.abi b/lib/libzfs/libzfs.abi
index 9f2e199cd0bb..8c3e0c5dcdad 100644
--- a/lib/libzfs/libzfs.abi
+++ b/lib/libzfs/libzfs.abi
@@ -414,47 +414,47 @@
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -557,9 +557,10 @@
-
+
+
-
+
@@ -1055,26 +1056,26 @@
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
@@ -1085,28 +1086,28 @@
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
@@ -1256,45 +1257,45 @@
-
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
@@ -1386,52 +1387,52 @@
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
@@ -1461,7 +1462,7 @@
-
+
@@ -1573,64 +1574,64 @@
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
+
@@ -1646,49 +1647,49 @@
-
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
@@ -1718,23 +1719,23 @@
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
@@ -1749,132 +1750,132 @@
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
+
@@ -1883,96 +1884,96 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
@@ -1990,47 +1991,47 @@
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
@@ -2272,43 +2273,43 @@
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
@@ -2479,13 +2480,13 @@
-
+
-
+
@@ -2500,13 +2501,13 @@
-
+
-
+
@@ -2536,7 +2537,7 @@
-
+
@@ -2545,10 +2546,10 @@
-
+
-
+
@@ -2587,16 +2588,16 @@
-
+
-
+
-
+
@@ -2676,13 +2677,13 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
@@ -2716,7 +2717,7 @@
-
+
@@ -2732,7 +2733,7 @@
-
+
@@ -2743,18 +2744,18 @@
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
@@ -2788,60 +2789,60 @@
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
@@ -2872,7 +2873,7 @@
-
+
@@ -2899,33 +2900,33 @@
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
@@ -2941,87 +2942,87 @@
-
-
-
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
@@ -3031,82 +3032,82 @@
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
@@ -3206,7 +3207,7 @@
-
+
@@ -3216,26 +3217,26 @@
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
+
@@ -3247,128 +3248,128 @@
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
+
+
+
@@ -3383,29 +3384,29 @@
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+
@@ -3429,26 +3430,26 @@
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
+
@@ -3459,71 +3460,71 @@
-
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
+
-
+
@@ -3540,155 +3541,155 @@
-
-
-
-
-
+
+
+
+
+
-
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
+
@@ -3726,37 +3727,37 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
@@ -3831,7 +3832,7 @@
-
+
@@ -3858,7 +3859,7 @@
-
+
@@ -3876,22 +3877,22 @@
-
+
-
+
-
+
-
+
-
+
-
+
@@ -3903,7 +3904,7 @@
-
+
@@ -3919,7 +3920,7 @@
-
+
@@ -3964,13 +3965,13 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
@@ -4031,51 +4032,51 @@
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
@@ -4189,7 +4190,7 @@
-
+
@@ -4227,7 +4228,7 @@
-
+
@@ -4235,71 +4236,71 @@
-
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
+
-
-
+
+
-
+
-
-
-
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
@@ -4356,21 +4357,21 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
@@ -4421,200 +4422,200 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
-
-
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
@@ -4692,7 +4693,7 @@
-
+
@@ -4746,7 +4747,7 @@
-
+
@@ -4795,7 +4796,7 @@
-
+
@@ -4839,7 +4840,7 @@
-
+
@@ -4851,7 +4852,7 @@
-
+
@@ -4948,7 +4949,7 @@
-
+
@@ -5060,7 +5061,7 @@
-
+
@@ -5069,32 +5070,32 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -5134,33 +5135,34 @@
-
+
+
-
-
+
+
-
-
+
+
-
+
-
+
-
-
+
+
-
+
@@ -5191,7 +5193,7 @@
-
+
@@ -5210,21 +5212,21 @@
-
+
-
+
-
+
-
+
-
+
-
+
@@ -5244,7 +5246,7 @@
-
+
@@ -5296,7 +5298,7 @@
-
+
@@ -5310,7 +5312,7 @@
-
+
@@ -5333,7 +5335,7 @@
-
+
@@ -5534,7 +5536,7 @@
-
+
@@ -5591,20 +5593,20 @@
-
+
-
+
-
+
-
+
-
+
@@ -5680,64 +5682,64 @@
-
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
@@ -5747,52 +5749,52 @@
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
@@ -5886,7 +5888,7 @@
-
+
@@ -5956,7 +5958,7 @@
-
+
@@ -6069,115 +6071,115 @@
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
+
+
+
-
+
-
+
@@ -6205,7 +6207,7 @@
-
+
@@ -6231,7 +6233,7 @@
-
+
diff --git a/lib/libzfs_core/libzfs_core.abi b/lib/libzfs_core/libzfs_core.abi
index a16f5874bca4..7a9e064faae8 100644
--- a/lib/libzfs_core/libzfs_core.abi
+++ b/lib/libzfs_core/libzfs_core.abi
@@ -292,7 +292,7 @@
-
+
@@ -325,25 +325,25 @@
-
-
-
+
+
+
-
-
-
+
+
+
-
+
-
+
@@ -351,13 +351,13 @@
-
-
-
-
+
+
+
+
-
+
@@ -369,61 +369,61 @@
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
@@ -431,82 +431,82 @@
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
@@ -1030,68 +1030,68 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1102,122 +1102,122 @@
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
@@ -1225,19 +1225,19 @@
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
+
@@ -1361,18 +1361,18 @@
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
@@ -1403,7 +1403,7 @@
-
+
@@ -1449,38 +1449,38 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -1488,35 +1488,35 @@
-
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
@@ -1546,7 +1546,7 @@
-
+
@@ -1624,6 +1624,9 @@
+
+
+
@@ -1700,29 +1703,29 @@
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
@@ -1733,15 +1736,15 @@
-
-
-
-
-
+
+
+
+
+
-
-
+
+
@@ -1751,49 +1754,49 @@
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -1801,12 +1804,12 @@
-
+
-
-
-
+
+
+
@@ -1828,7 +1831,7 @@
-
+
@@ -1887,7 +1890,7 @@
-
+
@@ -2106,7 +2109,7 @@
-
+
@@ -2349,7 +2352,7 @@
-
+
@@ -2447,58 +2450,58 @@
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -2563,29 +2566,29 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -2602,46 +2605,46 @@
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
@@ -2748,7 +2751,7 @@
-
+
@@ -2760,13 +2763,13 @@
-
+
-
-
+
+
@@ -2779,7 +2782,7 @@
-
+
@@ -3038,7 +3041,7 @@
-
+
@@ -3061,99 +3064,99 @@
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
+
-
-
-
+
+
+
@@ -3166,34 +3169,34 @@
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
@@ -3209,13 +3212,13 @@
-
+
-
+
@@ -3231,8 +3234,8 @@
-
-
+
+
@@ -3345,45 +3348,45 @@
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
diff --git a/lib/libzfsbootenv/libzfsbootenv.abi b/lib/libzfsbootenv/libzfsbootenv.abi
index cf44cd5b49d7..4d0860e45966 100644
--- a/lib/libzfsbootenv/libzfsbootenv.abi
+++ b/lib/libzfsbootenv/libzfsbootenv.abi
@@ -15,16 +15,16 @@
-
+
-
-
-
+
+
+
@@ -34,10 +34,10 @@
-
-
-
-
+
+
+
+
@@ -102,38 +102,38 @@
-
+
-
-
-
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
@@ -224,7 +224,7 @@
-
+
@@ -335,10 +335,10 @@
-
-
-
-
+
+
+
+
diff --git a/man/man5/zpool-features.5 b/man/man5/zpool-features.5
index c97870dbbe82..f3ddb8ccacee 100644
--- a/man/man5/zpool-features.5
+++ b/man/man5/zpool-features.5
@@ -1021,6 +1021,27 @@ and may take a while to complete for filesystems containing a large number of
files.
.RE
+.sp
+.ne 2
+.na
+\fBxattr_compat\fR
+.ad
+.RS 4n
+.TS
+l l .
+GUID com.ixsystems:xattr_compat
+READ\-ONLY COMPATIBLE yes
+DEPENDENCIES extensible_dataset
+.TE
+
+This feature enables the use of a cross-platform compatible encoding for xattrs
+in the user namespace on Linux.
+
+This feature becomes \fBactive\fR on a filesystem when an xattr is written to
+the user namespace on Linux after the feature has been enabled, and returns to
+\fBenabled\fR after all filesystems with the feature active have been destroyed.
+.RE
+
.sp
.ne 2
.na
diff --git a/man/man8/zfsprops.8 b/man/man8/zfsprops.8
index aa49839ee791..1c6d10b600ae 100644
--- a/man/man8/zfsprops.8
+++ b/man/man8/zfsprops.8
@@ -1815,6 +1815,38 @@ are equivalent to the
and
.Sy noxattr
mount options.
+.It Sy xattr_compat Ns = Ns Sy all Ns | Ns Sy linux
+Controls the preferred encoding of xattrs in the user namespace.
+When set to
+.Sy all
+(the default) with
+.Sy feature@xattr_compat
+enabled on the pool, xattrs written in the user namespace are stored in a
+format compatible across all supported platforms, and xattrs in the user
+namespace from all platforms are accessible.
+There is no notion of xattr namespaces on illumos, so all xattrs from
+illumos are presented in the user namespace on other platforms.
+The xattrs not in the user namespace are considered platform-specific and are
+not exposed on other platforms.
+Existing xattrs in the
+.Sy xattr_compat=linux
+format are accessible and are replaced with the cross-platform compatible
+format when written.
+When
+.Sy feature@xattr_compat
+is disabled, xattrs behave as with
+.Sy xattr_compat=linux .
+When set to
+.Sy linux ,
+xattrs written in the user namespace are stored in a format that is compatible
+with ZFS on Linux prior to
+.Sy feature@xattr_compat
+but not compatible with ZFS on other platforms prior to this feature.
+See
+.Sy feature@xattr_compat
+in
+.Xr zpool-features 5
+for more information.
.It Sy jailed Ns = Ns Sy off Ns | Ns Sy on
Controls whether the dataset is managed from a jail. See the
.Qq Sx Jails
diff --git a/module/os/freebsd/zfs/zfs_vfsops.c b/module/os/freebsd/zfs/zfs_vfsops.c
index 0b13780b8a5a..4756a69f97a1 100644
--- a/module/os/freebsd/zfs/zfs_vfsops.c
+++ b/module/os/freebsd/zfs/zfs_vfsops.c
@@ -56,6 +56,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -478,6 +479,31 @@ xattr_changed_cb(void *arg, uint64_t newval)
}
}
+static void
+xattr_compat_changed_cb(void *arg, uint64_t newval)
+{
+ zfsvfs_t *zfsvfs = arg;
+
+ /*
+ * Force the old cross-platform compatible behavior if
+ * feature@xattr_compat is disabled. This contrasts with
+ * Linux where the behavior prior to feature@xattr_compat
+ * was to use the incompatible Linux-only xattr format.
+ */
+ if (!spa_feature_is_enabled(dmu_objset_spa(zfsvfs->z_os),
+ SPA_FEATURE_XATTR_COMPAT))
+ newval = ZFS_XATTR_COMPAT_ALL;
+
+ switch (newval) {
+ case ZFS_XATTR_COMPAT_ALL:
+ zfsvfs->z_flags |= ZSB_XATTR_COMPAT;
+ break;
+ case ZFS_XATTR_COMPAT_LINUX:
+ zfsvfs->z_flags &= ~ZSB_XATTR_COMPAT;
+ break;
+ }
+}
+
static void
blksz_changed_cb(void *arg, uint64_t newval)
{
@@ -721,6 +747,9 @@ zfs_register_callbacks(vfs_t *vfsp)
zfs_prop_to_name(ZFS_PROP_ATIME), atime_changed_cb, zfsvfs);
error = error ? error : dsl_prop_register(ds,
zfs_prop_to_name(ZFS_PROP_XATTR), xattr_changed_cb, zfsvfs);
+ error = error ? error : dsl_prop_register(ds,
+ zfs_prop_to_name(ZFS_PROP_XATTR_COMPAT), xattr_compat_changed_cb,
+ zfsvfs);
error = error ? error : dsl_prop_register(ds,
zfs_prop_to_name(ZFS_PROP_RECORDSIZE), blksz_changed_cb, zfsvfs);
error = error ? error : dsl_prop_register(ds,
@@ -1237,6 +1266,10 @@ zfs_domount(vfs_t *vfsp, char *osname)
"xattr", &pval, NULL)))
goto out;
xattr_changed_cb(zfsvfs, pval);
+ if ((error = dsl_prop_get_integer(osname,
+ "xattr_compat", &pval, NULL)))
+ goto out;
+ xattr_compat_changed_cb(zfsvfs, pval);
if ((error = dsl_prop_get_integer(osname,
"acltype", &pval, NULL)))
goto out;
diff --git a/module/os/freebsd/zfs/zfs_vnops_os.c b/module/os/freebsd/zfs/zfs_vnops_os.c
index a18ac21dc79f..84353b0f83cf 100644
--- a/module/os/freebsd/zfs/zfs_vnops_os.c
+++ b/module/os/freebsd/zfs/zfs_vnops_os.c
@@ -5247,43 +5247,54 @@ zfs_freebsd_pathconf(struct vop_pathconf_args *ap)
}
}
+static int
+zfs_check_attrname(const char *name)
+{
+ /* We don't allow '/' character in attribute name. */
+ if (strchr(name, '/') != NULL)
+ return (SET_ERROR(EINVAL));
+ /* We don't allow attribute names that start with a namespace prefix. */
+ if (ZFS_XA_NS_PREFIX_FORBIDDEN(name))
+ return (SET_ERROR(EINVAL));
+ return (0);
+}
+
/*
* FreeBSD's extended attributes namespace defines file name prefix for ZFS'
* extended attribute name:
*
- * NAMESPACE PREFIX
- * system freebsd:system:
- * user (none, can be used to access ZFS fsattr(5) attributes
- * created on Solaris)
+ * NAMESPACE XATTR_COMPAT PREFIX
+ * system * freebsd:system:
+ * user all (none, can be used to access ZFS
+ * fsattr(5) attributes created on Solaris)
+ * user linux user.
*/
static int
zfs_create_attrname(int attrnamespace, const char *name, char *attrname,
- size_t size)
+ size_t size, boolean_t xattr_compat)
{
const char *namespace, *prefix, *suffix;
- /* We don't allow '/' character in attribute name. */
- if (strchr(name, '/') != NULL)
- return (SET_ERROR(EINVAL));
- /* We don't allow attribute names that start with "freebsd:" string. */
- if (strncmp(name, "freebsd:", 8) == 0)
- return (SET_ERROR(EINVAL));
-
bzero(attrname, size);
switch (attrnamespace) {
case EXTATTR_NAMESPACE_USER:
-#if 0
- prefix = "freebsd:";
- namespace = EXTATTR_NAMESPACE_USER_STRING;
- suffix = ":";
-#else
- /*
- * This is the default namespace by which we can access all
- * attributes created on Solaris.
- */
- prefix = namespace = suffix = "";
-#endif
+ if (xattr_compat) {
+ /*
+ * This is the default namespace by which we can access
+ * all attributes created on Solaris.
+ */
+ prefix = namespace = suffix = "";
+ } else {
+ /*
+ * This is compatible with the user namespace encoding
+ * on Linux prior to feature@xattr_compat, but nothing
+ * else.
+ */
+ prefix = "";
+ namespace = "user";
+ suffix = ".";
+ }
break;
case EXTATTR_NAMESPACE_SYSTEM:
prefix = "freebsd:";
@@ -5351,13 +5362,12 @@ zfs_getextattr_dir(struct vop_getextattr_args *ap, const char *attrname)
return (error);
flags = FREAD;
- NDINIT_ATVP(&nd, LOOKUP, NOFOLLOW, UIO_SYSSPACE, attrname,
- xvp, td);
+ NDINIT_ATVP(&nd, LOOKUP, NOFOLLOW, UIO_SYSSPACE, attrname, xvp, td);
error = vn_open_cred(&nd, &flags, 0, VN_OPEN_INVFS, ap->a_cred, NULL);
vp = nd.ni_vp;
NDFREE(&nd, NDF_ONLY_PNBUF);
if (error != 0)
- return (error);
+ return (SET_ERROR(error));
if (ap->a_size != NULL) {
error = VOP_GETATTR(vp, &va, ap->a_cred);
@@ -5365,6 +5375,8 @@ zfs_getextattr_dir(struct vop_getextattr_args *ap, const char *attrname)
*ap->a_size = (size_t)va.va_size;
} else if (ap->a_uio != NULL)
error = VOP_READ(vp, ap->a_uio, IO_UNIT, ap->a_cred);
+ if (error != 0)
+ error = SET_ERROR(error);
VOP_UNLOCK1(vp);
vn_close(vp, flags, ap->a_cred, td);
@@ -5388,14 +5400,37 @@ zfs_getextattr_sa(struct vop_getextattr_args *ap, const char *attrname)
error = nvlist_lookup_byte_array(zp->z_xattr_cached, attrname,
&nv_value, &nv_size);
- if (error)
- return (error);
+ if (error != 0)
+ return (SET_ERROR(error));
if (ap->a_size != NULL)
*ap->a_size = nv_size;
else if (ap->a_uio != NULL)
error = uiomove(nv_value, nv_size, ap->a_uio);
+ if (error != 0)
+ return (SET_ERROR(error));
+
+ return (0);
+}
+
+static int
+zfs_getextattr_impl(struct vop_getextattr_args *ap, boolean_t compat)
+{
+ znode_t *zp = VTOZ(ap->a_vp);
+ zfsvfs_t *zfsvfs = ZTOZSB(zp);
+ char attrname[EXTATTR_MAXNAMELEN+1];
+ int error;
+
+ error = zfs_create_attrname(ap->a_attrnamespace, ap->a_name, attrname,
+ sizeof (attrname), compat);
+ if (error != 0)
+ return (error);
+ error = ENOENT;
+ if (zfsvfs->z_use_sa && zp->z_is_sa)
+ error = zfs_getextattr_sa(ap, attrname);
+ if (error == ENOENT)
+ error = zfs_getextattr_dir(ap, attrname);
return (error);
}
@@ -5407,7 +5442,6 @@ zfs_getextattr(struct vop_getextattr_args *ap)
{
znode_t *zp = VTOZ(ap->a_vp);
zfsvfs_t *zfsvfs = ZTOZSB(zp);
- char attrname[EXTATTR_MAXNAMELEN+1];
int error;
/*
@@ -5419,10 +5453,9 @@ zfs_getextattr(struct vop_getextattr_args *ap)
error = extattr_check_cred(ap->a_vp, ap->a_attrnamespace,
ap->a_cred, ap->a_td, VREAD);
if (error != 0)
- return (error);
+ return (SET_ERROR(error));
- error = zfs_create_attrname(ap->a_attrnamespace, ap->a_name, attrname,
- sizeof (attrname));
+ error = zfs_check_attrname(ap->a_name);
if (error != 0)
return (error);
@@ -5430,10 +5463,18 @@ zfs_getextattr(struct vop_getextattr_args *ap)
ZFS_ENTER(zfsvfs);
ZFS_VERIFY_ZP(zp)
rw_enter(&zp->z_xattr_lock, RW_READER);
- if (zfsvfs->z_use_sa && zp->z_is_sa)
- error = zfs_getextattr_sa(ap, attrname);
+
+ boolean_t compat = !!(zfsvfs->z_flags & ZSB_XATTR_COMPAT);
+ error = zfs_getextattr_impl(ap, compat);
+ if (error == ENOENT && ap->a_attrnamespace == EXTATTR_NAMESPACE_USER)
+ /*
+ * Fall back to the alternate namespace format if we failed to
+ * find a user xattr.
+ */
+ error = zfs_getextattr_impl(ap, !compat);
if (error == ENOENT)
- error = zfs_getextattr_dir(ap, attrname);
+ error = SET_ERROR(ENOATTR);
+
rw_exit(&zp->z_xattr_lock);
ZFS_EXIT(zfsvfs);
if (error == ENOENT)
@@ -5470,10 +5511,12 @@ zfs_deleteextattr_dir(struct vop_deleteextattr_args *ap, const char *attrname)
vp = nd.ni_vp;
if (error != 0) {
NDFREE(&nd, NDF_ONLY_PNBUF);
- return (error);
+ return (SET_ERROR(error));
}
error = VOP_REMOVE(nd.ni_dvp, vp, &nd.ni_cnd);
+ if (error != 0)
+ error = SET_ERROR(error);
NDFREE(&nd, NDF_ONLY_PNBUF);
vput(nd.ni_dvp);
@@ -5503,6 +5546,8 @@ zfs_deleteextattr_sa(struct vop_deleteextattr_args *ap, const char *attrname)
error = nvlist_remove(nvl, attrname, DATA_TYPE_BYTE_ARRAY);
if (error == 0)
error = zfs_sa_set_xattr(zp);
+ else
+ error = SET_ERROR(error);
if (error != 0) {
zp->z_xattr_cached = NULL;
nvlist_free(nvl);
@@ -5510,30 +5555,16 @@ zfs_deleteextattr_sa(struct vop_deleteextattr_args *ap, const char *attrname)
return (error);
}
-/*
- * Vnode operation to remove a named attribute.
- */
static int
-zfs_deleteextattr(struct vop_deleteextattr_args *ap)
+zfs_deleteextattr_impl(struct vop_deleteextattr_args *ap, boolean_t compat)
{
znode_t *zp = VTOZ(ap->a_vp);
zfsvfs_t *zfsvfs = ZTOZSB(zp);
char attrname[EXTATTR_MAXNAMELEN+1];
int error;
- /*
- * If the xattr property is off, refuse the request.
- */
- if (!(zfsvfs->z_flags & ZSB_XATTR))
- return (SET_ERROR(EOPNOTSUPP));
-
- error = extattr_check_cred(ap->a_vp, ap->a_attrnamespace,
- ap->a_cred, ap->a_td, VWRITE);
- if (error != 0)
- return (error);
-
error = zfs_create_attrname(ap->a_attrnamespace, ap->a_name, attrname,
- sizeof (attrname));
+ sizeof (attrname), compat);
if (error != 0)
return (error);
@@ -5558,6 +5589,45 @@ zfs_deleteextattr(struct vop_deleteextattr_args *ap)
if (error == 0)
error = zfs_deleteextattr_dir(ap, attrname);
}
+ return (error);
+}
+
+/*
+ * Vnode operation to remove a named attribute.
+ */
+static int
+zfs_deleteextattr(struct vop_deleteextattr_args *ap)
+{
+ znode_t *zp = VTOZ(ap->a_vp);
+ zfsvfs_t *zfsvfs = ZTOZSB(zp);
+ int error;
+
+ /*
+ * If the xattr property is off, refuse the request.
+ */
+ if (!(zfsvfs->z_flags & ZSB_XATTR))
+ return (SET_ERROR(EOPNOTSUPP));
+
+ error = extattr_check_cred(ap->a_vp, ap->a_attrnamespace,
+ ap->a_cred, ap->a_td, VWRITE);
+ if (error != 0)
+ return (SET_ERROR(error));
+
+ error = zfs_check_attrname(ap->a_name);
+ if (error != 0)
+ return (error);
+
+ ZFS_ENTER(zfsvfs);
+ ZFS_VERIFY_ZP(zp);
+ rw_enter(&zp->z_xattr_lock, RW_WRITER);
+
+ boolean_t compat = !!(zfsvfs->z_flags & ZSB_XATTR_COMPAT);
+ error = zfs_deleteextattr_impl(ap, compat);
+ if (error == ENOENT && ap->a_attrnamespace == EXTATTR_NAMESPACE_USER)
+ error = zfs_deleteextattr_impl(ap, !compat);
+ if (error == ENOENT)
+ error = SET_ERROR(ENOATTR);
+
rw_exit(&zp->z_xattr_lock);
ZFS_EXIT(zfsvfs);
if (error == ENOENT)
@@ -5597,13 +5667,15 @@ zfs_setextattr_dir(struct vop_setextattr_args *ap, const char *attrname)
vp = nd.ni_vp;
NDFREE(&nd, NDF_ONLY_PNBUF);
if (error != 0)
- return (error);
+ return (SET_ERROR(error));
VATTR_NULL(&va);
va.va_size = 0;
error = VOP_SETATTR(vp, &va, ap->a_cred);
if (error == 0)
VOP_WRITE(vp, ap->a_uio, IO_UNIT, ap->a_cred);
+ else
+ error = SET_ERROR(error);
VOP_UNLOCK1(vp);
vn_close(vp, flags, ap->a_cred, td);
@@ -5631,13 +5703,15 @@ zfs_setextattr_sa(struct vop_setextattr_args *ap, const char *attrname)
return (SET_ERROR(EFBIG));
error = nvlist_size(nvl, &sa_size, NV_ENCODE_XDR);
if (error != 0)
- return (error);
+ return (SET_ERROR(error));
if (sa_size > DXATTR_MAX_SA_SIZE)
return (SET_ERROR(EFBIG));
uchar_t *buf = kmem_alloc(entry_size, KM_SLEEP);
error = uiomove(buf, entry_size, ap->a_uio);
if (error == 0)
error = nvlist_add_byte_array(nvl, attrname, buf, entry_size);
+ else
+ error = SET_ERROR(error);
kmem_free(buf, entry_size);
if (error == 0)
error = zfs_sa_set_xattr(zp);
@@ -5648,30 +5722,16 @@ zfs_setextattr_sa(struct vop_setextattr_args *ap, const char *attrname)
return (error);
}
-/*
- * Vnode operation to set a named attribute.
- */
static int
-zfs_setextattr(struct vop_setextattr_args *ap)
+zfs_setextattr_impl(struct vop_setextattr_args *ap, boolean_t compat)
{
znode_t *zp = VTOZ(ap->a_vp);
zfsvfs_t *zfsvfs = ZTOZSB(zp);
char attrname[EXTATTR_MAXNAMELEN+1];
int error;
- /*
- * If the xattr property is off, refuse the request.
- */
- if (!(zfsvfs->z_flags & ZSB_XATTR))
- return (SET_ERROR(EOPNOTSUPP));
-
- error = extattr_check_cred(ap->a_vp, ap->a_attrnamespace,
- ap->a_cred, ap->a_td, VWRITE);
- if (error != 0)
- return (error);
-
error = zfs_create_attrname(ap->a_attrnamespace, ap->a_name, attrname,
- sizeof (attrname));
+ sizeof (attrname), compat);
if (error != 0)
return (error);
@@ -5681,9 +5741,6 @@ zfs_setextattr(struct vop_setextattr_args *ap)
.a_td = ap->a_td,
};
error = ENOENT;
- ZFS_ENTER(zfsvfs);
- ZFS_VERIFY_ZP(zp);
- rw_enter(&zp->z_xattr_lock, RW_WRITER);
if (zfsvfs->z_use_sa && zp->z_is_sa && zfsvfs->z_xattr_sa) {
error = zfs_setextattr_sa(ap, attrname);
if (error == 0)
@@ -5702,6 +5759,51 @@ zfs_setextattr(struct vop_setextattr_args *ap)
*/
zfs_deleteextattr_sa(&vda, attrname);
}
+ if (error == 0 && ap->a_attrnamespace == EXTATTR_NAMESPACE_USER) {
+ /*
+ * Also clear all versions of the alternate compat name.
+ */
+ if (zfs_create_attrname(ap->a_attrnamespace, ap->a_name,
+ attrname, sizeof (attrname), !compat) == 0) {
+ zfs_deleteextattr_sa(&vda, attrname);
+ zfs_deleteextattr_dir(&vda, attrname);
+ }
+ }
+ return (error);
+}
+
+/*
+ * Vnode operation to set a named attribute.
+ */
+static int
+zfs_setextattr(struct vop_setextattr_args *ap)
+{
+ znode_t *zp = VTOZ(ap->a_vp);
+ zfsvfs_t *zfsvfs = ZTOZSB(zp);
+ int error;
+
+ /*
+ * If the xattr property is off, refuse the request.
+ */
+ if (!(zfsvfs->z_flags & ZSB_XATTR))
+ return (SET_ERROR(EOPNOTSUPP));
+
+ error = extattr_check_cred(ap->a_vp, ap->a_attrnamespace,
+ ap->a_cred, ap->a_td, VWRITE);
+ if (error != 0)
+ return (SET_ERROR(error));
+
+ error = zfs_check_attrname(ap->a_name);
+ if (error != 0)
+ return (error);
+
+ ZFS_ENTER(zfsvfs);
+ ZFS_VERIFY_ZP(zp);
+ rw_enter(&zp->z_xattr_lock, RW_WRITER);
+
+ boolean_t compat = !!(zfsvfs->z_flags & ZSB_XATTR_COMPAT);
+ error = zfs_setextattr_impl(ap, compat);
+
rw_exit(&zp->z_xattr_lock);
ZFS_EXIT(zfsvfs);
return (error);
@@ -5747,7 +5849,7 @@ zfs_listextattr_dir(struct vop_listextattr_args *ap, const char *attrprefix)
vp = nd.ni_vp;
NDFREE(&nd, NDF_ONLY_PNBUF);
if (error != 0)
- return (error);
+ return (SET_ERROR(error));
auio.uio_iov = &aiov;
auio.uio_iovcnt = 1;
@@ -5763,8 +5865,10 @@ zfs_listextattr_dir(struct vop_listextattr_args *ap, const char *attrprefix)
aiov.iov_len = sizeof (dirbuf);
auio.uio_resid = sizeof (dirbuf);
error = VOP_READDIR(vp, &auio, ap->a_cred, &eof, NULL, NULL);
- if (error != 0)
+ if (error != 0) {
+ error = SET_ERROR(error);
break;
+ }
int done = sizeof (dirbuf) - auio.uio_resid;
for (int pos = 0; pos < done; ) {
struct dirent *dp = (struct dirent *)(dirbuf + pos);
@@ -5776,7 +5880,7 @@ zfs_listextattr_dir(struct vop_listextattr_args *ap, const char *attrprefix)
if (dp->d_type != DT_REG && dp->d_type != DT_UNKNOWN)
continue;
else if (plen == 0 &&
- strncmp(dp->d_name, "freebsd:", 8) == 0)
+ ZFS_XA_NS_PREFIX_FORBIDDEN(dp->d_name))
continue;
else if (strncmp(dp->d_name, attrprefix, plen) != 0)
continue;
@@ -5793,8 +5897,10 @@ zfs_listextattr_dir(struct vop_listextattr_args *ap, const char *attrprefix)
char *namep = dp->d_name + plen;
error = uiomove(namep, nlen, ap->a_uio);
}
- if (error != 0)
+ if (error != 0) {
+ error = SET_ERROR(error);
break;
+ }
}
}
} while (!eof && error == 0);
@@ -5839,14 +5945,36 @@ zfs_listextattr_sa(struct vop_listextattr_args *ap, const char *attrprefix)
char *namep = __DECONST(char *, name) + plen;
error = uiomove(namep, nlen, ap->a_uio);
}
- if (error != 0)
+ if (error != 0) {
+ error = SET_ERROR(error);
break;
+ }
}
}
return (error);
}
+static int
+zfs_listextattr_impl(struct vop_listextattr_args *ap, boolean_t compat)
+{
+ znode_t *zp = VTOZ(ap->a_vp);
+ zfsvfs_t *zfsvfs = ZTOZSB(zp);
+ char attrprefix[16];
+ int error;
+
+ error = zfs_create_attrname(ap->a_attrnamespace, "", attrprefix,
+ sizeof (attrprefix), compat);
+ if (error != 0)
+ return (error);
+
+ if (zfsvfs->z_use_sa && zp->z_is_sa)
+ error = zfs_listextattr_sa(ap, attrprefix);
+ if (error == 0)
+ error = zfs_listextattr_dir(ap, attrprefix);
+ return (error);
+}
+
/*
* Vnode operation to retrieve extended attributes on a vnode.
*/
@@ -5855,7 +5983,6 @@ zfs_listextattr(struct vop_listextattr_args *ap)
{
znode_t *zp = VTOZ(ap->a_vp);
zfsvfs_t *zfsvfs = ZTOZSB(zp);
- char attrprefix[16];
int error;
if (ap->a_size != NULL)
@@ -5870,20 +5997,18 @@ zfs_listextattr(struct vop_listextattr_args *ap)
error = extattr_check_cred(ap->a_vp, ap->a_attrnamespace,
ap->a_cred, ap->a_td, VREAD);
if (error != 0)
- return (error);
-
- error = zfs_create_attrname(ap->a_attrnamespace, "", attrprefix,
- sizeof (attrprefix));
- if (error != 0)
- return (error);
+ return (SET_ERROR(error));
ZFS_ENTER(zfsvfs);
ZFS_VERIFY_ZP(zp);
rw_enter(&zp->z_xattr_lock, RW_READER);
- if (zfsvfs->z_use_sa && zp->z_is_sa)
- error = zfs_listextattr_sa(ap, attrprefix);
- if (error == 0)
- error = zfs_listextattr_dir(ap, attrprefix);
+
+ boolean_t compat = !!(zfsvfs->z_flags & ZSB_XATTR_COMPAT);
+ error = zfs_listextattr_impl(ap, compat);
+ if (error == 0 && ap->a_attrnamespace == EXTATTR_NAMESPACE_USER)
+ /* Also list user xattrs with the alternate format. */
+ error = zfs_listextattr_impl(ap, !compat);
+
rw_exit(&zp->z_xattr_lock);
ZFS_EXIT(zfsvfs);
return (error);
diff --git a/module/os/linux/zfs/zfs_vfsops.c b/module/os/linux/zfs/zfs_vfsops.c
index 40da975fb2a8..1ad88c313fe5 100644
--- a/module/os/linux/zfs/zfs_vfsops.c
+++ b/module/os/linux/zfs/zfs_vfsops.c
@@ -58,6 +58,7 @@
#include
#include
#include
+#include
#include
#include
#include "zfs_comutil.h"
@@ -352,6 +353,29 @@ xattr_changed_cb(void *arg, uint64_t newval)
}
}
+static void
+xattr_compat_changed_cb(void *arg, uint64_t newval)
+{
+ zfsvfs_t *zfsvfs = arg;
+
+ /*
+ * Force the old incompatible Linux behavior
+ * if feature@xattr_compat is disabled.
+ */
+ if (!spa_feature_is_enabled(dmu_objset_spa(zfsvfs->z_os),
+ SPA_FEATURE_XATTR_COMPAT))
+ newval = ZFS_XATTR_COMPAT_LINUX;
+
+ switch (newval) {
+ case ZFS_XATTR_COMPAT_ALL:
+ zfsvfs->z_flags |= ZSB_XATTR_COMPAT;
+ break;
+ case ZFS_XATTR_COMPAT_LINUX:
+ zfsvfs->z_flags &= ~ZSB_XATTR_COMPAT;
+ break;
+ }
+}
+
static void
acltype_changed_cb(void *arg, uint64_t newval)
{
@@ -510,6 +534,9 @@ zfs_register_callbacks(vfs_t *vfsp)
zfs_prop_to_name(ZFS_PROP_RELATIME), relatime_changed_cb, zfsvfs);
error = error ? error : dsl_prop_register(ds,
zfs_prop_to_name(ZFS_PROP_XATTR), xattr_changed_cb, zfsvfs);
+ error = error ? error : dsl_prop_register(ds,
+ zfs_prop_to_name(ZFS_PROP_XATTR_COMPAT), xattr_compat_changed_cb,
+ zfsvfs);
error = error ? error : dsl_prop_register(ds,
zfs_prop_to_name(ZFS_PROP_RECORDSIZE), blksz_changed_cb, zfsvfs);
error = error ? error : dsl_prop_register(ds,
@@ -1528,6 +1555,10 @@ zfs_domount(struct super_block *sb, zfs_mnt_t *zm, int silent)
"xattr", &pval, NULL)))
goto out;
xattr_changed_cb(zfsvfs, pval);
+ if ((error = dsl_prop_get_integer(osname,
+ "xattr_compat", &pval, NULL)))
+ goto out;
+ xattr_compat_changed_cb(zfsvfs, pval);
if ((error = dsl_prop_get_integer(osname,
"acltype", &pval, NULL)))
goto out;
diff --git a/module/os/linux/zfs/zpl_xattr.c b/module/os/linux/zfs/zpl_xattr.c
index 78baf1f956f1..9c506c6541d8 100644
--- a/module/os/linux/zfs/zpl_xattr.c
+++ b/module/os/linux/zfs/zpl_xattr.c
@@ -89,15 +89,6 @@
#define NFS41ACL_XATTR "system.nfs4_acl_xdr"
-typedef struct xattr_filldir {
- size_t size;
- size_t offset;
- char *buf;
- struct dentry *dentry;
-} xattr_filldir_t;
-
-static const struct xattr_handler *zpl_xattr_handler(const char *);
-
static const struct {
int kmask;
int zfsperm;
@@ -117,31 +108,21 @@ static const struct {
#define GENERIC_MASK(mask) ((mask & ~(MAY_READ | MAY_WRITE | MAY_EXEC)) == 0)
-static int
-zpl_xattr_permission(xattr_filldir_t *xf, const char *name, int name_len)
-{
- static const struct xattr_handler *handler;
- struct dentry *d = xf->dentry;
-
- handler = zpl_xattr_handler(name);
- if (!handler)
- return (0);
+enum xattr_permission {
+ XAPERM_DENY,
+ XAPERM_ALLOW,
+ XAPERM_COMPAT,
+};
- if (handler->list) {
-#if defined(HAVE_XATTR_LIST_SIMPLE)
- if (!handler->list(d))
- return (0);
-#elif defined(HAVE_XATTR_LIST_DENTRY)
- if (!handler->list(d, NULL, 0, name, name_len, 0))
- return (0);
-#elif defined(HAVE_XATTR_LIST_HANDLER)
- if (!handler->list(handler, d, NULL, 0, name, name_len))
- return (0);
-#endif
- }
+typedef struct xattr_filldir {
+ size_t size;
+ size_t offset;
+ char *buf;
+ struct dentry *dentry;
+} xattr_filldir_t;
- return (1);
-}
+static enum xattr_permission zpl_xattr_permission(xattr_filldir_t *,
+ const char *, int);
/*
* Determine is a given xattr name should be visible and if so copy it
@@ -150,10 +131,27 @@ zpl_xattr_permission(xattr_filldir_t *xf, const char *name, int name_len)
static int
zpl_xattr_filldir(xattr_filldir_t *xf, const char *name, int name_len)
{
+ enum xattr_permission perm;
+
/* Check permissions using the per-namespace list xattr handler. */
- if (!zpl_xattr_permission(xf, name, name_len))
+ perm = zpl_xattr_permission(xf, name, name_len);
+ if (perm == XAPERM_DENY)
return (0);
+ /* Prefix the name with "user." if it does not have a namespace. */
+ if (perm == XAPERM_COMPAT) {
+ if (xf->buf) {
+ if (xf->offset + XATTR_USER_PREFIX_LEN + 1 > xf->size)
+ return (-ERANGE);
+
+ memcpy(xf->buf + xf->offset, XATTR_USER_PREFIX,
+ XATTR_USER_PREFIX_LEN);
+ xf->buf[xf->offset + XATTR_USER_PREFIX_LEN] = '\0';
+ }
+
+ xf->offset += XATTR_USER_PREFIX_LEN;
+ }
+
/* When xf->buf is NULL only calculate the required size. */
if (xf->buf) {
if (xf->offset + name_len + 1 > xf->size)
@@ -730,19 +728,31 @@ static int
__zpl_xattr_user_get(struct inode *ip, const char *name,
void *value, size_t size)
{
- char *xattr_name;
+ boolean_t compat = !!(ITOZSB(ip)->z_flags & ZSB_XATTR_COMPAT);
int error;
/* xattr_resolve_name will do this for us if this is defined */
#ifndef HAVE_XATTR_HANDLER_NAME
if (strcmp(name, "") == 0)
return (-EINVAL);
#endif
+ if (ZFS_XA_NS_PREFIX_FORBIDDEN(name))
+ return (-EINVAL);
if (!(ITOZSB(ip)->z_flags & ZSB_XATTR))
return (-EOPNOTSUPP);
- xattr_name = kmem_asprintf("%s%s", XATTR_USER_PREFIX, name);
- error = zpl_xattr_get(ip, xattr_name, value, size);
- kmem_strfree(xattr_name);
+ /*
+ * Try to look up the name without the namespace prefix first for
+ * compatibility with xattrs from other platforms. If that fails,
+ * try again with the namespace prefix.
+ */
+ if (compat)
+ error = zpl_xattr_get(ip, name, value, size);
+ if (!compat || error == -ENODATA) {
+ char *xattr_name;
+ xattr_name = kmem_asprintf("%s%s", XATTR_USER_PREFIX, name);
+ error = zpl_xattr_get(ip, xattr_name, value, size);
+ kmem_strfree(xattr_name);
+ }
return (error);
}
@@ -753,19 +763,43 @@ __zpl_xattr_user_set(struct inode *ip, const char *name,
const void *value, size_t size, int flags)
{
char *xattr_name;
+ boolean_t compat = !!(ITOZSB(ip)->z_flags & ZSB_XATTR_COMPAT);
int error;
/* xattr_resolve_name will do this for us if this is defined */
#ifndef HAVE_XATTR_HANDLER_NAME
if (strcmp(name, "") == 0)
return (-EINVAL);
#endif
+ if (ZFS_XA_NS_PREFIX_FORBIDDEN(name))
+ return (-EINVAL);
if (!(ITOZSB(ip)->z_flags & ZSB_XATTR))
return (-EOPNOTSUPP);
+ /*
+ * Remove any namespaced version of the xattr so we only set the
+ * version compatible with other platforms.
+ *
+ * The following flags must be handled correctly:
+ *
+ * XATTR_CREATE: fail if xattr already exists
+ * XATTR_REPLACE: fail if xattr does not exist
+ */
xattr_name = kmem_asprintf("%s%s", XATTR_USER_PREFIX, name);
- error = zpl_xattr_set(ip, xattr_name, value, size, flags);
+ if (compat)
+ error = zpl_xattr_set(ip, xattr_name, NULL, 0, flags);
+ else
+ error = zpl_xattr_set(ip, xattr_name, value, size, flags);
kmem_strfree(xattr_name);
+ if (!compat || error == -EEXIST)
+ return (error);
+ if (error == 0 && (flags & XATTR_REPLACE))
+ flags &= ~XATTR_REPLACE;
+ error = zpl_xattr_set(ip, name, value, size, flags);
+
+ dsl_dataset_t *ds = dmu_objset_ds(ITOZSB(ip)->z_os);
+ ds->ds_feature_activation[SPA_FEATURE_XATTR_COMPAT] = (void *)B_TRUE;
+
return (error);
}
ZPL_XATTR_SET_WRAPPER(zpl_xattr_user_set);
@@ -1867,6 +1901,45 @@ zpl_xattr_handler(const char *name)
return (NULL);
}
+static enum xattr_permission
+zpl_xattr_permission(xattr_filldir_t *xf, const char *name, int name_len)
+{
+ const struct xattr_handler *handler;
+ struct dentry *d = xf->dentry;
+ boolean_t compat = !!(ITOZSB(d->d_inode)->z_flags & ZSB_XATTR_COMPAT);
+ enum xattr_permission perm = XAPERM_ALLOW;
+
+ handler = zpl_xattr_handler(name);
+ if (handler == NULL) {
+ if (!compat)
+ return (XAPERM_DENY);
+ /* Do not expose FreeBSD system namespace xattrs. */
+ if (ZFS_XA_NS_PREFIX_MATCH(FREEBSD, name))
+ return (XAPERM_DENY);
+ /*
+ * Anything that doesn't match a known namespace gets put in the
+ * user namespace for compatibility with other platforms.
+ */
+ perm = XAPERM_COMPAT;
+ handler = &zpl_xattr_user_handler;
+ }
+
+ if (handler->list) {
+#if defined(HAVE_XATTR_LIST_SIMPLE)
+ if (!handler->list(d))
+ return (XAPERM_DENY);
+#elif defined(HAVE_XATTR_LIST_DENTRY)
+ if (!handler->list(d, NULL, 0, name, name_len, 0))
+ return (XAPERM_DENY);
+#elif defined(HAVE_XATTR_LIST_HANDLER)
+ if (!handler->list(handler, d, NULL, 0, name, name_len))
+ return (XAPERM_DENY);
+#endif
+ }
+
+ return (perm);
+}
+
#if !defined(HAVE_POSIX_ACL_RELEASE) || defined(HAVE_POSIX_ACL_RELEASE_GPL_ONLY)
struct acl_rel_struct {
struct acl_rel_struct *next;
diff --git a/module/zcommon/zfeature_common.c b/module/zcommon/zfeature_common.c
index fc0e09605eef..48c90d384ff2 100644
--- a/module/zcommon/zfeature_common.c
+++ b/module/zcommon/zfeature_common.c
@@ -598,6 +598,19 @@ zpool_feature_init(void)
zfeature_register(SPA_FEATURE_DRAID,
"org.openzfs:draid", "draid", "Support for distributed spare RAID",
ZFEATURE_FLAG_MOS, ZFEATURE_TYPE_BOOLEAN, NULL);
+
+
+ {
+ static const spa_feature_t xattr_compat_deps[] = {
+ SPA_FEATURE_EXTENSIBLE_DATASET,
+ SPA_FEATURE_NONE
+ };
+ zfeature_register(SPA_FEATURE_XATTR_COMPAT,
+ "com.ixsystems:xattr_compat", "xattr_compat",
+ "Support for cross-platform compatible xattr namespace encoding",
+ ZFEATURE_FLAG_PER_DATASET, ZFEATURE_TYPE_BOOLEAN,
+ xattr_compat_deps);
+ }
}
#if defined(_KERNEL)
diff --git a/module/zcommon/zfs_prop.c b/module/zcommon/zfs_prop.c
index 402d749c1aeb..b4eb16158fe8 100644
--- a/module/zcommon/zfs_prop.c
+++ b/module/zcommon/zfs_prop.c
@@ -357,6 +357,12 @@ zfs_prop_init(void)
{ NULL }
};
+ static zprop_index_t xattr_compat_table[] = {
+ { "linux", ZFS_XATTR_COMPAT_LINUX },
+ { "all", ZFS_XATTR_COMPAT_ALL },
+ { NULL }
+ };
+
static zprop_index_t dnsize_table[] = {
{ "legacy", ZFS_DNSIZE_LEGACY },
{ "auto", ZFS_DNSIZE_AUTO },
@@ -459,6 +465,10 @@ zfs_prop_init(void)
zprop_register_index(ZFS_PROP_XATTR, "xattr", ZFS_XATTR_DIR,
PROP_INHERIT, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_SNAPSHOT,
"on | off | dir | sa", "XATTR", xattr_table);
+ zprop_register_index(ZFS_PROP_XATTR_COMPAT, "xattr_compat",
+ ZFS_XATTR_COMPAT_ALL, PROP_INHERIT,
+ ZFS_TYPE_FILESYSTEM | ZFS_TYPE_SNAPSHOT,
+ "all | linux", "XATTR_COMPAT", xattr_compat_table);
zprop_register_index(ZFS_PROP_DNODESIZE, "dnodesize",
ZFS_DNSIZE_LEGACY, PROP_INHERIT, ZFS_TYPE_FILESYSTEM,
"legacy | auto | 1k | 2k | 4k | 8k | 16k", "DNSIZE", dnsize_table);
diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg b/tests/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg
index 6075e1f1abbd..20d29c9169e6 100644
--- a/tests/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg
+++ b/tests/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg
@@ -82,6 +82,7 @@ typeset -a properties=(
"feature@log_spacemap"
"feature@device_rebuild"
"feature@draid"
+ "feature@xattr_compat"
)
if is_linux || is_freebsd; then