aboutsummaryrefslogtreecommitdiffstats
path: root/module
diff options
context:
space:
mode:
authorRyan Moeller <[email protected]>2021-04-29 03:27:57 +0000
committerTony Hutter <[email protected]>2021-09-14 12:09:35 -0700
commit6fe6192796f6835163523db94130fb842022f6cf (patch)
tree6b9f20b1bb757885df72683f5825f89b4b07a793 /module
parent18260685236ee15db24a333de5d89d73b3165358 (diff)
FreeBSD: Implement xattr=sa
FreeBSD historically has not cared about the xattr property; it was always treated as xattr=on. With xattr=on, xattrs are stored as files in a hidden xattr directory. With xattr=sa, xattrs are stored as system attributes and get cached in nvlists during xattr operations. This makes SA xattrs simpler and more efficient to manipulate. FreeBSD needs to implement the SA xattr operations for feature parity with Linux and to ensure that SA xattrs are accessible when migrated or replicated from Linux. Following the example set by Linux, refactor our existing extattr vnops to split off the parts handling dir style xattrs, and add the corresponding SA handling parts. Reviewed-by: Brian Behlendorf <[email protected]> Reviewed-by: Alexander Motin <[email protected]> Signed-off-by: Ryan Moeller <[email protected]> Closes #11997
Diffstat (limited to 'module')
-rw-r--r--module/os/freebsd/zfs/zfs_vfsops.c4
-rw-r--r--module/os/freebsd/zfs/zfs_vnops_os.c514
-rw-r--r--module/os/freebsd/zfs/zfs_znode.c24
3 files changed, 395 insertions, 147 deletions
diff --git a/module/os/freebsd/zfs/zfs_vfsops.c b/module/os/freebsd/zfs/zfs_vfsops.c
index cb46a9143..c53430935 100644
--- a/module/os/freebsd/zfs/zfs_vfsops.c
+++ b/module/os/freebsd/zfs/zfs_vfsops.c
@@ -846,6 +846,10 @@ zfsvfs_init(zfsvfs_t *zfsvfs, objset_t *os)
&sa_obj);
if (error != 0)
return (error);
+
+ error = zfs_get_zplprop(os, ZFS_PROP_XATTR, &val);
+ if (error == 0 && val == ZFS_XATTR_SA)
+ zfsvfs->z_xattr_sa = B_TRUE;
}
error = sa_setup(os, sa_obj, zfs_attr_table, ZPL_END,
diff --git a/module/os/freebsd/zfs/zfs_vnops_os.c b/module/os/freebsd/zfs/zfs_vnops_os.c
index 8205e82dc..a18ac21dc 100644
--- a/module/os/freebsd/zfs/zfs_vnops_os.c
+++ b/module/os/freebsd/zfs/zfs_vnops_os.c
@@ -5301,6 +5301,29 @@ zfs_create_attrname(int attrnamespace, const char *name, char *attrname,
return (0);
}
+static int
+zfs_ensure_xattr_cached(znode_t *zp)
+{
+ int error = 0;
+
+ ASSERT(RW_LOCK_HELD(&zp->z_xattr_lock));
+
+ if (zp->z_xattr_cached != NULL)
+ return (0);
+
+ if (rw_write_held(&zp->z_xattr_lock))
+ return (zfs_sa_get_xattr(zp));
+
+ if (!rw_tryupgrade(&zp->z_xattr_lock)) {
+ rw_exit(&zp->z_xattr_lock);
+ rw_enter(&zp->z_xattr_lock, RW_WRITER);
+ }
+ if (zp->z_xattr_cached == NULL)
+ error = zfs_sa_get_xattr(zp);
+ rw_downgrade(&zp->z_xattr_lock);
+ return (error);
+}
+
#ifndef _SYS_SYSPROTO_H_
struct vop_getextattr {
IN struct vnode *a_vp;
@@ -5313,45 +5336,19 @@ struct vop_getextattr {
};
#endif
-/*
- * Vnode operating to retrieve a named extended attribute.
- */
static int
-zfs_getextattr(struct vop_getextattr_args *ap)
+zfs_getextattr_dir(struct vop_getextattr_args *ap, const char *attrname)
{
- zfsvfs_t *zfsvfs = VTOZ(ap->a_vp)->z_zfsvfs;
struct thread *td = ap->a_td;
struct nameidata nd;
- char attrname[255];
struct vattr va;
vnode_t *xvp = NULL, *vp;
int error, flags;
- /*
- * 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, VREAD);
- if (error != 0)
- return (error);
-
- error = zfs_create_attrname(ap->a_attrnamespace, ap->a_name, attrname,
- sizeof (attrname));
- if (error != 0)
- return (error);
-
- ZFS_ENTER(zfsvfs);
-
error = zfs_lookup(ap->a_vp, NULL, &xvp, NULL, 0, ap->a_cred, td,
LOOKUP_XATTR, B_FALSE);
- if (error != 0) {
- ZFS_EXIT(zfsvfs);
+ if (error != 0)
return (error);
- }
flags = FREAD;
NDINIT_ATVP(&nd, LOOKUP, NOFOLLOW, UIO_SYSSPACE, attrname,
@@ -5359,12 +5356,8 @@ zfs_getextattr(struct vop_getextattr_args *ap)
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) {
- ZFS_EXIT(zfsvfs);
- if (error == ENOENT)
- error = ENOATTR;
+ if (error != 0)
return (error);
- }
if (ap->a_size != NULL) {
error = VOP_GETATTR(vp, &va, ap->a_cred);
@@ -5375,42 +5368,56 @@ zfs_getextattr(struct vop_getextattr_args *ap)
VOP_UNLOCK1(vp);
vn_close(vp, flags, ap->a_cred, td);
- ZFS_EXIT(zfsvfs);
return (error);
}
-#ifndef _SYS_SYSPROTO_H_
-struct vop_deleteextattr {
- IN struct vnode *a_vp;
- IN int a_attrnamespace;
- IN const char *a_name;
- IN struct ucred *a_cred;
- IN struct thread *a_td;
-};
-#endif
+static int
+zfs_getextattr_sa(struct vop_getextattr_args *ap, const char *attrname)
+{
+ znode_t *zp = VTOZ(ap->a_vp);
+ uchar_t *nv_value;
+ uint_t nv_size;
+ int error;
+
+ error = zfs_ensure_xattr_cached(zp);
+ if (error != 0)
+ return (error);
+
+ ASSERT(RW_LOCK_HELD(&zp->z_xattr_lock));
+ ASSERT3P(zp->z_xattr_cached, !=, NULL);
+
+ error = nvlist_lookup_byte_array(zp->z_xattr_cached, attrname,
+ &nv_value, &nv_size);
+ if (error)
+ return (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);
+
+ return (error);
+}
/*
- * Vnode operation to remove a named attribute.
+ * Vnode operation to retrieve a named extended attribute.
*/
static int
-zfs_deleteextattr(struct vop_deleteextattr_args *ap)
+zfs_getextattr(struct vop_getextattr_args *ap)
{
- zfsvfs_t *zfsvfs = VTOZ(ap->a_vp)->z_zfsvfs;
- struct thread *td = ap->a_td;
- struct nameidata nd;
- char attrname[255];
- vnode_t *xvp = NULL, *vp;
+ 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)) {
+ 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);
+ ap->a_cred, ap->a_td, VREAD);
if (error != 0)
return (error);
@@ -5419,24 +5426,50 @@ zfs_deleteextattr(struct vop_deleteextattr_args *ap)
if (error != 0)
return (error);
+ error = ENOENT;
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);
+ if (error == ENOENT)
+ error = zfs_getextattr_dir(ap, attrname);
+ rw_exit(&zp->z_xattr_lock);
+ ZFS_EXIT(zfsvfs);
+ if (error == ENOENT)
+ error = SET_ERROR(ENOATTR);
+ return (error);
+}
+
+#ifndef _SYS_SYSPROTO_H_
+struct vop_deleteextattr {
+ IN struct vnode *a_vp;
+ IN int a_attrnamespace;
+ IN const char *a_name;
+ IN struct ucred *a_cred;
+ IN struct thread *a_td;
+};
+#endif
+
+static int
+zfs_deleteextattr_dir(struct vop_deleteextattr_args *ap, const char *attrname)
+{
+ struct thread *td = ap->a_td;
+ struct nameidata nd;
+ vnode_t *xvp = NULL, *vp;
+ int error;
error = zfs_lookup(ap->a_vp, NULL, &xvp, NULL, 0, ap->a_cred, td,
LOOKUP_XATTR, B_FALSE);
- if (error != 0) {
- ZFS_EXIT(zfsvfs);
+ if (error != 0)
return (error);
- }
NDINIT_ATVP(&nd, DELETE, NOFOLLOW | LOCKPARENT | LOCKLEAF,
UIO_SYSSPACE, attrname, xvp, td);
error = namei(&nd);
vp = nd.ni_vp;
if (error != 0) {
- ZFS_EXIT(zfsvfs);
NDFREE(&nd, NDF_ONLY_PNBUF);
- if (error == ENOENT)
- error = ENOATTR;
return (error);
}
@@ -5448,72 +5481,123 @@ zfs_deleteextattr(struct vop_deleteextattr_args *ap)
vrele(vp);
else
vput(vp);
- ZFS_EXIT(zfsvfs);
return (error);
}
-#ifndef _SYS_SYSPROTO_H_
-struct vop_setextattr {
- IN struct vnode *a_vp;
- IN int a_attrnamespace;
- IN const char *a_name;
- INOUT struct uio *a_uio;
- IN struct ucred *a_cred;
- IN struct thread *a_td;
-};
-#endif
+static int
+zfs_deleteextattr_sa(struct vop_deleteextattr_args *ap, const char *attrname)
+{
+ znode_t *zp = VTOZ(ap->a_vp);
+ nvlist_t *nvl;
+ int error;
+
+ error = zfs_ensure_xattr_cached(zp);
+ if (error != 0)
+ return (error);
+
+ ASSERT(RW_WRITE_HELD(&zp->z_xattr_lock));
+ ASSERT3P(zp->z_xattr_cached, !=, NULL);
+
+ nvl = zp->z_xattr_cached;
+ error = nvlist_remove(nvl, attrname, DATA_TYPE_BYTE_ARRAY);
+ if (error == 0)
+ error = zfs_sa_set_xattr(zp);
+ if (error != 0) {
+ zp->z_xattr_cached = NULL;
+ nvlist_free(nvl);
+ }
+ return (error);
+}
/*
- * Vnode operation to set a named attribute.
+ * Vnode operation to remove a named attribute.
*/
static int
-zfs_setextattr(struct vop_setextattr_args *ap)
+zfs_deleteextattr(struct vop_deleteextattr_args *ap)
{
- zfsvfs_t *zfsvfs = VTOZ(ap->a_vp)->z_zfsvfs;
- struct thread *td = ap->a_td;
- struct nameidata nd;
- char attrname[255];
- struct vattr va;
- vnode_t *xvp = NULL, *vp;
- int error, flags;
+ 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)) {
+ 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));
if (error != 0)
return (error);
+ size_t size = 0;
+ struct vop_getextattr_args vga = {
+ .a_vp = ap->a_vp,
+ .a_size = &size,
+ .a_cred = ap->a_cred,
+ .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) {
+ error = zfs_getextattr_sa(&vga, attrname);
+ if (error == 0)
+ error = zfs_deleteextattr_sa(ap, attrname);
+ }
+ if (error == ENOENT) {
+ error = zfs_getextattr_dir(&vga, attrname);
+ if (error == 0)
+ error = zfs_deleteextattr_dir(ap, attrname);
+ }
+ rw_exit(&zp->z_xattr_lock);
+ ZFS_EXIT(zfsvfs);
+ if (error == ENOENT)
+ error = SET_ERROR(ENOATTR);
+ return (error);
+}
+
+#ifndef _SYS_SYSPROTO_H_
+struct vop_setextattr {
+ IN struct vnode *a_vp;
+ IN int a_attrnamespace;
+ IN const char *a_name;
+ INOUT struct uio *a_uio;
+ IN struct ucred *a_cred;
+ IN struct thread *a_td;
+};
+#endif
+
+static int
+zfs_setextattr_dir(struct vop_setextattr_args *ap, const char *attrname)
+{
+ struct thread *td = ap->a_td;
+ struct nameidata nd;
+ struct vattr va;
+ vnode_t *xvp = NULL, *vp;
+ int error, flags;
error = zfs_lookup(ap->a_vp, NULL, &xvp, NULL, 0, ap->a_cred, td,
LOOKUP_XATTR | CREATE_XATTR_DIR, B_FALSE);
- if (error != 0) {
- ZFS_EXIT(zfsvfs);
+ if (error != 0)
return (error);
- }
flags = FFLAGS(O_WRONLY | O_CREAT);
- 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, 0600, VN_OPEN_INVFS, ap->a_cred,
NULL);
vp = nd.ni_vp;
NDFREE(&nd, NDF_ONLY_PNBUF);
- if (error != 0) {
- ZFS_EXIT(zfsvfs);
+ if (error != 0)
return (error);
- }
VATTR_NULL(&va);
va.va_size = 0;
@@ -5523,70 +5607,131 @@ zfs_setextattr(struct vop_setextattr_args *ap)
VOP_UNLOCK1(vp);
vn_close(vp, flags, ap->a_cred, td);
- ZFS_EXIT(zfsvfs);
return (error);
}
-#ifndef _SYS_SYSPROTO_H_
-struct vop_listextattr {
- IN struct vnode *a_vp;
- IN int a_attrnamespace;
- INOUT struct uio *a_uio;
- OUT size_t *a_size;
- IN struct ucred *a_cred;
- IN struct thread *a_td;
-};
-#endif
+static int
+zfs_setextattr_sa(struct vop_setextattr_args *ap, const char *attrname)
+{
+ znode_t *zp = VTOZ(ap->a_vp);
+ nvlist_t *nvl;
+ size_t sa_size;
+ int error;
+
+ error = zfs_ensure_xattr_cached(zp);
+ if (error != 0)
+ return (error);
+
+ ASSERT(RW_WRITE_HELD(&zp->z_xattr_lock));
+ ASSERT3P(zp->z_xattr_cached, !=, NULL);
+
+ nvl = zp->z_xattr_cached;
+ size_t entry_size = ap->a_uio->uio_resid;
+ if (entry_size > DXATTR_MAX_ENTRY_SIZE)
+ return (SET_ERROR(EFBIG));
+ error = nvlist_size(nvl, &sa_size, NV_ENCODE_XDR);
+ if (error != 0)
+ return (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);
+ kmem_free(buf, entry_size);
+ if (error == 0)
+ error = zfs_sa_set_xattr(zp);
+ if (error != 0) {
+ zp->z_xattr_cached = NULL;
+ nvlist_free(nvl);
+ }
+ return (error);
+}
/*
- * Vnode operation to retrieve extended attributes on a vnode.
+ * Vnode operation to set a named attribute.
*/
static int
-zfs_listextattr(struct vop_listextattr_args *ap)
+zfs_setextattr(struct vop_setextattr_args *ap)
{
- zfsvfs_t *zfsvfs = VTOZ(ap->a_vp)->z_zfsvfs;
- struct thread *td = ap->a_td;
- struct nameidata nd;
- char attrprefix[16];
- uint8_t dirbuf[sizeof (struct dirent)];
- struct dirent *dp;
- struct iovec aiov;
- struct uio auio;
- size_t *sizep = ap->a_size;
- size_t plen;
- vnode_t *xvp = NULL, *vp;
- int done, error, eof, pos;
- zfs_uio_t uio;
-
- zfs_uio_init(&uio, ap->a_uio);
+ 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)) {
+ 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, VREAD);
+ ap->a_cred, ap->a_td, VWRITE);
if (error != 0)
return (error);
- error = zfs_create_attrname(ap->a_attrnamespace, "", attrprefix,
- sizeof (attrprefix));
+ error = zfs_create_attrname(ap->a_attrnamespace, ap->a_name, attrname,
+ sizeof (attrname));
if (error != 0)
return (error);
- plen = strlen(attrprefix);
+ struct vop_deleteextattr_args vda = {
+ .a_vp = ap->a_vp,
+ .a_cred = ap->a_cred,
+ .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)
+ /*
+ * Successfully put into SA, we need to clear the one
+ * in dir if present.
+ */
+ zfs_deleteextattr_dir(&vda, attrname);
+ }
+ if (error) {
+ error = zfs_setextattr_dir(ap, attrname);
+ if (error == 0)
+ /*
+ * Successfully put into dir, we need to clear the one
+ * in SA if present.
+ */
+ zfs_deleteextattr_sa(&vda, attrname);
+ }
+ rw_exit(&zp->z_xattr_lock);
+ ZFS_EXIT(zfsvfs);
+ return (error);
+}
- if (sizep != NULL)
- *sizep = 0;
+#ifndef _SYS_SYSPROTO_H_
+struct vop_listextattr {
+ IN struct vnode *a_vp;
+ IN int a_attrnamespace;
+ INOUT struct uio *a_uio;
+ OUT size_t *a_size;
+ IN struct ucred *a_cred;
+ IN struct thread *a_td;
+};
+#endif
+
+static int
+zfs_listextattr_dir(struct vop_listextattr_args *ap, const char *attrprefix)
+{
+ struct thread *td = ap->a_td;
+ struct nameidata nd;
+ uint8_t dirbuf[sizeof (struct dirent)];
+ struct iovec aiov;
+ struct uio auio;
+ vnode_t *xvp = NULL, *vp;
+ int error, eof;
error = zfs_lookup(ap->a_vp, NULL, &xvp, NULL, 0, ap->a_cred, td,
LOOKUP_XATTR, B_FALSE);
if (error != 0) {
- ZFS_EXIT(zfsvfs);
/*
* ENOATTR means that the EA directory does not yet exist,
* i.e. there are no extended attributes there.
@@ -5601,10 +5746,8 @@ zfs_listextattr(struct vop_listextattr_args *ap)
error = namei(&nd);
vp = nd.ni_vp;
NDFREE(&nd, NDF_ONLY_PNBUF);
- if (error != 0) {
- ZFS_EXIT(zfsvfs);
+ if (error != 0)
return (error);
- }
auio.uio_iov = &aiov;
auio.uio_iovcnt = 1;
@@ -5613,18 +5756,18 @@ zfs_listextattr(struct vop_listextattr_args *ap)
auio.uio_rw = UIO_READ;
auio.uio_offset = 0;
- do {
- uint8_t nlen;
+ size_t plen = strlen(attrprefix);
+ do {
aiov.iov_base = (void *)dirbuf;
aiov.iov_len = sizeof (dirbuf);
auio.uio_resid = sizeof (dirbuf);
error = VOP_READDIR(vp, &auio, ap->a_cred, &eof, NULL, NULL);
- done = sizeof (dirbuf) - auio.uio_resid;
if (error != 0)
break;
- for (pos = 0; pos < done; ) {
- dp = (struct dirent *)(dirbuf + pos);
+ int done = sizeof (dirbuf) - auio.uio_resid;
+ for (int pos = 0; pos < done; ) {
+ struct dirent *dp = (struct dirent *)(dirbuf + pos);
pos += dp->d_reclen;
/*
* XXX: Temporarily we also accept DT_UNKNOWN, as this
@@ -5632,24 +5775,23 @@ zfs_listextattr(struct vop_listextattr_args *ap)
*/
if (dp->d_type != DT_REG && dp->d_type != DT_UNKNOWN)
continue;
- if (plen == 0 &&
+ else if (plen == 0 &&
strncmp(dp->d_name, "freebsd:", 8) == 0)
continue;
else if (strncmp(dp->d_name, attrprefix, plen) != 0)
continue;
- nlen = dp->d_namlen - plen;
- if (sizep != NULL)
- *sizep += 1 + nlen;
- else if (GET_UIO_STRUCT(&uio) != NULL) {
+ uint8_t nlen = dp->d_namlen - plen;
+ if (ap->a_size != NULL) {
+ *ap->a_size += 1 + nlen;
+ } else if (ap->a_uio != NULL) {
/*
* Format of extattr name entry is one byte for
* length and the rest for name.
*/
- error = zfs_uiomove(&nlen, 1, zfs_uio_rw(&uio),
- &uio);
+ error = uiomove(&nlen, 1, ap->a_uio);
if (error == 0) {
- error = zfs_uiomove(dp->d_name + plen,
- nlen, zfs_uio_rw(&uio), &uio);
+ char *namep = dp->d_name + plen;
+ error = uiomove(namep, nlen, ap->a_uio);
}
if (error != 0)
break;
@@ -5658,8 +5800,92 @@ zfs_listextattr(struct vop_listextattr_args *ap)
} while (!eof && error == 0);
vput(vp);
- ZFS_EXIT(zfsvfs);
+ return (error);
+}
+
+static int
+zfs_listextattr_sa(struct vop_listextattr_args *ap, const char *attrprefix)
+{
+ znode_t *zp = VTOZ(ap->a_vp);
+ int error;
+
+ error = zfs_ensure_xattr_cached(zp);
+ if (error != 0)
+ return (error);
+
+ ASSERT(RW_LOCK_HELD(&zp->z_xattr_lock));
+ ASSERT3P(zp->z_xattr_cached, !=, NULL);
+
+ size_t plen = strlen(attrprefix);
+ nvpair_t *nvp = NULL;
+ while ((nvp = nvlist_next_nvpair(zp->z_xattr_cached, nvp)) != NULL) {
+ ASSERT3U(nvpair_type(nvp), ==, DATA_TYPE_BYTE_ARRAY);
+
+ const char *name = nvpair_name(nvp);
+ if (plen == 0 && strncmp(name, "freebsd:", 8) == 0)
+ continue;
+ else if (strncmp(name, attrprefix, plen) != 0)
+ continue;
+ uint8_t nlen = strlen(name) - plen;
+ if (ap->a_size != NULL) {
+ *ap->a_size += 1 + nlen;
+ } else if (ap->a_uio != NULL) {
+ /*
+ * Format of extattr name entry is one byte for
+ * length and the rest for name.
+ */
+ error = uiomove(&nlen, 1, ap->a_uio);
+ if (error == 0) {
+ char *namep = __DECONST(char *, name) + plen;
+ error = uiomove(namep, nlen, ap->a_uio);
+ }
+ if (error != 0)
+ break;
+ }
+ }
+
+ return (error);
+}
+
+/*
+ * Vnode operation to retrieve extended attributes on a vnode.
+ */
+static int
+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)
+ *ap->a_size = 0;
+ /*
+ * 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, VREAD);
+ if (error != 0)
+ return (error);
+
+ error = zfs_create_attrname(ap->a_attrnamespace, "", attrprefix,
+ sizeof (attrprefix));
+ if (error != 0)
+ return (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);
+ rw_exit(&zp->z_xattr_lock);
+ ZFS_EXIT(zfsvfs);
return (error);
}
diff --git a/module/os/freebsd/zfs/zfs_znode.c b/module/os/freebsd/zfs/zfs_znode.c
index d6824f873..85f71f99e 100644
--- a/module/os/freebsd/zfs/zfs_znode.c
+++ b/module/os/freebsd/zfs/zfs_znode.c
@@ -145,10 +145,13 @@ zfs_znode_cache_constructor(void *buf, void *arg, int kmflags)
mutex_init(&zp->z_lock, NULL, MUTEX_DEFAULT, NULL);
mutex_init(&zp->z_acl_lock, NULL, MUTEX_DEFAULT, NULL);
+ rw_init(&zp->z_xattr_lock, NULL, RW_DEFAULT, NULL);
zfs_rangelock_init(&zp->z_rangelock, zfs_rangelock_cb, zp);
zp->z_acl_cached = NULL;
+ zp->z_xattr_cached = NULL;
+ zp->z_xattr_parent = 0;
zp->z_vnode = NULL;
return (0);
}
@@ -164,9 +167,11 @@ zfs_znode_cache_destructor(void *buf, void *arg)
ASSERT(!list_link_active(&zp->z_link_node));
mutex_destroy(&zp->z_lock);
mutex_destroy(&zp->z_acl_lock);
+ rw_destroy(&zp->z_xattr_lock);
zfs_rangelock_fini(&zp->z_rangelock);
ASSERT3P(zp->z_acl_cached, ==, NULL);
+ ASSERT3P(zp->z_xattr_cached, ==, NULL);
}
@@ -211,7 +216,10 @@ zfs_znode_alloc_kmem(int flags)
static void
zfs_znode_free_kmem(znode_t *zp)
{
-
+ if (zp->z_xattr_cached) {
+ nvlist_free(zp->z_xattr_cached);
+ zp->z_xattr_cached = NULL;
+ }
uma_zfree_smr(znode_uma_zone, zp);
}
#else
@@ -237,7 +245,10 @@ zfs_znode_alloc_kmem(int flags)
static void
zfs_znode_free_kmem(znode_t *zp)
{
-
+ if (zp->z_xattr_cached) {
+ nvlist_free(zp->z_xattr_cached);
+ zp->z_xattr_cached = NULL;
+ }
kmem_cache_free(znode_cache, zp);
}
#endif
@@ -1083,8 +1094,15 @@ zfs_rezget(znode_t *zp)
zfs_acl_free(zp->z_acl_cached);
zp->z_acl_cached = NULL;
}
-
mutex_exit(&zp->z_acl_lock);
+
+ rw_enter(&zp->z_xattr_lock, RW_WRITER);
+ if (zp->z_xattr_cached) {
+ nvlist_free(zp->z_xattr_cached);
+ zp->z_xattr_cached = NULL;
+ }
+ rw_exit(&zp->z_xattr_lock);
+
ASSERT3P(zp->z_sa_hdl, ==, NULL);
err = sa_buf_hold(zfsvfs->z_os, obj_num, NULL, &db);
if (err) {