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 <behlendorf1@llnl.gov> Reviewed-by: Alexander Motin <mav@FreeBSD.org> Signed-off-by: Ryan Moeller <ryan@iXsystems.com> Closes #11997
This commit is contained in:
parent
57f43735ca
commit
7100db9793
|
@ -38,7 +38,7 @@
|
||||||
.\" Copyright 2019 Joyent, Inc.
|
.\" Copyright 2019 Joyent, Inc.
|
||||||
.\" Copyright (c) 2019, Kjeld Schouten-Lebbing
|
.\" Copyright (c) 2019, Kjeld Schouten-Lebbing
|
||||||
.\"
|
.\"
|
||||||
.Dd September 1, 2020
|
.Dd May 5, 2021
|
||||||
.Dt ZFSPROPS 8
|
.Dt ZFSPROPS 8
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
@ -1778,7 +1778,7 @@ The default value is
|
||||||
This property is not used on Linux.
|
This property is not used on Linux.
|
||||||
.It Sy xattr Ns = Ns Sy on Ns | Ns Sy off Ns | Ns Sy sa
|
.It Sy xattr Ns = Ns Sy on Ns | Ns Sy off Ns | Ns Sy sa
|
||||||
Controls whether extended attributes are enabled for this file system. Two
|
Controls whether extended attributes are enabled for this file system. Two
|
||||||
styles of extended attributes are supported either directory based or system
|
styles of extended attributes are supported: either directory based or system
|
||||||
attribute based.
|
attribute based.
|
||||||
.Pp
|
.Pp
|
||||||
The default value of
|
The default value of
|
||||||
|
@ -1790,7 +1790,7 @@ can be set on a file. Although under Linux the
|
||||||
and
|
and
|
||||||
.Xr setxattr 2
|
.Xr setxattr 2
|
||||||
system calls limit the maximum size to 64K. This is the most compatible
|
system calls limit the maximum size to 64K. This is the most compatible
|
||||||
style of extended attribute and is supported by all OpenZFS implementations.
|
style of extended attribute and is supported by all ZFS implementations.
|
||||||
.Pp
|
.Pp
|
||||||
System attribute based xattrs can be enabled by setting the value to
|
System attribute based xattrs can be enabled by setting the value to
|
||||||
.Sy sa .
|
.Sy sa .
|
||||||
|
@ -1803,6 +1803,9 @@ based xattr. System attribute based extended attributes are not accessible
|
||||||
on platforms which do not support the
|
on platforms which do not support the
|
||||||
.Sy xattr=sa
|
.Sy xattr=sa
|
||||||
feature.
|
feature.
|
||||||
|
OpenZFS supports
|
||||||
|
.Sy xattr=sa
|
||||||
|
on both FreeBSD and Linux.
|
||||||
.Pp
|
.Pp
|
||||||
The use of system attribute based xattrs is strongly encouraged for users of
|
The use of system attribute based xattrs is strongly encouraged for users of
|
||||||
SELinux or POSIX ACLs. Both of these features heavily rely on extended
|
SELinux or POSIX ACLs. Both of these features heavily rely on extended
|
||||||
|
|
|
@ -846,6 +846,10 @@ zfsvfs_init(zfsvfs_t *zfsvfs, objset_t *os)
|
||||||
&sa_obj);
|
&sa_obj);
|
||||||
if (error != 0)
|
if (error != 0)
|
||||||
return (error);
|
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,
|
error = sa_setup(os, sa_obj, zfs_attr_table, ZPL_END,
|
||||||
|
|
|
@ -5227,6 +5227,29 @@ zfs_create_attrname(int attrnamespace, const char *name, char *attrname,
|
||||||
return (0);
|
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_
|
#ifndef _SYS_SYSPROTO_H_
|
||||||
struct vop_getextattr {
|
struct vop_getextattr {
|
||||||
IN struct vnode *a_vp;
|
IN struct vnode *a_vp;
|
||||||
|
@ -5239,26 +5262,85 @@ struct vop_getextattr {
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
|
||||||
* Vnode operating to retrieve a named extended attribute.
|
|
||||||
*/
|
|
||||||
static int
|
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 thread *td = ap->a_td;
|
||||||
struct nameidata nd;
|
struct nameidata nd;
|
||||||
char attrname[255];
|
|
||||||
struct vattr va;
|
struct vattr va;
|
||||||
vnode_t *xvp = NULL, *vp;
|
vnode_t *xvp = NULL, *vp;
|
||||||
int error, flags;
|
int error, flags;
|
||||||
|
|
||||||
|
error = zfs_lookup(ap->a_vp, NULL, &xvp, NULL, 0, ap->a_cred, td,
|
||||||
|
LOOKUP_XATTR, B_FALSE);
|
||||||
|
if (error != 0)
|
||||||
|
return (error);
|
||||||
|
|
||||||
|
flags = FREAD;
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (ap->a_size != NULL) {
|
||||||
|
error = VOP_GETATTR(vp, &va, ap->a_cred);
|
||||||
|
if (error == 0)
|
||||||
|
*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);
|
||||||
|
|
||||||
|
VOP_UNLOCK1(vp);
|
||||||
|
vn_close(vp, flags, ap->a_cred, td);
|
||||||
|
return (error);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 retrieve a named extended attribute.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
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;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the xattr property is off, refuse the request.
|
* 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));
|
return (SET_ERROR(EOPNOTSUPP));
|
||||||
}
|
|
||||||
|
|
||||||
error = extattr_check_cred(ap->a_vp, ap->a_attrnamespace,
|
error = extattr_check_cred(ap->a_vp, ap->a_attrnamespace,
|
||||||
ap->a_cred, ap->a_td, VREAD);
|
ap->a_cred, ap->a_td, VREAD);
|
||||||
|
@ -5270,38 +5352,18 @@ zfs_getextattr(struct vop_getextattr_args *ap)
|
||||||
if (error != 0)
|
if (error != 0)
|
||||||
return (error);
|
return (error);
|
||||||
|
|
||||||
|
error = ENOENT;
|
||||||
ZFS_ENTER(zfsvfs);
|
ZFS_ENTER(zfsvfs);
|
||||||
|
ZFS_VERIFY_ZP(zp)
|
||||||
error = zfs_lookup(ap->a_vp, NULL, &xvp, NULL, 0, ap->a_cred, td,
|
rw_enter(&zp->z_xattr_lock, RW_READER);
|
||||||
LOOKUP_XATTR, B_FALSE);
|
if (zfsvfs->z_use_sa && zp->z_is_sa)
|
||||||
if (error != 0) {
|
error = zfs_getextattr_sa(ap, attrname);
|
||||||
ZFS_EXIT(zfsvfs);
|
if (error == ENOENT)
|
||||||
return (error);
|
error = zfs_getextattr_dir(ap, attrname);
|
||||||
}
|
rw_exit(&zp->z_xattr_lock);
|
||||||
|
|
||||||
flags = FREAD;
|
|
||||||
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) {
|
|
||||||
ZFS_EXIT(zfsvfs);
|
|
||||||
if (error == ENOENT)
|
|
||||||
error = ENOATTR;
|
|
||||||
return (error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ap->a_size != NULL) {
|
|
||||||
error = VOP_GETATTR(vp, &va, ap->a_cred);
|
|
||||||
if (error == 0)
|
|
||||||
*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);
|
|
||||||
|
|
||||||
VOP_UNLOCK1(vp);
|
|
||||||
vn_close(vp, flags, ap->a_cred, td);
|
|
||||||
ZFS_EXIT(zfsvfs);
|
ZFS_EXIT(zfsvfs);
|
||||||
|
if (error == ENOENT)
|
||||||
|
error = SET_ERROR(ENOATTR);
|
||||||
return (error);
|
return (error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5315,54 +5377,25 @@ struct vop_deleteextattr {
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
|
||||||
* Vnode operation to remove a named attribute.
|
|
||||||
*/
|
|
||||||
static int
|
static int
|
||||||
zfs_deleteextattr(struct vop_deleteextattr_args *ap)
|
zfs_deleteextattr_dir(struct vop_deleteextattr_args *ap, const char *attrname)
|
||||||
{
|
{
|
||||||
zfsvfs_t *zfsvfs = VTOZ(ap->a_vp)->z_zfsvfs;
|
|
||||||
struct thread *td = ap->a_td;
|
struct thread *td = ap->a_td;
|
||||||
struct nameidata nd;
|
struct nameidata nd;
|
||||||
char attrname[255];
|
|
||||||
vnode_t *xvp = NULL, *vp;
|
vnode_t *xvp = NULL, *vp;
|
||||||
int error;
|
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));
|
|
||||||
if (error != 0)
|
|
||||||
return (error);
|
|
||||||
|
|
||||||
ZFS_ENTER(zfsvfs);
|
|
||||||
|
|
||||||
error = zfs_lookup(ap->a_vp, NULL, &xvp, NULL, 0, ap->a_cred, td,
|
error = zfs_lookup(ap->a_vp, NULL, &xvp, NULL, 0, ap->a_cred, td,
|
||||||
LOOKUP_XATTR, B_FALSE);
|
LOOKUP_XATTR, B_FALSE);
|
||||||
if (error != 0) {
|
if (error != 0)
|
||||||
ZFS_EXIT(zfsvfs);
|
|
||||||
return (error);
|
return (error);
|
||||||
}
|
|
||||||
|
|
||||||
NDINIT_ATVP(&nd, DELETE, NOFOLLOW | LOCKPARENT | LOCKLEAF,
|
NDINIT_ATVP(&nd, DELETE, NOFOLLOW | LOCKPARENT | LOCKLEAF,
|
||||||
UIO_SYSSPACE, attrname, xvp, td);
|
UIO_SYSSPACE, attrname, xvp, td);
|
||||||
error = namei(&nd);
|
error = namei(&nd);
|
||||||
vp = nd.ni_vp;
|
vp = nd.ni_vp;
|
||||||
if (error != 0) {
|
if (error != 0) {
|
||||||
ZFS_EXIT(zfsvfs);
|
|
||||||
NDFREE(&nd, NDF_ONLY_PNBUF);
|
NDFREE(&nd, NDF_ONLY_PNBUF);
|
||||||
if (error == ENOENT)
|
|
||||||
error = ENOATTR;
|
|
||||||
return (error);
|
return (error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5374,11 +5407,90 @@ zfs_deleteextattr(struct vop_deleteextattr_args *ap)
|
||||||
vrele(vp);
|
vrele(vp);
|
||||||
else
|
else
|
||||||
vput(vp);
|
vput(vp);
|
||||||
ZFS_EXIT(zfsvfs);
|
|
||||||
|
|
||||||
return (error);
|
return (error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 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);
|
||||||
|
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));
|
||||||
|
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_
|
#ifndef _SYS_SYSPROTO_H_
|
||||||
struct vop_setextattr {
|
struct vop_setextattr {
|
||||||
IN struct vnode *a_vp;
|
IN struct vnode *a_vp;
|
||||||
|
@ -5390,56 +5502,28 @@ struct vop_setextattr {
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
|
||||||
* Vnode operation to set a named attribute.
|
|
||||||
*/
|
|
||||||
static int
|
static int
|
||||||
zfs_setextattr(struct vop_setextattr_args *ap)
|
zfs_setextattr_dir(struct vop_setextattr_args *ap, const char *attrname)
|
||||||
{
|
{
|
||||||
zfsvfs_t *zfsvfs = VTOZ(ap->a_vp)->z_zfsvfs;
|
|
||||||
struct thread *td = ap->a_td;
|
struct thread *td = ap->a_td;
|
||||||
struct nameidata nd;
|
struct nameidata nd;
|
||||||
char attrname[255];
|
|
||||||
struct vattr va;
|
struct vattr va;
|
||||||
vnode_t *xvp = NULL, *vp;
|
vnode_t *xvp = NULL, *vp;
|
||||||
int error, flags;
|
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, VWRITE);
|
|
||||||
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,
|
error = zfs_lookup(ap->a_vp, NULL, &xvp, NULL, 0, ap->a_cred, td,
|
||||||
LOOKUP_XATTR | CREATE_XATTR_DIR, B_FALSE);
|
LOOKUP_XATTR | CREATE_XATTR_DIR, B_FALSE);
|
||||||
if (error != 0) {
|
if (error != 0)
|
||||||
ZFS_EXIT(zfsvfs);
|
|
||||||
return (error);
|
return (error);
|
||||||
}
|
|
||||||
|
|
||||||
flags = FFLAGS(O_WRONLY | O_CREAT);
|
flags = FFLAGS(O_WRONLY | O_CREAT);
|
||||||
NDINIT_ATVP(&nd, LOOKUP, NOFOLLOW, UIO_SYSSPACE, attrname,
|
NDINIT_ATVP(&nd, LOOKUP, NOFOLLOW, UIO_SYSSPACE, attrname, xvp, td);
|
||||||
xvp, td);
|
|
||||||
error = vn_open_cred(&nd, &flags, 0600, VN_OPEN_INVFS, ap->a_cred,
|
error = vn_open_cred(&nd, &flags, 0600, VN_OPEN_INVFS, ap->a_cred,
|
||||||
NULL);
|
NULL);
|
||||||
vp = nd.ni_vp;
|
vp = nd.ni_vp;
|
||||||
NDFREE(&nd, NDF_ONLY_PNBUF);
|
NDFREE(&nd, NDF_ONLY_PNBUF);
|
||||||
if (error != 0) {
|
if (error != 0)
|
||||||
ZFS_EXIT(zfsvfs);
|
|
||||||
return (error);
|
return (error);
|
||||||
}
|
|
||||||
|
|
||||||
VATTR_NULL(&va);
|
VATTR_NULL(&va);
|
||||||
va.va_size = 0;
|
va.va_size = 0;
|
||||||
|
@ -5449,6 +5533,102 @@ zfs_setextattr(struct vop_setextattr_args *ap)
|
||||||
|
|
||||||
VOP_UNLOCK1(vp);
|
VOP_UNLOCK1(vp);
|
||||||
vn_close(vp, flags, ap->a_cred, td);
|
vn_close(vp, flags, ap->a_cred, td);
|
||||||
|
return (error);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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);
|
||||||
|
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));
|
||||||
|
if (error != 0)
|
||||||
|
return (error);
|
||||||
|
|
||||||
|
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);
|
ZFS_EXIT(zfsvfs);
|
||||||
return (error);
|
return (error);
|
||||||
}
|
}
|
||||||
|
@ -5464,55 +5644,20 @@ struct vop_listextattr {
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
|
||||||
* Vnode operation to retrieve extended attributes on a vnode.
|
|
||||||
*/
|
|
||||||
static int
|
static int
|
||||||
zfs_listextattr(struct vop_listextattr_args *ap)
|
zfs_listextattr_dir(struct vop_listextattr_args *ap, const char *attrprefix)
|
||||||
{
|
{
|
||||||
zfsvfs_t *zfsvfs = VTOZ(ap->a_vp)->z_zfsvfs;
|
|
||||||
struct thread *td = ap->a_td;
|
struct thread *td = ap->a_td;
|
||||||
struct nameidata nd;
|
struct nameidata nd;
|
||||||
char attrprefix[16];
|
|
||||||
uint8_t dirbuf[sizeof (struct dirent)];
|
uint8_t dirbuf[sizeof (struct dirent)];
|
||||||
struct dirent *dp;
|
|
||||||
struct iovec aiov;
|
struct iovec aiov;
|
||||||
struct uio auio;
|
struct uio auio;
|
||||||
size_t *sizep = ap->a_size;
|
|
||||||
size_t plen;
|
|
||||||
vnode_t *xvp = NULL, *vp;
|
vnode_t *xvp = NULL, *vp;
|
||||||
int done, error, eof, pos;
|
int error, eof;
|
||||||
zfs_uio_t uio;
|
|
||||||
|
|
||||||
zfs_uio_init(&uio, ap->a_uio);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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);
|
|
||||||
plen = strlen(attrprefix);
|
|
||||||
|
|
||||||
ZFS_ENTER(zfsvfs);
|
|
||||||
|
|
||||||
if (sizep != NULL)
|
|
||||||
*sizep = 0;
|
|
||||||
|
|
||||||
error = zfs_lookup(ap->a_vp, NULL, &xvp, NULL, 0, ap->a_cred, td,
|
error = zfs_lookup(ap->a_vp, NULL, &xvp, NULL, 0, ap->a_cred, td,
|
||||||
LOOKUP_XATTR, B_FALSE);
|
LOOKUP_XATTR, B_FALSE);
|
||||||
if (error != 0) {
|
if (error != 0) {
|
||||||
ZFS_EXIT(zfsvfs);
|
|
||||||
/*
|
/*
|
||||||
* ENOATTR means that the EA directory does not yet exist,
|
* ENOATTR means that the EA directory does not yet exist,
|
||||||
* i.e. there are no extended attributes there.
|
* i.e. there are no extended attributes there.
|
||||||
|
@ -5527,10 +5672,8 @@ zfs_listextattr(struct vop_listextattr_args *ap)
|
||||||
error = namei(&nd);
|
error = namei(&nd);
|
||||||
vp = nd.ni_vp;
|
vp = nd.ni_vp;
|
||||||
NDFREE(&nd, NDF_ONLY_PNBUF);
|
NDFREE(&nd, NDF_ONLY_PNBUF);
|
||||||
if (error != 0) {
|
if (error != 0)
|
||||||
ZFS_EXIT(zfsvfs);
|
|
||||||
return (error);
|
return (error);
|
||||||
}
|
|
||||||
|
|
||||||
auio.uio_iov = &aiov;
|
auio.uio_iov = &aiov;
|
||||||
auio.uio_iovcnt = 1;
|
auio.uio_iovcnt = 1;
|
||||||
|
@ -5539,18 +5682,18 @@ zfs_listextattr(struct vop_listextattr_args *ap)
|
||||||
auio.uio_rw = UIO_READ;
|
auio.uio_rw = UIO_READ;
|
||||||
auio.uio_offset = 0;
|
auio.uio_offset = 0;
|
||||||
|
|
||||||
do {
|
size_t plen = strlen(attrprefix);
|
||||||
uint8_t nlen;
|
|
||||||
|
|
||||||
|
do {
|
||||||
aiov.iov_base = (void *)dirbuf;
|
aiov.iov_base = (void *)dirbuf;
|
||||||
aiov.iov_len = sizeof (dirbuf);
|
aiov.iov_len = sizeof (dirbuf);
|
||||||
auio.uio_resid = sizeof (dirbuf);
|
auio.uio_resid = sizeof (dirbuf);
|
||||||
error = VOP_READDIR(vp, &auio, ap->a_cred, &eof, NULL, NULL);
|
error = VOP_READDIR(vp, &auio, ap->a_cred, &eof, NULL, NULL);
|
||||||
done = sizeof (dirbuf) - auio.uio_resid;
|
|
||||||
if (error != 0)
|
if (error != 0)
|
||||||
break;
|
break;
|
||||||
for (pos = 0; pos < done; ) {
|
int done = sizeof (dirbuf) - auio.uio_resid;
|
||||||
dp = (struct dirent *)(dirbuf + pos);
|
for (int pos = 0; pos < done; ) {
|
||||||
|
struct dirent *dp = (struct dirent *)(dirbuf + pos);
|
||||||
pos += dp->d_reclen;
|
pos += dp->d_reclen;
|
||||||
/*
|
/*
|
||||||
* XXX: Temporarily we also accept DT_UNKNOWN, as this
|
* XXX: Temporarily we also accept DT_UNKNOWN, as this
|
||||||
|
@ -5558,24 +5701,23 @@ zfs_listextattr(struct vop_listextattr_args *ap)
|
||||||
*/
|
*/
|
||||||
if (dp->d_type != DT_REG && dp->d_type != DT_UNKNOWN)
|
if (dp->d_type != DT_REG && dp->d_type != DT_UNKNOWN)
|
||||||
continue;
|
continue;
|
||||||
if (plen == 0 &&
|
else if (plen == 0 &&
|
||||||
strncmp(dp->d_name, "freebsd:", 8) == 0)
|
strncmp(dp->d_name, "freebsd:", 8) == 0)
|
||||||
continue;
|
continue;
|
||||||
else if (strncmp(dp->d_name, attrprefix, plen) != 0)
|
else if (strncmp(dp->d_name, attrprefix, plen) != 0)
|
||||||
continue;
|
continue;
|
||||||
nlen = dp->d_namlen - plen;
|
uint8_t nlen = dp->d_namlen - plen;
|
||||||
if (sizep != NULL)
|
if (ap->a_size != NULL) {
|
||||||
*sizep += 1 + nlen;
|
*ap->a_size += 1 + nlen;
|
||||||
else if (GET_UIO_STRUCT(&uio) != NULL) {
|
} else if (ap->a_uio != NULL) {
|
||||||
/*
|
/*
|
||||||
* Format of extattr name entry is one byte for
|
* Format of extattr name entry is one byte for
|
||||||
* length and the rest for name.
|
* length and the rest for name.
|
||||||
*/
|
*/
|
||||||
error = zfs_uiomove(&nlen, 1, zfs_uio_rw(&uio),
|
error = uiomove(&nlen, 1, ap->a_uio);
|
||||||
&uio);
|
|
||||||
if (error == 0) {
|
if (error == 0) {
|
||||||
error = zfs_uiomove(dp->d_name + plen,
|
char *namep = dp->d_name + plen;
|
||||||
nlen, zfs_uio_rw(&uio), &uio);
|
error = uiomove(namep, nlen, ap->a_uio);
|
||||||
}
|
}
|
||||||
if (error != 0)
|
if (error != 0)
|
||||||
break;
|
break;
|
||||||
|
@ -5584,8 +5726,92 @@ zfs_listextattr(struct vop_listextattr_args *ap)
|
||||||
} while (!eof && error == 0);
|
} while (!eof && error == 0);
|
||||||
|
|
||||||
vput(vp);
|
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);
|
return (error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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_lock, NULL, MUTEX_DEFAULT, NULL);
|
||||||
mutex_init(&zp->z_acl_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);
|
zfs_rangelock_init(&zp->z_rangelock, zfs_rangelock_cb, zp);
|
||||||
|
|
||||||
zp->z_acl_cached = NULL;
|
zp->z_acl_cached = NULL;
|
||||||
|
zp->z_xattr_cached = NULL;
|
||||||
|
zp->z_xattr_parent = 0;
|
||||||
zp->z_vnode = NULL;
|
zp->z_vnode = NULL;
|
||||||
return (0);
|
return (0);
|
||||||
}
|
}
|
||||||
|
@ -164,9 +167,11 @@ zfs_znode_cache_destructor(void *buf, void *arg)
|
||||||
ASSERT(!list_link_active(&zp->z_link_node));
|
ASSERT(!list_link_active(&zp->z_link_node));
|
||||||
mutex_destroy(&zp->z_lock);
|
mutex_destroy(&zp->z_lock);
|
||||||
mutex_destroy(&zp->z_acl_lock);
|
mutex_destroy(&zp->z_acl_lock);
|
||||||
|
rw_destroy(&zp->z_xattr_lock);
|
||||||
zfs_rangelock_fini(&zp->z_rangelock);
|
zfs_rangelock_fini(&zp->z_rangelock);
|
||||||
|
|
||||||
ASSERT(zp->z_acl_cached == NULL);
|
ASSERT3P(zp->z_acl_cached, ==, NULL);
|
||||||
|
ASSERT3P(zp->z_xattr_cached, ==, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -211,7 +216,10 @@ zfs_znode_alloc_kmem(int flags)
|
||||||
static void
|
static void
|
||||||
zfs_znode_free_kmem(znode_t *zp)
|
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);
|
uma_zfree_smr(znode_uma_zone, zp);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
@ -237,7 +245,10 @@ zfs_znode_alloc_kmem(int flags)
|
||||||
static void
|
static void
|
||||||
zfs_znode_free_kmem(znode_t *zp)
|
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);
|
kmem_cache_free(znode_cache, zp);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -1078,9 +1089,16 @@ zfs_rezget(znode_t *zp)
|
||||||
zfs_acl_free(zp->z_acl_cached);
|
zfs_acl_free(zp->z_acl_cached);
|
||||||
zp->z_acl_cached = NULL;
|
zp->z_acl_cached = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_exit(&zp->z_acl_lock);
|
mutex_exit(&zp->z_acl_lock);
|
||||||
ASSERT(zp->z_sa_hdl == NULL);
|
|
||||||
|
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);
|
err = sa_buf_hold(zfsvfs->z_os, obj_num, NULL, &db);
|
||||||
if (err) {
|
if (err) {
|
||||||
ZFS_OBJ_HOLD_EXIT(zfsvfs, obj_num);
|
ZFS_OBJ_HOLD_EXIT(zfsvfs, obj_num);
|
||||||
|
|
Loading…
Reference in New Issue