summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/fs/xattr.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/common/fs/xattr.c')
-rw-r--r--usr/src/uts/common/fs/xattr.c1428
1 files changed, 1428 insertions, 0 deletions
diff --git a/usr/src/uts/common/fs/xattr.c b/usr/src/uts/common/fs/xattr.c
new file mode 100644
index 0000000000..65e0cd80d0
--- /dev/null
+++ b/usr/src/uts/common/fs/xattr.c
@@ -0,0 +1,1428 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/param.h>
+#include <sys/isa_defs.h>
+#include <sys/types.h>
+#include <sys/sysmacros.h>
+#include <sys/cred.h>
+#include <sys/systm.h>
+#include <sys/errno.h>
+#include <sys/fcntl.h>
+#include <sys/pathname.h>
+#include <sys/stat.h>
+#include <sys/vfs.h>
+#include <sys/acl.h>
+#include <sys/file.h>
+#include <sys/sunddi.h>
+#include <sys/debug.h>
+#include <sys/cmn_err.h>
+#include <sys/vnode.h>
+#include <sys/mode.h>
+#include <sys/nvpair.h>
+#include <sys/attr.h>
+#include <sys/gfs.h>
+#include <sys/mutex.h>
+#include <fs/fs_subr.h>
+#include <sys/kidmap.h>
+
+typedef struct {
+ gfs_file_t gfs_private;
+ xattr_view_t xattr_view;
+} xattr_file_t;
+
+/* ARGSUSED */
+static int
+xattr_file_open(vnode_t **vpp, int flags, cred_t *cr, caller_context_t *ct)
+{
+ xattr_file_t *np = (*vpp)->v_data;
+
+ if ((np->xattr_view == XATTR_VIEW_READONLY) && (flags & FWRITE))
+ return (EACCES);
+
+ return (0);
+}
+
+/* ARGSUSED */
+static int
+xattr_file_access(vnode_t *vp, int mode, int flags, cred_t *cr,
+ caller_context_t *ct)
+{
+ xattr_file_t *np = vp->v_data;
+
+ if ((np->xattr_view == XATTR_VIEW_READONLY) && (mode & VWRITE))
+ return (EACCES);
+
+ return (0);
+}
+
+/* ARGSUSED */
+static int
+xattr_file_close(vnode_t *vp, int flags, int count, offset_t off,
+ cred_t *cr, caller_context_t *ct)
+{
+ cleanlocks(vp, ddi_get_pid(), 0);
+ cleanshares(vp, ddi_get_pid());
+ return (0);
+}
+
+static int
+xattr_common_fid(vnode_t *vp, fid_t *fidp, caller_context_t *ct)
+{
+ xattr_fid_t *xfidp;
+ vnode_t *pvp, *savevp;
+ int error;
+ uint16_t orig_len;
+
+ if (fidp->fid_len < XATTR_FIDSZ) {
+ fidp->fid_len = XATTR_FIDSZ;
+ return (ENOSPC);
+ }
+
+ savevp = pvp = gfs_file_parent(vp);
+ mutex_enter(&savevp->v_lock);
+ if (pvp->v_flag & V_XATTRDIR) {
+ pvp = gfs_file_parent(pvp);
+ }
+ mutex_exit(&savevp->v_lock);
+
+ xfidp = (xattr_fid_t *)fidp;
+ orig_len = fidp->fid_len;
+ fidp->fid_len = sizeof (xfidp->parent_fid);
+
+ error = VOP_FID(pvp, fidp, ct);
+ if (error) {
+ fidp->fid_len = orig_len;
+ return (error);
+ }
+
+ xfidp->parent_len = fidp->fid_len;
+ fidp->fid_len = XATTR_FIDSZ;
+ xfidp->dir_offset = gfs_file_inode(vp);
+
+ return (0);
+}
+
+/* ARGSUSED */
+static int
+xattr_fill_nvlist(vnode_t *vp, xattr_view_t xattr_view, nvlist_t *nvlp,
+ cred_t *cr, caller_context_t *ct)
+{
+ int error;
+ f_attr_t attr;
+ uint64_t fsid;
+ dev_t mdev;
+ xvattr_t xvattr;
+ xoptattr_t *xoap; /* Pointer to optional attributes */
+ vnode_t *ppvp;
+ const char *domain;
+ uint32_t rid;
+
+ xva_init(&xvattr);
+
+ if ((xoap = xva_getxoptattr(&xvattr)) == NULL)
+ return (EINVAL);
+
+ /*
+ * For detecting ephemeral uid/gid
+ */
+ xvattr.xva_vattr.va_mask |= (AT_UID|AT_GID);
+
+ /*
+ * We need to access the real fs object.
+ * vp points to a GFS file; ppvp points to the real object.
+ */
+ ppvp = gfs_file_parent(gfs_file_parent(vp));
+
+ /*
+ * Iterate through the attrs associated with this view
+ */
+
+ for (attr = 0; attr < F_ATTR_ALL; attr++) {
+ if (xattr_view != attr_to_xattr_view(attr)) {
+ continue;
+ }
+
+ switch (attr) {
+ case F_SYSTEM:
+ XVA_SET_REQ(&xvattr, XAT_SYSTEM);
+ break;
+ case F_READONLY:
+ XVA_SET_REQ(&xvattr, XAT_READONLY);
+ break;
+ case F_HIDDEN:
+ XVA_SET_REQ(&xvattr, XAT_HIDDEN);
+ break;
+ case F_ARCHIVE:
+ XVA_SET_REQ(&xvattr, XAT_ARCHIVE);
+ break;
+ case F_IMMUTABLE:
+ XVA_SET_REQ(&xvattr, XAT_IMMUTABLE);
+ break;
+ case F_APPENDONLY:
+ XVA_SET_REQ(&xvattr, XAT_APPENDONLY);
+ break;
+ case F_NOUNLINK:
+ XVA_SET_REQ(&xvattr, XAT_NOUNLINK);
+ break;
+ case F_OPAQUE:
+ XVA_SET_REQ(&xvattr, XAT_OPAQUE);
+ break;
+ case F_NODUMP:
+ XVA_SET_REQ(&xvattr, XAT_NODUMP);
+ break;
+ case F_AV_QUARANTINED:
+ XVA_SET_REQ(&xvattr, XAT_AV_QUARANTINED);
+ break;
+ case F_AV_MODIFIED:
+ XVA_SET_REQ(&xvattr, XAT_AV_MODIFIED);
+ break;
+ case F_AV_SCANSTAMP:
+ if (ppvp->v_type == VREG)
+ XVA_SET_REQ(&xvattr, XAT_AV_SCANSTAMP);
+ break;
+ case F_CRTIME:
+ XVA_SET_REQ(&xvattr, XAT_CREATETIME);
+ break;
+ case F_FSID:
+ fsid = (((uint64_t)vp->v_vfsp->vfs_fsid.val[0] << 32) |
+ (uint64_t)(vp->v_vfsp->vfs_fsid.val[1] &
+ 0xffffffff));
+ VERIFY(nvlist_add_uint64(nvlp, attr_to_name(attr),
+ fsid) == 0);
+ break;
+ case F_MDEV:
+ mdev = ((int16_t)vp->v_vfsp->vfs_dev);
+ VERIFY(nvlist_add_uint16(nvlp, attr_to_name(attr),
+ mdev) == 0);
+ break;
+ default:
+ break;
+ }
+ }
+
+ error = VOP_GETATTR(ppvp, &xvattr.xva_vattr, 0, cr, ct);
+ if (error)
+ return (error);
+
+ /*
+ * Process all the optional attributes together here. Notice that
+ * xoap was set when the optional attribute bits were set above.
+ */
+ if ((xvattr.xva_vattr.va_mask & AT_XVATTR) && xoap) {
+ if (XVA_ISSET_RTN(&xvattr, XAT_READONLY)) {
+ VERIFY(nvlist_add_boolean_value(nvlp,
+ attr_to_name(F_READONLY),
+ xoap->xoa_readonly) == 0);
+ }
+ if (XVA_ISSET_RTN(&xvattr, XAT_HIDDEN)) {
+ VERIFY(nvlist_add_boolean_value(nvlp,
+ attr_to_name(F_HIDDEN),
+ xoap->xoa_hidden) == 0);
+ }
+ if (XVA_ISSET_RTN(&xvattr, XAT_SYSTEM)) {
+ VERIFY(nvlist_add_boolean_value(nvlp,
+ attr_to_name(F_SYSTEM),
+ xoap->xoa_system) == 0);
+ }
+ if (XVA_ISSET_RTN(&xvattr, XAT_ARCHIVE)) {
+ VERIFY(nvlist_add_boolean_value(nvlp,
+ attr_to_name(F_ARCHIVE),
+ xoap->xoa_archive) == 0);
+ }
+ if (XVA_ISSET_RTN(&xvattr, XAT_IMMUTABLE)) {
+ VERIFY(nvlist_add_boolean_value(nvlp,
+ attr_to_name(F_IMMUTABLE),
+ xoap->xoa_immutable) == 0);
+ }
+ if (XVA_ISSET_RTN(&xvattr, XAT_NOUNLINK)) {
+ VERIFY(nvlist_add_boolean_value(nvlp,
+ attr_to_name(F_NOUNLINK),
+ xoap->xoa_nounlink) == 0);
+ }
+ if (XVA_ISSET_RTN(&xvattr, XAT_APPENDONLY)) {
+ VERIFY(nvlist_add_boolean_value(nvlp,
+ attr_to_name(F_APPENDONLY),
+ xoap->xoa_appendonly) == 0);
+ }
+ if (XVA_ISSET_RTN(&xvattr, XAT_NODUMP)) {
+ VERIFY(nvlist_add_boolean_value(nvlp,
+ attr_to_name(F_NODUMP),
+ xoap->xoa_nodump) == 0);
+ }
+ if (XVA_ISSET_RTN(&xvattr, XAT_OPAQUE)) {
+ VERIFY(nvlist_add_boolean_value(nvlp,
+ attr_to_name(F_OPAQUE),
+ xoap->xoa_opaque) == 0);
+ }
+ if (XVA_ISSET_RTN(&xvattr, XAT_AV_QUARANTINED)) {
+ VERIFY(nvlist_add_boolean_value(nvlp,
+ attr_to_name(F_AV_QUARANTINED),
+ xoap->xoa_av_quarantined) == 0);
+ }
+ if (XVA_ISSET_RTN(&xvattr, XAT_AV_MODIFIED)) {
+ VERIFY(nvlist_add_boolean_value(nvlp,
+ attr_to_name(F_AV_MODIFIED),
+ xoap->xoa_av_modified) == 0);
+ }
+ if (XVA_ISSET_RTN(&xvattr, XAT_AV_SCANSTAMP)) {
+ VERIFY(nvlist_add_uint8_array(nvlp,
+ attr_to_name(F_AV_SCANSTAMP),
+ xoap->xoa_av_scanstamp,
+ sizeof (xoap->xoa_av_scanstamp)) == 0);
+ }
+ if (XVA_ISSET_RTN(&xvattr, XAT_CREATETIME)) {
+ VERIFY(nvlist_add_uint64_array(nvlp,
+ attr_to_name(F_CRTIME),
+ (uint64_t *)&(xoap->xoa_createtime),
+ sizeof (xoap->xoa_createtime) /
+ sizeof (uint64_t)) == 0);
+ }
+ }
+ /*
+ * Check for optional ownersid/groupsid
+ */
+
+ if (xvattr.xva_vattr.va_uid > MAXUID) {
+ nvlist_t *nvl_sid;
+
+ if (nvlist_alloc(&nvl_sid, NV_UNIQUE_NAME, KM_SLEEP))
+ return (ENOMEM);
+
+ if (kidmap_getsidbyuid(xvattr.xva_vattr.va_uid,
+ &domain, &rid) == 0) {
+ VERIFY(nvlist_add_string(nvl_sid,
+ SID_DOMAIN, domain) == 0);
+ VERIFY(nvlist_add_uint32(nvl_sid, SID_RID, rid) == 0);
+ VERIFY(nvlist_add_nvlist(nvlp, attr_to_name(F_OWNERSID),
+ nvl_sid) == 0);
+ }
+ nvlist_free(nvl_sid);
+ }
+ if (xvattr.xva_vattr.va_gid > MAXUID) {
+ nvlist_t *nvl_sid;
+
+ if (nvlist_alloc(&nvl_sid, NV_UNIQUE_NAME, KM_SLEEP))
+ return (ENOMEM);
+
+ if (kidmap_getsidbygid(xvattr.xva_vattr.va_gid,
+ &domain, &rid) == 0) {
+ VERIFY(nvlist_add_string(nvl_sid,
+ SID_DOMAIN, domain) == 0);
+ VERIFY(nvlist_add_uint32(nvl_sid, SID_RID, rid) == 0);
+ VERIFY(nvlist_add_nvlist(nvlp, attr_to_name(F_GROUPSID),
+ nvl_sid) == 0);
+ }
+ nvlist_free(nvl_sid);
+ }
+
+ return (0);
+}
+
+/*
+ * The size of a sysattr file is the size of the nvlist that will be
+ * returned by xattr_file_read(). A call to xattr_file_write() could
+ * change the size of that nvlist. That size is not stored persistently
+ * so xattr_fill_nvlist() calls VOP_GETATTR so that it can be calculated.
+ */
+static int
+xattr_file_size(vnode_t *vp, xattr_view_t xattr_view, size_t *size,
+ cred_t *cr, caller_context_t *ct)
+{
+ nvlist_t *nvl;
+
+ if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, KM_SLEEP)) {
+ return (ENOMEM);
+ }
+
+ if (xattr_fill_nvlist(vp, xattr_view, nvl, cr, ct)) {
+ nvlist_free(nvl);
+ return (EFAULT);
+ }
+
+ VERIFY(nvlist_size(nvl, size, NV_ENCODE_XDR) == 0);
+ nvlist_free(nvl);
+ return (0);
+}
+
+/* ARGSUSED */
+static int
+xattr_file_getattr(vnode_t *vp, vattr_t *vap, int flags, cred_t *cr,
+ caller_context_t *ct)
+{
+ xattr_file_t *np = vp->v_data;
+ timestruc_t now;
+ size_t size;
+ int error;
+ vnode_t *pvp;
+ vattr_t pvattr;
+
+ vap->va_type = VREG;
+ vap->va_mode = MAKEIMODE(vap->va_type,
+ (np->xattr_view == XATTR_VIEW_READONLY ? 0444 : 0644));
+ vap->va_nodeid = gfs_file_inode(vp);
+ vap->va_nlink = 1;
+ pvp = gfs_file_parent(vp);
+ (void) memset(&pvattr, 0, sizeof (pvattr));
+ pvattr.va_mask = AT_CTIME|AT_MTIME;
+ error = VOP_GETATTR(pvp, &pvattr, flags, cr, ct);
+ if (error) {
+ return (error);
+ }
+ vap->va_ctime = pvattr.va_ctime;
+ vap->va_mtime = pvattr.va_mtime;
+ gethrestime(&now);
+ vap->va_atime = now;
+ vap->va_uid = 0;
+ vap->va_gid = 0;
+ vap->va_rdev = 0;
+ vap->va_blksize = DEV_BSIZE;
+ vap->va_seq = 0;
+ vap->va_fsid = vp->v_vfsp->vfs_dev;
+ error = xattr_file_size(vp, np->xattr_view, &size, cr, ct);
+ vap->va_size = size;
+ vap->va_nblocks = howmany(vap->va_size, vap->va_blksize);
+ return (error);
+}
+
+/* ARGSUSED */
+static int
+xattr_file_read(vnode_t *vp, uio_t *uiop, int ioflag, cred_t *cr,
+ caller_context_t *ct)
+{
+ xattr_file_t *np = vp->v_data;
+ xattr_view_t xattr_view = np->xattr_view;
+ char *buf;
+ size_t filesize;
+ nvlist_t *nvl;
+ int error;
+
+ /*
+ * Validate file offset and fasttrack empty reads
+ */
+ if (uiop->uio_loffset < (offset_t)0)
+ return (EINVAL);
+
+ if (uiop->uio_resid == 0)
+ return (0);
+
+ if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, KM_SLEEP))
+ return (ENOMEM);
+
+ if (xattr_fill_nvlist(vp, xattr_view, nvl, cr, ct)) {
+ nvlist_free(nvl);
+ return (EFAULT);
+ }
+
+ VERIFY(nvlist_size(nvl, &filesize, NV_ENCODE_XDR) == 0);
+
+ if (uiop->uio_loffset >= filesize) {
+ nvlist_free(nvl);
+ return (0);
+ }
+
+ buf = kmem_alloc(filesize, KM_SLEEP);
+ VERIFY(nvlist_pack(nvl, &buf, &filesize, NV_ENCODE_XDR,
+ KM_SLEEP) == 0);
+
+ error = uiomove((caddr_t)buf, filesize, UIO_READ, uiop);
+ kmem_free(buf, filesize);
+ nvlist_free(nvl);
+ return (error);
+}
+
+/* ARGSUSED */
+static int
+xattr_file_write(vnode_t *vp, uio_t *uiop, int ioflag, cred_t *cr,
+ caller_context_t *ct)
+{
+ int error = 0;
+ char *buf;
+ char *domain;
+ uint32_t rid;
+ ssize_t size = uiop->uio_resid;
+ nvlist_t *nvp;
+ nvpair_t *pair = NULL;
+ vnode_t *ppvp;
+ xvattr_t xvattr;
+ xoptattr_t *xoap = NULL; /* Pointer to optional attributes */
+
+ /*
+ * Validate file offset and size.
+ */
+ if (uiop->uio_loffset < (offset_t)0)
+ return (EINVAL);
+
+ if (size == 0)
+ return (EINVAL);
+
+ xva_init(&xvattr);
+
+ if ((xoap = xva_getxoptattr(&xvattr)) == NULL) {
+ return (EINVAL);
+ }
+
+ /*
+ * Copy and unpack the nvlist
+ */
+ buf = kmem_alloc(size, KM_SLEEP);
+ if (uiomove((caddr_t)buf, size, UIO_WRITE, uiop)) {
+ return (EFAULT);
+ }
+
+ if (nvlist_unpack(buf, size, &nvp, KM_SLEEP) != 0) {
+ kmem_free(buf, size);
+ uiop->uio_resid = size;
+ return (EINVAL);
+ }
+ kmem_free(buf, size);
+
+ /*
+ * Fasttrack empty writes (nvlist with no nvpairs)
+ */
+ if (nvlist_next_nvpair(nvp, NULL) == 0)
+ return (0);
+
+ ppvp = gfs_file_parent(gfs_file_parent(vp));
+
+ while (pair = nvlist_next_nvpair(nvp, pair)) {
+ data_type_t type;
+ f_attr_t attr;
+ boolean_t value;
+ uint64_t *time, *times;
+ uint_t elem, nelems;
+ nvlist_t *nvp_sid;
+ uint8_t *scanstamp;
+
+ /*
+ * Validate the name and type of each attribute.
+ * Log any unknown names and continue. This will
+ * help if additional attributes are added later.
+ */
+ type = nvpair_type(pair);
+ if ((attr = name_to_attr(nvpair_name(pair))) == F_ATTR_INVAL) {
+ cmn_err(CE_WARN, "Unknown attribute %s",
+ nvpair_name(pair));
+ continue;
+ }
+
+ /*
+ * Verify nvlist type matches required type and view is OK
+ */
+
+ if (type != attr_to_data_type(attr) ||
+ (attr_to_xattr_view(attr) == XATTR_VIEW_READONLY)) {
+ nvlist_free(nvp);
+ return (EINVAL);
+ }
+
+ /*
+ * For OWNERSID/GROUPSID make sure the target
+ * file system support ephemeral ID's
+ */
+ if ((attr == F_OWNERSID || attr == F_GROUPSID) &&
+ (!(vp->v_vfsp->vfs_flag & VFS_XID))) {
+ nvlist_free(nvp);
+ return (EINVAL);
+ }
+
+ /*
+ * Retrieve data from nvpair
+ */
+ switch (type) {
+ case DATA_TYPE_BOOLEAN_VALUE:
+ if (nvpair_value_boolean_value(pair, &value)) {
+ nvlist_free(nvp);
+ return (EINVAL);
+ }
+ break;
+ case DATA_TYPE_UINT64_ARRAY:
+ if (nvpair_value_uint64_array(pair, &times, &nelems)) {
+ nvlist_free(nvp);
+ return (EINVAL);
+ }
+ break;
+ case DATA_TYPE_NVLIST:
+ if (nvpair_value_nvlist(pair, &nvp_sid)) {
+ nvlist_free(nvp);
+ return (EINVAL);
+ }
+ break;
+ case DATA_TYPE_UINT8_ARRAY:
+ if (nvpair_value_uint8_array(pair,
+ &scanstamp, &nelems)) {
+ nvlist_free(nvp);
+ return (EINVAL);
+ }
+ break;
+ default:
+ nvlist_free(nvp);
+ return (EINVAL);
+ }
+
+ switch (attr) {
+ /*
+ * If we have several similar optional attributes to
+ * process then we should do it all together here so that
+ * xoap and the requested bitmap can be set in one place.
+ */
+ case F_READONLY:
+ XVA_SET_REQ(&xvattr, XAT_READONLY);
+ xoap->xoa_readonly = value;
+ break;
+ case F_HIDDEN:
+ XVA_SET_REQ(&xvattr, XAT_HIDDEN);
+ xoap->xoa_hidden = value;
+ break;
+ case F_SYSTEM:
+ XVA_SET_REQ(&xvattr, XAT_SYSTEM);
+ xoap->xoa_system = value;
+ break;
+ case F_ARCHIVE:
+ XVA_SET_REQ(&xvattr, XAT_ARCHIVE);
+ xoap->xoa_archive = value;
+ break;
+ case F_IMMUTABLE:
+ XVA_SET_REQ(&xvattr, XAT_IMMUTABLE);
+ xoap->xoa_immutable = value;
+ break;
+ case F_NOUNLINK:
+ XVA_SET_REQ(&xvattr, XAT_NOUNLINK);
+ xoap->xoa_nounlink = value;
+ break;
+ case F_APPENDONLY:
+ XVA_SET_REQ(&xvattr, XAT_APPENDONLY);
+ xoap->xoa_appendonly = value;
+ break;
+ case F_NODUMP:
+ XVA_SET_REQ(&xvattr, XAT_NODUMP);
+ xoap->xoa_nodump = value;
+ break;
+ case F_AV_QUARANTINED:
+ XVA_SET_REQ(&xvattr, XAT_AV_QUARANTINED);
+ xoap->xoa_av_quarantined = value;
+ break;
+ case F_AV_MODIFIED:
+ XVA_SET_REQ(&xvattr, XAT_AV_MODIFIED);
+ xoap->xoa_av_modified = value;
+ break;
+ case F_CRTIME:
+ XVA_SET_REQ(&xvattr, XAT_CREATETIME);
+ time = (uint64_t *)&(xoap->xoa_createtime);
+ for (elem = 0; elem < nelems; elem++)
+ *time++ = times[elem];
+ break;
+ case F_OWNERSID:
+ case F_GROUPSID:
+ if (nvlist_lookup_string(nvp_sid, SID_DOMAIN,
+ &domain) || nvlist_lookup_uint32(nvp_sid, SID_RID,
+ &rid)) {
+ nvlist_free(nvp);
+ return (EINVAL);
+ }
+
+ /*
+ * Now map domain+rid to ephemeral id's
+ *
+ * If mapping fails, then the uid/gid will
+ * be set to UID_NOBODY by Winchester.
+ */
+
+ if (attr == F_OWNERSID) {
+ (void) kidmap_getuidbysid(domain, rid,
+ &xvattr.xva_vattr.va_uid);
+ xvattr.xva_vattr.va_mask |= AT_UID;
+ } else {
+ (void) kidmap_getgidbysid(domain, rid,
+ &xvattr.xva_vattr.va_gid);
+ xvattr.xva_vattr.va_mask |= AT_GID;
+ }
+ break;
+ case F_AV_SCANSTAMP:
+ if (ppvp->v_type == VREG) {
+ XVA_SET_REQ(&xvattr, XAT_AV_SCANSTAMP);
+ (void) memcpy(xoap->xoa_av_scanstamp,
+ scanstamp, nelems);
+ } else {
+ nvlist_free(nvp);
+ return (EINVAL);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ ppvp = gfs_file_parent(gfs_file_parent(vp));
+ error = VOP_SETATTR(ppvp, &xvattr.xva_vattr, 0, cr, ct);
+ if (error)
+ uiop->uio_resid = size;
+
+ nvlist_free(nvp);
+ return (error);
+}
+
+static int
+xattr_file_pathconf(vnode_t *vp, int cmd, ulong_t *valp, cred_t *cr,
+ caller_context_t *ct)
+{
+ switch (cmd) {
+ case _PC_XATTR_EXISTS:
+ case _PC_SATTR_ENABLED:
+ case _PC_SATTR_EXISTS:
+ *valp = 0;
+ return (0);
+ default:
+ return (fs_pathconf(vp, cmd, valp, cr, ct));
+ }
+}
+
+vnodeops_t *xattr_file_ops;
+
+static const fs_operation_def_t xattr_file_tops[] = {
+ { VOPNAME_OPEN, { .vop_open = xattr_file_open } },
+ { VOPNAME_CLOSE, { .vop_close = xattr_file_close } },
+ { VOPNAME_READ, { .vop_read = xattr_file_read } },
+ { VOPNAME_WRITE, { .vop_write = xattr_file_write } },
+ { VOPNAME_IOCTL, { .error = fs_ioctl } },
+ { VOPNAME_GETATTR, { .vop_getattr = xattr_file_getattr } },
+ { VOPNAME_ACCESS, { .vop_access = xattr_file_access } },
+ { VOPNAME_READDIR, { .error = fs_notdir } },
+ { VOPNAME_SEEK, { .vop_seek = fs_seek } },
+ { VOPNAME_INACTIVE, { .vop_inactive = gfs_vop_inactive } },
+ { VOPNAME_FID, { .vop_fid = xattr_common_fid } },
+ { VOPNAME_PATHCONF, { .vop_pathconf = xattr_file_pathconf } },
+ { VOPNAME_PUTPAGE, { .error = fs_putpage } },
+ { VOPNAME_FSYNC, { .error = fs_fsync } },
+ { NULL }
+};
+
+vnode_t *
+xattr_mkfile(vnode_t *pvp, xattr_view_t xattr_view)
+{
+ vnode_t *vp;
+ xattr_file_t *np;
+
+ vp = gfs_file_create(sizeof (xattr_file_t), pvp, xattr_file_ops);
+ np = vp->v_data;
+ np->xattr_view = xattr_view;
+ vp->v_flag |= V_SYSATTR;
+ return (vp);
+}
+
+vnode_t *
+xattr_mkfile_ro(vnode_t *pvp)
+{
+ return (xattr_mkfile(pvp, XATTR_VIEW_READONLY));
+}
+
+vnode_t *
+xattr_mkfile_rw(vnode_t *pvp)
+{
+ return (xattr_mkfile(pvp, XATTR_VIEW_READWRITE));
+}
+
+vnodeops_t *xattr_dir_ops;
+
+static gfs_dirent_t xattr_dirents[] = {
+ { VIEW_READONLY, xattr_mkfile_ro, GFS_CACHE_VNODE, },
+ { VIEW_READWRITE, xattr_mkfile_rw, GFS_CACHE_VNODE, },
+ { NULL },
+};
+
+#define XATTRDIR_NENTS ((sizeof (xattr_dirents) / sizeof (gfs_dirent_t)) - 1)
+
+static int
+is_sattr_name(char *s)
+{
+ int i;
+
+ for (i = 0; i < XATTRDIR_NENTS; ++i) {
+ if (strcmp(s, xattr_dirents[i].gfse_name) == 0) {
+ return (1);
+ }
+ }
+ return (0);
+}
+
+static int
+xattr_copy(vnode_t *sdvp, char *snm, vnode_t *tdvp, char *tnm,
+ cred_t *cr, caller_context_t *ct)
+{
+ xvattr_t xvattr;
+ vnode_t *pdvp;
+ int error;
+
+ /*
+ * Only copy system attrs if the views are the same
+ */
+ if (strcmp(snm, tnm) != 0)
+ return (EINVAL);
+
+ xva_init(&xvattr);
+
+ XVA_SET_REQ(&xvattr, XAT_SYSTEM);
+ XVA_SET_REQ(&xvattr, XAT_READONLY);
+ XVA_SET_REQ(&xvattr, XAT_HIDDEN);
+ XVA_SET_REQ(&xvattr, XAT_ARCHIVE);
+ XVA_SET_REQ(&xvattr, XAT_APPENDONLY);
+ XVA_SET_REQ(&xvattr, XAT_NOUNLINK);
+ XVA_SET_REQ(&xvattr, XAT_IMMUTABLE);
+ XVA_SET_REQ(&xvattr, XAT_NODUMP);
+ XVA_SET_REQ(&xvattr, XAT_AV_MODIFIED);
+ XVA_SET_REQ(&xvattr, XAT_AV_QUARANTINED);
+ XVA_SET_REQ(&xvattr, XAT_CREATETIME);
+
+ pdvp = gfs_file_parent(sdvp);
+ error = VOP_GETATTR(pdvp, &xvattr.xva_vattr, 0, cr, ct);
+ if (error)
+ return (error);
+
+ pdvp = gfs_file_parent(tdvp);
+ error = VOP_SETATTR(pdvp, &xvattr.xva_vattr, 0, cr, ct);
+ return (error);
+}
+
+static int
+xattr_dir_realdir(vnode_t *dvp, vnode_t **realdvp, int lookup_flags,
+ cred_t *cr, caller_context_t *ct)
+{
+ vnode_t *pvp;
+ int error;
+ struct pathname pn;
+ char *startnm = "";
+
+ *realdvp = NULL;
+
+ pvp = gfs_file_parent(dvp);
+
+ error = pn_get(startnm, UIO_SYSSPACE, &pn);
+ if (error) {
+ VN_RELE(pvp);
+ return (error);
+ }
+
+ /*
+ * Set the LOOKUP_HAVE_SYSATTR_DIR flag so that we don't get into an
+ * infinite loop with fop_lookup calling back to xattr_dir_lookup.
+ */
+ lookup_flags |= LOOKUP_HAVE_SYSATTR_DIR;
+ error = VOP_LOOKUP(pvp, startnm, realdvp, &pn, lookup_flags,
+ rootvp, cr, ct, NULL, NULL);
+ pn_free(&pn);
+
+ return (error);
+}
+
+/* ARGSUSED */
+static int
+xattr_dir_open(vnode_t **vpp, int flags, cred_t *cr, caller_context_t *ct)
+{
+ if (flags & FWRITE) {
+ return (EACCES);
+ }
+
+ return (0);
+}
+
+/* ARGSUSED */
+static int
+xattr_dir_close(vnode_t *vpp, int flags, int count, offset_t off, cred_t *cr,
+ caller_context_t *ct)
+{
+ return (0);
+}
+
+/* ARGSUSED */
+static int
+xattr_dir_getattr(vnode_t *vp, vattr_t *vap, int flags, cred_t *cr,
+ caller_context_t *ct)
+{
+ timestruc_t now;
+ vnode_t *pvp;
+ int error;
+ vattr_t pvattr;
+
+ error = xattr_dir_realdir(vp, &pvp, LOOKUP_XATTR, cr, ct);
+ if (error == 0) {
+ error = VOP_GETATTR(pvp, vap, 0, cr, ct);
+ VN_RELE(pvp);
+ if (error) {
+ return (error);
+ }
+ vap->va_nlink += XATTRDIR_NENTS;
+ vap->va_size += XATTRDIR_NENTS;
+ return (0);
+ }
+
+ /*
+ * There is no real xattr directory. Cobble together
+ * an entry using info from the parent object.
+ */
+ pvp = gfs_file_parent(vp);
+ (void) memset(&pvattr, 0, sizeof (pvattr));
+ pvattr.va_mask = AT_UID|AT_GID|AT_RDEV|AT_CTIME|AT_MTIME;
+ error = VOP_GETATTR(pvp, &pvattr, 0, cr, ct);
+ if (error) {
+ return (error);
+ }
+ *vap = pvattr;
+ vap->va_type = VDIR;
+ vap->va_mode = MAKEIMODE(vap->va_type, S_ISVTX | 0777);
+ vap->va_fsid = vp->v_vfsp->vfs_dev;
+ vap->va_nodeid = gfs_file_inode(vp);
+ vap->va_nlink = XATTRDIR_NENTS+2;
+ vap->va_size = vap->va_nlink;
+ gethrestime(&now);
+ vap->va_atime = now;
+ vap->va_blksize = 0;
+ vap->va_nblocks = 0;
+ vap->va_seq = 0;
+ return (0);
+}
+
+static int
+xattr_dir_setattr(vnode_t *vp, vattr_t *vap, int flags, cred_t *cr,
+ caller_context_t *ct)
+{
+ vnode_t *realvp;
+ int error;
+
+ /*
+ * If there is a real xattr directory, do the setattr there.
+ * Otherwise, just return success. The GFS directory is transient,
+ * and any setattr changes can disappear anyway.
+ */
+ error = xattr_dir_realdir(vp, &realvp, LOOKUP_XATTR, cr, ct);
+ if (error == 0) {
+ error = VOP_SETATTR(realvp, vap, flags, cr, ct);
+ VN_RELE(realvp);
+ }
+ if (error == ENOENT) {
+ error = 0;
+ }
+ return (error);
+}
+
+/* ARGSUSED */
+static int
+xattr_dir_access(vnode_t *vp, int mode, int flags, cred_t *cr,
+ caller_context_t *ct)
+{
+ int error;
+ vnode_t *realvp = NULL;
+
+ if (mode & VWRITE) {
+ return (EACCES);
+ }
+
+ error = xattr_dir_realdir(vp, &realvp, LOOKUP_XATTR, cr, ct);
+
+ if (realvp)
+ VN_RELE(realvp);
+
+ /*
+ * No real xattr dir isn't an error
+ * an error of EINVAL indicates attributes on attributes
+ * are not supported. In that case just allow access to the
+ * transient directory.
+ */
+ return ((error == ENOENT || error == EINVAL) ? 0 : error);
+}
+
+static int
+xattr_dir_create(vnode_t *dvp, char *name, vattr_t *vap, vcexcl_t excl,
+ int mode, vnode_t **vpp, cred_t *cr, int flag, caller_context_t *ct,
+ vsecattr_t *vsecp)
+{
+ vnode_t *pvp;
+ int error;
+
+ *vpp = NULL;
+
+ /*
+ * Don't allow creation of extended attributes with sysattr names.
+ */
+ if (is_sattr_name(name)) {
+ return (gfs_dir_lookup(dvp, name, vpp, cr));
+ }
+
+ error = xattr_dir_realdir(dvp, &pvp, LOOKUP_XATTR|CREATE_XATTR_DIR,
+ cr, ct);
+ if (error == 0) {
+ error = VOP_CREATE(pvp, name, vap, excl, mode, vpp, cr, flag,
+ ct, vsecp);
+ VN_RELE(pvp);
+ }
+ return (error);
+}
+
+static int
+xattr_dir_remove(vnode_t *dvp, char *name, cred_t *cr, caller_context_t *ct,
+ int flags)
+{
+ vnode_t *pvp;
+ int error;
+
+ if (is_sattr_name(name)) {
+ return (EACCES);
+ }
+
+ error = xattr_dir_realdir(dvp, &pvp, LOOKUP_XATTR, cr, ct);
+ if (error == 0) {
+ error = VOP_REMOVE(pvp, name, cr, ct, flags);
+ VN_RELE(pvp);
+ }
+ return (error);
+}
+
+static int
+xattr_dir_link(vnode_t *tdvp, vnode_t *svp, char *name, cred_t *cr,
+ caller_context_t *ct, int flags)
+{
+ vnode_t *pvp;
+ int error;
+
+ if (svp->v_flag & V_SYSATTR) {
+ return (EINVAL);
+ }
+
+ error = xattr_dir_realdir(tdvp, &pvp, LOOKUP_XATTR, cr, ct);
+ if (error == 0) {
+ error = VOP_LINK(pvp, svp, name, cr, ct, flags);
+ VN_RELE(pvp);
+ }
+ return (error);
+}
+
+static int
+xattr_dir_rename(vnode_t *sdvp, char *snm, vnode_t *tdvp, char *tnm,
+ cred_t *cr, caller_context_t *ct, int flags)
+{
+ vnode_t *spvp, *tpvp;
+ int error;
+ int held_tgt;
+
+ if (is_sattr_name(snm) || is_sattr_name(tnm))
+ return (xattr_copy(sdvp, snm, tdvp, tnm, cr, ct));
+ /*
+ * We know that sdvp is a GFS dir, or we wouldn't be here.
+ * Get the real unnamed directory.
+ */
+ error = xattr_dir_realdir(sdvp, &spvp, LOOKUP_XATTR, cr, ct);
+ if (error) {
+ return (error);
+ }
+
+ if (sdvp == tdvp) {
+ /*
+ * If the source and target are the same GFS directory, the
+ * underlying unnamed source and target dir will be the same.
+ */
+ tpvp = spvp;
+ VN_HOLD(tpvp);
+ held_tgt = 1;
+ } else if (tdvp->v_flag & V_SYSATTR) {
+ /*
+ * If the target dir is a different GFS directory,
+ * find its underlying unnamed dir.
+ */
+ error = xattr_dir_realdir(tdvp, &tpvp, LOOKUP_XATTR, cr, ct);
+ if (error) {
+ VN_RELE(spvp);
+ return (error);
+ }
+ held_tgt = 1;
+ } else {
+ /*
+ * Target dir is outside of GFS, pass it on through.
+ */
+ tpvp = tdvp;
+ held_tgt = 0;
+ }
+
+ error = VOP_RENAME(spvp, snm, tpvp, tnm, cr, ct, flags);
+
+ if (held_tgt) {
+ VN_RELE(tpvp);
+ }
+ VN_RELE(spvp);
+
+ return (error);
+}
+
+static int
+xattr_dir_readdir(vnode_t *dvp, uio_t *uiop, cred_t *cr, int *eofp,
+ caller_context_t *ct, int flags)
+{
+ vnode_t *pvp;
+ int error;
+ int local_eof = 0;
+ int reset_off = 0;
+ int has_xattrs = 0;
+
+ if (eofp == NULL) {
+ eofp = &local_eof;
+ }
+
+ /*
+ * See if there is a real extended attribute directory.
+ */
+ error = xattr_dir_realdir(dvp, &pvp, LOOKUP_XATTR, cr, ct);
+ if (error == 0) {
+ has_xattrs = 1;
+ }
+
+ /*
+ * Start by reading up the static entries.
+ */
+ if (uiop->uio_loffset == 0) {
+ if (has_xattrs) {
+ /*
+ * If there is a real xattr dir, skip . and ..
+ * in the GFS dir. We'll pick them up below
+ * when we call into the underlying fs.
+ */
+ uiop->uio_loffset = GFS_STATIC_ENTRY_OFFSET;
+ }
+ error = gfs_dir_readdir(dvp, uiop, eofp, NULL, cr, ct);
+ if (error) {
+ if (has_xattrs) {
+ VN_RELE(pvp);
+ }
+ return (error);
+ }
+ /*
+ * We must read all of the static entries in the first
+ * call. Otherwise we won't know if uio_loffset in a
+ * subsequent call refers to the static entries or to those
+ * in an underlying fs.
+ */
+ ASSERT(*eofp);
+ reset_off = 1;
+ }
+
+ if (!has_xattrs) {
+ *eofp = 1;
+ return (0);
+ }
+
+ *eofp = 0;
+ if (reset_off) {
+ uiop->uio_loffset = 0;
+ }
+ (void) VOP_RWLOCK(pvp, V_WRITELOCK_FALSE, NULL);
+ error = VOP_READDIR(pvp, uiop, cr, eofp, ct, flags);
+ VOP_RWUNLOCK(pvp, V_WRITELOCK_FALSE, NULL);
+ VN_RELE(pvp);
+
+ return (error);
+}
+
+/* ARGSUSED */
+static void
+xattr_dir_inactive(vnode_t *vp, cred_t *cr, caller_context_t *ct)
+{
+ gfs_file_t *fp;
+
+ fp = gfs_dir_inactive(vp);
+ if (fp != NULL) {
+ kmem_free(fp, fp->gfs_size);
+ }
+}
+
+static int
+xattr_dir_pathconf(vnode_t *vp, int cmd, ulong_t *valp, cred_t *cr,
+ caller_context_t *ct)
+{
+ switch (cmd) {
+ case _PC_XATTR_EXISTS:
+ case _PC_SATTR_ENABLED:
+ case _PC_SATTR_EXISTS:
+ *valp = 0;
+ return (0);
+ default:
+ return (fs_pathconf(vp, cmd, valp, cr, ct));
+ }
+}
+
+static const fs_operation_def_t xattr_dir_tops[] = {
+ { VOPNAME_OPEN, { .vop_open = xattr_dir_open } },
+ { VOPNAME_CLOSE, { .vop_close = xattr_dir_close } },
+ { VOPNAME_IOCTL, { .error = fs_inval } },
+ { VOPNAME_GETATTR, { .vop_getattr = xattr_dir_getattr } },
+ { VOPNAME_SETATTR, { .vop_setattr = xattr_dir_setattr } },
+ { VOPNAME_ACCESS, { .vop_access = xattr_dir_access } },
+ { VOPNAME_READDIR, { .vop_readdir = xattr_dir_readdir } },
+ { VOPNAME_LOOKUP, { .vop_lookup = gfs_vop_lookup } },
+ { VOPNAME_CREATE, { .vop_create = xattr_dir_create } },
+ { VOPNAME_REMOVE, { .vop_remove = xattr_dir_remove } },
+ { VOPNAME_LINK, { .vop_link = xattr_dir_link } },
+ { VOPNAME_RENAME, { .vop_rename = xattr_dir_rename } },
+ { VOPNAME_MKDIR, { .error = fs_inval } },
+ { VOPNAME_SEEK, { .vop_seek = fs_seek } },
+ { VOPNAME_INACTIVE, { .vop_inactive = xattr_dir_inactive } },
+ { VOPNAME_FID, { .vop_fid = xattr_common_fid } },
+ { VOPNAME_PATHCONF, { .vop_pathconf = xattr_dir_pathconf } },
+ { NULL, NULL }
+};
+
+static gfs_opsvec_t xattr_opsvec[] = {
+ { "xattr dir", xattr_dir_tops, &xattr_dir_ops },
+ { "system attributes", xattr_file_tops, &xattr_file_ops },
+ { NULL, NULL, NULL }
+};
+
+static int
+xattr_lookup_cb(vnode_t *vp, const char *nm, vnode_t **vpp, ino64_t *inop,
+ cred_t *cr)
+{
+ vnode_t *pvp;
+ struct pathname pn;
+ int error;
+
+ *vpp = NULL;
+ *inop = 0;
+
+ error = xattr_dir_realdir(vp, &pvp, LOOKUP_XATTR|CREATE_XATTR_DIR,
+ cr, NULL);
+
+ /*
+ * Return ENOENT for EACCES requests during lookup. Once an
+ * attribute create is attempted EACCES will be returned.
+ */
+ if (error) {
+ if (error == EACCES)
+ return (ENOENT);
+ return (error);
+ }
+
+ error = pn_get((char *)nm, UIO_SYSSPACE, &pn);
+ if (error == 0) {
+ error = VOP_LOOKUP(pvp, (char *)nm, vpp, &pn, 0, rootvp,
+ cr, NULL, NULL, NULL);
+ pn_free(&pn);
+ }
+ VN_RELE(pvp);
+
+ return (error);
+}
+
+/* ARGSUSED */
+static ino64_t
+xattrdir_do_ino(vnode_t *vp, int index)
+{
+ /*
+ * We use index 0 for the directory fid. Start
+ * the file numbering at 1.
+ */
+ return ((ino64_t)index+1);
+}
+
+void
+xattr_init(void)
+{
+ VERIFY(gfs_make_opsvec(xattr_opsvec) == 0);
+}
+
+int
+xattr_dir_lookup(vnode_t *dvp, vnode_t **vpp, int flags, cred_t *cr)
+{
+ int error = 0;
+
+ *vpp = NULL;
+
+ if (dvp->v_type != VDIR && dvp->v_type != VREG)
+ return (EINVAL);
+
+ mutex_enter(&dvp->v_lock);
+
+ /*
+ * If we're already in sysattr space, don't allow creation
+ * of another level of sysattrs.
+ */
+ if (dvp->v_flag & V_SYSATTR) {
+ mutex_exit(&dvp->v_lock);
+ return (EINVAL);
+ }
+
+ if (dvp->v_xattrdir != NULL) {
+ *vpp = dvp->v_xattrdir;
+ VN_HOLD(*vpp);
+ } else {
+ ulong_t val;
+ int xattrs_allowed = dvp->v_vfsp->vfs_flag & VFS_XATTR;
+ int sysattrs_allowed = 1;
+
+ /*
+ * We have to drop the lock on dvp. gfs_dir_create will
+ * grab it for a VN_HOLD.
+ */
+ mutex_exit(&dvp->v_lock);
+
+ /*
+ * If dvp allows xattr creation, but not sysattr
+ * creation, return the real xattr dir vp. We can't
+ * use the vfs feature mask here because _PC_SATTR_ENABLED
+ * has vnode-level granularity (e.g. .zfs).
+ */
+ error = VOP_PATHCONF(dvp, _PC_SATTR_ENABLED, &val, cr, NULL);
+ if (error != 0 || val == 0)
+ sysattrs_allowed = 0;
+
+ if (!xattrs_allowed && !sysattrs_allowed)
+ return (EINVAL);
+
+ if (!sysattrs_allowed) {
+ struct pathname pn;
+ char *nm = "";
+
+ error = pn_get(nm, UIO_SYSSPACE, &pn);
+ if (error)
+ return (error);
+ error = VOP_LOOKUP(dvp, nm, vpp, &pn,
+ flags|LOOKUP_HAVE_SYSATTR_DIR, rootvp, cr, NULL,
+ NULL, NULL);
+ pn_free(&pn);
+ return (error);
+ }
+
+ /*
+ * Note that we act as if we were given CREATE_XATTR_DIR,
+ * but only for creation of the GFS directory.
+ */
+ *vpp = gfs_dir_create(
+ sizeof (gfs_dir_t), dvp, xattr_dir_ops, xattr_dirents,
+ xattrdir_do_ino, MAXNAMELEN, NULL, xattr_lookup_cb);
+ mutex_enter(&dvp->v_lock);
+ if (dvp->v_xattrdir != NULL) {
+ /*
+ * We lost the race to create the xattr dir.
+ * Destroy this one, use the winner. We can't
+ * just call VN_RELE(*vpp), because the vnode
+ * is only partially initialized.
+ */
+ gfs_dir_t *dp = (*vpp)->v_data;
+
+ ASSERT((*vpp)->v_count == 1);
+ vn_free(*vpp);
+
+ mutex_destroy(&dp->gfsd_lock);
+ kmem_free(dp->gfsd_static,
+ dp->gfsd_nstatic * sizeof (gfs_dirent_t));
+ kmem_free(dp, dp->gfsd_file.gfs_size);
+
+ /*
+ * There is an implied VN_HOLD(dvp) here. We should
+ * be doing a VN_RELE(dvp) to clean up the reference
+ * from *vpp, and then a VN_HOLD(dvp) for the new
+ * reference. Instead, we just leave the count alone.
+ */
+
+ *vpp = dvp->v_xattrdir;
+ VN_HOLD(*vpp);
+ } else {
+ (*vpp)->v_flag |= (V_XATTRDIR|V_SYSATTR);
+ dvp->v_xattrdir = *vpp;
+ }
+ }
+ mutex_exit(&dvp->v_lock);
+
+ return (error);
+}
+
+int
+xattr_dir_vget(vfs_t *vfsp, vnode_t **vpp, fid_t *fidp)
+{
+ int error;
+ vnode_t *pvp, *dvp;
+ xattr_fid_t *xfidp;
+ struct pathname pn;
+ char *nm;
+ uint16_t orig_len;
+
+ *vpp = NULL;
+
+ if (fidp->fid_len < XATTR_FIDSZ)
+ return (EINVAL);
+
+ xfidp = (xattr_fid_t *)fidp;
+ orig_len = fidp->fid_len;
+ fidp->fid_len = xfidp->parent_len;
+
+ error = VFS_VGET(vfsp, &pvp, fidp);
+ fidp->fid_len = orig_len;
+ if (error)
+ return (error);
+
+ /*
+ * Start by getting the GFS sysattr directory. We might need
+ * to recreate it during the VOP_LOOKUP.
+ */
+ nm = "";
+ error = pn_get(nm, UIO_SYSSPACE, &pn);
+ if (error) {
+ VN_RELE(pvp);
+ return (EINVAL);
+ }
+
+ error = VOP_LOOKUP(pvp, nm, &dvp, &pn, LOOKUP_XATTR|CREATE_XATTR_DIR,
+ rootvp, CRED(), NULL, NULL, NULL);
+ pn_free(&pn);
+ VN_RELE(pvp);
+ if (error)
+ return (error);
+
+ if (xfidp->dir_offset == 0) {
+ /*
+ * If we were looking for the directory, we're done.
+ */
+ *vpp = dvp;
+ return (0);
+ }
+
+ if (xfidp->dir_offset > XATTRDIR_NENTS) {
+ VN_RELE(dvp);
+ return (EINVAL);
+ }
+
+ nm = xattr_dirents[xfidp->dir_offset - 1].gfse_name;
+
+ error = pn_get(nm, UIO_SYSSPACE, &pn);
+ if (error) {
+ VN_RELE(dvp);
+ return (EINVAL);
+ }
+
+ error = VOP_LOOKUP(dvp, nm, vpp, &pn, 0, rootvp, CRED(), NULL,
+ NULL, NULL);
+
+ pn_free(&pn);
+ VN_RELE(dvp);
+
+ return (error);
+}