/*      $NetBSD: msdosfs_vnops.c,v 1.113 2024/09/11 00:27:54 perseant Exp $     */

/*-
* Copyright (C) 1994, 1995, 1997 Wolfgang Solfrank.
* Copyright (C) 1994, 1995, 1997 TooLs GmbH.
* All rights reserved.
* Original code by Paul Popelka ([email protected]) (see below).
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
*    must display the following acknowledgement:
*      This product includes software developed by TooLs GmbH.
* 4. The name of TooLs GmbH may not be used to endorse or promote products
*    derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY TOOLS GMBH ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL TOOLS GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Written by Paul Popelka ([email protected])
*
* You can do anything you want with this software, just don't say you wrote
* it, and don't remove this notice.
*
* This software is provided "as is".
*
* The author supplies this software to be publicly redistributed on the
* understanding that the author is not responsible for the correct
* functioning of this software in any circumstances and is not liable for
* any damages caused by this software.
*
* October 1992
*/

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: msdosfs_vnops.c,v 1.113 2024/09/11 00:27:54 perseant Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/namei.h>
#include <sys/resourcevar.h>    /* defines plimit structure in proc struct */
#include <sys/kernel.h>
#include <sys/file.h>           /* define FWRITE ... */
#include <sys/stat.h>
#include <sys/buf.h>
#include <sys/proc.h>
#include <sys/mount.h>
#include <sys/vnode.h>
#include <sys/signalvar.h>
#include <sys/malloc.h>
#include <sys/dirent.h>
#include <sys/lockf.h>
#include <sys/kauth.h>

#include <miscfs/genfs/genfs.h>
#include <miscfs/specfs/specdev.h> /* XXX */    /* defines v_rdev */

#include <uvm/uvm_extern.h>

#include <fs/msdosfs/bpb.h>
#include <fs/msdosfs/direntry.h>
#include <fs/msdosfs/denode.h>
#include <fs/msdosfs/msdosfsmount.h>
#include <fs/msdosfs/fat.h>

/*
* Some general notes:
*
* In the ufs filesystem the inodes, superblocks, and indirect blocks are
* read/written using the vnode for the filesystem. Blocks that represent
* the contents of a file are read/written using the vnode for the file
* (including directories when they are read/written as files). This
* presents problems for the dos filesystem because data that should be in
* an inode (if dos had them) resides in the directory itself.  Since we
* must update directory entries without the benefit of having the vnode
* for the directory we must use the vnode for the filesystem.  This means
* that when a directory is actually read/written (via read, write, or
* readdir, or seek) we must use the vnode for the filesystem instead of
* the vnode for the directory as would happen in ufs. This is to insure we
* retrieve the correct block from the buffer cache since the hash value is
* based upon the vnode address and the desired block number.
*/

/*
* Create a regular file. On entry the directory to contain the file being
* created is locked.  We must release before we return.
*/
int
msdosfs_create(void *v)
{
       struct vop_create_v3_args /* {
               struct vnode *a_dvp;
               struct vnode **a_vpp;
               struct componentname *a_cnp;
               struct vattr *a_vap;
       } */ *ap = v;
       struct componentname *cnp = ap->a_cnp;
       struct denode ndirent;
       struct denode *dep;
       struct denode *pdep = VTODE(ap->a_dvp);
       int error;

#ifdef MSDOSFS_DEBUG
       printf("msdosfs_create(cnp %p, vap %p\n", cnp, ap->a_vap);
#endif

       /*
        * If this is the root directory and there is no space left we
        * can't do anything.  This is because the root directory can not
        * change size.
        */
       if (pdep->de_StartCluster == MSDOSFSROOT
           && pdep->de_crap.mlr_fndoffset >= pdep->de_FileSize) {
               error = ENOSPC;
               goto bad;
       }

       /*
        * Create a directory entry for the file, then call createde() to
        * have it installed. NOTE: DOS files are always executable.  We
        * use the absence of the owner write bit to make the file
        * readonly.
        */
       memset(&ndirent, 0, sizeof(ndirent));
       if ((error = msdosfs_uniqdosname(pdep, cnp, ndirent.de_Name)) != 0)
               goto bad;

       ndirent.de_Attributes = (ap->a_vap->va_mode & S_IWUSR) ?
                               ATTR_ARCHIVE : ATTR_ARCHIVE | ATTR_READONLY;
       ndirent.de_StartCluster = 0;
       ndirent.de_FileSize = 0;
       ndirent.de_dev = pdep->de_dev;
       ndirent.de_devvp = pdep->de_devvp;
       ndirent.de_pmp = pdep->de_pmp;
       ndirent.de_flag = DE_ACCESS | DE_CREATE | DE_UPDATE;
       DETIMES(&ndirent, NULL, NULL, NULL, pdep->de_pmp->pm_gmtoff);
       if ((error = msdosfs_createde(&ndirent, pdep, &pdep->de_crap, &dep,
           cnp)) != 0)
               goto bad;
       *ap->a_vpp = DETOV(dep);
       cache_enter(ap->a_dvp, *ap->a_vpp, cnp->cn_nameptr, cnp->cn_namelen,
           cnp->cn_flags);
       return (0);

bad:
       return (error);
}

int
msdosfs_close(void *v)
{
       struct vop_close_args /* {
               struct vnode *a_vp;
               int a_fflag;
               kauth_cred_t a_cred;
       } */ *ap = v;
       struct vnode *vp = ap->a_vp;
       struct denode *dep = VTODE(vp);

       mutex_enter(vp->v_interlock);
       if (vrefcnt(vp) > 1)
               DETIMES(dep, NULL, NULL, NULL, dep->de_pmp->pm_gmtoff);
       mutex_exit(vp->v_interlock);
       return (0);
}

static int
msdosfs_check_possible(struct vnode *vp, struct denode *dep, accmode_t accmode)
{

       /*
        * Disallow write attempts on read-only file systems;
        * unless the file is a socket, fifo, or a block or
        * character device resident on the file system.
        */
       if (accmode & VWRITE) {
               switch (vp->v_type) {
               case VDIR:
               case VLNK:
               case VREG:
                       if (vp->v_mount->mnt_flag & MNT_RDONLY)
                               return (EROFS);
               default:
                       break;
               }
       }

       return 0;
}

static int
msdosfs_check_permitted(struct vnode *vp, struct denode *dep, accmode_t accmode,
   kauth_cred_t cred)
{
       struct msdosfsmount *pmp = dep->de_pmp;
       mode_t file_mode;

       if ((dep->de_Attributes & ATTR_READONLY) == 0)
               file_mode = S_IRWXU|S_IRWXG|S_IRWXO;
       else
               file_mode = S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH;

       file_mode &= (vp->v_type == VDIR ? pmp->pm_dirmask : pmp->pm_mask);

       return kauth_authorize_vnode(cred, KAUTH_ACCESS_ACTION(accmode,
           vp->v_type, file_mode), vp, NULL, genfs_can_access(vp, cred,
           pmp->pm_uid, pmp->pm_gid, file_mode, NULL, accmode));
}

int
msdosfs_access(void *v)
{
       struct vop_access_args /* {
               struct vnode *a_vp;
               accmode_t a_accmode;
               kauth_cred_t a_cred;
       } */ *ap = v;
       struct vnode *vp = ap->a_vp;
       struct denode *dep = VTODE(vp);
       int error;

       error = msdosfs_check_possible(vp, dep, ap->a_accmode);
       if (error)
               return error;

       error = msdosfs_check_permitted(vp, dep, ap->a_accmode, ap->a_cred);

       return error;
}

int
msdosfs_getattr(void *v)
{
       struct vop_getattr_args /* {
               struct vnode *a_vp;
               struct vattr *a_vap;
               kauth_cred_t a_cred;
       } */ *ap = v;
       struct denode *dep = VTODE(ap->a_vp);
       struct msdosfsmount *pmp = dep->de_pmp;
       struct vattr *vap = ap->a_vap;
       mode_t mode;
       u_long dirsperblk = pmp->pm_BytesPerSec / sizeof(struct direntry);
       ino_t fileid;

       DETIMES(dep, NULL, NULL, NULL, pmp->pm_gmtoff);
       vap->va_fsid = dep->de_dev;
       /*
        * The following computation of the fileid must be the same as that
        * used in msdosfs_readdir() to compute d_fileno. If not, pwd
        * doesn't work.
        */
       if (dep->de_Attributes & ATTR_DIRECTORY) {
               fileid = cntobn(pmp, (ino_t)dep->de_StartCluster) * dirsperblk;
               if (dep->de_StartCluster == MSDOSFSROOT)
                       fileid = 1;
       } else {
               fileid = cntobn(pmp, (ino_t)dep->de_dirclust) * dirsperblk;
               if (dep->de_dirclust == MSDOSFSROOT)
                       fileid = roottobn(pmp, 0) * dirsperblk;
               fileid += dep->de_diroffset / sizeof(struct direntry);
       }
       vap->va_fileid = fileid;
       if ((dep->de_Attributes & ATTR_READONLY) == 0)
               mode = S_IRWXU|S_IRWXG|S_IRWXO;
       else
               mode = S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH;
       vap->va_mode =
           mode & (ap->a_vp->v_type == VDIR ? pmp->pm_dirmask : pmp->pm_mask);
       vap->va_uid = pmp->pm_uid;
       vap->va_gid = pmp->pm_gid;
       vap->va_nlink = 1;
       vap->va_rdev = 0;
       vap->va_size = ap->a_vp->v_size;
       msdosfs_dos2unixtime(dep->de_MDate, dep->de_MTime, 0, pmp->pm_gmtoff,
           &vap->va_mtime);
       if (dep->de_pmp->pm_flags & MSDOSFSMNT_LONGNAME) {
               msdosfs_dos2unixtime(dep->de_ADate, 0, 0, pmp->pm_gmtoff,
                   &vap->va_atime);
               msdosfs_dos2unixtime(dep->de_CDate, dep->de_CTime, dep->de_CHun,
                   pmp->pm_gmtoff, &vap->va_ctime);
       } else {
               vap->va_atime = vap->va_mtime;
               vap->va_ctime = vap->va_mtime;
       }
       vap->va_flags = 0;
       if ((dep->de_Attributes & ATTR_ARCHIVE) == 0) {
               vap->va_flags |= SF_ARCHIVED;
               vap->va_mode  |= S_ARCH1;
       }
       vap->va_gen = 0;
       vap->va_blocksize = pmp->pm_bpcluster;
       vap->va_bytes =
           (dep->de_FileSize + pmp->pm_crbomask) & ~pmp->pm_crbomask;
       vap->va_type = ap->a_vp->v_type;
       return (0);
}

int
msdosfs_setattr(void *v)
{
       struct vop_setattr_args /* {
               struct vnode *a_vp;
               struct vattr *a_vap;
               kauth_cred_t a_cred;
       } */ *ap = v;
       int error = 0, de_changed = 0;
       struct denode *dep = VTODE(ap->a_vp);
       struct msdosfsmount *pmp = dep->de_pmp;
       struct vnode *vp  = ap->a_vp;
       struct vattr *vap = ap->a_vap;
       kauth_cred_t cred = ap->a_cred;

#ifdef MSDOSFS_DEBUG
       printf("msdosfs_setattr(): vp %p, vap %p, cred %p\n",
           ap->a_vp, vap, cred);
#endif
       /*
        * Note we silently ignore uid or gid changes.
        */
       if ((vap->va_type != VNON) || (vap->va_nlink != (nlink_t)VNOVAL) ||
           (vap->va_fsid != VNOVAL) || (vap->va_fileid != VNOVAL) ||
           (vap->va_blocksize != VNOVAL) || (vap->va_rdev != VNOVAL) ||
           (vap->va_bytes != VNOVAL) || (vap->va_gen != VNOVAL) ||
           (vap->va_uid != VNOVAL && vap->va_uid != pmp->pm_uid) ||
           (vap->va_gid != VNOVAL && vap->va_gid != pmp->pm_gid)) {
#ifdef MSDOSFS_DEBUG
               printf("msdosfs_setattr(): returning EINVAL\n");
               printf("    va_type %d, va_nlink %x, va_fsid %"PRIx64", va_fileid %llx\n",
                   vap->va_type, vap->va_nlink, vap->va_fsid,
                   (unsigned long long)vap->va_fileid);
               printf("    va_blocksize %lx, va_rdev %"PRIx64", va_bytes %"PRIx64", va_gen %lx\n",
                   vap->va_blocksize, vap->va_rdev, vap->va_bytes, vap->va_gen);
#endif
               return (EINVAL);
       }
       /*
        * Silently ignore attributes modifications on directories.
        */
       if (ap->a_vp->v_type == VDIR)
               return 0;

       if (vap->va_size != VNOVAL) {
               if (vp->v_mount->mnt_flag & MNT_RDONLY) {
                       error = EROFS;
                       goto bad;
               }
               error = msdosfs_detrunc(dep, (u_long)vap->va_size, 0, cred);
               if (error)
                       goto bad;
               de_changed = 1;
       }
       if (vap->va_atime.tv_sec != VNOVAL || vap->va_mtime.tv_sec != VNOVAL) {
               if (vp->v_mount->mnt_flag & MNT_RDONLY) {
                       error = EROFS;
                       goto bad;
               }
               error = kauth_authorize_vnode(cred, KAUTH_VNODE_WRITE_TIMES,
                   ap->a_vp, NULL, genfs_can_chtimes(ap->a_vp, cred,
                       pmp->pm_uid, vap->va_vaflags));
               if (error)
                       goto bad;
               if ((pmp->pm_flags & MSDOSFSMNT_NOWIN95) == 0 &&
                   vap->va_atime.tv_sec != VNOVAL)
                       msdosfs_unix2dostime(&vap->va_atime, pmp->pm_gmtoff,
                           &dep->de_ADate, NULL, NULL);
               if (vap->va_mtime.tv_sec != VNOVAL)
                       msdosfs_unix2dostime(&vap->va_mtime, pmp->pm_gmtoff,
                           &dep->de_MDate, &dep->de_MTime, NULL);
               dep->de_Attributes |= ATTR_ARCHIVE;
               dep->de_flag |= DE_MODIFIED;
               de_changed = 1;
       }
       /*
        * DOS files only have the ability to have their writability
        * attribute set, so we use the owner write bit to set the readonly
        * attribute.
        */
       if (vap->va_mode != (mode_t)VNOVAL) {
               if (vp->v_mount->mnt_flag & MNT_RDONLY) {
                       error = EROFS;
                       goto bad;
               }
               error = kauth_authorize_vnode(cred, KAUTH_VNODE_WRITE_FLAGS, vp,
                   NULL, genfs_can_chflags(vp, cred, pmp->pm_uid, false));
               if (error)
                       goto bad;
               /* We ignore the read and execute bits. */
               if (vap->va_mode & S_IWUSR)
                       dep->de_Attributes &= ~ATTR_READONLY;
               else
                       dep->de_Attributes |= ATTR_READONLY;
               dep->de_flag |= DE_MODIFIED;
               de_changed = 1;
       }
       /*
        * Allow the `archived' bit to be toggled.
        */
       if (vap->va_flags != VNOVAL) {
               if (vp->v_mount->mnt_flag & MNT_RDONLY) {
                       error = EROFS;
                       goto bad;
               }
               error = kauth_authorize_vnode(cred, KAUTH_VNODE_WRITE_FLAGS, vp,
                   NULL, genfs_can_chflags(vp, cred, pmp->pm_uid, false));
               if (error)
                       goto bad;
               if (vap->va_flags & SF_ARCHIVED)
                       dep->de_Attributes &= ~ATTR_ARCHIVE;
               else
                       dep->de_Attributes |= ATTR_ARCHIVE;
               dep->de_flag |= DE_MODIFIED;
               de_changed = 1;
       }

       if (de_changed) {
               error = msdosfs_deupdat(dep, 1);
               if (error)
                       goto bad;
       }

bad:
       return error;
}

int
msdosfs_read(void *v)
{
       struct vop_read_args /* {
               struct vnode *a_vp;
               struct uio *a_uio;
               int a_ioflag;
               kauth_cred_t a_cred;
       } */ *ap = v;
       int error = 0;
       int64_t diff;
       int blsize;
       long n;
       long on;
       daddr_t lbn;
       vsize_t bytelen;
       struct buf *bp;
       struct vnode *vp = ap->a_vp;
       struct denode *dep = VTODE(vp);
       struct msdosfsmount *pmp = dep->de_pmp;
       struct uio *uio = ap->a_uio;

       /*
        * If they didn't ask for any data, then we are done.
        */

       if (uio->uio_resid == 0)
               return (0);
       if (uio->uio_offset < 0)
               return (EINVAL);
       if (uio->uio_offset >= dep->de_FileSize)
               return (0);

       if (vp->v_type == VREG) {
               const int advice = IO_ADV_DECODE(ap->a_ioflag);

               while (uio->uio_resid > 0) {
                       bytelen = MIN(dep->de_FileSize - uio->uio_offset,
                                     uio->uio_resid);

                       if (bytelen == 0)
                               break;
                       error = ubc_uiomove(&vp->v_uobj, uio, bytelen, advice,
                           UBC_READ | UBC_PARTIALOK | UBC_VNODE_FLAGS(vp));
                       if (error)
                               break;
               }
               dep->de_flag |= DE_ACCESS;
               goto out;
       }

       /* this loop is only for directories now */
       do {
               lbn = de_cluster(pmp, uio->uio_offset);
               on = uio->uio_offset & pmp->pm_crbomask;
               n = MIN(pmp->pm_bpcluster - on, uio->uio_resid);
               if (uio->uio_offset >= dep->de_FileSize) {
                       return (0);
               }
               /* file size (and hence diff) may be up to 4GB */
               diff = dep->de_FileSize - uio->uio_offset;
               if (diff < n)
                       n = (long) diff;

               /* convert cluster # to sector # */
               error = msdosfs_pcbmap(dep, lbn, &lbn, 0, &blsize);
               if (error)
                       goto bad;

               /*
                * If we are operating on a directory file then be sure to
                * do i/o with the vnode for the filesystem instead of the
                * vnode for the directory.
                */
               error = bread(pmp->pm_devvp, de_bn2kb(pmp, lbn), blsize,
                   0, &bp);
               if (error) {
                       goto bad;
               }
               n = MIN(n, pmp->pm_bpcluster - bp->b_resid);
               error = uiomove((char *)bp->b_data + on, (int) n, uio);
               brelse(bp, 0);
       } while (error == 0 && uio->uio_resid > 0 && n != 0);

out:
       if ((ap->a_ioflag & IO_SYNC) == IO_SYNC) {
               int uerror;

               uerror = msdosfs_deupdat(dep, 1);
               if (error == 0)
                       error = uerror;
       }
bad:
       return (error);
}

/*
* Write data to a file or directory.
*/
int
msdosfs_write(void *v)
{
       struct vop_write_args /* {
               struct vnode *a_vp;
               struct uio *a_uio;
               int a_ioflag;
               kauth_cred_t a_cred;
       } */ *ap = v;
       int resid;
       int error = 0;
       int ioflag = ap->a_ioflag;
       u_long osize;
       u_long count;
       vsize_t bytelen;
       off_t oldoff;
       size_t rem;
       struct uio *uio = ap->a_uio;
       struct vnode *vp = ap->a_vp;
       struct denode *dep = VTODE(vp);
       struct msdosfsmount *pmp = dep->de_pmp;
       kauth_cred_t cred = ap->a_cred;
       bool async;

#ifdef MSDOSFS_DEBUG
       printf("msdosfs_write(vp %p, uio %p, ioflag %x, cred %p\n",
           vp, uio, ioflag, cred);
       printf("msdosfs_write(): diroff %lu, dirclust %lu, startcluster %lu\n",
           dep->de_diroffset, dep->de_dirclust, dep->de_StartCluster);
#endif

       switch (vp->v_type) {
       case VREG:
               if (ioflag & IO_APPEND)
                       uio->uio_offset = dep->de_FileSize;
               break;
       case VDIR:
               return EISDIR;
       default:
               panic("msdosfs_write(): bad file type");
       }

       if (uio->uio_offset < 0)
               return (EINVAL);

       if (uio->uio_resid == 0)
               return (0);

       /* Don't bother to try to write files larger than the fs limit */
       if (uio->uio_offset + uio->uio_resid > MSDOSFS_FILESIZE_MAX)
               return (EFBIG);

       /*
        * If the offset we are starting the write at is beyond the end of
        * the file, then they've done a seek.  Unix filesystems allow
        * files with holes in them, DOS doesn't so we must fill the hole
        * with zeroed blocks.
        */
       if (uio->uio_offset > dep->de_FileSize) {
               if ((error = msdosfs_deextend(dep, uio->uio_offset,
                   cred)) != 0) {
                       return (error);
               }
       }

       /*
        * Remember some values in case the write fails.
        */
       async = vp->v_mount->mnt_flag & MNT_ASYNC;
       resid = uio->uio_resid;
       osize = dep->de_FileSize;

       /*
        * If we write beyond the end of the file, extend it to its ultimate
        * size ahead of the time to hopefully get a contiguous area.
        */
       if (uio->uio_offset + resid > osize) {
               count = de_clcount(pmp, uio->uio_offset + resid) -
                       de_clcount(pmp, osize);
               if ((error = msdosfs_extendfile(dep, count, NULL, NULL, 0)))
                       goto errexit;

               dep->de_FileSize = uio->uio_offset + resid;
               /* hint uvm to not read in extended part */
               uvm_vnp_setwritesize(vp, dep->de_FileSize);
               /* zero out the remainder of the last page */
               rem = round_page(dep->de_FileSize) - dep->de_FileSize;
               if (rem > 0)
                       ubc_zerorange(&vp->v_uobj, (off_t)dep->de_FileSize,
                           rem, UBC_VNODE_FLAGS(vp));
       }

       do {
               oldoff = uio->uio_offset;
               bytelen = uio->uio_resid;

               error = ubc_uiomove(&vp->v_uobj, uio, bytelen,
                   IO_ADV_DECODE(ioflag), UBC_WRITE | UBC_VNODE_FLAGS(vp));
               if (error)
                       break;

               /*
                * flush what we just wrote if necessary.
                * XXXUBC simplistic async flushing.
                */

               if (!async && oldoff >> 16 != uio->uio_offset >> 16) {
                       rw_enter(vp->v_uobj.vmobjlock, RW_WRITER);
                       error = VOP_PUTPAGES(vp, (oldoff >> 16) << 16,
                           (uio->uio_offset >> 16) << 16,
                           PGO_CLEANIT | PGO_LAZY);
               }
       } while (error == 0 && uio->uio_resid > 0);

       /* set final size */
       uvm_vnp_setsize(vp, dep->de_FileSize);
       if (error == 0 && ioflag & IO_SYNC) {
               rw_enter(vp->v_uobj.vmobjlock, RW_WRITER);
               error = VOP_PUTPAGES(vp, trunc_page(oldoff),
                   round_page(oldoff + bytelen), PGO_CLEANIT | PGO_SYNCIO);
       }
       dep->de_flag |= DE_UPDATE;

       /*
        * If the write failed and they want us to, truncate the file back
        * to the size it was before the write was attempted.
        */
errexit:
       if (error) {
               msdosfs_detrunc(dep, osize, ioflag & IO_SYNC, NOCRED);
               uio->uio_offset -= resid - uio->uio_resid;
               uio->uio_resid = resid;
       } else if ((ioflag & IO_SYNC) == IO_SYNC)
               error = msdosfs_deupdat(dep, 1);
       KASSERT(vp->v_size == dep->de_FileSize);
       return (error);
}

int
msdosfs_update(struct vnode *vp, const struct timespec *acc,
   const struct timespec *mod, int flags)
{
       struct buf *bp;
       struct direntry *dirp;
       struct denode *dep;
       int error;

       if (vp->v_mount->mnt_flag & MNT_RDONLY)
               return (0);
       dep = VTODE(vp);
       DETIMES(dep, acc, mod, NULL, dep->de_pmp->pm_gmtoff);
       if ((dep->de_flag & DE_MODIFIED) == 0)
               return (0);
       dep->de_flag &= ~DE_MODIFIED;
       if (dep->de_Attributes & ATTR_DIRECTORY)
               return (0);
       if (dep->de_refcnt <= 0)
               return (0);
       error = msdosfs_readde(dep, &bp, &dirp);
       if (error)
               return (error);
       DE_EXTERNALIZE(dirp, dep);
       if (flags & (UPDATE_WAIT|UPDATE_DIROP))
               return (bwrite(bp));
       else {
               bdwrite(bp);
               return (0);
       }
}

int
msdosfs_remove(void *v)
{
       struct vop_remove_v3_args /* {
               struct vnode *a_dvp;
               struct vnode *a_vp;
               struct componentname *a_cnp;
               nlink_t ctx_vp_new_nlink;
       } */ *ap = v;
       struct denode *dep = VTODE(ap->a_vp);
       struct denode *ddep = VTODE(ap->a_dvp);
       int error;

       if (ap->a_vp->v_type == VDIR)
               error = EPERM;
       else
               error = msdosfs_removede(ddep, dep, &ddep->de_crap);
#ifdef MSDOSFS_DEBUG
       printf("msdosfs_remove(), dep %p, usecount %d\n",
               dep, vrefcnt(ap->a_vp));
#endif
       if (ddep == dep)
               vrele(ap->a_vp);
       else
               vput(ap->a_vp); /* causes msdosfs_inactive() to be called
                                * via vrele() */

       return (error);
}

static const struct {
       struct direntry dot;
       struct direntry dotdot;
} dosdirtemplate = {
       {       ".       ", "   ",                      /* the . entry */
               ATTR_DIRECTORY,                         /* file attribute */
               0,                                      /* reserved */
               0, { 0, 0 }, { 0, 0 },                  /* create time & date */
               { 0, 0 },                               /* access date */
               { 0, 0 },                               /* high bits of start cluster */
               { 210, 4 }, { 210, 4 },                 /* modify time & date */
               { 0, 0 },                               /* startcluster */
               { 0, 0, 0, 0 }                          /* filesize */
       },
       {       "..      ", "   ",                      /* the .. entry */
               ATTR_DIRECTORY,                         /* file attribute */
               0,                                      /* reserved */
               0, { 0, 0 }, { 0, 0 },                  /* create time & date */
               { 0, 0 },                               /* access date */
               { 0, 0 },                               /* high bits of start cluster */
               { 210, 4 }, { 210, 4 },                 /* modify time & date */
               { 0, 0 },                               /* startcluster */
               { 0, 0, 0, 0 }                          /* filesize */
       }
};

int
msdosfs_mkdir(void *v)
{
       struct vop_mkdir_v3_args /* {
               struct vnode *a_dvp;
               struvt vnode **a_vpp;
               struvt componentname *a_cnp;
               struct vattr *a_vap;
       } */ *ap = v;
       struct componentname *cnp = ap->a_cnp;
       struct denode ndirent;
       struct denode *dep;
       struct denode *pdep = VTODE(ap->a_dvp);
       int error;
       int bn;
       u_long newcluster, pcl;
       daddr_t lbn;
       struct direntry *denp;
       struct msdosfsmount *pmp = pdep->de_pmp;
       struct buf *bp;
       int async = pdep->de_pmp->pm_mountp->mnt_flag & MNT_ASYNC;

       /*
        * If this is the root directory and there is no space left we
        * can't do anything.  This is because the root directory can not
        * change size.
        */
       if (pdep->de_StartCluster == MSDOSFSROOT
           && pdep->de_crap.mlr_fndoffset >= pdep->de_FileSize) {
               error = ENOSPC;
               goto bad2;
       }

       /*
        * Allocate a cluster to hold the about to be created directory.
        */
       error = msdosfs_clusteralloc(pmp, 0, 1, &newcluster, NULL);
       if (error)
               goto bad2;

       memset(&ndirent, 0, sizeof(ndirent));
       ndirent.de_pmp = pmp;
       ndirent.de_flag = DE_ACCESS | DE_CREATE | DE_UPDATE;
       DETIMES(&ndirent, NULL, NULL, NULL, pmp->pm_gmtoff);

       /*
        * Now fill the cluster with the "." and ".." entries. And write
        * the cluster to disk.  This way it is there for the parent
        * directory to be pointing at if there were a crash.
        */
       bn = cntobn(pmp, newcluster);
       lbn = de_bn2kb(pmp, bn);
       /* always succeeds */
       bp = getblk(pmp->pm_devvp, lbn, pmp->pm_bpcluster, 0, 0);
       memset(bp->b_data, 0, pmp->pm_bpcluster);
       memcpy(bp->b_data, &dosdirtemplate, sizeof dosdirtemplate);
       denp = (struct direntry *)bp->b_data;
       putushort(denp[0].deStartCluster, newcluster);
       putushort(denp[0].deCDate, ndirent.de_CDate);
       putushort(denp[0].deCTime, ndirent.de_CTime);
       denp[0].deCHundredth = ndirent.de_CHun;
       putushort(denp[0].deADate, ndirent.de_ADate);
       putushort(denp[0].deMDate, ndirent.de_MDate);
       putushort(denp[0].deMTime, ndirent.de_MTime);
       pcl = pdep->de_StartCluster;
       if (FAT32(pmp) && pcl == pmp->pm_rootdirblk)
               pcl = 0;
       putushort(denp[1].deStartCluster, pcl);
       putushort(denp[1].deCDate, ndirent.de_CDate);
       putushort(denp[1].deCTime, ndirent.de_CTime);
       denp[1].deCHundredth = ndirent.de_CHun;
       putushort(denp[1].deADate, ndirent.de_ADate);
       putushort(denp[1].deMDate, ndirent.de_MDate);
       putushort(denp[1].deMTime, ndirent.de_MTime);
       if (FAT32(pmp)) {
               putushort(denp[0].deHighClust, newcluster >> 16);
               putushort(denp[1].deHighClust, pdep->de_StartCluster >> 16);
       } else {
               putushort(denp[0].deHighClust, 0);
               putushort(denp[1].deHighClust, 0);
       }

       if (async)
               bdwrite(bp);
       else if ((error = bwrite(bp)) != 0)
               goto bad;

       /*
        * Now build up a directory entry pointing to the newly allocated
        * cluster.  This will be written to an empty slot in the parent
        * directory.
        */
       if ((error = msdosfs_uniqdosname(pdep, cnp, ndirent.de_Name)) != 0)
               goto bad;

       ndirent.de_Attributes = ATTR_DIRECTORY;
       ndirent.de_StartCluster = newcluster;
       ndirent.de_FileSize = 0;
       ndirent.de_dev = pdep->de_dev;
       ndirent.de_devvp = pdep->de_devvp;
       if ((error = msdosfs_createde(&ndirent, pdep, &pdep->de_crap, &dep,
           cnp)) != 0)
               goto bad;
       *ap->a_vpp = DETOV(dep);
       return (0);

bad:
       msdosfs_clusterfree(pmp, newcluster, NULL);
bad2:
       return (error);
}

int
msdosfs_rmdir(void *v)
{
       struct vop_rmdir_v2_args /* {
               struct vnode *a_dvp;
               struct vnode *a_vp;
               struct componentname *a_cnp;
       } */ *ap = v;
       struct vnode *vp = ap->a_vp;
       struct vnode *dvp = ap->a_dvp;
       struct componentname *cnp = ap->a_cnp;
       struct denode *ip, *dp;
       int error;

       ip = VTODE(vp);
       dp = VTODE(dvp);
       /*
        * No rmdir "." please.
        */
       if (dp == ip) {
               vrele(vp);
               return (EINVAL);
       }
       /*
        * Verify the directory is empty (and valid).
        * (Rmdir ".." won't be valid since
        *  ".." will contain a reference to
        *  the current directory and thus be
        *  non-empty.)
        */
       error = 0;
       if (!msdosfs_dosdirempty(ip) || ip->de_flag & DE_RENAME) {
               error = ENOTEMPTY;
               goto out;
       }
       /*
        * Delete the entry from the directory.  For dos filesystems this
        * gets rid of the directory entry on disk, the in memory copy
        * still exists but the de_refcnt is <= 0.  This prevents it from
        * being found by deget().  When the vput() on dep is done we give
        * up access and eventually msdosfs_reclaim() will be called which
        * will remove it from the denode cache.
        */
       if ((error = msdosfs_removede(dp, ip, &dp->de_crap)) != 0)
               goto out;
       /*
        * This is where we decrement the link count in the parent
        * directory.  Since dos filesystems don't do this we just purge
        * the name cache and let go of the parent directory denode.
        */
       cache_purge(dvp);
       /*
        * Truncate the directory that is being deleted.
        */
       error = msdosfs_detrunc(ip, (u_long)0, IO_SYNC, cnp->cn_cred);
       cache_purge(vp);
out:
       vput(vp);
       return (error);
}

int
msdosfs_readdir(void *v)
{
       struct vop_readdir_args /* {
               struct vnode *a_vp;
               struct uio *a_uio;
               kauth_cred_t a_cred;
               int *a_eofflag;
               off_t **a_cookies;
               int *a_ncookies;
       } */ *ap = v;
       int error = 0;
       int diff;
       long n;
       int blsize;
       long on;
       long lost;
       long count;
       u_long cn;
       ino_t fileno;
       u_long dirsperblk;
       long bias = 0;
       daddr_t bn, lbn;
       struct buf *bp;
       struct denode *dep = VTODE(ap->a_vp);
       struct msdosfsmount *pmp = dep->de_pmp;
       struct direntry *dentp;
       struct dirent *dirbuf;
       struct uio *uio = ap->a_uio;
       off_t *cookies = NULL;
       int ncookies = 0, nc = 0;
       off_t offset, uio_off;
       int chksum = -1;
       uint16_t namlen;

#ifdef MSDOSFS_DEBUG
       printf("msdosfs_readdir(): vp %p, uio %p, cred %p, eofflagp %p\n",
           ap->a_vp, uio, ap->a_cred, ap->a_eofflag);
#endif

       /*
        * msdosfs_readdir() won't operate properly on regular files since
        * it does i/o only with the filesystem vnode, and hence can
        * retrieve the wrong block from the buffer cache for a plain file.
        * So, fail attempts to readdir() on a plain file.
        */
       if ((dep->de_Attributes & ATTR_DIRECTORY) == 0)
               return (ENOTDIR);

       /*
        * If the user buffer is smaller than the size of one dos directory
        * entry or the file offset is not a multiple of the size of a
        * directory entry, then we fail the read.
        */
       count = uio->uio_resid & ~(sizeof(struct direntry) - 1);
       offset = uio->uio_offset;
       if (count < sizeof(struct direntry) ||
           (offset & (sizeof(struct direntry) - 1)))
               return (EINVAL);
       lost = uio->uio_resid - count;
       uio->uio_resid = count;
       uio_off = uio->uio_offset;


       /* Allocate a temporary dirent buffer. */
       dirbuf = malloc(sizeof(struct dirent), M_MSDOSFSTMP, M_WAITOK | M_ZERO);

       if (ap->a_ncookies) {
               nc = uio->uio_resid / _DIRENT_MINSIZE((struct dirent *)0);
               cookies = malloc(nc * sizeof (off_t), M_TEMP, M_WAITOK);
               *ap->a_cookies = cookies;
       }

       dirsperblk = pmp->pm_BytesPerSec / sizeof(struct direntry);

       /*
        * If they are reading from the root directory then, we simulate
        * the . and .. entries since these don't exist in the root
        * directory.  We also set the offset bias to make up for having to
        * simulate these entries. By this I mean that at file offset 64 we
        * read the first entry in the root directory that lives on disk.
        */
       if (dep->de_StartCluster == MSDOSFSROOT
           || (FAT32(pmp) && dep->de_StartCluster == pmp->pm_rootdirblk)) {
#if 0
               printf("msdosfs_readdir(): going after . or .. in root dir, "
                   "offset %" PRIu64 "\n", offset);
#endif
               bias = 2 * sizeof(struct direntry);
               if (offset < bias) {
                       for (n = (int)offset / sizeof(struct direntry);
                            n < 2; n++) {
                               if (FAT32(pmp))
                                       dirbuf->d_fileno = cntobn(pmp,
                                            (ino_t)pmp->pm_rootdirblk)
                                            * dirsperblk;
                               else
                                       dirbuf->d_fileno = 1;
                               dirbuf->d_type = DT_DIR;
                               switch (n) {
                               case 0:
                                       dirbuf->d_namlen = 1;
                                       strlcpy(dirbuf->d_name, ".",
                                           sizeof(dirbuf->d_name));
                                       break;
                               case 1:
                                       dirbuf->d_namlen = 2;
                                       strlcpy(dirbuf->d_name, "..",
                                           sizeof(dirbuf->d_name));
                                       break;
                               }
                               dirbuf->d_reclen = _DIRENT_SIZE(dirbuf);
                               if (uio->uio_resid < dirbuf->d_reclen)
                                       goto out;
                               error = uiomove(dirbuf, dirbuf->d_reclen, uio);
                               if (error)
                                       goto out;
                               offset += sizeof(struct direntry);
                               uio_off = offset;
                               if (cookies) {
                                       *cookies++ = offset;
                                       ncookies++;
                                       if (ncookies >= nc)
                                               goto out;
                               }
                       }
               }
       }

       while (uio->uio_resid > 0) {
               lbn = de_cluster(pmp, offset - bias);
               on = (offset - bias) & pmp->pm_crbomask;
               n = MIN(pmp->pm_bpcluster - on, uio->uio_resid);
               diff = dep->de_FileSize - (offset - bias);
               if (diff <= 0)
                       break;
               n = MIN(n, diff);
               if ((error = msdosfs_pcbmap(dep, lbn, &bn, &cn, &blsize)) != 0)
                       break;
               error = bread(pmp->pm_devvp, de_bn2kb(pmp, bn), blsize,
                   0, &bp);
               if (error) {
                       goto bad;
               }
               n = MIN(n, blsize - bp->b_resid);

               /*
                * Convert from dos directory entries to fs-independent
                * directory entries.
                */
               for (dentp = (struct direntry *)((char *)bp->b_data + on);
                    (char *)dentp < (char *)bp->b_data + on + n;
                    dentp++, offset += sizeof(struct direntry)) {
#if 0

                       printf("rd: dentp %08x prev %08x crnt %08x deName %02x attr %02x\n",
                           dentp, prev, crnt, dentp->deName[0], dentp->deAttributes);
#endif
                       /*
                        * If this is an unused entry, we can stop.
                        */
                       if (dentp->deName[0] == SLOT_EMPTY) {
                               brelse(bp, 0);
                               goto out;
                       }
                       /*
                        * Skip deleted entries.
                        */
                       if (dentp->deName[0] == SLOT_DELETED) {
                               chksum = -1;
                               continue;
                       }

                       /*
                        * Handle Win95 long directory entries
                        */
                       if (dentp->deAttributes == ATTR_WIN95) {
                               if (pmp->pm_flags & MSDOSFSMNT_SHORTNAME)
                                       continue;
                               chksum =
                                   msdosfs_win2unixfn((struct winentry *)dentp,
                                   dirbuf, chksum, &namlen,
                                   pmp->pm_flags & MSDOSFSMNT_UTF8);
                               if (chksum != -1)
                                       dirbuf->d_namlen = namlen;
                               continue;
                       }

                       /*
                        * Skip volume labels
                        */
                       if (dentp->deAttributes & ATTR_VOLUME) {
                               chksum = -1;
                               continue;
                       }
                       /*
                        * This computation of d_fileno must match
                        * the computation of va_fileid in
                        * msdosfs_getattr.
                        */
                       if (dentp->deAttributes & ATTR_DIRECTORY) {
                               fileno = getushort(dentp->deStartCluster);
                               if (FAT32(pmp))
                                       fileno |= ((ino_t)getushort(dentp->deHighClust)) << 16;
                               /* if this is the root directory */
                               if (fileno == MSDOSFSROOT)
                                       if (FAT32(pmp))
                                               fileno = cntobn(pmp,
                                                   (ino_t)pmp->pm_rootdirblk)
                                                   * dirsperblk;
                                       else
                                               fileno = 1;
                               else
                                       fileno = cntobn(pmp, fileno) * dirsperblk;
                               dirbuf->d_fileno = fileno;
                               dirbuf->d_type = DT_DIR;
                       } else {
                               dirbuf->d_fileno =
                                   offset / sizeof(struct direntry);
                               dirbuf->d_type = DT_REG;
                       }
                       if (chksum != msdosfs_winChksum(dentp->deName)) {
                               char deName[11];

                               memcpy(deName, dentp->deName,
                                      sizeof dentp->deName);
                               memcpy(deName + 8, dentp->deExtension,
                                      sizeof dentp->deExtension);
                               assert(sizeof(deName) == sizeof(dentp->deName) +
                                       sizeof(dentp->deExtension));
                               dirbuf->d_namlen =
                                   msdosfs_dos2unixfn(deName,
                                       (u_char *)dirbuf->d_name,
                                       pmp->pm_flags & MSDOSFSMNT_SHORTNAME);
                       } else
                               dirbuf->d_name[dirbuf->d_namlen] = 0;
                       namlen = dirbuf->d_namlen;
                       chksum = -1;
                       dirbuf->d_reclen = _DIRENT_SIZE(dirbuf);
                       if (uio->uio_resid < dirbuf->d_reclen) {
                               brelse(bp, 0);
                               goto out;
                       }
                       error = uiomove(dirbuf, dirbuf->d_reclen, uio);
                       if (error) {
                               brelse(bp, 0);
                               goto out;
                       }
                       uio_off = offset + sizeof(struct direntry);
                       if (cookies) {
                               *cookies++ = offset + sizeof(struct direntry);
                               ncookies++;
                               if (ncookies >= nc) {
                                       brelse(bp, 0);
                                       goto out;
                               }
                       }
               }
               brelse(bp, 0);
       }

out:
       uio->uio_offset = uio_off;
       uio->uio_resid += lost;
       if (dep->de_FileSize - (offset - bias) <= 0)
               *ap->a_eofflag = 1;
       else
               *ap->a_eofflag = 0;

       if (ap->a_ncookies) {
               if (error) {
                       free(*ap->a_cookies, M_TEMP);
                       *ap->a_ncookies = 0;
                       *ap->a_cookies = NULL;
               } else
                       *ap->a_ncookies = ncookies;
       }

bad:
       free(dirbuf, M_MSDOSFSTMP);
       return (error);
}

/*
* vp  - address of vnode file the file
* bn  - which cluster we are interested in mapping to a filesystem block number.
* vpp - returns the vnode for the block special file holding the filesystem
*       containing the file of interest
* bnp - address of where to return the filesystem relative block number
*/
int
msdosfs_bmap(void *v)
{
       struct vop_bmap_args /* {
               struct vnode *a_vp;
               daddr_t a_bn;
               struct vnode **a_vpp;
               daddr_t *a_bnp;
               int *a_runp;
       } */ *ap = v;
       struct denode *dep = VTODE(ap->a_vp);
       int run, maxrun;
       daddr_t runbn;
       int status;

       if (ap->a_vpp != NULL)
               *ap->a_vpp = dep->de_devvp;
       if (ap->a_bnp == NULL)
               return (0);
       status = msdosfs_pcbmap(dep, ap->a_bn, ap->a_bnp, 0, 0);

       /*
        * From FreeBSD:
        * A little kludgy, but we loop calling pcbmap until we
        * reach the end of the contiguous piece, or reach MAXPHYS.
        * Since it reduces disk I/Os, the "wasted" CPU is put to
        * good use (4 to 5 fold sequential read I/O improvement on USB
        * drives).
        */
       if (ap->a_runp != NULL) {
               /* taken from ufs_bmap */
               maxrun = ulmin(MAXPHYS / dep->de_pmp->pm_bpcluster - 1,
                              dep->de_pmp->pm_maxcluster - ap->a_bn);
               for (run = 1; run <= maxrun; run++) {
                       if (msdosfs_pcbmap(dep, ap->a_bn + run, &runbn, NULL,
                           NULL) != 0 || runbn !=
                                   *ap->a_bnp + de_cn2bn(dep->de_pmp, run))
                               break;
               }
               *ap->a_runp = run - 1;
       }

       /*
        * We need to scale *ap->a_bnp by sector_size/DEV_BSIZE
        */
       *ap->a_bnp = de_bn2kb(dep->de_pmp, *ap->a_bnp);
       return status;
}

int
msdosfs_strategy(void *v)
{
       struct vop_strategy_args /* {
               struct vnode *a_vp;
               struct buf *a_bp;
       } */ *ap = v;
       struct vnode *vp = ap->a_vp;
       struct buf *bp = ap->a_bp;
       struct denode *dep = VTODE(bp->b_vp);
       int error = 0;

       if (vp->v_type == VBLK || vp->v_type == VCHR)
               panic("msdosfs_strategy: spec");
       /*
        * If we don't already know the filesystem relative block number
        * then get it using pcbmap().  If pcbmap() returns the block
        * number as -1 then we've got a hole in the file.  DOS filesystems
        * don't allow files with holes, so we shouldn't ever see this.
        */
       if (bp->b_blkno == bp->b_lblkno) {
               error = msdosfs_pcbmap(dep, de_bn2cn(dep->de_pmp, bp->b_lblkno),
                              &bp->b_blkno, 0, 0);
               if (error)
                       bp->b_blkno = -1;
               if (bp->b_blkno == -1)
                       clrbuf(bp);
               else
                       bp->b_blkno = de_bn2kb(dep->de_pmp, bp->b_blkno);
       }
       if (bp->b_blkno == -1) {
               biodone(bp);
               return (error);
       }

       /*
        * Read/write the block from/to the disk that contains the desired
        * file block.
        */

       vp = dep->de_devvp;
       return (VOP_STRATEGY(vp, bp));
}

int
msdosfs_print(void *v)
{
       struct vop_print_args /* {
               struct vnode *vp;
       } */ *ap = v;
       struct denode *dep = VTODE(ap->a_vp);

       printf(
           "tag VT_MSDOSFS, startcluster %ld, dircluster %ld, diroffset %ld ",
           dep->de_StartCluster, dep->de_dirclust, dep->de_diroffset);
       printf(" dev %llu, %llu ", (unsigned long long)major(dep->de_dev),
           (unsigned long long)minor(dep->de_dev));
       printf("\n");
       return (0);
}

int
msdosfs_advlock(void *v)
{
       struct vop_advlock_args /* {
               struct vnode *a_vp;
               void *a_id;
               int a_op;
               struct flock *a_fl;
               int a_flags;
       } */ *ap = v;
       struct denode *dep = VTODE(ap->a_vp);

       return lf_advlock(ap, &dep->de_lockf, dep->de_FileSize);
}

int
msdosfs_pathconf(void *v)
{
       struct vop_pathconf_args /* {
               struct vnode *a_vp;
               int a_name;
               register_t *a_retval;
       } */ *ap = v;

       switch (ap->a_name) {
       case _PC_LINK_MAX:
               *ap->a_retval = 1;
               return (0);
       case _PC_NAME_MAX:
               *ap->a_retval = ap->a_vp->v_mount->mnt_stat.f_namemax;
               return (0);
       case _PC_PATH_MAX:
               *ap->a_retval = PATH_MAX;
               return (0);
       case _PC_CHOWN_RESTRICTED:
               *ap->a_retval = 1;
               return (0);
       case _PC_NO_TRUNC:
               *ap->a_retval = 1;
               return (0);
       case _PC_SYNC_IO:
               *ap->a_retval = 1;
               return (0);
       case _PC_FILESIZEBITS:
               *ap->a_retval = 32;
               return (0);
       default:
               return genfs_pathconf(ap);
       }
       /* NOTREACHED */
}

/*
* Flush the blocks of a file to disk.
*
* This function is worthless for vnodes that represent directories. Maybe we
* could just do a sync if they try an fsync on a directory file.
*/
int
msdosfs_fsync(void *v)
{
       struct vop_fsync_args /* {
               struct vnode *a_vp;
               kauth_cred_t a_cred;
               int a_flags;
               off_t offlo;
               off_t offhi;
       } */ *ap = v;
       struct vnode *vp = ap->a_vp;
       int wait;
       int error;

       wait = (ap->a_flags & FSYNC_WAIT) != 0;
       error = vflushbuf(vp, ap->a_flags);
       if (error == 0 && (ap->a_flags & FSYNC_DATAONLY) == 0)
               error = msdosfs_update(vp, NULL, NULL, wait ? UPDATE_WAIT : 0);

       if (error == 0 && ap->a_flags & FSYNC_CACHE) {
               struct denode *dep = VTODE(vp);
               struct vnode *devvp = dep->de_devvp;

               int l = 0;
               error = VOP_IOCTL(devvp, DIOCCACHESYNC, &l, FWRITE,
                                         curlwp->l_cred);
       }

       return (error);
}

void
msdosfs_detimes(struct denode *dep, const struct timespec *acc,
   const struct timespec *mod, const struct timespec *cre, int gmtoff)
{
       struct timespec *ts = NULL, tsb;

       KASSERT(dep->de_flag & (DE_UPDATE | DE_CREATE | DE_ACCESS));
       /* XXX just call getnanotime early and use result if needed? */
       dep->de_flag |= DE_MODIFIED;
       if (dep->de_flag & DE_UPDATE) {
               if (mod == NULL) {
                       getnanotime(&tsb);
                       mod = ts = &tsb;
               }
               msdosfs_unix2dostime(mod, gmtoff, &dep->de_MDate,
                   &dep->de_MTime, NULL);
               dep->de_Attributes |= ATTR_ARCHIVE;
       }
       if ((dep->de_pmp->pm_flags & MSDOSFSMNT_NOWIN95) == 0) {
               if (dep->de_flag & DE_ACCESS)  {
                       if (acc == NULL)
                               acc = ts == NULL ?
                                   (getnanotime(&tsb), ts = &tsb) : ts;
                       msdosfs_unix2dostime(acc, gmtoff, &dep->de_ADate,
                           NULL, NULL);
               }
               if (dep->de_flag & DE_CREATE) {
                       if (cre == NULL)
                               cre = ts == NULL ?
                                   (getnanotime(&tsb), ts = &tsb) : ts;
                       msdosfs_unix2dostime(cre, gmtoff, &dep->de_CDate,
                           &dep->de_CTime, &dep->de_CHun);
               }
       }

       dep->de_flag &= ~(DE_UPDATE | DE_CREATE | DE_ACCESS);
}

/* Global vfs data structures for msdosfs */
int (**msdosfs_vnodeop_p)(void *);
const struct vnodeopv_entry_desc msdosfs_vnodeop_entries[] = {
       { &vop_default_desc, vn_default_error },
       { &vop_parsepath_desc, genfs_parsepath },       /* parsepath */
       { &vop_lookup_desc, msdosfs_lookup },           /* lookup */
       { &vop_create_desc, msdosfs_create },           /* create */
       { &vop_mknod_desc, genfs_eopnotsupp },          /* mknod */
       { &vop_open_desc, genfs_nullop },               /* open */
       { &vop_close_desc, msdosfs_close },             /* close */
       { &vop_access_desc, msdosfs_access },           /* access */
       { &vop_accessx_desc, genfs_accessx },           /* accessx */
       { &vop_getattr_desc, msdosfs_getattr },         /* getattr */
       { &vop_setattr_desc, msdosfs_setattr },         /* setattr */
       { &vop_read_desc, msdosfs_read },               /* read */
       { &vop_write_desc, msdosfs_write },             /* write */
       { &vop_fallocate_desc, genfs_eopnotsupp },      /* fallocate */
       { &vop_fdiscard_desc, genfs_eopnotsupp },       /* fdiscard */
       { &vop_fcntl_desc, genfs_fcntl },               /* fcntl */
       { &vop_ioctl_desc, genfs_enoioctl },            /* ioctl */
       { &vop_poll_desc, genfs_poll },                 /* poll */
       { &vop_kqfilter_desc, genfs_kqfilter },         /* kqfilter */
       { &vop_revoke_desc, genfs_revoke },             /* revoke */
       { &vop_mmap_desc, genfs_mmap },                 /* mmap */
       { &vop_fsync_desc, msdosfs_fsync },             /* fsync */
       { &vop_seek_desc, genfs_seek },                 /* seek */
       { &vop_remove_desc, msdosfs_remove },           /* remove */
       { &vop_link_desc, genfs_eopnotsupp },           /* link */
       { &vop_rename_desc, msdosfs_rename },           /* rename */
       { &vop_mkdir_desc, msdosfs_mkdir },             /* mkdir */
       { &vop_rmdir_desc, msdosfs_rmdir },             /* rmdir */
       { &vop_symlink_desc, genfs_eopnotsupp },        /* symlink */
       { &vop_readdir_desc, msdosfs_readdir },         /* readdir */
       { &vop_readlink_desc, genfs_einval },           /* readlink */
       { &vop_abortop_desc, genfs_abortop },           /* abortop */
       { &vop_inactive_desc, msdosfs_inactive },       /* inactive */
       { &vop_reclaim_desc, msdosfs_reclaim },         /* reclaim */
       { &vop_lock_desc, genfs_lock },                 /* lock */
       { &vop_unlock_desc, genfs_unlock },             /* unlock */
       { &vop_bmap_desc, msdosfs_bmap },               /* bmap */
       { &vop_strategy_desc, msdosfs_strategy },       /* strategy */
       { &vop_print_desc, msdosfs_print },             /* print */
       { &vop_islocked_desc, genfs_islocked },         /* islocked */
       { &vop_pathconf_desc, msdosfs_pathconf },       /* pathconf */
       { &vop_advlock_desc, msdosfs_advlock },         /* advlock */
       { &vop_bwrite_desc, vn_bwrite },                /* bwrite */
       { &vop_getpages_desc, genfs_getpages },         /* getpages */
       { &vop_putpages_desc, genfs_putpages },         /* putpages */
       { NULL, NULL }
};
const struct vnodeopv_desc msdosfs_vnodeop_opv_desc =
       { &msdosfs_vnodeop_p, msdosfs_vnodeop_entries };