+ else {
+ getattrlist_findnamecomp(vp->v_mount->mnt_vfsstat.f_mntonname, cnpp, cnlp);
+ }
+ }
+ else {
+ *cnpp = *vnamep = vnode_getname(vp);
+ *cnlp = 0;
+ if (*cnpp != NULL) {
+ *cnlp = strlen(*cnpp);
+ }
+ }
+ } else {
+ *cnlp = 0;
+ }
+ *varsizep += roundup(*cnlp + 1, 4);
+ }
+
+ /*
+ * Compute the full path to this vnode, if necessary. This attribute is almost certainly
+ * not supported by any filesystem, so build the path to this vnode at this time.
+ */
+ if (vp && (alp->commonattr & ATTR_CMN_FULLPATH)) {
+ int len = MAXPATHLEN;
+ int err;
+
+ /* call build_path making sure NOT to use the cache-only behavior */
+ err = build_path(vp, fullpathptr, len, &len, 0, vfs_context_current());
+ if (err) {
+ error = err;
+ goto out;
+ }
+ *fullpathlenp = 0;
+ if (fullpathptr){
+ *fullpathlenp = strlen(fullpathptr);
+ }
+ *varsizep += roundup(((*fullpathlenp) + 1), 4);
+ }
+
+ /*
+ * Compute this vnode's volume relative path.
+ */
+ if (vp && (alp->forkattr & ATTR_CMNEXT_RELPATH)) {
+ int len;
+ int err;
+
+ /* call build_path making sure NOT to use the cache-only behavior */
+ err = build_path(vp, relpathptr, MAXPATHLEN, &len, BUILDPATH_VOLUME_RELATIVE, vfs_context_current());
+ if (err) {
+ error = err;
+ goto out;
+ }
+
+ //`len' includes trailing null
+ *relpathlenp = len - 1;
+ *varsizep += roundup(len, 4);
+ }
+
+ /*
+ * We have a kauth_acl_t but we will be returning a kauth_filesec_t.
+ *
+ * XXX This needs to change at some point; since the blob is opaque in
+ * user-space this is OK.
+ */
+ if ((alp->commonattr & ATTR_CMN_EXTENDED_SECURITY) &&
+ VATTR_IS_SUPPORTED(vap, va_acl) &&
+ (vap->va_acl != NULL)) {
+
+ /*
+ * Since we have a kauth_acl_t (not a kauth_filesec_t), we have to check against
+ * KAUTH_FILESEC_NOACL ourselves
+ */
+ if (vap->va_acl->acl_entrycount == KAUTH_FILESEC_NOACL) {
+ *varsizep += roundup((KAUTH_FILESEC_SIZE(0)), 4);
+ }
+ else {
+ *varsizep += roundup ((KAUTH_FILESEC_SIZE(vap->va_acl->acl_entrycount)), 4);
+ }
+ }
+
+out:
+ return (error);
+}
+
+static errno_t
+vfs_attr_pack_internal(vnode_t vp, uio_t auio, struct attrlist *alp,
+ uint64_t options, struct vnode_attr *vap, __unused void *fndesc,
+ vfs_context_t ctx, int is_bulk, enum vtype vtype, ssize_t fixedsize)
+{
+ struct _attrlist_buf ab;
+ ssize_t buf_size;
+ size_t copy_size;
+ ssize_t varsize;
+ const char *vname = NULL;
+ const char *cnp;
+ ssize_t cnl;
+ char *fullpathptr;
+ ssize_t fullpathlen;
+ char *relpathptr;
+ ssize_t relpathlen;
+ int error;
+ int proc_is64;
+ int return_valid;
+ int pack_invalid;
+ int alloc_local_buf;
+ const int use_fork = options & FSOPT_ATTR_CMN_EXTENDED;
+
+ proc_is64 = proc_is64bit(vfs_context_proc(ctx));
+ ab.base = NULL;
+ cnp = "unknown";
+ cnl = 0;
+ fullpathptr = NULL;
+ fullpathlen = 0;
+ relpathptr = NULL;
+ relpathlen = 0;
+ error = 0;
+ alloc_local_buf = 0;
+
+ buf_size = (ssize_t)uio_resid(auio);
+ if ((buf_size <= 0) || (uio_iovcnt(auio) > 1))
+ return (EINVAL);
+
+ copy_size = 0;
+ /* Check for special packing semantics */
+ return_valid = (alp->commonattr & ATTR_CMN_RETURNED_ATTRS) ? 1 : 0;
+ pack_invalid = (options & FSOPT_PACK_INVAL_ATTRS) ? 1 : 0;
+
+ if (pack_invalid) {
+ /* Generate a valid mask for post processing */
+ bcopy(&(alp->commonattr), &ab.valid, sizeof (attribute_set_t));
+ }
+
+ /* did we ask for something the filesystem doesn't support? */
+ if (vap->va_active && !VATTR_ALL_SUPPORTED(vap)) {
+ vattr_get_alt_data(vp, alp, vap, return_valid, is_bulk,
+ ctx);
+
+ /* check again */
+ if (!VATTR_ALL_SUPPORTED(vap)) {
+ if (return_valid && pack_invalid) {
+ /* Fix up valid mask for post processing */
+ getattrlist_fixupattrs(&ab.valid, vap, use_fork);
+
+ /* Force packing of everything asked for */
+ vap->va_supported = vap->va_active;
+ } else if (return_valid) {
+ /* Adjust the requested attributes */
+ getattrlist_fixupattrs(
+ (attribute_set_t *)&(alp->commonattr), vap, use_fork);
+ } else {
+ error = EINVAL;
+ }
+ }
+
+ if (error)
+ goto out;
+ }
+
+ //if a path is requested, allocate a temporary buffer to build it
+ if (vp && (alp->commonattr & (ATTR_CMN_FULLPATH))) {
+ fullpathptr = (char*) kalloc(MAXPATHLEN);
+ if (fullpathptr == NULL) {
+ error = ENOMEM;
+ VFS_DEBUG(ctx,vp, "ATTRLIST - ERROR: cannot allocate fullpath buffer");
+ goto out;
+ }
+ bzero(fullpathptr, MAXPATHLEN);
+ }
+
+ // only interpret fork attributes if they're used as new common attributes
+ if (vp && use_fork && (alp->forkattr & (ATTR_CMNEXT_RELPATH))) {
+ relpathptr = (char*) kalloc(MAXPATHLEN);
+ if (relpathptr == NULL) {
+ error = ENOMEM;
+ VFS_DEBUG(ctx,vp, "ATTRLIST - ERROR: cannot allocate relpath buffer");
+ goto out;
+ }
+ bzero(relpathptr, MAXPATHLEN);
+ }
+
+ /*
+ * Compute variable-space requirements.
+ */
+ error = calc_varsize(vp, alp, vap, &varsize, fullpathptr, &fullpathlen,
+ relpathptr, &relpathlen, &vname, &cnp, &cnl);
+ if (error)
+ goto out;
+
+ /*
+ * Allocate a target buffer for attribute results.
+ *
+ * Note that we won't ever copy out more than the caller requested, even though
+ * we might have to allocate more than they offer so that the diagnostic checks
+ * don't result in a panic if the caller's buffer is too small..
+ */
+ ab.allocated = fixedsize + varsize;
+ /* Cast 'allocated' to an unsigned to verify allocation size */
+ if ( ((size_t)ab.allocated) > ATTR_MAX_BUFFER) {
+ error = ENOMEM;
+ VFS_DEBUG(ctx, vp, "ATTRLIST - ERROR: buffer size too large (%d limit %d)", ab.allocated, ATTR_MAX_BUFFER);
+ goto out;
+ }
+
+ /*
+ * Special handling for bulk calls, align to 8 (and only if enough
+ * space left.
+ */
+ if (is_bulk) {
+ if (buf_size < ab.allocated) {
+ goto out;
+ } else {
+ uint32_t newlen;
+
+ newlen = (ab.allocated + 7) & ~0x07;
+ /* Align only if enough space for alignment */
+ if (newlen <= (uint32_t)buf_size)
+ ab.allocated = newlen;
+ }
+ }
+
+ /*
+ * See if we can reuse buffer passed in i.e. it is a kernel buffer
+ * and big enough.
+ */
+ if (uio_isuserspace(auio) || (buf_size < ab.allocated)) {
+ MALLOC(ab.base, char *, ab.allocated, M_TEMP,
+ M_ZERO | M_WAITOK);
+ alloc_local_buf = 1;
+ } else {
+ /*
+ * In case this is a kernel buffer and sufficiently
+ * big, this function will try to use that buffer
+ * instead of allocating another buffer and bcopy'ing
+ * into it.
+ *
+ * The calculation below figures out where to start
+ * writing in the buffer and once all the data has been
+ * filled in, uio_resid is updated to reflect the usage
+ * of the buffer.
+ *
+ * uio_offset cannot be used here to determine the
+ * starting location as uio_offset could be set to a
+ * value which has nothing to do the location
+ * in the buffer.
+ */
+ ab.base = (char *)uio_curriovbase(auio) +
+ ((ssize_t)uio_curriovlen(auio) - buf_size);
+ bzero(ab.base, ab.allocated);
+ }
+
+ if (ab.base == NULL) {
+ error = ENOMEM;
+ VFS_DEBUG(ctx, vp, "ATTRLIST - ERROR: could not allocate %d for copy buffer", ab.allocated);
+ goto out;
+ }
+
+
+ /* set the S_IFMT bits for the mode */
+ if (alp->commonattr & ATTR_CMN_ACCESSMASK) {
+ if (vp) {
+ switch (vp->v_type) {
+ case VREG:
+ vap->va_mode |= S_IFREG;
+ break;
+ case VDIR:
+ vap->va_mode |= S_IFDIR;
+ break;
+ case VBLK:
+ vap->va_mode |= S_IFBLK;
+ break;
+ case VCHR:
+ vap->va_mode |= S_IFCHR;
+ break;
+ case VLNK:
+ vap->va_mode |= S_IFLNK;
+ break;
+ case VSOCK:
+ vap->va_mode |= S_IFSOCK;
+ break;
+ case VFIFO:
+ vap->va_mode |= S_IFIFO;
+ break;
+ default:
+ error = EBADF;
+ goto out;
+ }
+ }
+ }
+
+ /*
+ * Pack results into the destination buffer.
+ */
+ ab.fixedcursor = ab.base + sizeof(uint32_t);
+ if (return_valid) {
+ ab.fixedcursor += sizeof (attribute_set_t);
+ bzero(&ab.actual, sizeof (ab.actual));
+ }
+ ab.varcursor = ab.base + fixedsize;
+ ab.needed = ab.allocated;
+
+ /* common attributes ************************************************/
+ error = attr_pack_common(ctx, vp, alp, &ab, vap, proc_is64, cnp, cnl,
+ fullpathptr, fullpathlen, return_valid, pack_invalid, vtype, is_bulk);
+
+ /* directory attributes *********************************************/
+ if (!error && alp->dirattr && (vtype == VDIR)) {
+ error = attr_pack_dir(vp, alp, &ab, vap, return_valid, pack_invalid);
+ }
+
+ /* file attributes **************************************************/
+ if (!error && alp->fileattr && (vtype != VDIR)) {
+ error = attr_pack_file(ctx, vp, alp, &ab, vap, return_valid,
+ pack_invalid, is_bulk);
+ }
+
+ /* common extended attributes *****************************************/
+ if (!error && use_fork) {
+ error = attr_pack_common_extended(vp, alp, &ab, relpathptr, relpathlen,
+ vap, return_valid, pack_invalid);
+ }
+
+ if (error)
+ goto out;
+
+ /* diagnostic */
+ if (!return_valid && (ab.fixedcursor - ab.base) != fixedsize)
+ panic("packed field size mismatch; allocated %ld but packed %ld for common %08x vol %08x",
+ fixedsize, (long) (ab.fixedcursor - ab.base), alp->commonattr, alp->volattr);
+ if (!return_valid && ab.varcursor != (ab.base + ab.needed))
+ panic("packed variable field size mismatch; used %ld but expected %ld", (long) (ab.varcursor - ab.base), ab.needed);
+
+ /*
+ * In the compatible case, we report the smaller of the required and returned sizes.
+ * If the FSOPT_REPORT_FULLSIZE option is supplied, we report the full (required) size
+ * of the result buffer, even if we copied less out. The caller knows how big a buffer
+ * they gave us, so they can always check for truncation themselves.
+ */
+ *(uint32_t *)ab.base = (options & FSOPT_REPORT_FULLSIZE) ? ab.needed : imin(ab.allocated, ab.needed);
+
+ /* Return attribute set output if requested. */
+ if (return_valid) {
+ ab.actual.commonattr |= ATTR_CMN_RETURNED_ATTRS;
+ if (pack_invalid) {
+ /* Only report the attributes that are valid */
+ ab.actual.commonattr &= ab.valid.commonattr;
+ ab.actual.dirattr &= ab.valid.dirattr;
+ ab.actual.fileattr &= ab.valid.fileattr;
+ }
+ bcopy(&ab.actual, ab.base + sizeof(uint32_t), sizeof (ab.actual));
+ }
+
+ copy_size = imin(buf_size, ab.allocated);
+
+ /* Only actually copyout as much out as the user buffer can hold */
+ if (alloc_local_buf) {
+ error = uiomove(ab.base, copy_size, auio);
+ } else {
+ off_t orig_offset = uio_offset(auio);
+
+ /*
+ * The buffer in the uio struct was used directly
+ * (i.e. it was a kernel buffer and big enough
+ * to hold the data required) in order to avoid
+ * un-needed allocation and copies.
+ *
+ * At this point, update the resid value to what it
+ * would be if this was the result of a uiomove. The
+ * offset is also incremented, though it may not
+ * mean anything to the caller but that is what
+ * uiomove does as well.
+ */
+ uio_setresid(auio, buf_size - copy_size);
+ uio_setoffset(auio, orig_offset + (off_t)copy_size);
+ }
+
+out:
+ if (vname)
+ vnode_putname(vname);
+ if (fullpathptr)
+ kfree(fullpathptr, MAXPATHLEN);
+ if (relpathptr)
+ kfree(relpathptr, MAXPATHLEN);
+ if (ab.base != NULL && alloc_local_buf)
+ FREE(ab.base, M_TEMP);
+ return (error);
+}
+
+errno_t
+vfs_attr_pack(vnode_t vp, uio_t uio, struct attrlist *alp, uint64_t options,
+ struct vnode_attr *vap, __unused void *fndesc, vfs_context_t ctx)
+{
+ int error;
+ ssize_t fixedsize;
+ uint64_t orig_active;
+ struct attrlist orig_al;
+ enum vtype v_type;
+
+ if (vp)
+ v_type = vnode_vtype(vp);
+ else
+ v_type = vap->va_objtype;
+
+ orig_al = *alp;
+ orig_active = vap->va_active;
+ vap->va_active = 0;
+
+ error = getattrlist_setupvattr_all(alp, vap, v_type, &fixedsize,
+ proc_is64bit(vfs_context_proc(ctx)), options & FSOPT_ATTR_CMN_EXTENDED);
+
+ if (error) {
+ VFS_DEBUG(ctx, vp,
+ "ATTRLIST - ERROR: setup for request failed");
+ goto out;
+ }
+
+ error = vfs_attr_pack_internal(vp, uio, alp,
+ options|FSOPT_REPORT_FULLSIZE, vap, NULL, ctx, 1, v_type,
+ fixedsize);
+
+ VATTR_CLEAR_SUPPORTED_ALL(vap);
+ vap->va_active = orig_active;
+ *alp = orig_al;
+out:
+ return (error);
+}
+
+/*
+ * Obtain attribute information about a filesystem object.
+ *
+ * Note: The alt_name parameter can be used by the caller to pass in the vnode
+ * name obtained from some authoritative source (eg. readdir vnop); where
+ * filesystems' getattr vnops do not support ATTR_CMN_NAME, the alt_name will be
+ * used as the ATTR_CMN_NAME attribute returned in vnode_attr.va_name.
+ *
+ */
+static int
+getattrlist_internal(vfs_context_t ctx, vnode_t vp, struct attrlist *alp,
+ user_addr_t attributeBuffer, size_t bufferSize, uint64_t options,
+ enum uio_seg segflg, char* authoritative_name, struct ucred *file_cred)
+{
+ struct vnode_attr va;
+ kauth_action_t action;
+ ssize_t fixedsize;
+ char *va_name;
+ int proc_is64;
+ int error;
+ int return_valid;
+ int pack_invalid;
+ int vtype = 0;
+ uio_t auio;
+ char uio_buf[ UIO_SIZEOF(1)];
+ // must be true for fork attributes to be used as new common attributes
+ const int use_fork = (options & FSOPT_ATTR_CMN_EXTENDED) != 0;
+
+ if (bufferSize < sizeof(uint32_t))
+ return (ERANGE);
+
+ proc_is64 = proc_is64bit(vfs_context_proc(ctx));
+
+ if (segflg == UIO_USERSPACE) {
+ if (proc_is64)
+ segflg = UIO_USERSPACE64;
+ else
+ segflg = UIO_USERSPACE32;
+ }
+ auio = uio_createwithbuffer(1, 0, segflg, UIO_READ,
+ &uio_buf[0], sizeof(uio_buf));
+ uio_addiov(auio, attributeBuffer, bufferSize);
+
+ VATTR_INIT(&va);
+ va_name = NULL;
+
+ if (alp->bitmapcount != ATTR_BIT_MAP_COUNT) {
+ error = EINVAL;
+ goto out;
+ }
+
+ VFS_DEBUG(ctx, vp, "%p ATTRLIST - %s request common %08x vol %08x file %08x dir %08x fork %08x %sfollow on '%s'",
+ vp, p->p_comm, alp->commonattr, alp->volattr, alp->fileattr, alp->dirattr, alp->forkattr,
+ (options & FSOPT_NOFOLLOW) ? "no":"", vp->v_name);
+
+#if CONFIG_MACF
+ error = mac_vnode_check_getattrlist(ctx, vp, alp);
+ if (error)
+ goto out;
+#endif /* MAC */
+
+ /*
+ * It is legal to request volume or file attributes, but not both.
+ *
+ * 26903449 fork attributes can also be requested, but only if they're
+ * interpreted as new, common attributes
+ */
+ if (alp->volattr) {
+ if (alp->fileattr || alp->dirattr || (alp->forkattr && !use_fork)) {
+ error = EINVAL;
+ VFS_DEBUG(ctx, vp, "ATTRLIST - ERROR: mixed volume/file/directory attributes");
+ goto out;
+ }
+ /* handle volume attribute request */
+ error = getvolattrlist(ctx, vp, alp, attributeBuffer,
+ bufferSize, options, segflg, proc_is64);
+ goto out;
+ }
+
+ /*
+ * ATTR_CMN_GEN_COUNT and ATTR_CMN_DOCUMENT_ID reuse the bits
+ * originally allocated to ATTR_CMN_NAMEDATTRCOUNT and
+ * ATTR_CMN_NAMEDATTRLIST.
+ */
+ if ((alp->commonattr & (ATTR_CMN_GEN_COUNT | ATTR_CMN_DOCUMENT_ID)) &&
+ !(options & FSOPT_ATTR_CMN_EXTENDED)) {
+ error = EINVAL;
+ goto out;
+ }
+
+ /* common extended attributes require FSOPT_ATTR_CMN_EXTENDED option */
+ if (!(use_fork) && (alp->forkattr & ATTR_CMNEXT_VALIDMASK)) {
+ error = EINVAL;
+ goto out;
+ }
+
+ /* FSOPT_ATTR_CMN_EXTENDED requires forkattrs are not referenced */
+ if ((options & FSOPT_ATTR_CMN_EXTENDED) && (alp->forkattr & (ATTR_FORK_VALIDMASK))) {
+ error = EINVAL;
+ goto out;
+ }
+
+ /* Check for special packing semantics */
+ return_valid = (alp->commonattr & ATTR_CMN_RETURNED_ATTRS) ? 1 : 0;
+ pack_invalid = (options & FSOPT_PACK_INVAL_ATTRS) ? 1 : 0;
+ if (pack_invalid) {
+ /* FSOPT_PACK_INVAL_ATTRS requires ATTR_CMN_RETURNED_ATTRS */
+ if (!return_valid || (alp->forkattr && !use_fork)) {
+ error = EINVAL;
+ goto out;
+ }
+ /* Keep invalid attrs from being uninitialized */
+ bzero(&va, sizeof (va));
+ }
+
+ /* Pick up the vnode type. If the FS is bad and changes vnode types on us, we
+ * will have a valid snapshot that we can work from here.
+ */
+ vtype = vp->v_type;
+
+ /*
+ * Set up the vnode_attr structure and authorise.
+ */
+ if ((error = getattrlist_setupvattr(alp, &va, &fixedsize, &action, proc_is64, (vtype == VDIR), use_fork)) != 0) {
+ VFS_DEBUG(ctx, vp, "ATTRLIST - ERROR: setup for request failed");
+ goto out;
+ }
+ if ((error = vnode_authorize(vp, NULL, action, ctx)) != 0) {
+ VFS_DEBUG(ctx, vp, "ATTRLIST - ERROR: authorisation failed/denied");
+ goto out;
+ }
+
+
+ if (va.va_active != 0) {
+ uint64_t va_active = va.va_active;
+
+ /*
+ * If we're going to ask for va_name, allocate a buffer to point it at
+ */
+ if (VATTR_IS_ACTIVE(&va, va_name)) {
+ MALLOC_ZONE(va_name, char *, MAXPATHLEN, M_NAMEI,
+ M_WAITOK);
+ if (va_name == NULL) {
+ error = ENOMEM;
+ VFS_DEBUG(ctx, vp, "ATTRLIST - ERROR: cannot allocate va_name buffer");
+ goto out;
+ }
+ /*
+ * If we have an authoritative_name, prefer that name.
+ *
+ * N.B. Since authoritative_name implies this is coming from getattrlistbulk,
+ * we know the name is authoritative. For /dev/fd, we want to use the file
+ * descriptor as the name not the underlying name of the associate vnode in a
+ * particular file system.
+ */
+ if (authoritative_name) {
+ /* Don't ask the file system */
+ VATTR_CLEAR_ACTIVE(&va, va_name);
+ strlcpy(va_name, authoritative_name, MAXPATHLEN);
+ }
+ }
+
+ va.va_name = authoritative_name ? NULL : va_name;
+
+ /*
+ * Call the filesystem.
+ */
+ if ((error = vnode_getattr(vp, &va, ctx)) != 0) {
+ VFS_DEBUG(ctx, vp, "ATTRLIST - ERROR: filesystem returned %d", error);
+ goto out;
+ }
+#if CONFIG_MACF
+ /*
+ * Give MAC polices a chance to reject or filter the
+ * attributes returned by the filesystem. Note that MAC
+ * policies are consulted *after* calling the filesystem
+ * because filesystems can return more attributes than
+ * were requested so policies wouldn't be authoritative
+ * is consulted beforehand. This also gives policies an
+ * opportunity to change the values of attributes
+ * retrieved.
+ */
+ error = mac_vnode_check_getattr(ctx, file_cred, vp, &va);
+ if (error) {
+ VFS_DEBUG(ctx, vp, "ATTRLIST - ERROR: MAC framework returned %d", error);
+ goto out;
+ }
+#else
+ (void)file_cred;
+#endif
+
+ /*
+ * It we ask for the name, i.e., vname is non null and
+ * we have an authoritative name, then reset va_name is
+ * active and if needed set va_name is supported.
+ *
+ * A (buggy) filesystem may change fields which belong
+ * to us. We try to deal with that here as well.
+ */
+ va.va_active = va_active;
+ if (authoritative_name && va_name) {
+ VATTR_SET_ACTIVE(&va, va_name);
+ if (!(VATTR_IS_SUPPORTED(&va, va_name))) {
+ VATTR_SET_SUPPORTED(&va, va_name);
+ }
+ }
+ va.va_name = va_name;
+ }
+
+ error = vfs_attr_pack_internal(vp, auio, alp, options, &va, NULL, ctx,
+ 0, vtype, fixedsize);
+
+out:
+ if (va_name)
+ FREE_ZONE(va_name, MAXPATHLEN, M_NAMEI);
+ if (VATTR_IS_SUPPORTED(&va, va_acl) && (va.va_acl != NULL))
+ kauth_acl_free(va.va_acl);
+
+ VFS_DEBUG(ctx, vp, "ATTRLIST - returning %d", error);
+ return(error);
+}
+
+int
+fgetattrlist(proc_t p, struct fgetattrlist_args *uap, __unused int32_t *retval)
+{
+ vfs_context_t ctx;
+ vnode_t vp;
+ int error;
+ struct attrlist al;
+ struct fileproc *fp;
+
+ ctx = vfs_context_current();
+ vp = NULL;
+ fp = NULL;
+ error = 0;
+
+ if ((error = file_vnode(uap->fd, &vp)) != 0)
+ return (error);
+
+ if ((error = fp_lookup(p, uap->fd, &fp, 0)) != 0 ||
+ (error = vnode_getwithref(vp)) != 0)
+ goto out;
+
+ /*
+ * Fetch the attribute request.
+ */
+ error = copyin(uap->alist, &al, sizeof(al));
+ if (error)
+ goto out;
+
+ /* Default to using the vnode's name. */
+ error = getattrlist_internal(ctx, vp, &al, uap->attributeBuffer,
+ uap->bufferSize, uap->options,
+ (IS_64BIT_PROCESS(p) ? UIO_USERSPACE64 : \
+ UIO_USERSPACE32), NULL,
+ fp->f_fglob->fg_cred);
+
+out:
+ if (fp)
+ fp_drop(p, uap->fd, fp, 0);
+ if (vp)
+ vnode_put(vp);
+ file_drop(uap->fd);
+
+ return error;
+}
+
+static int
+getattrlistat_internal(vfs_context_t ctx, user_addr_t path,
+ struct attrlist *alp, user_addr_t attributeBuffer, size_t bufferSize,
+ uint64_t options, enum uio_seg segflg, enum uio_seg pathsegflg, int fd)
+{
+ struct nameidata nd;
+ vnode_t vp;
+ int32_t nameiflags;
+ int error;
+
+ nameiflags = 0;
+ /*
+ * Look up the file.
+ */
+ if (!(options & FSOPT_NOFOLLOW))
+ nameiflags |= FOLLOW;
+
+ nameiflags |= AUDITVNPATH1;
+ NDINIT(&nd, LOOKUP, OP_GETATTR, nameiflags, pathsegflg,
+ path, ctx);
+
+ error = nameiat(&nd, fd);
+
+ if (error)
+ return (error);
+
+ vp = nd.ni_vp;
+
+ error = getattrlist_internal(ctx, vp, alp, attributeBuffer,
+ bufferSize, options, segflg, NULL, NOCRED);
+
+ /* Retain the namei reference until the getattrlist completes. */
+ nameidone(&nd);
+ vnode_put(vp);
+ return (error);
+}
+
+int
+getattrlist(proc_t p, struct getattrlist_args *uap, __unused int32_t *retval)
+{
+ enum uio_seg segflg;
+ struct attrlist al;
+ int error;
+
+ segflg = IS_64BIT_PROCESS(p) ? UIO_USERSPACE64 : UIO_USERSPACE32;
+
+ /*
+ * Fetch the attribute request.
+ */
+ error = copyin(uap->alist, &al, sizeof(al));
+ if (error)
+ return error;
+
+ return (getattrlistat_internal(vfs_context_current(),
+ CAST_USER_ADDR_T(uap->path), &al,
+ CAST_USER_ADDR_T(uap->attributeBuffer), uap->bufferSize,
+ (uint64_t)uap->options, segflg, segflg, AT_FDCWD));
+}
+
+int
+getattrlistat(proc_t p, struct getattrlistat_args *uap, __unused int32_t *retval)
+{
+ enum uio_seg segflg;
+ struct attrlist al;
+ int error;
+
+ segflg = IS_64BIT_PROCESS(p) ? UIO_USERSPACE64 : UIO_USERSPACE32;
+
+ /*
+ * Fetch the attribute request.
+ */
+ error = copyin(uap->alist, &al, sizeof(al));
+ if (error)
+ return error;
+
+ return (getattrlistat_internal(vfs_context_current(),
+ CAST_USER_ADDR_T(uap->path), &al,
+ CAST_USER_ADDR_T(uap->attributeBuffer), uap->bufferSize,
+ (uint64_t)uap->options, segflg, segflg, uap->fd));
+}
+
+/*
+ * This refills the per-fd direntries cache by issuing a VNOP_READDIR.
+ * It attempts to try and find a size the filesystem responds to, so
+ * it first tries 1 direntry sized buffer and going from 1 to 2 to 4
+ * direntry sized buffers to readdir. If the filesystem does not respond
+ * to 4 * direntry it returns the error by the filesystem (if any) and sets
+ * EOF.
+ *
+ * This function also tries again if the last "refill" returned an EOF
+ * to try and get any additional entries if they were added after the last
+ * refill.
+ */
+static int
+refill_fd_direntries(vfs_context_t ctx, vnode_t dvp, struct fd_vn_data *fvd,
+ int *eofflagp)
+{
+ uio_t rdir_uio;
+ char uio_buf[UIO_SIZEOF(1)];
+ size_t rdirbufsiz;
+ size_t rdirbufused;
+ int eofflag;
+ int nentries;
+ int error;
+
+ /*
+ * If the last readdir returned EOF, don't try again.
+ */
+ if (fvd->fv_eofflag) {
+ *eofflagp = 1;
+ if (fvd->fv_buf) {
+ FREE(fvd->fv_buf, M_FD_DIRBUF);
+ fvd->fv_buf = NULL;
+ }
+ return 0;
+ }
+
+ error = 0;
+
+ /*
+ * If there is a cached allocation size of the dirbuf that should be
+ * allocated, use that. Otherwise start with a allocation size of
+ * FV_DIRBUF_START_SIZ. This start size may need to be increased if the
+ * filesystem doesn't respond to the initial size.
+ */
+
+ if (fvd->fv_offset && fvd->fv_bufallocsiz) {
+ rdirbufsiz = fvd->fv_bufallocsiz;
+ } else {
+ rdirbufsiz = FV_DIRBUF_START_SIZ;
+ }
+
+ *eofflagp = 0;
+
+ rdir_uio = uio_createwithbuffer(1, 0, UIO_SYSSPACE, UIO_READ,
+ &uio_buf[0], sizeof(uio_buf));
+
+retry_alloc:
+ /*
+ * Don't explicitly zero out this buffer since this is
+ * not copied out to user space.
+ */
+ if (!fvd->fv_buf) {
+ MALLOC(fvd->fv_buf, caddr_t, rdirbufsiz, M_FD_DIRBUF, M_WAITOK);
+ fvd->fv_bufdone = 0;
+ }
+
+ uio_reset(rdir_uio, fvd->fv_eoff, UIO_SYSSPACE, UIO_READ);
+ uio_addiov(rdir_uio, CAST_USER_ADDR_T(fvd->fv_buf), rdirbufsiz);
+
+ /*
+ * Some filesystems do not set nentries or eofflag...
+ */
+ eofflag = 0;
+ nentries = 0;
+ error = vnode_readdir64(dvp, rdir_uio, VNODE_READDIR_EXTENDED,
+ &eofflag, &nentries, ctx);
+
+ rdirbufused = rdirbufsiz - (size_t)uio_resid(rdir_uio);
+
+ if (!error && (rdirbufused > 0) && (rdirbufused <= rdirbufsiz)) {
+ /* Save offsets */
+ fvd->fv_soff = fvd->fv_eoff;
+ fvd->fv_eoff = uio_offset(rdir_uio);
+ /* Save eofflag state but don't return EOF for this time.*/
+ fvd->fv_eofflag = eofflag;
+ eofflag = 0;
+ /* Reset buffer parameters */
+ fvd->fv_bufsiz = rdirbufused;
+ fvd->fv_bufdone = 0;
+ bzero(fvd->fv_buf + rdirbufused, rdirbufsiz - rdirbufused);
+ /* Cache allocation size the Filesystem responded to */
+ fvd->fv_bufallocsiz = rdirbufsiz;
+ } else if (!eofflag && (rdirbufsiz < FV_DIRBUF_MAX_SIZ)) {
+ /*
+ * Some Filesystems have higher requirements for the
+ * smallest buffer size they will respond to for a
+ * directory listing. Start (relatively) small but increase
+ * it upto FV_DIRBUF_MAX_SIZ. Most should be good with
+ * 1*direntry. Cache the size found so that this does not need
+ * need to be done every time. This also means that an error
+ * from VNOP_READDIR is ignored until at least FV_DIRBUF_MAX_SIZ
+ * has been attempted.
+ */
+ FREE(fvd->fv_buf, M_FD_DIRBUF);
+ fvd->fv_buf = NULL;
+ rdirbufsiz = 2 * rdirbufsiz;
+ fvd->fv_bufallocsiz = 0;
+ goto retry_alloc;
+ } else if (!error) {
+ /*
+ * The Filesystem did not set eofflag but also did not
+ * return any entries (or an error). It is presumed that
+ * EOF has been reached.
+ */
+ fvd->fv_eofflag = eofflag = 1;
+ }
+
+ /*
+ * If the filesystem returned an error and it had previously returned
+ * EOF, ignore the error and set EOF.
+ */
+ if (error && fvd->fv_eofflag) {
+ eofflag = 1;
+ error = 0;
+ }
+
+ /*
+ * If either the directory has either hit EOF or an error, now is a good
+ * time to free up directory entry buffer.
+ */
+ if ((error || eofflag) && fvd->fv_buf) {
+ FREE(fvd->fv_buf, M_FD_DIRBUF);
+ fvd->fv_buf = NULL;
+ }
+
+ *eofflagp = eofflag;
+
+ return (error);
+}
+
+/*
+ * gets the current direntry. To advance to the next direntry this has to be
+ * paired with a direntry_done.
+ *
+ * Since directories have restrictions on where directory enumeration
+ * can restart from, entries are first read into* a per fd diectory entry
+ * "cache" and entries provided from that cache.
+ */
+static int
+get_direntry(vfs_context_t ctx, vnode_t dvp, struct fd_vn_data *fvd,
+ int *eofflagp, struct direntry **dpp)
+{
+ int eofflag;
+ int error;
+
+ *eofflagp = 0;
+ *dpp = NULL;
+ error = 0;
+ if (!fvd->fv_bufsiz) {
+ error = refill_fd_direntries(ctx, dvp, fvd, &eofflag);
+ if (error) {
+ return (error);
+ }
+ if (eofflag) {
+ *eofflagp = eofflag;
+ return (error);
+ }
+ }
+
+ *dpp = (struct direntry *)(fvd->fv_buf + fvd->fv_bufdone);
+ return (error);
+}
+
+/*
+ * Advances to the next direntry.
+ */
+static void
+direntry_done(struct fd_vn_data *fvd)
+{
+ struct direntry *dp;
+
+ dp = (struct direntry *)(fvd->fv_buf + fvd->fv_bufdone);
+ if (dp->d_reclen) {
+ fvd->fv_bufdone += dp->d_reclen;
+ if (fvd->fv_bufdone > fvd->fv_bufsiz) {
+ fvd->fv_bufdone = fvd->fv_bufsiz;
+ }
+ } else {
+ fvd->fv_bufdone = fvd->fv_bufsiz;
+ }
+
+ /*
+ * If we're at the end the fd direntries cache, reset the
+ * cache trackers.
+ */
+ if (fvd->fv_bufdone == fvd->fv_bufsiz) {
+ fvd->fv_bufdone = 0;
+ fvd->fv_bufsiz = 0;
+ }
+}
+
+/*
+ * A stripped down version of getattrlist_internal to fill in only select
+ * attributes in case of an error from getattrlist_internal.
+ *
+ * It always returns at least ATTR_BULK_REQUIRED i.e. the name (but may also
+ * return some other attributes which can be obtained from the vnode).
+ *
+ * It does not change the value of the passed in attrlist.
+ *
+ * The objective of this function is to fill in an "error entry", i.e.
+ * an entry with ATTR_CMN_RETURNED_ATTRS & ATTR_CMN_NAME. If the caller
+ * has also asked for ATTR_CMN_ERROR, it is filled in as well.
+ *
+ * Input
+ * vp - vnode pointer
+ * alp - pointer to attrlist struct.
+ * options - options passed to getattrlistbulk(2)
+ * kern_attr_buf - Kernel buffer to fill data (assumes offset 0 in
+ * buffer)
+ * kern_attr_buf_siz - Size of buffer.
+ * needs_error_attr - Whether the caller asked for ATTR_CMN_ERROR
+ * error_attr - This value is used to fill ATTR_CMN_ERROR (if the user
+ * has requested it in the attribute list.
+ * namebuf - This is used to fill in the name.
+ * ctx - vfs context of caller.
+ */
+static void
+get_error_attributes(vnode_t vp, struct attrlist *alp, uint64_t options,
+ user_addr_t kern_attr_buf, size_t kern_attr_buf_siz, int error_attr,
+ caddr_t namebuf, vfs_context_t ctx)
+{
+ size_t fsiz, vsiz;
+ struct _attrlist_buf ab;
+ int namelen;
+ kauth_action_t action;
+ struct attrlist al;
+ int needs_error_attr = (alp->commonattr & ATTR_CMN_ERROR);
+
+ /*
+ * To calculate fixed size required, in the FSOPT_PACK_INVAL_ATTRS case,
+ * the fixedsize should include space for all the attributes asked by
+ * the user. Only ATTR_BULK_REQUIRED (and ATTR_CMN_ERROR) will be filled
+ * and will be valid. All other attributes are zeroed out later.
+ *
+ * ATTR_CMN_RETURNED_ATTRS, ATTR_CMN_ERROR and ATTR_CMN_NAME
+ * (the only valid ones being returned from here) happen to be
+ * the first three attributes by order as well.
+ */
+ al = *alp;
+ if (!(options & FSOPT_PACK_INVAL_ATTRS)) {
+ /*
+ * In this case the fixedsize only needs to be only for the
+ * attributes being actually returned.
+ */
+ al.commonattr = ATTR_BULK_REQUIRED;
+ if (needs_error_attr) {
+ al.commonattr |= ATTR_CMN_ERROR;
+ }
+ al.fileattr = 0;
+ al.dirattr = 0;
+ }
+
+ /*
+ * Passing NULL for the vnode_attr pointer is valid for
+ * getattrlist_setupvattr. All that is required is the size.
+ */
+ fsiz = 0;
+ (void)getattrlist_setupvattr(&al, NULL, (ssize_t *)&fsiz,
+ &action, proc_is64bit(vfs_context_proc(ctx)),
+ (vnode_vtype(vp) == VDIR), (options & FSOPT_ATTR_CMN_EXTENDED));
+
+ namelen = strlen(namebuf);
+ vsiz = namelen + 1;
+ vsiz = ((vsiz + 3) & ~0x03);
+
+ bzero(&ab, sizeof(ab));
+ ab.base = (char *)kern_attr_buf;
+ ab.needed = fsiz + vsiz;
+
+ /* Fill in the size needed */
+ *((uint32_t *)ab.base) = ab.needed;
+ if (ab.needed > (ssize_t)kern_attr_buf_siz) {
+ goto out;
+ }
+
+ /*
+ * Setup to pack results into the destination buffer.
+ */
+ ab.fixedcursor = ab.base + sizeof(uint32_t);
+ /*
+ * Zero out buffer, ab.fixedbuffer starts after the first uint32_t
+ * which gives the length. This ensures everything that we don't
+ * fill in explicitly later is zeroed out correctly.
+ */
+ bzero(ab.fixedcursor, fsiz);
+ /*
+ * variable size data should start after all the fixed
+ * size data.
+ */
+ ab.varcursor = ab.base + fsiz;
+ /*
+ * Initialise the value for ATTR_CMN_RETURNED_ATTRS and leave space
+ * Leave space for filling in its value here at the end.
+ */
+ bzero(&ab.actual, sizeof (ab.actual));
+ ab.fixedcursor += sizeof (attribute_set_t);
+
+ ab.allocated = ab.needed;
+
+ /* Fill ATTR_CMN_ERROR (if asked for) */
+ if (needs_error_attr) {
+ ATTR_PACK4(ab, error_attr);
+ ab.actual.commonattr |= ATTR_CMN_ERROR;
+ }
+
+ /*
+ * Fill ATTR_CMN_NAME, The attrrefrence is packed at this location
+ * but the actual string itself is packed after fixedsize which set
+ * to different lengths based on whether FSOPT_PACK_INVAL_ATTRS
+ * was passed.
+ */
+ attrlist_pack_string(&ab, namebuf, namelen);
+
+ /*
+ * Now Fill in ATTR_CMN_RETURNED_ATTR. This copies to a
+ * location after the count i.e. before ATTR_CMN_ERROR and
+ * ATTR_CMN_NAME.
+ */
+ ab.actual.commonattr |= ATTR_CMN_NAME | ATTR_CMN_RETURNED_ATTRS;
+ bcopy(&ab.actual, ab.base + sizeof(uint32_t), sizeof (ab.actual));
+out:
+ return;
+}
+
+/*
+ * This is the buffer size required to return at least 1 entry. We need space
+ * for the length, for ATTR_CMN_RETURNED_ATTRS and ATTR_CMN_NAME. Assuming the
+ * smallest filename of a single byte we get
+ */
+
+#define MIN_BUF_SIZE_REQUIRED (sizeof(uint32_t) + sizeof(attribute_set_t) +\
+ sizeof(attrreference_t))
+
+/*
+ * Read directory entries and get attributes filled in for each directory
+ */
+static int
+readdirattr(vnode_t dvp, struct fd_vn_data *fvd, uio_t auio,
+ struct attrlist *alp, uint64_t options, int *count, int *eofflagp,
+ vfs_context_t ctx)
+{
+ caddr_t kern_attr_buf;
+ size_t kern_attr_buf_siz;
+ caddr_t max_path_name_buf = NULL;
+ int error = 0;
+
+ *count = 0;
+ *eofflagp = 0;
+
+ if (uio_iovcnt(auio) > 1) {
+ return (EINVAL);
+ }
+
+ /*
+ * We fill in a kernel buffer for the attributes and uiomove each
+ * entry's attributes (as returned by getattrlist_internal)
+ */
+ kern_attr_buf_siz = uio_resid(auio);
+ if (kern_attr_buf_siz > ATTR_MAX_BUFFER) {
+ kern_attr_buf_siz = ATTR_MAX_BUFFER;
+ } else if (kern_attr_buf_siz == 0) {
+ /* Nothing to do */
+ return (error);
+ }
+
+ MALLOC(kern_attr_buf, caddr_t, kern_attr_buf_siz, M_TEMP, M_WAITOK);
+
+ while (uio_resid(auio) > (user_ssize_t)MIN_BUF_SIZE_REQUIRED) {
+ struct direntry *dp;
+ user_addr_t name_buffer;
+ struct nameidata nd;
+ vnode_t vp;
+ struct attrlist al;
+ size_t entlen;
+ size_t bytes_left;
+ size_t pad_bytes;
+ ssize_t new_resid;
+
+ /*
+ * get_direntry returns the current direntry and does not
+ * advance. A move to the next direntry only happens if
+ * direntry_done is called.
+ */
+ error = get_direntry(ctx, dvp, fvd, eofflagp, &dp);
+ if (error || (*eofflagp) || !dp) {
+ break;
+ }
+
+ /*
+ * skip "." and ".." (and a bunch of other invalid conditions.)
+ */
+ if (!dp->d_reclen || dp->d_ino == 0 || dp->d_namlen == 0 ||
+ (dp->d_namlen == 1 && dp->d_name[0] == '.') ||
+ (dp->d_namlen == 2 && dp->d_name[0] == '.' &&
+ dp->d_name[1] == '.')) {
+ direntry_done(fvd);
+ continue;
+ }
+
+ /*
+ * try to deal with not-null terminated filenames.
+ */
+ if (dp->d_name[dp->d_namlen] != '\0') {
+ if (!max_path_name_buf) {
+ MALLOC(max_path_name_buf, caddr_t, MAXPATHLEN,
+ M_TEMP, M_WAITOK);