/*
 * Copyright (c) 2002 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * The contents of this file constitute Original Code as defined in and
 * are subject to the Apple Public Source License Version 1.1 (the
 * "License").  You may not use this file except in compliance with the
 * License.  Please obtain a copy of the License at
 * http://www.apple.com/publicsource and read it before using this file.
 * 
 * This Original Code and all software distributed under the License are
 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

/*
 * msdosfs_attrlist.c - DOS FAT attribute list processing
 *
 * Copyright (c) 2002, Apple Computer, Inc.  All Rights Reserved.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/vnode.h>
#include <miscfs/specfs/specdev.h>
#include <sys/malloc.h>
#include <sys/attr.h>

#include "bpb.h"
#include "direntry.h"
#include "denode.h"
#include "msdosfsmount.h"
#include "fat.h"

enum {
    MSDOSFS_ATTR_CMN_NATIVE		= ATTR_CMN_DEVID | ATTR_CMN_FSID | ATTR_CMN_OBJTYPE | ATTR_CMN_OBJTAG | ATTR_CMN_OBJID |
                                ATTR_CMN_CRTIME | ATTR_CMN_MODTIME | ATTR_CMN_ACCTIME | ATTR_CMN_FLAGS,
    MSDOSFS_ATTR_CMN_SUPPORTED	= MSDOSFS_ATTR_CMN_NATIVE | ATTR_CMN_OWNERID | ATTR_CMN_GRPID | ATTR_CMN_ACCESSMASK |
                                ATTR_CMN_USERACCESS,
    MSDOSFS_ATTR_VOL_NATIVE		= ATTR_VOL_FSTYPE | ATTR_VOL_SIZE | ATTR_VOL_SPACEFREE | ATTR_VOL_SPACEAVAIL |
                                ATTR_VOL_MINALLOCATION | ATTR_VOL_ALLOCATIONCLUMP | ATTR_VOL_IOBLOCKSIZE |
                                ATTR_VOL_MAXOBJCOUNT | ATTR_VOL_MOUNTPOINT | ATTR_VOL_MOUNTFLAGS |
                                ATTR_VOL_MOUNTEDDEVICE | ATTR_VOL_CAPABILITIES,
    MSDOSFS_ATTR_VOL_SUPPORTED	= MSDOSFS_ATTR_VOL_NATIVE,
    MSDOSFS_ATTR_DIR_NATIVE		= ATTR_DIR_MOUNTSTATUS,
    MSDOSFS_ATTR_DIR_SUPPORTED	= MSDOSFS_ATTR_DIR_NATIVE | ATTR_DIR_LINKCOUNT,
    MSDOSFS_ATTR_FILE_NATIVE	= ATTR_FILE_TOTALSIZE | ATTR_FILE_ALLOCSIZE | ATTR_FILE_IOBLOCKSIZE | ATTR_FILE_CLUMPSIZE |
                                ATTR_FILE_DATALENGTH | ATTR_FILE_DATAALLOCSIZE,
    MSDOSFS_ATTR_FILE_SUPPORTED	= MSDOSFS_ATTR_FILE_NATIVE | ATTR_FILE_LINKCOUNT,
    MSDOSFS_ATTR_FORK_NATIVE	= 0,
    MSDOSFS_ATTR_FORK_SUPPORTED	= MSDOSFS_ATTR_FORK_NATIVE
};

/*
 * Pack a C-style string into an attribute buffer.  Returns the new varptr.
 */
static void *packstr(char *s, void *attrptr, void *varptr)
{
    struct attrreference *ref = attrptr;
    u_long length;
    
    length = strlen(s) + 1;		/* String, plus terminator */
    
    /* In fixed-length part of buffer, store offset and length of variable-length data */
    ref->attr_dataoffset = (u_int8_t *) varptr - (u_int8_t *) attrptr;
    ref->attr_length = length;
    
    /* Copy the string to variable-length part of buffer */
    (void) strncpy((unsigned char *)varptr, s, length);
    
    /* Advance pointer past string, and round up to multiple of 4 bytes */
    varptr += length + ((4 - (length & 3)) & 3);
        
    return varptr;
}

/*
 * msdosfs_packvolattr
 *
 * Pack the volume-related attributes from a getattrlist call into result buffers.
 * Fields are packed in order based on the bitmap masks.  Attributes with smaller
 * masks are packed first.
 *
 * The buffer pointers are updated to point past the data that was returned.
 */
static void msdosfs_packvolattr(
    struct attrlist *alist,		/* Desired attributes */
    struct denode *dep,			/* DOS-specific vnode information */
    void **attrptrptr,			/* Buffer for fixed-size attributes */
    void **varptrptr,			/* Buffer for variable-length attributes */
    struct ucred *cred)			/* Credentials (used for ATTR_CMN_USERACCESS) */
{
    attrgroup_t a;
    void *attrptr = *attrptrptr;
    void *varptr = *varptrptr;
    struct msdosfsmount *pmp = dep->de_pmp;
    struct mount *mp = pmp->pm_mountp;
    
    a = alist->commonattr;
    if (a)
    {
#if 0
        /* We don't currently support volume names, but we could later */
        if (a & ATTR_CMN_NAME) {
            varptr = packstr("VOL_LABEL", attrptr, varptr);
            ++((struct attrreference *)attrptr);
        }
#endif
        if (a & ATTR_CMN_DEVID) *((dev_t *) attrptr)++ = pmp->pm_devvp->v_rdev;
        if (a & ATTR_CMN_FSID) *((fsid_t *)attrptr)++ = DETOV(dep)->v_mount->mnt_stat.f_fsid;
        if (a & ATTR_CMN_OBJTYPE) *((fsobj_type_t *)attrptr)++ = DETOV(dep)->v_type;
        if (a & ATTR_CMN_OBJTAG) *((fsobj_tag_t *)attrptr)++ = VT_MSDOSFS;
        if (a & ATTR_CMN_OBJID) {
            u_long fileid = 0;
            u_long dirsperblk = pmp->pm_BytesPerSec / sizeof(struct direntry);

            /* This calculation should match the one in msdosfs_getattr */
            if (dep->de_Attributes & ATTR_DIRECTORY) {
                fileid = cntobn(pmp, dep->de_StartCluster) * dirsperblk;
                if (dep->de_StartCluster == MSDOSFSROOT)
                    fileid = 1;
            } else {
                fileid = cntobn(pmp, dep->de_dirclust) * dirsperblk;
                if (dep->de_dirclust == MSDOSFSROOT)
                    fileid = roottobn(pmp, 0) * dirsperblk;
                fileid += dep->de_diroffset / sizeof(struct direntry);
            }

			((fsobj_id_t *)attrptr)->fid_objno = fileid;
			((fsobj_id_t *)attrptr)->fid_generation = 0;
			++((fsobj_id_t *)attrptr);
        }
        /* ATTR_CMN_OBJPERMANENTID and ATTR_CMN_PAROBJID not supported */
        if (a & ATTR_CMN_CRTIME) {
            dos2unixtime(dep->de_CDate, dep->de_CTime, dep->de_CHun, attrptr);
            ++((struct timespec *)attrptr);
        }
        if (a & ATTR_CMN_MODTIME) {
            dos2unixtime(dep->de_MDate, dep->de_MTime, 0, attrptr);
            ++((struct timespec *)attrptr);
        }
        /* ATTR_CMN_CHGTIME not supported */
        if (a & ATTR_CMN_ACCTIME) {
            dos2unixtime(dep->de_ADate, 0, 0, attrptr);
            ++((struct timespec *)attrptr);
        }
        /* ATTR_CMN_BKUPTIME and ATTR_CMN_FNDRINFO not supported */
        if (a & ATTR_CMN_OWNERID) *((uid_t *)attrptr)++ = pmp->pm_uid;
        if (a & ATTR_CMN_GRPID) *((gid_t *)attrptr)++ = pmp->pm_gid;
        if (a & ATTR_CMN_ACCESSMASK) *((u_long *)attrptr)++ = ALLPERMS & pmp->pm_mask;
        if (a & ATTR_CMN_FLAGS)
        {
            u_long flags;									/* chflags-style flags */
            
            flags = 0;										/* Assume no special flags set */
            if ((dep->de_Attributes & ATTR_ARCHIVE) == 0)	/* DOS: flag set means "needs to be archived" */
                flags |= SF_ARCHIVED;						/* BSD: flag set means "has been archived" */
            if (dep->de_Attributes & ATTR_READONLY)			/* DOS read-only becomes user immutable */
                flags |= UF_IMMUTABLE;
    
            *((u_long *)attrptr)++ = flags;
        }
        if (a & ATTR_CMN_USERACCESS)
        {
            if (cred->cr_uid == 0)
                *((u_long *)attrptr)++ = R_OK | W_OK | X_OK;		/* Root always has full access */
            else
                *((u_long *)attrptr)++ = pmp->pm_mask & S_IRWXO;	/* Everybody else has the same mask */
        }
    }
    
    a = alist->volattr;
    if (a)
    {
        if (a & ATTR_VOL_FSTYPE) *((u_long *)attrptr)++ = (u_long) mp->mnt_vfc->vfc_typenum;
        /* ATTR_VOL_SIGNATURE not supported */
        if (a & ATTR_VOL_SIZE) *((off_t *)attrptr)++ = (off_t) (pmp->pm_maxcluster-2) * (off_t) pmp->pm_bpcluster;
        if (a & ATTR_VOL_SPACEFREE) *((off_t *)attrptr)++ = (off_t) pmp->pm_freeclustercount * (off_t) pmp->pm_bpcluster;
        if (a & ATTR_VOL_SPACEAVAIL) *((off_t *)attrptr)++ = (off_t) pmp->pm_freeclustercount * (off_t) pmp->pm_bpcluster;
        if (a & ATTR_VOL_MINALLOCATION) *((off_t *)attrptr)++ = pmp->pm_bpcluster;
        if (a & ATTR_VOL_ALLOCATIONCLUMP) *((off_t *)attrptr)++ = pmp->pm_bpcluster;
        if (a & ATTR_VOL_IOBLOCKSIZE) *((u_long *)attrptr)++ =  pmp->pm_bpcluster;
        /*
         * ATTR_VOL_OBJCOUNT, ATTR_VOL_FILECOUNT, ATTR_VOL_DIRCOUNT are not supported.
         * If they were really needed, we'd have to iterate over the entire contents of
         * the volume.
         */
        if (a & ATTR_VOL_MAXOBJCOUNT) *((u_long *)attrptr)++ = 0xFFFFFFFF;
        if (a & ATTR_VOL_MOUNTPOINT) {
            varptr = packstr(mp->mnt_stat.f_mntonname, attrptr, varptr);
            ++((struct attrreference *)attrptr);
        }
#if 0
        /* We don't currently support volume names, but we could later */
        if (a & ATTR_VOL_NAME) {
            varptr = packstr("VOL_LABEL", attrptr, varptr);	/*ее Should use volume label */
            ++((struct attrreference *)attrptr);
        }
#endif
        if (a & ATTR_VOL_MOUNTFLAGS) *((u_long *)attrptr)++ = mp->mnt_flag;
        if (a & ATTR_VOL_MOUNTEDDEVICE) {
            varptr = packstr(mp->mnt_stat.f_mntfromname, attrptr, varptr);
            ++((struct attrreference *)attrptr);
        }
        /* ATTR_VOL_ENCODINGSUSED not supported */
        if (a & ATTR_VOL_CAPABILITIES)
        {
            vol_capabilities_attr_t *vcapattrptr;
            
            vcapattrptr = (vol_capabilities_attr_t *) attrptr;
            
            vcapattrptr->capabilities[VOL_CAPABILITIES_FORMAT] = 0;
            vcapattrptr->capabilities[VOL_CAPABILITIES_INTERFACES] = VOL_CAP_INT_NFSEXPORT;
            vcapattrptr->capabilities[VOL_CAPABILITIES_RESERVED1] = 0;
            vcapattrptr->capabilities[VOL_CAPABILITIES_RESERVED2] = 0;
    
            vcapattrptr->valid[VOL_CAPABILITIES_FORMAT] =
                VOL_CAP_FMT_PERSISTENTOBJECTIDS |
                VOL_CAP_FMT_SYMBOLICLINKS |
                VOL_CAP_FMT_HARDLINKS;
            vcapattrptr->valid[VOL_CAPABILITIES_INTERFACES] =
                VOL_CAP_INT_SEARCHFS |
                VOL_CAP_INT_ATTRLIST |
                VOL_CAP_INT_NFSEXPORT |
                VOL_CAP_INT_READDIRATTR;
            vcapattrptr->valid[VOL_CAPABILITIES_RESERVED1] = 0;
            vcapattrptr->valid[VOL_CAPABILITIES_RESERVED2] = 0;
    
            ++((vol_capabilities_attr_t *)attrptr);
        }
        if (a & ATTR_VOL_ATTRIBUTES) {
        	((vol_attributes_attr_t *)attrptr)->validattr.commonattr = MSDOSFS_ATTR_CMN_SUPPORTED;
        	((vol_attributes_attr_t *)attrptr)->validattr.volattr = MSDOSFS_ATTR_VOL_SUPPORTED;
        	((vol_attributes_attr_t *)attrptr)->validattr.dirattr = MSDOSFS_ATTR_DIR_SUPPORTED;
        	((vol_attributes_attr_t *)attrptr)->validattr.fileattr = MSDOSFS_ATTR_FILE_SUPPORTED;
        	((vol_attributes_attr_t *)attrptr)->validattr.forkattr = MSDOSFS_ATTR_FORK_SUPPORTED;

        	((vol_attributes_attr_t *)attrptr)->nativeattr.commonattr = MSDOSFS_ATTR_CMN_NATIVE;
        	((vol_attributes_attr_t *)attrptr)->nativeattr.volattr = MSDOSFS_ATTR_VOL_NATIVE;
        	((vol_attributes_attr_t *)attrptr)->nativeattr.dirattr = MSDOSFS_ATTR_DIR_NATIVE;
        	((vol_attributes_attr_t *)attrptr)->nativeattr.fileattr = MSDOSFS_ATTR_FILE_NATIVE;
        	((vol_attributes_attr_t *)attrptr)->nativeattr.forkattr = MSDOSFS_ATTR_FORK_NATIVE;

            ++((vol_attributes_attr_t *)attrptr);
        }
    }
    
    /* Update the buffer pointers to point past what we just returned */
    *attrptrptr = attrptr;
    *varptrptr = varptr;
}

static void msdosfs_packcommonattr(
    struct attrlist *alist,		/* Desired attributes */
    struct denode *dep,			/* DOS-specific vnode information */
    void **attrptrptr,			/* Buffer for fixed-size attributes */
    void **varptrptr,			/* Buffer for variable-length attributes */
    struct ucred *cred)			/* Credentials (used for ATTR_CMN_USERACCESS) */
{
    attrgroup_t a;
    void *attrptr = *attrptrptr;
    void *varptr = *varptrptr;
    struct msdosfsmount *pmp = dep->de_pmp;
    u_long dirspersector = pmp->pm_BytesPerSec / sizeof(struct direntry);
    
    a = alist->commonattr;

#if 0
    /* We don't currently support file and directory names, but we could later */
    if (a & ATTR_CMN_NAME) {
        varptr = packstr(dep->de_Name, attrptr, varptr);	/* Should really be long name */
        ++((struct attrreference *)attrptr);
    }
#endif
    if (a & ATTR_CMN_DEVID) *((dev_t *)attrptr)++ = pmp->pm_devvp->v_rdev;
    if (a & ATTR_CMN_FSID) *((fsid_t *)attrptr)++ = DETOV(dep)->v_mount->mnt_stat.f_fsid;
    if (a & ATTR_CMN_OBJTYPE) *((fsobj_type_t *)attrptr)++ = DETOV(dep)->v_type;
    if (a & ATTR_CMN_OBJTAG) *((fsobj_tag_t *)attrptr)++ = VT_MSDOSFS;
    if (a & ATTR_CMN_OBJID) {
        u_long fileid;
    
        if (dep->de_Attributes & ATTR_DIRECTORY) {
            fileid = cntobn(pmp, dep->de_StartCluster) * dirspersector;
            if (dep->de_StartCluster == MSDOSFSROOT)
                fileid = 1;
        } else {
            fileid = cntobn(pmp, dep->de_dirclust) * dirspersector;
            if (dep->de_dirclust == MSDOSFSROOT)
                fileid = roottobn(pmp, 0) * dirspersector;
            fileid += dep->de_diroffset / sizeof(struct direntry);
        }

        ((fsobj_id_t *)attrptr)->fid_objno = fileid;
        ((fsobj_id_t *)attrptr)->fid_generation = 0;
        ++((fsobj_id_t *)attrptr);
    }
    /* ATTR_CMN_OBJPERMANENTID and ATTR_CMN_PAROBJID not supported */
    if (a & ATTR_CMN_CRTIME) {
        dos2unixtime(dep->de_CDate, dep->de_CTime, dep->de_CHun, attrptr);
        ++((struct timespec *)attrptr);
    }
    if (a & ATTR_CMN_MODTIME) {
        dos2unixtime(dep->de_MDate, dep->de_MTime, 0, attrptr);
        ++((struct timespec *)attrptr);
    }
    /* ATTR_CMN_CHGTIME not supported */
    if (a & ATTR_CMN_ACCTIME) {
        dos2unixtime(dep->de_ADate, 0, 0, attrptr);
        ++((struct timespec *)attrptr);
    }
    /* ATTR_CMN_BKUPTIME and ATTR_CMN_FNDRINFO not supported */
    if (a & ATTR_CMN_OWNERID) *((uid_t *)attrptr)++ = pmp->pm_uid;
    if (a & ATTR_CMN_GRPID) *((gid_t *)attrptr)++ = pmp->pm_gid;
    if (a & ATTR_CMN_ACCESSMASK) *((u_long *)attrptr)++ = ALLPERMS & pmp->pm_mask;
    if (a & ATTR_CMN_FLAGS)
    {
        u_long flags;									/* chflags-style flags */
        
        flags = 0;										/* Assume no special flags set */
        if ((dep->de_Attributes & ATTR_ARCHIVE) == 0)	/* DOS: flag set means "needs to be archived" */
            flags |= SF_ARCHIVED;						/* BSD: flag set means "has been archived" */
        if (dep->de_Attributes & ATTR_READONLY)			/* DOS read-only becomes user immutable */
        	flags |= UF_IMMUTABLE;

        *((u_long *)attrptr)++ = flags;
    }
    if (a & ATTR_CMN_USERACCESS)
    {
        if (cred->cr_uid == 0)
            *((u_long *)attrptr)++ = R_OK | W_OK | X_OK;		/* Root always has full access */
        else
            *((u_long *)attrptr)++ = pmp->pm_mask & S_IRWXO;	/* Everybody else has the same mask */
    }
    
    /* Update the buffer pointers to point past what we just returned */
    *attrptrptr = attrptr;
    *varptrptr = varptr;
}

static void msdosfs_packdirattr(struct attrlist *alist, struct denode *dep, void **attrptrptr)
{
    attrgroup_t a;
    void *attrptr = *attrptrptr;
    
    a = alist->dirattr;
    
    if (a & ATTR_DIR_LINKCOUNT) *((u_long *)attrptr)++ = 1;	/* A safe value to indicate we don't do hard links. */
    /* ATTR_DIR_ENTRYCOUNT not supported because it would require iterating over the directory contents. */
    if (a & ATTR_DIR_MOUNTSTATUS) {
        if (DETOV(dep)->v_mountedhere) {
            *((u_long *)attrptr)++ = DIR_MNTSTATUS_MNTPOINT;
        } else {
            *((u_long *)attrptr)++ = 0;
        }
    }
    
    *attrptrptr = attrptr;
}

static void msdosfs_packfileattr(struct attrlist *alist, struct denode *dep, void **attrptrptr)
{
    attrgroup_t a;
    void *attrptr = *attrptrptr;
    struct msdosfsmount *pmp = dep->de_pmp;
    off_t allocSize = 0;
    
    a = alist->fileattr;
    
    if (a & (ATTR_FILE_ALLOCSIZE | ATTR_FILE_DATAALLOCSIZE))
        allocSize = roundup((off_t) dep->de_FileSize, (off_t) pmp->pm_bpcluster);

    if (a & ATTR_FILE_LINKCOUNT) *((u_long *)attrptr)++ = 1; /* A safe value to indicate we don't do hard links. */
    if (a & ATTR_FILE_TOTALSIZE) *((off_t *)attrptr)++ = dep->de_FileSize;
    if (a & ATTR_FILE_ALLOCSIZE) *((off_t *)attrptr)++ = allocSize;
    if (a & ATTR_FILE_IOBLOCKSIZE) *((u_long *)attrptr)++ = pmp->pm_bpcluster;
    if (a & ATTR_FILE_CLUMPSIZE) *((u_long *)attrptr)++ = pmp->pm_bpcluster;
    /* ATTR_FILE_DEVTYPE, ATTR_FILE_FILETYPE, ATTR_FILE_FORKCOUNT, ATTR_FILE_FORKLIST not supported. */
    if (a & ATTR_FILE_DATALENGTH) *((off_t *)attrptr)++ = dep->de_FileSize;
    if (a & ATTR_FILE_DATAALLOCSIZE) *((off_t *)attrptr)++ = allocSize;
    /*
     * ATTR_FILE_DATAEXTENTS, ATTR_FILE_RSRCLENGTH, ATTR_FILE_RSRCALLOCSIZE, ATTR_FILE_RSRCEXTENTS
     * not supported.
     */

    *attrptrptr = attrptr;
}

static void msdosfs_packattr(struct attrlist *alist, struct denode *dep, void **attrptr, void **varptr, struct ucred *cred)
{
    if (alist->volattr != 0)
    {
        msdosfs_packvolattr(alist, dep, attrptr, varptr, cred);
    }
    else
    {
        msdosfs_packcommonattr(alist, dep, attrptr, varptr, cred);
        
        switch (DETOV(dep)->v_type)
        {
            case VDIR:
                msdosfs_packdirattr(alist, dep, attrptr);
                break;
            case VREG:
                msdosfs_packfileattr(alist, dep, attrptr);
                break;
            default:
                break;
        }
    }
}

static size_t msdosfs_attrsize(struct attrlist *attrlist)
{
	size_t size;
	attrgroup_t a=0;
	
#if ((ATTR_CMN_NAME | ATTR_CMN_DEVID | ATTR_CMN_FSID | ATTR_CMN_OBJTYPE	|  \
      ATTR_CMN_OBJTAG | ATTR_CMN_OBJID | ATTR_CMN_OBJPERMANENTID |         \
      ATTR_CMN_PAROBJID | ATTR_CMN_SCRIPT | ATTR_CMN_CRTIME |              \
      ATTR_CMN_MODTIME | ATTR_CMN_CHGTIME | ATTR_CMN_ACCTIME |             \
      ATTR_CMN_BKUPTIME | ATTR_CMN_FNDRINFO | ATTR_CMN_OWNERID |           \
      ATTR_CMN_GRPID | ATTR_CMN_ACCESSMASK | ATTR_CMN_NAMEDATTRCOUNT |     \
      ATTR_CMN_NAMEDATTRLIST | ATTR_CMN_FLAGS | ATTR_CMN_USERACCESS)       \
      != ATTR_CMN_VALIDMASK)
#error	msdosfs_attrsize: Missing bits in common mask computation!
#endif
//	DBG_ASSERT((attrlist->commonattr & ~ATTR_CMN_VALIDMASK) == 0);

#if ((ATTR_VOL_FSTYPE | ATTR_VOL_SIGNATURE | ATTR_VOL_SIZE |                \
      ATTR_VOL_SPACEFREE | ATTR_VOL_SPACEAVAIL | ATTR_VOL_MINALLOCATION |   \
      ATTR_VOL_ALLOCATIONCLUMP | ATTR_VOL_IOBLOCKSIZE |                     \
      ATTR_VOL_OBJCOUNT | ATTR_VOL_FILECOUNT | ATTR_VOL_DIRCOUNT |          \
      ATTR_VOL_MAXOBJCOUNT | ATTR_VOL_MOUNTPOINT | ATTR_VOL_NAME |          \
      ATTR_VOL_MOUNTFLAGS | ATTR_VOL_INFO | ATTR_VOL_MOUNTEDDEVICE |        \
      ATTR_VOL_ENCODINGSUSED | ATTR_VOL_CAPABILITIES | ATTR_VOL_ATTRIBUTES) \
      != ATTR_VOL_VALIDMASK)
#error	msdosfs_attrsize: Missing bits in volume mask computation!
#endif
//	DBG_ASSERT((attrlist->volattr & ~ATTR_VOL_VALIDMASK) == 0);

#if ((ATTR_DIR_LINKCOUNT | ATTR_DIR_ENTRYCOUNT | ATTR_DIR_MOUNTSTATUS)  \
      != ATTR_DIR_VALIDMASK)
#error	msdosfs_attrsize: Missing bits in directory mask computation!
#endif
//	DBG_ASSERT((attrlist->dirattr & ~ATTR_DIR_VALIDMASK) == 0);

#if ((ATTR_FILE_LINKCOUNT | ATTR_FILE_TOTALSIZE | ATTR_FILE_ALLOCSIZE |        \
      ATTR_FILE_IOBLOCKSIZE | ATTR_FILE_CLUMPSIZE | ATTR_FILE_DEVTYPE |        \
      ATTR_FILE_FILETYPE | ATTR_FILE_FORKCOUNT | ATTR_FILE_FORKLIST |          \
      ATTR_FILE_DATALENGTH | ATTR_FILE_DATAALLOCSIZE | ATTR_FILE_DATAEXTENTS | \
      ATTR_FILE_RSRCLENGTH | ATTR_FILE_RSRCALLOCSIZE | ATTR_FILE_RSRCEXTENTS)  \
      != ATTR_FILE_VALIDMASK)
#error	msdosfs_attrsize: Missing bits in file mask computation!
#endif
//	DBG_ASSERT((attrlist->fileattr & ~ATTR_FILE_VALIDMASK) == 0);

#if ((ATTR_FORK_TOTALSIZE | ATTR_FORK_ALLOCSIZE) != ATTR_FORK_VALIDMASK)
#error	msdosfs_attrsize: Missing bits in fork mask computation!
#endif
//	DBG_ASSERT((attrlist->forkattr & ~ATTR_FORK_VALIDMASK) == 0);

	size = 0;
	
	if ((a = attrlist->commonattr) != 0) {
        if (a & ATTR_CMN_NAME) size += sizeof(struct attrreference);
		if (a & ATTR_CMN_DEVID) size += sizeof(dev_t);
		if (a & ATTR_CMN_FSID) size += sizeof(fsid_t);
		if (a & ATTR_CMN_OBJTYPE) size += sizeof(fsobj_type_t);
		if (a & ATTR_CMN_OBJTAG) size += sizeof(fsobj_tag_t);
		if (a & ATTR_CMN_OBJID) size += sizeof(fsobj_id_t);
		if (a & ATTR_CMN_OBJPERMANENTID) size += sizeof(fsobj_id_t);
		if (a & ATTR_CMN_PAROBJID) size += sizeof(fsobj_id_t);
		if (a & ATTR_CMN_SCRIPT) size += sizeof(text_encoding_t);
		if (a & ATTR_CMN_CRTIME) size += sizeof(struct timespec);
		if (a & ATTR_CMN_MODTIME) size += sizeof(struct timespec);
		if (a & ATTR_CMN_CHGTIME) size += sizeof(struct timespec);
		if (a & ATTR_CMN_ACCTIME) size += sizeof(struct timespec);
		if (a & ATTR_CMN_BKUPTIME) size += sizeof(struct timespec);
		if (a & ATTR_CMN_FNDRINFO) size += 32 * sizeof(u_int8_t);
		if (a & ATTR_CMN_OWNERID) size += sizeof(uid_t);
		if (a & ATTR_CMN_GRPID) size += sizeof(gid_t);
		if (a & ATTR_CMN_ACCESSMASK) size += sizeof(u_long);
		if (a & ATTR_CMN_NAMEDATTRCOUNT) size += sizeof(u_long);
		if (a & ATTR_CMN_NAMEDATTRLIST) size += sizeof(struct attrreference);
		if (a & ATTR_CMN_FLAGS) size += sizeof(u_long);
		if (a & ATTR_CMN_USERACCESS) size += sizeof(u_long);
	};
	if ((a = attrlist->volattr) != 0) {
		if (a & ATTR_VOL_FSTYPE) size += sizeof(u_long);
		if (a & ATTR_VOL_SIGNATURE) size += sizeof(u_long);
		if (a & ATTR_VOL_SIZE) size += sizeof(off_t);
		if (a & ATTR_VOL_SPACEFREE) size += sizeof(off_t);
		if (a & ATTR_VOL_SPACEAVAIL) size += sizeof(off_t);
		if (a & ATTR_VOL_MINALLOCATION) size += sizeof(off_t);
		if (a & ATTR_VOL_ALLOCATIONCLUMP) size += sizeof(off_t);
		if (a & ATTR_VOL_IOBLOCKSIZE) size += sizeof(u_long);
		if (a & ATTR_VOL_OBJCOUNT) size += sizeof(u_long);
		if (a & ATTR_VOL_FILECOUNT) size += sizeof(u_long);
		if (a & ATTR_VOL_DIRCOUNT) size += sizeof(u_long);
		if (a & ATTR_VOL_MAXOBJCOUNT) size += sizeof(u_long);
		if (a & ATTR_VOL_MOUNTPOINT) size += sizeof(struct attrreference);
		if (a & ATTR_VOL_NAME) size += sizeof(struct attrreference);
		if (a & ATTR_VOL_MOUNTFLAGS) size += sizeof(u_long);
		if (a & ATTR_VOL_MOUNTEDDEVICE) size += sizeof(struct attrreference);
		if (a & ATTR_VOL_ENCODINGSUSED) size += sizeof(unsigned long long);
		if (a & ATTR_VOL_CAPABILITIES) size += sizeof(vol_capabilities_attr_t);
		if (a & ATTR_VOL_ATTRIBUTES) size += sizeof(vol_attributes_attr_t);
	};
	if ((a = attrlist->dirattr) != 0) {
		if (a & ATTR_DIR_LINKCOUNT) size += sizeof(u_long);
		if (a & ATTR_DIR_ENTRYCOUNT) size += sizeof(u_long);
		if (a & ATTR_DIR_MOUNTSTATUS) size += sizeof(u_long);
	};
	if ((a = attrlist->fileattr) != 0) {
		if (a & ATTR_FILE_LINKCOUNT) size += sizeof(u_long);
		if (a & ATTR_FILE_TOTALSIZE) size += sizeof(off_t);
		if (a & ATTR_FILE_ALLOCSIZE) size += sizeof(off_t);
		if (a & ATTR_FILE_IOBLOCKSIZE) size += sizeof(size_t);
		if (a & ATTR_FILE_CLUMPSIZE) size += sizeof(off_t);
		if (a & ATTR_FILE_DEVTYPE) size += sizeof(u_long);
		if (a & ATTR_FILE_FILETYPE) size += sizeof(u_long);
		if (a & ATTR_FILE_FORKCOUNT) size += sizeof(u_long);
		if (a & ATTR_FILE_FORKLIST) size += sizeof(struct attrreference);
		if (a & ATTR_FILE_DATALENGTH) size += sizeof(off_t);
		if (a & ATTR_FILE_DATAALLOCSIZE) size += sizeof(off_t);
		if (a & ATTR_FILE_DATAEXTENTS) size += sizeof(extentrecord);
		if (a & ATTR_FILE_RSRCLENGTH) size += sizeof(off_t);
		if (a & ATTR_FILE_RSRCALLOCSIZE) size += sizeof(off_t);
		if (a & ATTR_FILE_RSRCEXTENTS) size += sizeof(extentrecord);
	};
	if ((a = attrlist->forkattr) != 0) {
		if (a & ATTR_FORK_TOTALSIZE) size += sizeof(off_t);
		if (a & ATTR_FORK_ALLOCSIZE) size += sizeof(off_t);
	};

	return size;
}

/*
#
#% getattrlist	vp	= = =
#
 vop_getattrlist {
     IN struct vnode *vp;
     IN struct attrlist *alist;
     INOUT struct uio *uio;
     IN struct ucred *cred;
     IN struct proc *p;
 };

 */
__private_extern__
int msdosfs_getattrlist(struct vop_getattrlist_args *ap)
{
    struct vnode *vp = ap->a_vp;
    struct denode *dep = VTODE(vp);
	struct attrlist *alist = ap->a_alist;
	size_t fixedblocksize;
	size_t attrblocksize;
	size_t attrbufsize;
	void *attrbufptr;
	void *attrptr;
    void *varptr;
    int error;
    
    /* Check the attrlist for valid inputs (i.e. be sure we understand what caller is asking) */
	if ((alist->bitmapcount != ATTR_BIT_MAP_COUNT) ||
	    ((alist->commonattr & ~ATTR_CMN_VALIDMASK) != 0) ||
	    ((alist->volattr & ~ATTR_VOL_VALIDMASK) != 0) ||
	    ((alist->dirattr & ~ATTR_DIR_VALIDMASK) != 0) ||
	    ((alist->fileattr & ~ATTR_FILE_VALIDMASK) != 0) ||
        ((alist->forkattr & ~ATTR_FORK_VALIDMASK) != 0))
    {
		return EINVAL;
	}

	/*
	 * Requesting volume information requires setting the
	 * ATTR_VOL_INFO bit. Also, volume info requests are
	 * mutually exclusive with all other info requests.
	 */
	if ((alist->volattr != 0) &&
	    (((alist->volattr & ATTR_VOL_INFO) == 0) ||
	     (alist->dirattr != 0) || (alist->fileattr != 0) || alist->forkattr != 0))
    {
		return EINVAL;
	}

    /*
     * Make sure caller isn't asking for an attibute we don't support.
     */
    if ((alist->commonattr & ~MSDOSFS_ATTR_CMN_SUPPORTED) != 0 ||
        (alist->dirattr & ~MSDOSFS_ATTR_DIR_SUPPORTED) != 0 ||
        (alist->fileattr & ~MSDOSFS_ATTR_FILE_SUPPORTED) != 0 ||
        (alist->forkattr & ~MSDOSFS_ATTR_FORK_SUPPORTED) != 0)
    {
        return EOPNOTSUPP;
    }
    
    /*
     * Requesting volume information requires a vnode for the volume root.
     */
    if (alist->volattr && (vp->v_flag & VROOT) == 0)
    {
        return EINVAL;
    }

	fixedblocksize = msdosfs_attrsize(alist);
	attrblocksize = fixedblocksize + (sizeof(u_long));  /* u_long for length field */
	if (alist->commonattr & ATTR_CMN_NAME)
		attrblocksize += 256;
	if (alist->volattr & ATTR_VOL_MOUNTPOINT)
		attrblocksize += PATH_MAX;
    if (alist->volattr & ATTR_VOL_MOUNTEDDEVICE)
		attrblocksize += PATH_MAX;
	if (alist->volattr & ATTR_VOL_NAME)
		attrblocksize += 256;
	attrbufsize = MIN(ap->a_uio->uio_resid, attrblocksize);
	MALLOC(attrbufptr, void *, attrblocksize, M_TEMP, M_WAITOK);
	attrptr = attrbufptr;
	*((u_long *)attrptr) = 0;  /* Set buffer length in case of errors */
	++((u_long *)attrptr);     /* skip over length field */
	varptr = ((char *)attrptr) + fixedblocksize;

    msdosfs_packattr(alist, dep, &attrptr, &varptr, ap->a_cred);

    /* Don't return more data than was generated */
    attrbufsize = MIN(attrbufsize, (size_t) varptr - (size_t) attrbufptr);
    
    /* Return the actual buffer length */
    *((u_long *) attrbufptr) = attrbufsize;
    
    error = uiomove((caddr_t) attrbufptr, attrbufsize, ap->a_uio);
    
    FREE(attrbufptr, M_TEMP);
    return error;
}
