diff --git a/sys/fs/msdosfs/msdosfs_denode.c b/sys/fs/msdosfs/msdosfs_denode.c
index bdc7436079ea..6fba51708462 100644
--- a/sys/fs/msdosfs/msdosfs_denode.c
+++ b/sys/fs/msdosfs/msdosfs_denode.c
@@ -1,635 +1,636 @@
 /* $FreeBSD$ */
 /*	$NetBSD: msdosfs_denode.c,v 1.28 1998/02/10 14:10:00 mrg Exp $	*/
 
 /*-
  * SPDX-License-Identifier: BSD-4-Clause
  *
  * Copyright (C) 1994, 1995, 1997 Wolfgang Solfrank.
  * Copyright (C) 1994, 1995, 1997 TooLs GmbH.
  * All rights reserved.
  * Original code by Paul Popelka (paulp@uts.amdahl.com) (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 (paulp@uts.amdahl.com)
  *
  * 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/param.h>
 #include <sys/systm.h>
 #include <sys/buf.h>
 #include <sys/clock.h>
 #include <sys/kernel.h>
 #include <sys/malloc.h>
 #include <sys/mount.h>
 #include <sys/vmmeter.h>
 #include <sys/vnode.h>
 
 #include <vm/vm.h>
 #include <vm/vm_extern.h>
 
 #include <fs/msdosfs/bpb.h>
 #include <fs/msdosfs/direntry.h>
 #include <fs/msdosfs/denode.h>
 #include <fs/msdosfs/fat.h>
 #include <fs/msdosfs/msdosfsmount.h>
 
 static MALLOC_DEFINE(M_MSDOSFSNODE, "msdosfs_node", "MSDOSFS vnode private part");
 
 static int
 de_vncmpf(struct vnode *vp, void *arg)
 {
 	struct denode *de;
 	uint64_t *a;
 
 	a = arg;
 	de = VTODE(vp);
 	return (de->de_inode != *a) || (de->de_refcnt <= 0);
 }
 
 /*
  * If deget() succeeds it returns with the gotten denode locked().
  *
  * pmp	     - address of msdosfsmount structure of the filesystem containing
  *	       the denode of interest.  The address of
  *	       the msdosfsmount structure are used.
  * dirclust  - which cluster bp contains, if dirclust is 0 (root directory)
  *	       diroffset is relative to the beginning of the root directory,
  *	       otherwise it is cluster relative.
  * diroffset - offset past begin of cluster of denode we want
  * lkflags   - locking flags (LK_NOWAIT)
  * depp	     - returns the address of the gotten denode.
  */
 int
 deget(struct msdosfsmount *pmp, u_long dirclust, u_long diroffset,
     int lkflags, struct denode **depp)
 {
 	int error;
 	uint64_t inode;
 	struct mount *mntp = pmp->pm_mountp;
 	struct direntry *direntptr;
 	struct denode *ldep;
 	struct vnode *nvp, *xvp;
 	struct buf *bp;
 
 #ifdef MSDOSFS_DEBUG
 	printf("deget(pmp %p, dirclust %lu, diroffset %lx, flags %#x, "
 	    "depp %p)\n",
 	    pmp, dirclust, diroffset, flags, depp);
 #endif
 	MPASS((lkflags & LK_TYPE_MASK) == LK_EXCLUSIVE);
 
 	/*
 	 * On FAT32 filesystems, root is a (more or less) normal
 	 * directory
 	 */
 	if (FAT32(pmp) && dirclust == MSDOSFSROOT)
 		dirclust = pmp->pm_rootdirblk;
 
 	/*
 	 * See if the denode is in the denode cache. Use the location of
 	 * the directory entry to compute the hash value. For subdir use
 	 * address of "." entry. For root dir (if not FAT32) use cluster
 	 * MSDOSFSROOT, offset MSDOSFSROOT_OFS
 	 *
 	 * NOTE: de_vncmpf will explicitly skip any denodes that do not have
 	 * a de_refcnt > 0.  This insures that that we do not attempt to use
 	 * a denode that represents an unlinked but still open file.
 	 * These files are not to be accessible even when the directory
 	 * entry that represented the file happens to be reused while the
 	 * deleted file is still open.
 	 */
 	inode = (uint64_t)pmp->pm_bpcluster * dirclust + diroffset;
 
 	error = vfs_hash_get(mntp, inode, lkflags, curthread, &nvp,
 	    de_vncmpf, &inode);
 	if (error)
 		return (error);
 	if (nvp != NULL) {
 		*depp = VTODE(nvp);
 		if ((*depp)->de_dirclust != dirclust) {
 			printf("%s: wrong dir cluster %lu %lu\n",
 			    pmp->pm_mountp->mnt_stat.f_mntonname,
 			    (*depp)->de_dirclust, dirclust);
 			goto badoff;
 		}
 		if ((*depp)->de_diroffset != diroffset) {
 			printf("%s: wrong dir offset %lu %lu\n",
 			    pmp->pm_mountp->mnt_stat.f_mntonname,
 			    (*depp)->de_diroffset,  diroffset);
 			goto badoff;
 		}
 		return (0);
 badoff:
 		vgone(nvp);
 		vput(nvp);
+		msdosfs_integrity_error(pmp);
 		return (EBADF);
 	}
 	ldep = malloc(sizeof(struct denode), M_MSDOSFSNODE, M_WAITOK | M_ZERO);
 
 	/*
 	 * Directory entry was not in cache, have to create a vnode and
 	 * copy it from the passed disk buffer.
 	 */
 	/* getnewvnode() does a VREF() on the vnode */
 	error = getnewvnode("msdosfs", mntp, &msdosfs_vnodeops, &nvp);
 	if (error) {
 		*depp = NULL;
 		free(ldep, M_MSDOSFSNODE);
 		return error;
 	}
 	nvp->v_data = ldep;
 	ldep->de_vnode = nvp;
 	ldep->de_flag = 0;
 	ldep->de_dirclust = dirclust;
 	ldep->de_diroffset = diroffset;
 	ldep->de_inode = inode;
 	lockmgr(nvp->v_vnlock, LK_EXCLUSIVE, NULL);
 	VN_LOCK_AREC(nvp);	/* for doscheckpath */
 	fc_purge(ldep, 0);	/* init the FAT cache for this denode */
 	error = insmntque(nvp, mntp);
 	if (error != 0) {
 		free(ldep, M_MSDOSFSNODE);
 		*depp = NULL;
 		return (error);
 	}
 	error = vfs_hash_insert(nvp, inode, lkflags, curthread, &xvp,
 	    de_vncmpf, &inode);
 	if (error) {
 		*depp = NULL;
 		return (error);
 	}
 	if (xvp != NULL) {
 		*depp = xvp->v_data;
 		return (0);
 	}
 
 	ldep->de_pmp = pmp;
 	ldep->de_refcnt = 1;
 	/*
 	 * Copy the directory entry into the denode area of the vnode.
 	 */
 	if ((dirclust == MSDOSFSROOT
 	     || (FAT32(pmp) && dirclust == pmp->pm_rootdirblk))
 	    && diroffset == MSDOSFSROOT_OFS) {
 		/*
 		 * Directory entry for the root directory. There isn't one,
 		 * so we manufacture one. We should probably rummage
 		 * through the root directory and find a label entry (if it
 		 * exists), and then use the time and date from that entry
 		 * as the time and date for the root denode.
 		 */
 		nvp->v_vflag |= VV_ROOT; /* should be further down XXX */
 
 		ldep->de_Attributes = ATTR_DIRECTORY;
 		ldep->de_LowerCase = 0;
 		if (FAT32(pmp))
 			ldep->de_StartCluster = pmp->pm_rootdirblk;
 			/* de_FileSize will be filled in further down */
 		else {
 			ldep->de_StartCluster = MSDOSFSROOT;
 			ldep->de_FileSize = pmp->pm_rootdirsize * DEV_BSIZE;
 		}
 		/*
 		 * fill in time and date so that fattime2timespec() doesn't
 		 * spit up when called from msdosfs_getattr() with root
 		 * denode
 		 */
 		ldep->de_CHun = 0;
 		ldep->de_CTime = 0x0000;	/* 00:00:00	 */
 		ldep->de_CDate = (0 << DD_YEAR_SHIFT) | (1 << DD_MONTH_SHIFT)
 		    | (1 << DD_DAY_SHIFT);
 		/* Jan 1, 1980	 */
 		ldep->de_ADate = ldep->de_CDate;
 		ldep->de_MTime = ldep->de_CTime;
 		ldep->de_MDate = ldep->de_CDate;
 		/* leave the other fields as garbage */
 	} else {
 		error = readep(pmp, dirclust, diroffset, &bp, &direntptr);
 		if (error) {
 			/*
 			 * The denode does not contain anything useful, so
 			 * it would be wrong to leave it on its hash chain.
 			 * Arrange for vput() to just forget about it.
 			 */
 			ldep->de_Name[0] = SLOT_DELETED;
 			vgone(nvp);
 			vput(nvp);
 			*depp = NULL;
 			return (error);
 		}
 		(void)DE_INTERNALIZE(ldep, direntptr);
 		brelse(bp);
 	}
 
 	/*
 	 * Fill in a few fields of the vnode and finish filling in the
 	 * denode.  Then return the address of the found denode.
 	 */
 	if (ldep->de_Attributes & ATTR_DIRECTORY) {
 		/*
 		 * Since DOS directory entries that describe directories
 		 * have 0 in the filesize field, we take this opportunity
 		 * to find out the length of the directory and plug it into
 		 * the denode structure.
 		 */
 		u_long size;
 
 		/*
 		 * XXX it sometimes happens that the "." entry has cluster
 		 * number 0 when it shouldn't.  Use the actual cluster number
 		 * instead of what is written in directory entry.
 		 */
 		if (diroffset == 0 && ldep->de_StartCluster != dirclust) {
 #ifdef MSDOSFS_DEBUG
 			printf("deget(): \".\" entry at clust %lu != %lu\n",
 			    dirclust, ldep->de_StartCluster);
 #endif
 			ldep->de_StartCluster = dirclust;
 		}
 
 		nvp->v_type = VDIR;
 		if (ldep->de_StartCluster != MSDOSFSROOT) {
 			error = pcbmap(ldep, 0xffff, 0, &size, 0);
 			if (error == E2BIG) {
 				ldep->de_FileSize = de_cn2off(pmp, size);
 				error = 0;
 			} else {
 #ifdef MSDOSFS_DEBUG
 				printf("deget(): pcbmap returned %d\n", error);
 #endif
 			}
 		}
 	} else
 		nvp->v_type = VREG;
 	ldep->de_modrev = init_va_filerev();
 	*depp = ldep;
 	return (0);
 }
 
 int
 deupdat(struct denode *dep, int waitfor)
 {
 	struct direntry dir;
 	struct timespec ts;
 	struct buf *bp;
 	struct direntry *dirp;
 	int error;
 
 	if (DETOV(dep)->v_mount->mnt_flag & MNT_RDONLY) {
 		dep->de_flag &= ~(DE_UPDATE | DE_CREATE | DE_ACCESS |
 		    DE_MODIFIED);
 		return (0);
 	}
 	vfs_timestamp(&ts);
 	DETIMES(dep, &ts, &ts, &ts);
 	if ((dep->de_flag & DE_MODIFIED) == 0 && waitfor == 0)
 		return (0);
 	dep->de_flag &= ~DE_MODIFIED;
 	if (DETOV(dep)->v_vflag & VV_ROOT)
 		return (EINVAL);
 	if (dep->de_refcnt <= 0)
 		return (0);
 	error = readde(dep, &bp, &dirp);
 	if (error)
 		return (error);
 	DE_EXTERNALIZE(&dir, dep);
 	if (bcmp(dirp, &dir, sizeof(dir)) == 0) {
 		if (waitfor == 0 || (bp->b_flags & B_DELWRI) == 0) {
 			brelse(bp);
 			return (0);
 		}
 	} else
 		*dirp = dir;
 	if ((DETOV(dep)->v_mount->mnt_flag & MNT_NOCLUSTERW) == 0)
 		bp->b_flags |= B_CLUSTEROK;
 	if (waitfor)
 		error = bwrite(bp);
 	else if (vm_page_count_severe() || buf_dirty_count_severe())
 		bawrite(bp);
 	else
 		bdwrite(bp);
 	return (error);
 }
 
 /*
  * Truncate the file described by dep to the length specified by length.
  */
 int
 detrunc(struct denode *dep, u_long length, int flags, struct ucred *cred)
 {
 	int error;
 	int allerror;
 	u_long eofentry;
 	u_long chaintofree;
 	daddr_t bn;
 	int boff;
 	int isadir = dep->de_Attributes & ATTR_DIRECTORY;
 	struct buf *bp;
 	struct msdosfsmount *pmp = dep->de_pmp;
 
 #ifdef MSDOSFS_DEBUG
 	printf("detrunc(): file %s, length %lu, flags %x\n", dep->de_Name, length, flags);
 #endif
 
 	/*
 	 * Disallow attempts to truncate the root directory since it is of
 	 * fixed size.  That's just the way dos filesystems are.  We use
 	 * the VROOT bit in the vnode because checking for the directory
 	 * bit and a startcluster of 0 in the denode is not adequate to
 	 * recognize the root directory at this point in a file or
 	 * directory's life.
 	 */
 	if ((DETOV(dep)->v_vflag & VV_ROOT) && !FAT32(pmp)) {
 #ifdef MSDOSFS_DEBUG
 		printf("detrunc(): can't truncate root directory, clust %ld, offset %ld\n",
 		    dep->de_dirclust, dep->de_diroffset);
 #endif
 		return (EINVAL);
 	}
 
 	if (dep->de_FileSize < length) {
 		vnode_pager_setsize(DETOV(dep), length);
 		return deextend(dep, length, cred);
 	}
 
 	/*
 	 * If the desired length is 0 then remember the starting cluster of
 	 * the file and set the StartCluster field in the directory entry
 	 * to 0.  If the desired length is not zero, then get the number of
 	 * the last cluster in the shortened file.  Then get the number of
 	 * the first cluster in the part of the file that is to be freed.
 	 * Then set the next cluster pointer in the last cluster of the
 	 * file to CLUST_EOFE.
 	 */
 	if (length == 0) {
 		chaintofree = dep->de_StartCluster;
 		dep->de_StartCluster = 0;
 		eofentry = ~0;
 	} else {
 		error = pcbmap(dep, de_clcount(pmp, length) - 1, 0,
 			       &eofentry, 0);
 		if (error) {
 #ifdef MSDOSFS_DEBUG
 			printf("detrunc(): pcbmap fails %d\n", error);
 #endif
 			return (error);
 		}
 	}
 
 	fc_purge(dep, de_clcount(pmp, length));
 
 	/*
 	 * If the new length is not a multiple of the cluster size then we
 	 * must zero the tail end of the new last cluster in case it
 	 * becomes part of the file again because of a seek.
 	 */
 	if ((boff = length & pmp->pm_crbomask) != 0) {
 		if (isadir) {
 			bn = cntobn(pmp, eofentry);
 			error = bread(pmp->pm_devvp, bn, pmp->pm_bpcluster,
 			    NOCRED, &bp);
 		} else {
 			error = bread(DETOV(dep), de_cluster(pmp, length),
 			    pmp->pm_bpcluster, cred, &bp);
 		}
 		if (error) {
 #ifdef MSDOSFS_DEBUG
 			printf("detrunc(): bread fails %d\n", error);
 #endif
 			return (error);
 		}
 		memset(bp->b_data + boff, 0, pmp->pm_bpcluster - boff);
 		if ((flags & IO_SYNC) != 0)
 			bwrite(bp);
 		else
 			bdwrite(bp);
 	}
 
 	/*
 	 * Write out the updated directory entry.  Even if the update fails
 	 * we free the trailing clusters.
 	 */
 	dep->de_FileSize = length;
 	if (!isadir)
 		dep->de_flag |= DE_UPDATE | DE_MODIFIED;
 	allerror = vtruncbuf(DETOV(dep), length, pmp->pm_bpcluster);
 #ifdef MSDOSFS_DEBUG
 	if (allerror)
 		printf("detrunc(): vtruncbuf error %d\n", allerror);
 #endif
 	error = deupdat(dep, !DOINGASYNC((DETOV(dep))));
 	if (error != 0 && allerror == 0)
 		allerror = error;
 #ifdef MSDOSFS_DEBUG
 	printf("detrunc(): allerror %d, eofentry %lu\n",
 	       allerror, eofentry);
 #endif
 
 	/*
 	 * If we need to break the cluster chain for the file then do it
 	 * now.
 	 */
 	if (eofentry != ~0) {
 		error = fatentry(FAT_GET_AND_SET, pmp, eofentry,
 				 &chaintofree, CLUST_EOFE);
 		if (error) {
 #ifdef MSDOSFS_DEBUG
 			printf("detrunc(): fatentry errors %d\n", error);
 #endif
 			return (error);
 		}
 		fc_setcache(dep, FC_LASTFC, de_cluster(pmp, length - 1),
 			    eofentry);
 	}
 
 	/*
 	 * Now free the clusters removed from the file because of the
 	 * truncation.
 	 */
 	if (chaintofree != 0 && !MSDOSFSEOF(pmp, chaintofree))
 		freeclusterchain(pmp, chaintofree);
 
 	return (allerror);
 }
 
 /*
  * Extend the file described by dep to length specified by length.
  */
 int
 deextend(struct denode *dep, u_long length, struct ucred *cred)
 {
 	struct msdosfsmount *pmp = dep->de_pmp;
 	u_long count;
 	int error;
 
 	/*
 	 * The root of a DOS filesystem cannot be extended.
 	 */
 	if ((DETOV(dep)->v_vflag & VV_ROOT) && !FAT32(pmp))
 		return (EINVAL);
 
 	/*
 	 * Directories cannot be extended.
 	 */
 	if (dep->de_Attributes & ATTR_DIRECTORY)
 		return (EISDIR);
 
 	if (length <= dep->de_FileSize)
 		panic("deextend: file too large");
 
 	/*
 	 * Compute the number of clusters to allocate.
 	 */
 	count = de_clcount(pmp, length) - de_clcount(pmp, dep->de_FileSize);
 	if (count > 0) {
 		if (count > pmp->pm_freeclustercount)
 			return (ENOSPC);
 		error = extendfile(dep, count, NULL, NULL, DE_CLEAR);
 		if (error) {
 			/* truncate the added clusters away again */
 			(void) detrunc(dep, dep->de_FileSize, 0, cred);
 			return (error);
 		}
 	}
 	dep->de_FileSize = length;
 	dep->de_flag |= DE_UPDATE | DE_MODIFIED;
 	return (deupdat(dep, !DOINGASYNC(DETOV(dep))));
 }
 
 /*
  * Move a denode to its correct hash queue after the file it represents has
  * been moved to a new directory.
  */
 void
 reinsert(struct denode *dep)
 {
 	struct vnode *vp;
 
 	/*
 	 * Fix up the denode cache.  If the denode is for a directory,
 	 * there is nothing to do since the hash is based on the starting
 	 * cluster of the directory file and that hasn't changed.  If for a
 	 * file the hash is based on the location of the directory entry,
 	 * so we must remove it from the cache and re-enter it with the
 	 * hash based on the new location of the directory entry.
 	 */
 #if 0
 	if (dep->de_Attributes & ATTR_DIRECTORY)
 		return;
 #endif
 	vp = DETOV(dep);
 	dep->de_inode = (uint64_t)dep->de_pmp->pm_bpcluster * dep->de_dirclust +
 	    dep->de_diroffset;
 	vfs_hash_rehash(vp, dep->de_inode);
 }
 
 int
 msdosfs_reclaim(struct vop_reclaim_args *ap)
 {
 	struct vnode *vp = ap->a_vp;
 	struct denode *dep = VTODE(vp);
 
 #ifdef MSDOSFS_DEBUG
 	printf("msdosfs_reclaim(): dep %p, file %s, refcnt %ld\n",
 	    dep, dep->de_Name, dep->de_refcnt);
 #endif
 
 	/*
 	 * Remove the denode from its hash chain.
 	 */
 	vfs_hash_remove(vp);
 	/*
 	 * Purge old data structures associated with the denode.
 	 */
 #if 0 /* XXX */
 	dep->de_flag = 0;
 #endif
 	free(dep, M_MSDOSFSNODE);
 	vp->v_data = NULL;
 
 	return (0);
 }
 
 int
 msdosfs_inactive(struct vop_inactive_args *ap)
 {
 	struct vnode *vp = ap->a_vp;
 	struct denode *dep = VTODE(vp);
 	int error = 0;
 
 #ifdef MSDOSFS_DEBUG
 	printf("msdosfs_inactive(): dep %p, de_Name[0] %x\n", dep, dep->de_Name[0]);
 #endif
 
 	/*
 	 * Ignore denodes related to stale file handles.
 	 */
 	if (dep->de_Name[0] == SLOT_DELETED || dep->de_Name[0] == SLOT_EMPTY)
 		goto out;
 
 	/*
 	 * If the file has been deleted and it is on a read/write
 	 * filesystem, then truncate the file, and mark the directory slot
 	 * as empty.  (This may not be necessary for the dos filesystem.)
 	 */
 #ifdef MSDOSFS_DEBUG
 	printf("msdosfs_inactive(): dep %p, refcnt %ld, mntflag %llx, MNT_RDONLY %llx\n",
 	    dep, dep->de_refcnt, (unsigned long long)vp->v_mount->mnt_flag,
 	    (unsigned long long)MNT_RDONLY);
 #endif
 	if (dep->de_refcnt <= 0 && (vp->v_mount->mnt_flag & MNT_RDONLY) == 0) {
 		error = detrunc(dep, (u_long) 0, 0, NOCRED);
 		dep->de_flag |= DE_UPDATE;
 		dep->de_Name[0] = SLOT_DELETED;
 	}
 	deupdat(dep, 0);
 
 out:
 	/*
 	 * If we are done with the denode, reclaim it
 	 * so that it can be reused immediately.
 	 */
 #ifdef MSDOSFS_DEBUG
 	printf("msdosfs_inactive(): v_usecount %d, de_Name[0] %x\n",
 	       vrefcnt(vp), dep->de_Name[0]);
 #endif
 	if (dep->de_Name[0] == SLOT_DELETED || dep->de_Name[0] == SLOT_EMPTY)
 		vrecycle(vp);
 	return (error);
 }
diff --git a/sys/fs/msdosfs/msdosfs_fat.c b/sys/fs/msdosfs/msdosfs_fat.c
index fc1db705989b..e6d9b671e7d7 100644
--- a/sys/fs/msdosfs/msdosfs_fat.c
+++ b/sys/fs/msdosfs/msdosfs_fat.c
@@ -1,1209 +1,1210 @@
 /* $FreeBSD$ */
 /*	$NetBSD: msdosfs_fat.c,v 1.28 1997/11/17 15:36:49 ws Exp $	*/
 
 /*-
  * SPDX-License-Identifier: BSD-4-Clause
  *
  * Copyright (C) 1994, 1995, 1997 Wolfgang Solfrank.
  * Copyright (C) 1994, 1995, 1997 TooLs GmbH.
  * All rights reserved.
  * Original code by Paul Popelka (paulp@uts.amdahl.com) (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 (paulp@uts.amdahl.com)
  *
  * 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/param.h>
 #include <sys/systm.h>
 #include <sys/buf.h>
 #include <sys/mount.h>
 #include <sys/vmmeter.h>
 #include <sys/vnode.h>
 
 #include <fs/msdosfs/bpb.h>
 #include <fs/msdosfs/direntry.h>
 #include <fs/msdosfs/denode.h>
 #include <fs/msdosfs/fat.h>
 #include <fs/msdosfs/msdosfsmount.h>
 
 #define	FULL_RUN	((u_int)0xffffffff)
 
 static int	chainalloc(struct msdosfsmount *pmp, u_long start,
 		    u_long count, u_long fillwith, u_long *retcluster,
 		    u_long *got);
 static int	chainlength(struct msdosfsmount *pmp, u_long start,
 		    u_long count);
 static void	fatblock(struct msdosfsmount *pmp, u_long ofs, u_long *bnp,
 		    u_long *sizep, u_long *bop);
 static int	fatchain(struct msdosfsmount *pmp, u_long start, u_long count,
 		    u_long fillwith);
 static void	fc_lookup(struct denode *dep, u_long findcn, u_long *frcnp,
 		    u_long *fsrcnp);
 static void	updatefats(struct msdosfsmount *pmp, struct buf *bp,
 		    u_long fatbn);
 static __inline void
 		usemap_alloc(struct msdosfsmount *pmp, u_long cn);
 static int	usemap_free(struct msdosfsmount *pmp, u_long cn);
 static int	clusteralloc1(struct msdosfsmount *pmp, u_long start,
 		    u_long count, u_long fillwith, u_long *retcluster,
 		    u_long *got);
 
 static void
 fatblock(struct msdosfsmount *pmp, u_long ofs, u_long *bnp, u_long *sizep,
     u_long *bop)
 {
 	u_long bn, size;
 
 	bn = ofs / pmp->pm_fatblocksize * pmp->pm_fatblocksec;
 	size = min(pmp->pm_fatblocksec, pmp->pm_FATsecs - bn)
 	    * DEV_BSIZE;
 	bn += pmp->pm_fatblk + pmp->pm_curfat * pmp->pm_FATsecs;
 
 	if (bnp)
 		*bnp = bn;
 	if (sizep)
 		*sizep = size;
 	if (bop)
 		*bop = ofs % pmp->pm_fatblocksize;
 }
 
 /*
  * Map the logical cluster number of a file into a physical disk sector
  * that is filesystem relative.
  *
  * dep	  - address of denode representing the file of interest
  * findcn - file relative cluster whose filesystem relative cluster number
  *	    and/or block number are/is to be found
  * bnp	  - address of where to place the filesystem relative block number.
  *	    If this pointer is null then don't return this quantity.
  * cnp	  - address of where to place the filesystem relative cluster number.
  *	    If this pointer is null then don't return this quantity.
  * sp     - pointer to returned block size
  *
  * NOTE: Either bnp or cnp must be non-null.
  * This function has one side effect.  If the requested file relative cluster
  * is beyond the end of file, then the actual number of clusters in the file
  * is returned in *cnp.  This is useful for determining how long a directory is.
  *  If cnp is null, nothing is returned.
  */
 int
 pcbmap(struct denode *dep, u_long findcn, daddr_t *bnp, u_long *cnp, int *sp)
 {
 	int error;
 	u_long i;
 	u_long cn;
 	u_long prevcn = 0; /* XXX: prevcn could be used unititialized */
 	u_long byteoffset;
 	u_long bn;
 	u_long bo;
 	struct buf *bp = NULL;
 	u_long bp_bn = -1;
 	struct msdosfsmount *pmp = dep->de_pmp;
 	u_long bsize;
 
 	KASSERT(bnp != NULL || cnp != NULL || sp != NULL,
 	    ("pcbmap: extra call"));
 	ASSERT_VOP_ELOCKED(DETOV(dep), "pcbmap");
 
 	cn = dep->de_StartCluster;
 	/*
 	 * The "file" that makes up the root directory is contiguous,
 	 * permanently allocated, of fixed size, and is not made up of
 	 * clusters.  If the cluster number is beyond the end of the root
 	 * directory, then return the number of clusters in the file.
 	 */
 	if (cn == MSDOSFSROOT) {
 		if (dep->de_Attributes & ATTR_DIRECTORY) {
 			if (de_cn2off(pmp, findcn) >= dep->de_FileSize) {
 				if (cnp)
 					*cnp = de_bn2cn(pmp, pmp->pm_rootdirsize);
 				return (E2BIG);
 			}
 			if (bnp)
 				*bnp = pmp->pm_rootdirblk + de_cn2bn(pmp, findcn);
 			if (cnp)
 				*cnp = MSDOSFSROOT;
 			if (sp)
 				*sp = min(pmp->pm_bpcluster,
 				    dep->de_FileSize - de_cn2off(pmp, findcn));
 			return (0);
 		} else {		/* just an empty file */
 			if (cnp)
 				*cnp = 0;
 			return (E2BIG);
 		}
 	}
 
 	/*
 	 * All other files do I/O in cluster sized blocks
 	 */
 	if (sp)
 		*sp = pmp->pm_bpcluster;
 
 	/*
 	 * Rummage around in the FAT cache, maybe we can avoid tromping
 	 * through every FAT entry for the file. And, keep track of how far
 	 * off the cache was from where we wanted to be.
 	 */
 	i = 0;
 	fc_lookup(dep, findcn, &i, &cn);
 
 	/*
 	 * Handle all other files or directories the normal way.
 	 */
 	for (; i < findcn; i++) {
 		/*
 		 * Stop with all reserved clusters, not just with EOF.
 		 */
 		if ((cn | ~pmp->pm_fatmask) >= CLUST_RSRVD)
 			goto hiteof;
 		byteoffset = FATOFS(pmp, cn);
 		fatblock(pmp, byteoffset, &bn, &bsize, &bo);
 		if (bn != bp_bn) {
 			if (bp)
 				brelse(bp);
 			error = bread(pmp->pm_devvp, bn, bsize, NOCRED, &bp);
 			if (error) {
 				return (error);
 			}
 			bp_bn = bn;
 		}
 		prevcn = cn;
 		if (bo >= bsize) {
 			if (bp)
 				brelse(bp);
 			return (EIO);
 		}
 		if (FAT32(pmp))
 			cn = getulong(bp->b_data + bo);
 		else
 			cn = getushort(bp->b_data + bo);
 		if (FAT12(pmp) && (prevcn & 1))
 			cn >>= 4;
 		cn &= pmp->pm_fatmask;
 
 		/*
 		 * Force the special cluster numbers
 		 * to be the same for all cluster sizes
 		 * to let the rest of msdosfs handle
 		 * all cases the same.
 		 */
 		if ((cn | ~pmp->pm_fatmask) >= CLUST_RSRVD)
 			cn |= ~pmp->pm_fatmask;
 	}
 
 	if (!MSDOSFSEOF(pmp, cn)) {
 		if (bp)
 			brelse(bp);
 		if (bnp)
 			*bnp = cntobn(pmp, cn);
 		if (cnp)
 			*cnp = cn;
 		fc_setcache(dep, FC_LASTMAP, i, cn);
 		return (0);
 	}
 
 hiteof:;
 	if (cnp)
 		*cnp = i;
 	if (bp)
 		brelse(bp);
 	/* update last file cluster entry in the FAT cache */
 	fc_setcache(dep, FC_LASTFC, i - 1, prevcn);
 	return (E2BIG);
 }
 
 /*
  * Find the closest entry in the FAT cache to the cluster we are looking
  * for.
  */
 static void
 fc_lookup(struct denode *dep, u_long findcn, u_long *frcnp, u_long *fsrcnp)
 {
 	int i;
 	u_long cn;
 	struct fatcache *closest = NULL;
 
 	ASSERT_VOP_LOCKED(DETOV(dep), "fc_lookup");
 
 	for (i = 0; i < FC_SIZE; i++) {
 		cn = dep->de_fc[i].fc_frcn;
 		if (cn != FCE_EMPTY && cn <= findcn) {
 			if (closest == NULL || cn > closest->fc_frcn)
 				closest = &dep->de_fc[i];
 		}
 	}
 	if (closest) {
 		*frcnp = closest->fc_frcn;
 		*fsrcnp = closest->fc_fsrcn;
 	}
 }
 
 /*
  * Purge the FAT cache in denode dep of all entries relating to file
  * relative cluster frcn and beyond.
  */
 void
 fc_purge(struct denode *dep, u_int frcn)
 {
 	int i;
 	struct fatcache *fcp;
 
 	ASSERT_VOP_ELOCKED(DETOV(dep), "fc_purge");
 
 	fcp = dep->de_fc;
 	for (i = 0; i < FC_SIZE; i++, fcp++) {
 		if (fcp->fc_frcn >= frcn)
 			fcp->fc_frcn = FCE_EMPTY;
 	}
 }
 
 /*
  * Update the FAT.
  * If mirroring the FAT, update all copies, with the first copy as last.
  * Else update only the current FAT (ignoring the others).
  *
  * pmp	 - msdosfsmount structure for filesystem to update
  * bp	 - addr of modified FAT block
  * fatbn - block number relative to begin of filesystem of the modified FAT block.
  */
 static void
 updatefats(struct msdosfsmount *pmp, struct buf *bp, u_long fatbn)
 {
 	struct buf *bpn;
 	int cleanfat, i;
 
 #ifdef MSDOSFS_DEBUG
 	printf("updatefats(pmp %p, bp %p, fatbn %lu)\n", pmp, bp, fatbn);
 #endif
 
 	if (pmp->pm_flags & MSDOSFS_FATMIRROR) {
 		/*
 		 * Now copy the block(s) of the modified FAT to the other copies of
 		 * the FAT and write them out.  This is faster than reading in the
 		 * other FATs and then writing them back out.  This could tie up
 		 * the FAT for quite a while. Preventing others from accessing it.
 		 * To prevent us from going after the FAT quite so much we use
 		 * delayed writes, unless they specified "synchronous" when the
 		 * filesystem was mounted.  If synch is asked for then use
 		 * bwrite()'s and really slow things down.
 		 */
 		if (fatbn != pmp->pm_fatblk || FAT12(pmp))
 			cleanfat = 0;
 		else if (FAT16(pmp))
 			cleanfat = 16;
 		else
 			cleanfat = 32;
 		for (i = 1; i < pmp->pm_FATs; i++) {
 			fatbn += pmp->pm_FATsecs;
 			/* getblk() never fails */
 			bpn = getblk(pmp->pm_devvp, fatbn, bp->b_bcount,
 			    0, 0, 0);
 			memcpy(bpn->b_data, bp->b_data, bp->b_bcount);
 			/* Force the clean bit on in the other copies. */
 			if (cleanfat == 16)
 				((uint8_t *)bpn->b_data)[3] |= 0x80;
 			else if (cleanfat == 32)
 				((uint8_t *)bpn->b_data)[7] |= 0x08;
 			if (pmp->pm_mountp->mnt_flag & MNT_SYNCHRONOUS)
 				bwrite(bpn);
 			else
 				bdwrite(bpn);
 		}
 	}
 
 	/*
 	 * Write out the first (or current) FAT last.
 	 */
 	if (pmp->pm_mountp->mnt_flag & MNT_SYNCHRONOUS)
 		bwrite(bp);
 	else
 		bdwrite(bp);
 }
 
 /*
  * Updating entries in 12 bit FATs is a pain in the butt.
  *
  * The following picture shows where nibbles go when moving from a 12 bit
  * cluster number into the appropriate bytes in the FAT.
  *
  *	byte m        byte m+1      byte m+2
  *	+----+----+   +----+----+   +----+----+
  *	|  0    1 |   |  2    3 |   |  4    5 |   FAT bytes
  *	+----+----+   +----+----+   +----+----+
  *
  *	+----+----+----+   +----+----+----+
  *	|  3    0    1 |   |  4    5    2 |
  *	+----+----+----+   +----+----+----+
  *	cluster n  	   cluster n+1
  *
  * Where n is even. m = n + (n >> 2)
  *
  */
 static __inline void
 usemap_alloc(struct msdosfsmount *pmp, u_long cn)
 {
 
 	MSDOSFS_ASSERT_MP_LOCKED(pmp);
 
 	KASSERT(cn <= pmp->pm_maxcluster, ("cn too large %lu %lu", cn,
 	    pmp->pm_maxcluster));
 	KASSERT((pmp->pm_flags & MSDOSFSMNT_RONLY) == 0,
 	    ("usemap_alloc on ro msdosfs mount"));
 	KASSERT((pmp->pm_inusemap[cn / N_INUSEBITS] &
 	    (1U << (cn % N_INUSEBITS))) == 0,
 	    ("Allocating used sector %ld %ld %x", cn, cn % N_INUSEBITS,
 	    (unsigned)pmp->pm_inusemap[cn / N_INUSEBITS]));
 	pmp->pm_inusemap[cn / N_INUSEBITS] |= 1U << (cn % N_INUSEBITS);
 	KASSERT(pmp->pm_freeclustercount > 0, ("usemap_alloc: too little"));
 	pmp->pm_freeclustercount--;
 	pmp->pm_flags |= MSDOSFS_FSIMOD;
 }
 
 static int
 usemap_free(struct msdosfsmount *pmp, u_long cn)
 {
 
 	MSDOSFS_ASSERT_MP_LOCKED(pmp);
 
 	KASSERT(cn <= pmp->pm_maxcluster, ("cn too large %lu %lu", cn,
 	    pmp->pm_maxcluster));
 	KASSERT((pmp->pm_flags & MSDOSFSMNT_RONLY) == 0,
 	    ("usemap_free on ro msdosfs mount"));
 	if ((pmp->pm_inusemap[cn / N_INUSEBITS] &
 	    (1U << (cn % N_INUSEBITS))) == 0) {
 		printf("%s: Freeing unused sector %ld %ld %x\n",
 		    pmp->pm_mountp->mnt_stat.f_mntonname, cn, cn % N_INUSEBITS,
 		    (unsigned)pmp->pm_inusemap[cn / N_INUSEBITS]);
+		msdosfs_integrity_error(pmp);
 		return (EINTEGRITY);
 	}
 	pmp->pm_freeclustercount++;
 	pmp->pm_flags |= MSDOSFS_FSIMOD;
 	pmp->pm_inusemap[cn / N_INUSEBITS] &= ~(1U << (cn % N_INUSEBITS));
 	return (0);
 }
 
 void
 clusterfree(struct msdosfsmount *pmp, u_long cluster)
 {
 	int error;
 	u_long oldcn;
 
 	error = fatentry(FAT_GET_AND_SET, pmp, cluster, &oldcn, MSDOSFSFREE);
 	if (error != 0)
 		return;
 	/*
 	 * If the cluster was successfully marked free, then update
 	 * the count of free clusters, and turn off the "allocated"
 	 * bit in the "in use" cluster bit map.
 	 */
 	MSDOSFS_LOCK_MP(pmp);
 	error = usemap_free(pmp, cluster);
 	MSDOSFS_UNLOCK_MP(pmp);
 }
 
 /*
  * Get or Set or 'Get and Set' the cluster'th entry in the FAT.
  *
  * function	- whether to get or set a FAT entry
  * pmp		- address of the msdosfsmount structure for the filesystem
  *		  whose FAT is to be manipulated.
  * cn		- which cluster is of interest
  * oldcontents	- address of a word that is to receive the contents of the
  *		  cluster'th entry if this is a get function
  * newcontents	- the new value to be written into the cluster'th element of
  *		  the FAT if this is a set function.
  *
  * This function can also be used to free a cluster by setting the FAT entry
  * for a cluster to 0.
  *
  * All copies of the FAT are updated if this is a set function. NOTE: If
  * fatentry() marks a cluster as free it does not update the inusemap in
  * the msdosfsmount structure. This is left to the caller.
  */
 int
 fatentry(int function, struct msdosfsmount *pmp, u_long cn, u_long *oldcontents,
     u_long newcontents)
 {
 	int error;
 	u_long readcn;
 	u_long bn, bo, bsize, byteoffset;
 	struct buf *bp;
 
 #ifdef	MSDOSFS_DEBUG
 	printf("fatentry(func %d, pmp %p, clust %lu, oldcon %p, newcon %lx)\n",
 	    function, pmp, cn, oldcontents, newcontents);
 #endif
 
 #ifdef DIAGNOSTIC
 	/*
 	 * Be sure they asked us to do something.
 	 */
 	if ((function & (FAT_SET | FAT_GET)) == 0) {
 #ifdef MSDOSFS_DEBUG
 		printf("fatentry(): function code doesn't specify get or set\n");
 #endif
 		return (EINVAL);
 	}
 
 	/*
 	 * If they asked us to return a cluster number but didn't tell us
 	 * where to put it, give them an error.
 	 */
 	if ((function & FAT_GET) && oldcontents == NULL) {
 #ifdef MSDOSFS_DEBUG
 		printf("fatentry(): get function with no place to put result\n");
 #endif
 		return (EINVAL);
 	}
 #endif
 
 	/*
 	 * Be sure the requested cluster is in the filesystem.
 	 */
 	if (cn < CLUST_FIRST || cn > pmp->pm_maxcluster)
 		return (EINVAL);
 
 	byteoffset = FATOFS(pmp, cn);
 	fatblock(pmp, byteoffset, &bn, &bsize, &bo);
 	error = bread(pmp->pm_devvp, bn, bsize, NOCRED, &bp);
 	if (error) {
 		return (error);
 	}
 
 	if (function & FAT_GET) {
 		if (FAT32(pmp))
 			readcn = getulong(bp->b_data + bo);
 		else
 			readcn = getushort(bp->b_data + bo);
 		if (FAT12(pmp) & (cn & 1))
 			readcn >>= 4;
 		readcn &= pmp->pm_fatmask;
 		/* map reserved FAT entries to same values for all FATs */
 		if ((readcn | ~pmp->pm_fatmask) >= CLUST_RSRVD)
 			readcn |= ~pmp->pm_fatmask;
 		*oldcontents = readcn;
 	}
 	if (function & FAT_SET) {
 		switch (pmp->pm_fatmask) {
 		case FAT12_MASK:
 			readcn = getushort(bp->b_data + bo);
 			if (cn & 1) {
 				readcn &= 0x000f;
 				readcn |= newcontents << 4;
 			} else {
 				readcn &= 0xf000;
 				readcn |= newcontents & 0xfff;
 			}
 			putushort(bp->b_data + bo, readcn);
 			break;
 		case FAT16_MASK:
 			putushort(bp->b_data + bo, newcontents);
 			break;
 		case FAT32_MASK:
 			/*
 			 * According to spec we have to retain the
 			 * high order bits of the FAT entry.
 			 */
 			readcn = getulong(bp->b_data + bo);
 			readcn &= ~FAT32_MASK;
 			readcn |= newcontents & FAT32_MASK;
 			putulong(bp->b_data + bo, readcn);
 			break;
 		}
 		updatefats(pmp, bp, bn);
 		bp = NULL;
 		pmp->pm_fmod = 1;
 	}
 	if (bp)
 		brelse(bp);
 	return (0);
 }
 
 /*
  * Update a contiguous cluster chain
  *
  * pmp	    - mount point
  * start    - first cluster of chain
  * count    - number of clusters in chain
  * fillwith - what to write into FAT entry of last cluster
  */
 static int
 fatchain(struct msdosfsmount *pmp, u_long start, u_long count, u_long fillwith)
 {
 	int error;
 	u_long bn, bo, bsize, byteoffset, readcn, newc;
 	struct buf *bp;
 
 #ifdef MSDOSFS_DEBUG
 	printf("fatchain(pmp %p, start %lu, count %lu, fillwith %lx)\n",
 	    pmp, start, count, fillwith);
 #endif
 	/*
 	 * Be sure the clusters are in the filesystem.
 	 */
 	if (start < CLUST_FIRST || start + count - 1 > pmp->pm_maxcluster)
 		return (EINVAL);
 
 	while (count > 0) {
 		byteoffset = FATOFS(pmp, start);
 		fatblock(pmp, byteoffset, &bn, &bsize, &bo);
 		error = bread(pmp->pm_devvp, bn, bsize, NOCRED, &bp);
 		if (error) {
 			return (error);
 		}
 		while (count > 0) {
 			start++;
 			newc = --count > 0 ? start : fillwith;
 			switch (pmp->pm_fatmask) {
 			case FAT12_MASK:
 				readcn = getushort(bp->b_data + bo);
 				if (start & 1) {
 					readcn &= 0xf000;
 					readcn |= newc & 0xfff;
 				} else {
 					readcn &= 0x000f;
 					readcn |= newc << 4;
 				}
 				putushort(bp->b_data + bo, readcn);
 				bo++;
 				if (!(start & 1))
 					bo++;
 				break;
 			case FAT16_MASK:
 				putushort(bp->b_data + bo, newc);
 				bo += 2;
 				break;
 			case FAT32_MASK:
 				readcn = getulong(bp->b_data + bo);
 				readcn &= ~pmp->pm_fatmask;
 				readcn |= newc & pmp->pm_fatmask;
 				putulong(bp->b_data + bo, readcn);
 				bo += 4;
 				break;
 			}
 			if (bo >= bsize)
 				break;
 		}
 		updatefats(pmp, bp, bn);
 	}
 	pmp->pm_fmod = 1;
 	return (0);
 }
 
 /*
  * Check the length of a free cluster chain starting at start.
  *
  * pmp	 - mount point
  * start - start of chain
  * count - maximum interesting length
  */
 static int
 chainlength(struct msdosfsmount *pmp, u_long start, u_long count)
 {
 	u_long idx, max_idx;
 	u_int map;
 	u_long len;
 
 	MSDOSFS_ASSERT_MP_LOCKED(pmp);
 
 	if (start > pmp->pm_maxcluster)
 		return (0);
 	max_idx = pmp->pm_maxcluster / N_INUSEBITS;
 	idx = start / N_INUSEBITS;
 	start %= N_INUSEBITS;
 	map = pmp->pm_inusemap[idx];
 	map &= ~((1U << start) - 1);
 	if (map) {
 		len = ffs(map) - 1 - start;
 		len = MIN(len, count);
 		if (start + len > pmp->pm_maxcluster)
 			len = pmp->pm_maxcluster - start + 1;
 		return (len);
 	}
 	len = N_INUSEBITS - start;
 	if (len >= count) {
 		len = count;
 		if (start + len > pmp->pm_maxcluster)
 			len = pmp->pm_maxcluster - start + 1;
 		return (len);
 	}
 	while (++idx <= max_idx) {
 		if (len >= count)
 			break;
 		map = pmp->pm_inusemap[idx];
 		if (map) {
 			len += ffs(map) - 1;
 			break;
 		}
 		len += N_INUSEBITS;
 	}
 	len = MIN(len, count);
 	if (start + len > pmp->pm_maxcluster)
 		len = pmp->pm_maxcluster - start + 1;
 	return (len);
 }
 
 /*
  * Allocate contigous free clusters.
  *
  * pmp	      - mount point.
  * start      - start of cluster chain.
  * count      - number of clusters to allocate.
  * fillwith   - put this value into the FAT entry for the
  *		last allocated cluster.
  * retcluster - put the first allocated cluster's number here.
  * got	      - how many clusters were actually allocated.
  */
 static int
 chainalloc(struct msdosfsmount *pmp, u_long start, u_long count,
     u_long fillwith, u_long *retcluster, u_long *got)
 {
 	int error;
 	u_long cl, n;
 
 	MSDOSFS_ASSERT_MP_LOCKED(pmp);
 	KASSERT((pmp->pm_flags & MSDOSFSMNT_RONLY) == 0,
 	    ("chainalloc on ro msdosfs mount"));
 
 	for (cl = start, n = count; n-- > 0;)
 		usemap_alloc(pmp, cl++);
 	pmp->pm_nxtfree = start + count;
 	if (pmp->pm_nxtfree > pmp->pm_maxcluster)
 		pmp->pm_nxtfree = CLUST_FIRST;
 	pmp->pm_flags |= MSDOSFS_FSIMOD;
 	error = fatchain(pmp, start, count, fillwith);
 	if (error != 0) {
 		for (cl = start, n = count; n-- > 0;)
 			(void)usemap_free(pmp, cl++);
 		return (error);
 	}
 #ifdef MSDOSFS_DEBUG
 	printf("clusteralloc(): allocated cluster chain at %lu (%lu clusters)\n",
 	    start, count);
 #endif
 	if (retcluster)
 		*retcluster = start;
 	if (got)
 		*got = count;
 	return (0);
 }
 
 /*
  * Allocate contiguous free clusters.
  *
  * pmp	      - mount point.
  * start      - preferred start of cluster chain.
  * count      - number of clusters requested.
  * fillwith   - put this value into the FAT entry for the
  *		last allocated cluster.
  * retcluster - put the first allocated cluster's number here.
  * got	      - how many clusters were actually allocated.
  */
 int
 clusteralloc(struct msdosfsmount *pmp, u_long start, u_long count,
     u_long fillwith, u_long *retcluster, u_long *got)
 {
 	int error;
 
 	MSDOSFS_LOCK_MP(pmp);
 	error = clusteralloc1(pmp, start, count, fillwith, retcluster, got);
 	MSDOSFS_UNLOCK_MP(pmp);
 	return (error);
 }
 
 static int
 clusteralloc1(struct msdosfsmount *pmp, u_long start, u_long count,
     u_long fillwith, u_long *retcluster, u_long *got)
 {
 	u_long idx;
 	u_long len, newst, foundl, cn, l;
 	u_long foundcn = 0; /* XXX: foundcn could be used unititialized */
 	u_int map;
 
 	MSDOSFS_ASSERT_MP_LOCKED(pmp);
 
 #ifdef MSDOSFS_DEBUG
 	printf("clusteralloc(): find %lu clusters\n", count);
 #endif
 	if (start) {
 		if ((len = chainlength(pmp, start, count)) >= count)
 			return (chainalloc(pmp, start, count, fillwith, retcluster, got));
 	} else
 		len = 0;
 
 	newst = pmp->pm_nxtfree;
 	foundl = 0;
 
 	for (cn = newst; cn <= pmp->pm_maxcluster;) {
 		idx = cn / N_INUSEBITS;
 		map = pmp->pm_inusemap[idx];
 		map |= (1U << (cn % N_INUSEBITS)) - 1;
 		if (map != FULL_RUN) {
 			cn = idx * N_INUSEBITS + ffs(map ^ FULL_RUN) - 1;
 			if ((l = chainlength(pmp, cn, count)) >= count)
 				return (chainalloc(pmp, cn, count, fillwith, retcluster, got));
 			if (l > foundl) {
 				foundcn = cn;
 				foundl = l;
 			}
 			cn += l + 1;
 			continue;
 		}
 		cn += N_INUSEBITS - cn % N_INUSEBITS;
 	}
 	for (cn = 0; cn < newst;) {
 		idx = cn / N_INUSEBITS;
 		map = pmp->pm_inusemap[idx];
 		map |= (1U << (cn % N_INUSEBITS)) - 1;
 		if (map != FULL_RUN) {
 			cn = idx * N_INUSEBITS + ffs(map ^ FULL_RUN) - 1;
 			if ((l = chainlength(pmp, cn, count)) >= count)
 				return (chainalloc(pmp, cn, count, fillwith, retcluster, got));
 			if (l > foundl) {
 				foundcn = cn;
 				foundl = l;
 			}
 			cn += l + 1;
 			continue;
 		}
 		cn += N_INUSEBITS - cn % N_INUSEBITS;
 	}
 
 	if (!foundl)
 		return (ENOSPC);
 
 	if (len)
 		return (chainalloc(pmp, start, len, fillwith, retcluster, got));
 	else
 		return (chainalloc(pmp, foundcn, foundl, fillwith, retcluster, got));
 }
 
 /*
  * Free a chain of clusters.
  *
  * pmp		- address of the msdosfs mount structure for the filesystem
  *		  containing the cluster chain to be freed.
  * startcluster - number of the 1st cluster in the chain of clusters to be
  *		  freed.
  */
 int
 freeclusterchain(struct msdosfsmount *pmp, u_long cluster)
 {
 	int error;
 	struct buf *bp = NULL;
 	u_long bn, bo, bsize, byteoffset;
 	u_long readcn, lbn = -1;
 
 	MSDOSFS_LOCK_MP(pmp);
 	while (cluster >= CLUST_FIRST && cluster <= pmp->pm_maxcluster) {
 		byteoffset = FATOFS(pmp, cluster);
 		fatblock(pmp, byteoffset, &bn, &bsize, &bo);
 		if (lbn != bn) {
 			if (bp)
 				updatefats(pmp, bp, lbn);
 			error = bread(pmp->pm_devvp, bn, bsize, NOCRED, &bp);
 			if (error) {
 				MSDOSFS_UNLOCK_MP(pmp);
 				return (error);
 			}
 			lbn = bn;
 		}
 		error = usemap_free(pmp, cluster);
 		if (error != 0) {
 			updatefats(pmp, bp, lbn);
 			MSDOSFS_UNLOCK_MP(pmp);
 			return (error);
 		}
 		switch (pmp->pm_fatmask) {
 		case FAT12_MASK:
 			readcn = getushort(bp->b_data + bo);
 			if (cluster & 1) {
 				cluster = readcn >> 4;
 				readcn &= 0x000f;
 				readcn |= MSDOSFSFREE << 4;
 			} else {
 				cluster = readcn;
 				readcn &= 0xf000;
 				readcn |= MSDOSFSFREE & 0xfff;
 			}
 			putushort(bp->b_data + bo, readcn);
 			break;
 		case FAT16_MASK:
 			cluster = getushort(bp->b_data + bo);
 			putushort(bp->b_data + bo, MSDOSFSFREE);
 			break;
 		case FAT32_MASK:
 			cluster = getulong(bp->b_data + bo);
 			putulong(bp->b_data + bo,
 				 (MSDOSFSFREE & FAT32_MASK) | (cluster & ~FAT32_MASK));
 			break;
 		}
 		cluster &= pmp->pm_fatmask;
 		if ((cluster | ~pmp->pm_fatmask) >= CLUST_RSRVD)
 			cluster |= pmp->pm_fatmask;
 	}
 	if (bp)
 		updatefats(pmp, bp, bn);
 	MSDOSFS_UNLOCK_MP(pmp);
 	return (0);
 }
 
 /*
  * Read in FAT blocks looking for free clusters. For every free cluster
  * found turn off its corresponding bit in the pm_inusemap.
  */
 int
 fillinusemap(struct msdosfsmount *pmp)
 {
 	struct buf *bp;
 	u_long bn, bo, bsize, byteoffset, cn, readcn;
 	int error;
 
 	MSDOSFS_ASSERT_MP_LOCKED(pmp);
 	bp = NULL;
 
 	/*
 	 * Mark all clusters in use, we mark the free ones in the FAT scan
 	 * loop further down.
 	 */
 	for (cn = 0; cn < (pmp->pm_maxcluster + N_INUSEBITS) / N_INUSEBITS; cn++)
 		pmp->pm_inusemap[cn] = FULL_RUN;
 
 	/*
 	 * Figure how many free clusters are in the filesystem by ripping
 	 * through the FAT counting the number of entries whose content is
 	 * zero.  These represent free clusters.
 	 */
 	pmp->pm_freeclustercount = 0;
 	for (cn = 0; cn <= pmp->pm_maxcluster; cn++) {
 		byteoffset = FATOFS(pmp, cn);
 		bo = byteoffset % pmp->pm_fatblocksize;
 		if (bo == 0) {
 			/* Read new FAT block */
 			if (bp != NULL)
 				brelse(bp);
 			fatblock(pmp, byteoffset, &bn, &bsize, NULL);
 			error = bread(pmp->pm_devvp, bn, bsize, NOCRED, &bp);
 			if (error != 0)
 				return (error);
 		}
 		if (FAT32(pmp))
 			readcn = getulong(bp->b_data + bo);
 		else
 			readcn = getushort(bp->b_data + bo);
 		if (FAT12(pmp) && (cn & 1))
 			readcn >>= 4;
 		readcn &= pmp->pm_fatmask;
 
 		/*
 		 * Check if the FAT ID matches the BPB's media descriptor and
 		 * all other bits are set to 1.
 		 */
 		if (cn == 0 && readcn != ((pmp->pm_fatmask & 0xffffff00) |
 		    pmp->pm_bpb.bpbMedia)) {
 #ifdef MSDOSFS_DEBUG
 			printf("mountmsdosfs(): Media descriptor in BPB"
 			    "does not match FAT ID\n");
 #endif
 			brelse(bp);
 			return (EINVAL);
 		} else if (readcn == CLUST_FREE) {
 			error = usemap_free(pmp, cn);
 			if (error != 0) {
 				brelse(bp);
 				return (error);
 			}
 		}
 	}
 	if (bp != NULL)
 		brelse(bp);
 
 	for (cn = pmp->pm_maxcluster + 1; cn < (pmp->pm_maxcluster +
 	    N_INUSEBITS) / N_INUSEBITS; cn++)
 		pmp->pm_inusemap[cn / N_INUSEBITS] |= 1U << (cn % N_INUSEBITS);
 
 	return (0);
 }
 
 /*
  * Allocate a new cluster and chain it onto the end of the file.
  *
  * dep	 - the file to extend
  * count - number of clusters to allocate
  * bpp	 - where to return the address of the buf header for the first new
  *	   file block
  * ncp	 - where to put cluster number of the first newly allocated cluster
  *	   If this pointer is 0, do not return the cluster number.
  * flags - see fat.h
  *
  * NOTE: This function is not responsible for turning on the DE_UPDATE bit of
  * the de_flag field of the denode and it does not change the de_FileSize
  * field.  This is left for the caller to do.
  */
 int
 extendfile(struct denode *dep, u_long count, struct buf **bpp, u_long *ncp,
     int flags)
 {
 	int error;
 	u_long frcn;
 	u_long cn, got;
 	struct msdosfsmount *pmp = dep->de_pmp;
 	struct buf *bp;
 	daddr_t blkno;
 
 	/*
 	 * Don't try to extend the root directory
 	 */
 	if (dep->de_StartCluster == MSDOSFSROOT
 	    && (dep->de_Attributes & ATTR_DIRECTORY)) {
 #ifdef MSDOSFS_DEBUG
 		printf("extendfile(): attempt to extend root directory\n");
 #endif
 		return (ENOSPC);
 	}
 
 	/*
 	 * If the "file's last cluster" cache entry is empty, and the file
 	 * is not empty, then fill the cache entry by calling pcbmap().
 	 */
 	if (dep->de_fc[FC_LASTFC].fc_frcn == FCE_EMPTY &&
 	    dep->de_StartCluster != 0) {
 		error = pcbmap(dep, 0xffff, 0, &cn, 0);
 		/* we expect it to return E2BIG */
 		if (error != E2BIG)
 			return (error);
 	}
 
 	dep->de_fc[FC_NEXTTOLASTFC].fc_frcn =
 	    dep->de_fc[FC_LASTFC].fc_frcn;
 	dep->de_fc[FC_NEXTTOLASTFC].fc_fsrcn =
 	    dep->de_fc[FC_LASTFC].fc_fsrcn;
 	while (count > 0) {
 		/*
 		 * Allocate a new cluster chain and cat onto the end of the
 		 * file.  If the file is empty we make de_StartCluster point
 		 * to the new block.  Note that de_StartCluster being 0 is
 		 * sufficient to be sure the file is empty since we exclude
 		 * attempts to extend the root directory above, and the root
 		 * dir is the only file with a startcluster of 0 that has
 		 * blocks allocated (sort of).
 		 */
 		if (dep->de_StartCluster == 0)
 			cn = 0;
 		else
 			cn = dep->de_fc[FC_LASTFC].fc_fsrcn + 1;
 		error = clusteralloc(pmp, cn, count, CLUST_EOFE, &cn, &got);
 		if (error)
 			return (error);
 
 		count -= got;
 
 		/*
 		 * Give them the filesystem relative cluster number if they want
 		 * it.
 		 */
 		if (ncp) {
 			*ncp = cn;
 			ncp = NULL;
 		}
 
 		if (dep->de_StartCluster == 0) {
 			dep->de_StartCluster = cn;
 			frcn = 0;
 		} else {
 			error = fatentry(FAT_SET, pmp,
 					 dep->de_fc[FC_LASTFC].fc_fsrcn,
 					 0, cn);
 			if (error) {
 				clusterfree(pmp, cn);
 				return (error);
 			}
 			frcn = dep->de_fc[FC_LASTFC].fc_frcn + 1;
 		}
 
 		/*
 		 * Update the "last cluster of the file" entry in the
 		 * denode's FAT cache.
 		 */
 		fc_setcache(dep, FC_LASTFC, frcn + got - 1, cn + got - 1);
 
 		if (flags & DE_CLEAR) {
 			while (got-- > 0) {
 				/*
 				 * Get the buf header for the new block of the file.
 				 */
 				if (dep->de_Attributes & ATTR_DIRECTORY)
 					bp = getblk(pmp->pm_devvp,
 					    cntobn(pmp, cn++),
 					    pmp->pm_bpcluster, 0, 0, 0);
 				else {
 					bp = getblk(DETOV(dep),
 					    frcn++,
 					    pmp->pm_bpcluster, 0, 0, 0);
 					/*
 					 * Do the bmap now, as in msdosfs_write
 					 */
 					if (pcbmap(dep,
 					    bp->b_lblkno,
 					    &blkno, 0, 0))
 						bp->b_blkno = -1;
 					if (bp->b_blkno == -1)
 						panic("extendfile: pcbmap");
 					else
 						bp->b_blkno = blkno;
 				}
 				clrbuf(bp);
 				if (bpp) {
 					*bpp = bp;
 					bpp = NULL;
 				} else {
 					bdwrite(bp);
 				}
 				if (vm_page_count_severe() ||
 				    buf_dirty_count_severe())
 					vn_fsync_buf(DETOV(dep), MNT_WAIT);
 			}
 		}
 	}
 
 	return (0);
 }
 
 /*-
  * Routine to mark a FAT16 or FAT32 volume as "clean" or "dirty" by
  * manipulating the upper bit of the FAT entry for cluster 1.  Note that
  * this bit is not defined for FAT12 volumes, which are always assumed to
  * be clean.
  *
  * The fatentry() routine only works on cluster numbers that a file could
  * occupy, so it won't manipulate the entry for cluster 1.  So we have to do
  * it here.  The code was stolen from fatentry() and tailored for cluster 1.
  *
  * Inputs:
  *	pmp	The MS-DOS volume to mark
  *	dirty	Non-zero if the volume should be marked dirty; zero if it
  *		should be marked clean
  *
  * Result:
  *	0	Success
  *	EROFS	Volume is read-only
  *	?	(other errors from called routines)
  */
 int
 markvoldirty_upgrade(struct msdosfsmount *pmp, bool dirty, bool rw_upgrade)
 {
 	struct buf *bp;
 	u_long bn, bo, bsize, byteoffset, fatval;
 	int error;
 
 	/*
 	 * FAT12 does not support a "clean" bit, so don't do anything for
 	 * FAT12.
 	 */
 	if (FAT12(pmp))
 		return (0);
 
 	/*
 	 * Can't change the bit on a read-only filesystem, except as part of
 	 * ro->rw upgrade.
 	 */
 	if ((pmp->pm_flags & MSDOSFSMNT_RONLY) != 0 && !rw_upgrade)
 		return (EROFS);
 
 	/*
 	 * Fetch the block containing the FAT entry.  It is given by the
 	 * pseudo-cluster 1.
 	 */
 	byteoffset = FATOFS(pmp, 1);
 	fatblock(pmp, byteoffset, &bn, &bsize, &bo);
 	error = bread(pmp->pm_devvp, bn, bsize, NOCRED, &bp);
 	if (error)
 		return (error);
 
 	/*
 	 * Get the current value of the FAT entry and set/clear the relevant
 	 * bit.  Dirty means clear the "clean" bit; clean means set the
 	 * "clean" bit.
 	 */
 	if (FAT32(pmp)) {
 		/* FAT32 uses bit 27. */
 		fatval = getulong(&bp->b_data[bo]);
 		if (dirty)
 			fatval &= 0xF7FFFFFF;
 		else
 			fatval |= 0x08000000;
 		putulong(&bp->b_data[bo], fatval);
 	} else {
 		/* Must be FAT16; use bit 15. */
 		fatval = getushort(&bp->b_data[bo]);
 		if (dirty)
 			fatval &= 0x7FFF;
 		else
 			fatval |= 0x8000;
 		putushort(&bp->b_data[bo], fatval);
 	}
 
 	/*
 	 * The concern here is that a devvp may be readonly, without reporting
 	 * itself as such through the usual channels.  In that case, we'd like
 	 * it if attempting to mount msdosfs rw didn't panic the system.
 	 *
 	 * markvoldirty is invoked as the first write on backing devvps when
 	 * either msdosfs is mounted for the first time, or a ro mount is
 	 * upgraded to rw.
 	 *
 	 * In either event, if a write error occurs dirtying the volume:
 	 *   - No user data has been permitted to be written to cache yet.
 	 *   - We can abort the high-level operation (mount, or ro->rw) safely.
 	 *   - We don't derive any benefit from leaving a zombie dirty buf in
 	 *   the cache that can not be cleaned or evicted.
 	 *
 	 * So, mark B_INVALONERR to have bwrite() -> brelse() detect that
 	 * condition and force-invalidate our write to the block if it occurs.
 	 *
 	 * PR 210316 provides more context on the discovery and diagnosis of
 	 * the problem, as well as earlier attempts to solve it.
 	 */
 	bp->b_flags |= B_INVALONERR;
 
 	/* Write out the modified FAT block synchronously. */
 	return (bwrite(bp));
 }
diff --git a/sys/fs/msdosfs/msdosfs_lookup.c b/sys/fs/msdosfs/msdosfs_lookup.c
index e30866b5bedd..291f923d79fe 100644
--- a/sys/fs/msdosfs/msdosfs_lookup.c
+++ b/sys/fs/msdosfs/msdosfs_lookup.c
@@ -1,1096 +1,1100 @@
 /* $FreeBSD$ */
 /*	$NetBSD: msdosfs_lookup.c,v 1.37 1997/11/17 15:36:54 ws Exp $	*/
 
 /*-
  * SPDX-License-Identifier: BSD-4-Clause
  *
  * Copyright (C) 1994, 1995, 1997 Wolfgang Solfrank.
  * Copyright (C) 1994, 1995, 1997 TooLs GmbH.
  * All rights reserved.
  * Original code by Paul Popelka (paulp@uts.amdahl.com) (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 (paulp@uts.amdahl.com)
  *
  * 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/param.h>
 #include <sys/systm.h>
 #include <sys/buf.h>
 #include <sys/mount.h>
 #include <sys/namei.h>
 #include <sys/vnode.h>
 
 #include <fs/msdosfs/bpb.h>
 #include <fs/msdosfs/direntry.h>
 #include <fs/msdosfs/denode.h>
 #include <fs/msdosfs/fat.h>
 #include <fs/msdosfs/msdosfsmount.h>
 
 static int
 msdosfs_lookup_checker(struct msdosfsmount *pmp, struct vnode *dvp,
     struct denode *tdp, struct vnode **vpp)
 {
 	struct vnode *vp;
 
 	vp = DETOV(tdp);
 
 	/*
 	 * Lookup assumes that directory cannot be hardlinked.
 	 * Corrupted msdosfs filesystem could break this assumption.
 	 */
 	if (vp == dvp) {
 		vput(vp);
+		msdosfs_integrity_error(pmp);
 		*vpp = NULL;
 		return (EBADF);
 	}
 
 	*vpp = vp;
 	return (0);
 }
 
 int
 msdosfs_lookup(struct vop_cachedlookup_args *ap)
 {
 
 	return (msdosfs_lookup_ino(ap->a_dvp, ap->a_vpp, ap->a_cnp, NULL,
 	    NULL));
 }
 
 struct deget_dotdot {
 	u_long cluster;
 	int blkoff;
 };
 
 static int
 msdosfs_deget_dotdot(struct mount *mp, void *arg, int lkflags,
     struct vnode **rvp)
 {
 	struct deget_dotdot *dd_arg;
 	struct denode *rdp;
 	struct msdosfsmount *pmp;
 	int error;
 
 	pmp = VFSTOMSDOSFS(mp);
 	dd_arg = arg;
 	error = deget(pmp, dd_arg->cluster, dd_arg->blkoff,
 	    LK_EXCLUSIVE, &rdp);
 	if (error == 0)
 		*rvp = DETOV(rdp);
 	return (error);
 }
 
 /*
  * When we search a directory the blocks containing directory entries are
  * read and examined.  The directory entries contain information that would
  * normally be in the inode of a unix filesystem.  This means that some of
  * a directory's contents may also be in memory resident denodes (sort of
  * an inode).  This can cause problems if we are searching while some other
  * process is modifying a directory.  To prevent one process from accessing
  * incompletely modified directory information we depend upon being the
  * sole owner of a directory block.  bread/brelse provide this service.
  * This being the case, when a process modifies a directory it must first
  * acquire the disk block that contains the directory entry to be modified.
  * Then update the disk block and the denode, and then write the disk block
  * out to disk.  This way disk blocks containing directory entries and in
  * memory denode's will be in synch.
  */
 int
 msdosfs_lookup_ino(struct vnode *vdp, struct vnode **vpp, struct componentname
     *cnp, daddr_t *scnp, u_long *blkoffp)
 {
 	struct mbnambuf nb;
 	daddr_t bn;
 	int error;
 	int slotcount;
 	int slotoffset = 0;
 	int frcn;
 	u_long cluster;
 	u_long blkoff;
 	int diroff;
 	int blsize;
 	int isadir;		/* ~0 if found direntry is a directory	 */
 	daddr_t scn;		/* starting cluster number		 */
 	struct vnode *pdp;
 	struct denode *dp;
 	struct denode *tdp;
 	struct msdosfsmount *pmp;
 	struct buf *bp = NULL;
 	struct direntry *dep = NULL;
 	struct deget_dotdot dd_arg;
 	u_char dosfilename[12];
 	int flags = cnp->cn_flags;
 	int nameiop = cnp->cn_nameiop;
 	int unlen;
 	uint64_t inode1;
 
 	int wincnt = 1;
 	int chksum = -1, chksum_ok;
 	int olddos = 1;
 
 #ifdef MSDOSFS_DEBUG
 	printf("msdosfs_lookup(): looking for %s\n", cnp->cn_nameptr);
 #endif
 	dp = VTODE(vdp);
 	pmp = dp->de_pmp;
 #ifdef MSDOSFS_DEBUG
 	printf("msdosfs_lookup(): vdp %p, dp %p, Attr %02x\n",
 	    vdp, dp, dp->de_Attributes);
 #endif
 
  restart:
 	if (vpp != NULL)
 		*vpp = NULL;
 	/*
 	 * If they are going after the . or .. entry in the root directory,
 	 * they won't find it.  DOS filesystems don't have them in the root
 	 * directory.  So, we fake it. deget() is in on this scam too.
 	 */
 	if ((vdp->v_vflag & VV_ROOT) && cnp->cn_nameptr[0] == '.' &&
 	    (cnp->cn_namelen == 1 ||
 		(cnp->cn_namelen == 2 && cnp->cn_nameptr[1] == '.'))) {
 		isadir = ATTR_DIRECTORY;
 		scn = MSDOSFSROOT;
 #ifdef MSDOSFS_DEBUG
 		printf("msdosfs_lookup(): looking for . or .. in root directory\n");
 #endif
 		cluster = MSDOSFSROOT;
 		blkoff = MSDOSFSROOT_OFS;
 		goto foundroot;
 	}
 
 	switch (unix2dosfn((const u_char *)cnp->cn_nameptr, dosfilename,
 	    cnp->cn_namelen, 0, pmp)) {
 	case 0:
 		return (EINVAL);
 	case 1:
 		break;
 	case 2:
 		wincnt = winSlotCnt((const u_char *)cnp->cn_nameptr,
 		    cnp->cn_namelen, pmp) + 1;
 		break;
 	case 3:
 		olddos = 0;
 		wincnt = winSlotCnt((const u_char *)cnp->cn_nameptr,
 		    cnp->cn_namelen, pmp) + 1;
 		break;
 	}
 	if (pmp->pm_flags & MSDOSFSMNT_SHORTNAME) {
 		wincnt = 1;
 		olddos = 1;
 	}
 	unlen = winLenFixup(cnp->cn_nameptr, cnp->cn_namelen);
 
 	/*
 	 * Suppress search for slots unless creating
 	 * file and at end of pathname, in which case
 	 * we watch for a place to put the new file in
 	 * case it doesn't already exist.
 	 */
 	slotcount = wincnt;
 	if ((nameiop == CREATE || nameiop == RENAME) &&
 	    (flags & ISLASTCN))
 		slotcount = 0;
 
 #ifdef MSDOSFS_DEBUG
 	printf("msdosfs_lookup(): dos version of filename %s, length %ld\n",
 	    dosfilename, cnp->cn_namelen);
 #endif
 	/*
 	 * Search the directory pointed at by vdp for the name pointed at
 	 * by cnp->cn_nameptr.
 	 */
 	tdp = NULL;
 	mbnambuf_init(&nb);
 	/*
 	 * The outer loop ranges over the clusters that make up the
 	 * directory.  Note that the root directory is different from all
 	 * other directories.  It has a fixed number of blocks that are not
 	 * part of the pool of allocatable clusters.  So, we treat it a
 	 * little differently. The root directory starts at "cluster" 0.
 	 */
 	diroff = 0;
 	for (frcn = 0;; frcn++) {
 		error = pcbmap(dp, frcn, &bn, &cluster, &blsize);
 		if (error) {
 			if (error == E2BIG)
 				break;
 			return (error);
 		}
 		error = bread(pmp->pm_devvp, bn, blsize, NOCRED, &bp);
 		if (error) {
 			return (error);
 		}
 		for (blkoff = 0; blkoff < blsize;
 		     blkoff += sizeof(struct direntry),
 		     diroff += sizeof(struct direntry)) {
 			dep = (struct direntry *)(bp->b_data + blkoff);
 			/*
 			 * If the slot is empty and we are still looking
 			 * for an empty then remember this one.  If the
 			 * slot is not empty then check to see if it
 			 * matches what we are looking for.  If the slot
 			 * has never been filled with anything, then the
 			 * remainder of the directory has never been used,
 			 * so there is no point in searching it.
 			 */
 			if (dep->deName[0] == SLOT_EMPTY ||
 			    dep->deName[0] == SLOT_DELETED) {
 				/*
 				 * Drop memory of previous long matches
 				 */
 				chksum = -1;
 				mbnambuf_init(&nb);
 
 				if (slotcount < wincnt) {
 					slotcount++;
 					slotoffset = diroff;
 				}
 				if (dep->deName[0] == SLOT_EMPTY) {
 					brelse(bp);
 					goto notfound;
 				}
 			} else {
 				/*
 				 * If there wasn't enough space for our winentries,
 				 * forget about the empty space
 				 */
 				if (slotcount < wincnt)
 					slotcount = 0;
 
 				/*
 				 * Check for Win95 long filename entry
 				 */
 				if (dep->deAttributes == ATTR_WIN95) {
 					if (pmp->pm_flags & MSDOSFSMNT_SHORTNAME)
 						continue;
 
 					chksum = win2unixfn(&nb,
 					    (struct winentry *)dep, chksum,
 					    pmp);
 					continue;
 				}
 
 				chksum = winChkName(&nb,
 				    (const u_char *)cnp->cn_nameptr, unlen,
 				    chksum, pmp);
 				if (chksum == -2) {
 					chksum = -1;
 					continue;
 				}
 
 				/*
 				 * Ignore volume labels (anywhere, not just
 				 * the root directory).
 				 */
 				if (dep->deAttributes & ATTR_VOLUME) {
 					chksum = -1;
 					continue;
 				}
 
 				/*
 				 * Check for a checksum or name match
 				 */
 				chksum_ok = (chksum == winChksum(dep->deName));
 				if (!chksum_ok
 				    && (!olddos || bcmp(dosfilename, dep->deName, 11))) {
 					chksum = -1;
 					continue;
 				}
 #ifdef MSDOSFS_DEBUG
 				printf("msdosfs_lookup(): match blkoff %d, diroff %d\n",
 				    blkoff, diroff);
 #endif
 				/*
 				 * Remember where this directory
 				 * entry came from for whoever did
 				 * this lookup.
 				 */
 				dp->de_fndoffset = diroff;
 				if (chksum_ok && nameiop == RENAME) {
 					/*
 					 * Target had correct long name
 					 * directory entries, reuse them
 					 * as needed.
 					 */
 					dp->de_fndcnt = wincnt - 1;
 				} else {
 					/*
 					 * Long name directory entries
 					 * not present or corrupt, can only
 					 * reuse dos directory entry.
 					 */
 					dp->de_fndcnt = 0;
 				}
 
 				goto found;
 			}
 		}	/* for (blkoff = 0; .... */
 		/*
 		 * Release the buffer holding the directory cluster just
 		 * searched.
 		 */
 		brelse(bp);
 	}	/* for (frcn = 0; ; frcn++) */
 
 notfound:
 	/*
 	 * We hold no disk buffers at this point.
 	 */
 
 	/*
 	 * Fixup the slot description to point to the place where
 	 * we might put the new DOS direntry (putting the Win95
 	 * long name entries before that)
 	 */
 	if (!slotcount) {
 		slotcount = 1;
 		slotoffset = diroff;
 	}
 	if (wincnt > slotcount)
 		slotoffset += sizeof(struct direntry) * (wincnt - slotcount);
 
 	/*
 	 * If we get here we didn't find the entry we were looking for. But
 	 * that's ok if we are creating or renaming and are at the end of
 	 * the pathname and the directory hasn't been removed.
 	 */
 #ifdef MSDOSFS_DEBUG
 	printf("msdosfs_lookup(): op %d, refcnt %ld\n",
 	    nameiop, dp->de_refcnt);
 	printf("               slotcount %d, slotoffset %d\n",
 	       slotcount, slotoffset);
 #endif
 	if ((nameiop == CREATE || nameiop == RENAME) &&
 	    (flags & ISLASTCN) && dp->de_refcnt != 0) {
 		/*
 		 * Access for write is interpreted as allowing
 		 * creation of files in the directory.
 		 */
 		error = VOP_ACCESS(vdp, VWRITE, cnp->cn_cred, cnp->cn_thread);
 		if (error)
 			return (error);
 		/*
 		 * Return an indication of where the new directory
 		 * entry should be put.
 		 */
 		dp->de_fndoffset = slotoffset;
 		dp->de_fndcnt = wincnt - 1;
 
 		/*
 		 * We return with the directory locked, so that
 		 * the parameters we set up above will still be
 		 * valid if we actually decide to do a direnter().
 		 * We return ni_vp == NULL to indicate that the entry
 		 * does not currently exist; we leave a pointer to
 		 * the (locked) directory inode in ndp->ni_dvp.
 		 * The pathname buffer is saved so that the name
 		 * can be obtained later.
 		 *
 		 * NB - if the directory is unlocked, then this
 		 * information cannot be used.
 		 */
 		cnp->cn_flags |= SAVENAME;
 		return (EJUSTRETURN);
 	}
 #if 0
 	/*
 	 * Insert name into cache (as non-existent) if appropriate.
 	 *
 	 * XXX Negative caching is broken for msdosfs because the name
 	 * cache doesn't understand peculiarities such as case insensitivity
 	 * and 8.3 filenames.  Hence, it may not invalidate all negative
 	 * entries if a file with this name is later created.
 	 */
 	if ((cnp->cn_flags & MAKEENTRY) != 0)
 		cache_enter(vdp, *vpp, cnp);
 #endif
 	return (ENOENT);
 
 found:
 	/*
 	 * NOTE:  We still have the buffer with matched directory entry at
 	 * this point.
 	 */
 	isadir = dep->deAttributes & ATTR_DIRECTORY;
 	scn = getushort(dep->deStartCluster);
 	if (FAT32(pmp)) {
 		scn |= getushort(dep->deHighClust) << 16;
 		if (scn == pmp->pm_rootdirblk) {
 			/*
 			 * There should actually be 0 here.
 			 * Just ignore the error.
 			 */
 			scn = MSDOSFSROOT;
 		}
 	}
 
 	if (isadir) {
 		cluster = scn;
 		if (cluster == MSDOSFSROOT)
 			blkoff = MSDOSFSROOT_OFS;
 		else
 			blkoff = 0;
 	} else if (cluster == MSDOSFSROOT)
 		blkoff = diroff;
 
 	/*
 	 * Now release buf to allow deget to read the entry again.
 	 * Reserving it here and giving it to deget could result
 	 * in a deadlock.
 	 */
 	brelse(bp);
 	bp = NULL;
 
 foundroot:
 	/*
 	 * If we entered at foundroot, then we are looking for the . or ..
 	 * entry of the filesystems root directory.  isadir and scn were
 	 * setup before jumping here.  And, bp is already null.
 	 */
 	if (FAT32(pmp) && scn == MSDOSFSROOT)
 		scn = pmp->pm_rootdirblk;
 
 	if (scnp != NULL) {
 		*scnp = cluster;
 		*blkoffp = blkoff;
 		return (0);
 	}
 
 	/*
 	 * If deleting, and at end of pathname, return
 	 * parameters which can be used to remove file.
 	 */
 	if (nameiop == DELETE && (flags & ISLASTCN)) {
 		/*
 		 * Don't allow deleting the root.
 		 */
 		if (blkoff == MSDOSFSROOT_OFS)
 			return (EBUSY);
 
 		/*
 		 * Write access to directory required to delete files.
 		 */
 		error = VOP_ACCESS(vdp, VWRITE, cnp->cn_cred, cnp->cn_thread);
 		if (error)
 			return (error);
 
 		/*
 		 * Return pointer to current entry in dp->i_offset.
 		 * Save directory inode pointer in ndp->ni_dvp for dirremove().
 		 */
 		if (dp->de_StartCluster == scn && isadir) {	/* "." */
 			VREF(vdp);
 			*vpp = vdp;
 			return (0);
 		}
 		error = deget(pmp, cluster, blkoff, LK_EXCLUSIVE, &tdp);
 		if (error)
 			return (error);
 		return (msdosfs_lookup_checker(pmp, vdp, tdp, vpp));
 	}
 
 	/*
 	 * If rewriting (RENAME), return the inode and the
 	 * information required to rewrite the present directory
 	 * Must get inode of directory entry to verify it's a
 	 * regular file, or empty directory.
 	 */
 	if (nameiop == RENAME && (flags & ISLASTCN)) {
 		if (blkoff == MSDOSFSROOT_OFS)
 			return (EBUSY);
 
 		error = VOP_ACCESS(vdp, VWRITE, cnp->cn_cred, cnp->cn_thread);
 		if (error)
 			return (error);
 
 		/*
 		 * Careful about locking second inode.
 		 * This can only occur if the target is ".".
 		 */
 		if (dp->de_StartCluster == scn && isadir)
 			return (EISDIR);
 
 		if ((error = deget(pmp, cluster, blkoff, LK_EXCLUSIVE,
 		    &tdp)) != 0)
 			return (error);
 		if ((error = msdosfs_lookup_checker(pmp, vdp, tdp, vpp))
 		    != 0)
 			return (error);
 		cnp->cn_flags |= SAVENAME;
 		return (0);
 	}
 
 	/*
 	 * Step through the translation in the name.  We do not `vput' the
 	 * directory because we may need it again if a symbolic link
 	 * is relative to the current directory.  Instead we save it
 	 * unlocked as "pdp".  We must get the target inode before unlocking
 	 * the directory to insure that the inode will not be removed
 	 * before we get it.  We prevent deadlock by always fetching
 	 * inodes from the root, moving down the directory tree. Thus
 	 * when following backward pointers ".." we must unlock the
 	 * parent directory before getting the requested directory.
 	 */
 	pdp = vdp;
 	if (flags & ISDOTDOT) {
 		dd_arg.cluster = cluster;
 		dd_arg.blkoff = blkoff;
 		error = vn_vget_ino_gen(vdp, msdosfs_deget_dotdot,
 		    &dd_arg, cnp->cn_lkflags, vpp);
 		if (error != 0) {
 			*vpp = NULL;
 			return (error);
 		}
 		/*
 		 * Recheck that ".." still points to the inode we
 		 * looked up before pdp lock was dropped.
 		 */
 		error = msdosfs_lookup_ino(pdp, NULL, cnp, &scn, &blkoff);
 		if (error) {
 			vput(*vpp);
 			*vpp = NULL;
 			return (error);
 		}
 		if (FAT32(pmp) && scn == MSDOSFSROOT)
 			scn = pmp->pm_rootdirblk;
 		inode1 = scn * pmp->pm_bpcluster + blkoff;
 		if (VTODE(*vpp)->de_inode != inode1) {
 			vput(*vpp);
 			goto restart;
 		}
-		return (msdosfs_lookup_checker(pmp, vdp, VTODE(*vpp), vpp));
+		error = msdosfs_lookup_checker(pmp, vdp, VTODE(*vpp), vpp);
+		if (error != 0)
+			return (error);
 	} else if (dp->de_StartCluster == scn && isadir) {
 		if (cnp->cn_namelen != 1 || cnp->cn_nameptr[0] != '.') {
 			/* fs is corrupted, non-dot lookup returned dvp */
+			msdosfs_integrity_error(pmp);
 			return (EBADF);
 		}
 		VREF(vdp);	/* we want ourself, ie "." */
 		*vpp = vdp;
 	} else {
 		if ((error = deget(pmp, cluster, blkoff, LK_EXCLUSIVE,
 		    &tdp)) != 0)
 			return (error);
 		if ((error = msdosfs_lookup_checker(pmp, vdp, tdp, vpp)) != 0)
 			return (error);
 	}
 
 	/*
 	 * Insert name into cache if appropriate.
 	 */
 	if (cnp->cn_flags & MAKEENTRY)
 		cache_enter(vdp, *vpp, cnp);
 	return (0);
 }
 
 /*
  * dep  - directory entry to copy into the directory
  * ddep - directory to add to
  * depp - return the address of the denode for the created directory entry
  *	  if depp != 0
  * cnp  - componentname needed for Win95 long filenames
  */
 int
 createde(struct denode *dep, struct denode *ddep, struct denode **depp,
     struct componentname *cnp)
 {
 	int error;
 	u_long dirclust, diroffset;
 	struct direntry *ndep;
 	struct msdosfsmount *pmp = ddep->de_pmp;
 	struct buf *bp;
 	daddr_t bn;
 	int blsize;
 
 #ifdef MSDOSFS_DEBUG
 	printf("createde(dep %p, ddep %p, depp %p, cnp %p)\n",
 	    dep, ddep, depp, cnp);
 #endif
 
 	/*
 	 * If no space left in the directory then allocate another cluster
 	 * and chain it onto the end of the file.  There is one exception
 	 * to this.  That is, if the root directory has no more space it
 	 * can NOT be expanded.  extendfile() checks for and fails attempts
 	 * to extend the root directory.  We just return an error in that
 	 * case.
 	 */
 	if (ddep->de_fndoffset >= ddep->de_FileSize) {
 		diroffset = ddep->de_fndoffset + sizeof(struct direntry)
 		    - ddep->de_FileSize;
 		dirclust = de_clcount(pmp, diroffset);
 		error = extendfile(ddep, dirclust, 0, 0, DE_CLEAR);
 		if (error) {
 			(void)detrunc(ddep, ddep->de_FileSize, 0, NOCRED);
 			return error;
 		}
 
 		/*
 		 * Update the size of the directory
 		 */
 		ddep->de_FileSize += de_cn2off(pmp, dirclust);
 	}
 
 	/*
 	 * We just read in the cluster with space.  Copy the new directory
 	 * entry in.  Then write it to disk. NOTE:  DOS directories
 	 * do not get smaller as clusters are emptied.
 	 */
 	error = pcbmap(ddep, de_cluster(pmp, ddep->de_fndoffset),
 		       &bn, &dirclust, &blsize);
 	if (error)
 		return error;
 	diroffset = ddep->de_fndoffset;
 	if (dirclust != MSDOSFSROOT)
 		diroffset &= pmp->pm_crbomask;
 	if ((error = bread(pmp->pm_devvp, bn, blsize, NOCRED, &bp)) != 0) {
 		brelse(bp);
 		return error;
 	}
 	ndep = bptoep(pmp, bp, ddep->de_fndoffset);
 
 	DE_EXTERNALIZE(ndep, dep);
 
 	/*
 	 * Now write the Win95 long name
 	 */
 	if (ddep->de_fndcnt > 0) {
 		uint8_t chksum = winChksum(ndep->deName);
 		const u_char *un = (const u_char *)cnp->cn_nameptr;
 		int unlen = cnp->cn_namelen;
 		int cnt = 1;
 
 		while (--ddep->de_fndcnt >= 0) {
 			if (!(ddep->de_fndoffset & pmp->pm_crbomask)) {
 				if (DOINGASYNC(DETOV(ddep)))
 					bdwrite(bp);
 				else if ((error = bwrite(bp)) != 0)
 					return error;
 
 				ddep->de_fndoffset -= sizeof(struct direntry);
 				error = pcbmap(ddep,
 					       de_cluster(pmp,
 							  ddep->de_fndoffset),
 					       &bn, 0, &blsize);
 				if (error)
 					return error;
 
 				error = bread(pmp->pm_devvp, bn, blsize,
 					      NOCRED, &bp);
 				if (error) {
 					return error;
 				}
 				ndep = bptoep(pmp, bp, ddep->de_fndoffset);
 			} else {
 				ndep--;
 				ddep->de_fndoffset -= sizeof(struct direntry);
 			}
 			if (!unix2winfn(un, unlen, (struct winentry *)ndep,
 					cnt++, chksum, pmp))
 				break;
 		}
 	}
 
 	if (DOINGASYNC(DETOV(ddep)))
 		bdwrite(bp);
 	else if ((error = bwrite(bp)) != 0)
 		return error;
 
 	/*
 	 * If they want us to return with the denode gotten.
 	 */
 	if (depp) {
 		if (dep->de_Attributes & ATTR_DIRECTORY) {
 			dirclust = dep->de_StartCluster;
 			if (FAT32(pmp) && dirclust == pmp->pm_rootdirblk)
 				dirclust = MSDOSFSROOT;
 			if (dirclust == MSDOSFSROOT)
 				diroffset = MSDOSFSROOT_OFS;
 			else
 				diroffset = 0;
 		}
 		return (deget(pmp, dirclust, diroffset, LK_EXCLUSIVE, depp));
 	}
 
 	return 0;
 }
 
 /*
  * Be sure a directory is empty except for "." and "..". Return 1 if empty,
  * return 0 if not empty or error.
  */
 int
 dosdirempty(struct denode *dep)
 {
 	int blsize;
 	int error;
 	u_long cn;
 	daddr_t bn;
 	struct buf *bp;
 	struct msdosfsmount *pmp = dep->de_pmp;
 	struct direntry *dentp;
 
 	/*
 	 * Since the filesize field in directory entries for a directory is
 	 * zero, we just have to feel our way through the directory until
 	 * we hit end of file.
 	 */
 	for (cn = 0;; cn++) {
 		if ((error = pcbmap(dep, cn, &bn, 0, &blsize)) != 0) {
 			if (error == E2BIG)
 				return (1);	/* it's empty */
 			return (0);
 		}
 		error = bread(pmp->pm_devvp, bn, blsize, NOCRED, &bp);
 		if (error) {
 			return (0);
 		}
 		for (dentp = (struct direntry *)bp->b_data;
 		     (char *)dentp < bp->b_data + blsize;
 		     dentp++) {
 			if (dentp->deName[0] != SLOT_DELETED &&
 			    (dentp->deAttributes & ATTR_VOLUME) == 0) {
 				/*
 				 * In dos directories an entry whose name
 				 * starts with SLOT_EMPTY (0) starts the
 				 * beginning of the unused part of the
 				 * directory, so we can just return that it
 				 * is empty.
 				 */
 				if (dentp->deName[0] == SLOT_EMPTY) {
 					brelse(bp);
 					return (1);
 				}
 				/*
 				 * Any names other than "." and ".." in a
 				 * directory mean it is not empty.
 				 */
 				if (bcmp(dentp->deName, ".          ", 11) &&
 				    bcmp(dentp->deName, "..         ", 11)) {
 					brelse(bp);
 #ifdef MSDOSFS_DEBUG
 					printf("dosdirempty(): entry found %02x, %02x\n",
 					    dentp->deName[0], dentp->deName[1]);
 #endif
 					return (0);	/* not empty */
 				}
 			}
 		}
 		brelse(bp);
 	}
 	/* NOTREACHED */
 }
 
 /*
  * Check to see if the directory described by target is in some
  * subdirectory of source.  This prevents something like the following from
  * succeeding and leaving a bunch or files and directories orphaned. mv
  * /a/b/c /a/b/c/d/e/f Where c and f are directories.
  *
  * source - the inode for /a/b/c
  * target - the inode for /a/b/c/d/e/f
  *
  * Returns 0 if target is NOT a subdirectory of source.
  * Otherwise returns a non-zero error number.
  */
 int
 doscheckpath(struct denode *source, struct denode *target, daddr_t *wait_scn)
 {
 	daddr_t scn;
 	struct msdosfsmount *pmp;
 	struct direntry *ep;
 	struct denode *dep;
 	struct buf *bp = NULL;
 	int error = 0;
 
 	*wait_scn = 0;
 
 	pmp = target->de_pmp;
 	lockmgr_assert(&pmp->pm_checkpath_lock, KA_XLOCKED);
 	KASSERT(pmp == source->de_pmp,
 	    ("doscheckpath: source and target on different filesystems"));
 
 	if ((target->de_Attributes & ATTR_DIRECTORY) == 0 ||
 	    (source->de_Attributes & ATTR_DIRECTORY) == 0)
 		return (ENOTDIR);
 
 	if (target->de_StartCluster == source->de_StartCluster)
 		return (EEXIST);
 
 	if (target->de_StartCluster == MSDOSFSROOT ||
 	    (FAT32(pmp) && target->de_StartCluster == pmp->pm_rootdirblk))
 		return (0);
 
 	dep = target;
 	vget(DETOV(dep), LK_EXCLUSIVE);
 	for (;;) {
 		if ((dep->de_Attributes & ATTR_DIRECTORY) == 0) {
 			error = ENOTDIR;
 			break;
 		}
 		scn = dep->de_StartCluster;
 		error = bread(pmp->pm_devvp, cntobn(pmp, scn),
 		    pmp->pm_bpcluster, NOCRED, &bp);
 		if (error != 0)
 			break;
 
 		ep = (struct direntry *)bp->b_data + 1;
 		if ((ep->deAttributes & ATTR_DIRECTORY) == 0 ||
 		    bcmp(ep->deName, "..         ", 11) != 0) {
 			error = ENOTDIR;
 			brelse(bp);
 			break;
 		}
 
 		scn = getushort(ep->deStartCluster);
 		if (FAT32(pmp))
 			scn |= getushort(ep->deHighClust) << 16;
 		brelse(bp);
 
 		if (scn == source->de_StartCluster) {
 			error = EINVAL;
 			break;
 		}
 		if (scn == MSDOSFSROOT)
 			break;
 		if (FAT32(pmp) && scn == pmp->pm_rootdirblk) {
 			/*
 			 * scn should be 0 in this case,
 			 * but we silently ignore the error.
 			 */
 			break;
 		}
 
 		vput(DETOV(dep));
 		dep = NULL;
 		/* NOTE: deget() clears dep on error */
 		error = deget(pmp, scn, 0, LK_EXCLUSIVE | LK_NOWAIT, &dep);
 		if (error != 0) {
 			*wait_scn = scn;
 			break;
 		}
 	}
 #ifdef MSDOSFS_DEBUG
 	if (error == ENOTDIR)
 		printf("doscheckpath(): .. not a directory?\n");
 #endif
 	if (dep != NULL)
 		vput(DETOV(dep));
 	return (error);
 }
 
 /*
  * Read in the disk block containing the directory entry (dirclu, dirofs)
  * and return the address of the buf header, and the address of the
  * directory entry within the block.
  */
 int
 readep(struct msdosfsmount *pmp, u_long dirclust, u_long diroffset,
     struct buf **bpp, struct direntry **epp)
 {
 	int error;
 	daddr_t bn;
 	int blsize;
 
 	blsize = pmp->pm_bpcluster;
 	if (dirclust == MSDOSFSROOT
 	    && de_blk(pmp, diroffset + blsize) > pmp->pm_rootdirsize)
 		blsize = de_bn2off(pmp, pmp->pm_rootdirsize) & pmp->pm_crbomask;
 	bn = detobn(pmp, dirclust, diroffset);
 	if ((error = bread(pmp->pm_devvp, bn, blsize, NOCRED, bpp)) != 0) {
 		brelse(*bpp);
 		*bpp = NULL;
 		return (error);
 	}
 	if (epp)
 		*epp = bptoep(pmp, *bpp, diroffset);
 	return (0);
 }
 
 /*
  * Read in the disk block containing the directory entry dep came from and
  * return the address of the buf header, and the address of the directory
  * entry within the block.
  */
 int
 readde(struct denode *dep, struct buf **bpp, struct direntry **epp)
 {
 
 	return (readep(dep->de_pmp, dep->de_dirclust, dep->de_diroffset,
 	    bpp, epp));
 }
 
 /*
  * Remove a directory entry. At this point the file represented by the
  * directory entry to be removed is still full length until no one has it
  * open.  When the file no longer being used msdosfs_inactive() is called
  * and will truncate the file to 0 length.  When the vnode containing the
  * denode is needed for some other purpose by VFS it will call
  * msdosfs_reclaim() which will remove the denode from the denode cache.
  *
  * pdep	directory where the entry is removed
  * dep	file to be removed
  */
 int
 removede(struct denode *pdep, struct denode *dep)
 {
 	int error;
 	struct direntry *ep;
 	struct buf *bp;
 	daddr_t bn;
 	int blsize;
 	struct msdosfsmount *pmp = pdep->de_pmp;
 	u_long offset = pdep->de_fndoffset;
 
 #ifdef MSDOSFS_DEBUG
 	printf("removede(): filename %s, dep %p, offset %08lx\n",
 	    dep->de_Name, dep, offset);
 #endif
 
 	dep->de_refcnt--;
 	offset += sizeof(struct direntry);
 	do {
 		offset -= sizeof(struct direntry);
 		error = pcbmap(pdep, de_cluster(pmp, offset), &bn, 0, &blsize);
 		if (error)
 			return error;
 		error = bread(pmp->pm_devvp, bn, blsize, NOCRED, &bp);
 		if (error) {
 			return error;
 		}
 		ep = bptoep(pmp, bp, offset);
 		/*
 		 * Check whether, if we came here the second time, i.e.
 		 * when underflowing into the previous block, the last
 		 * entry in this block is a longfilename entry, too.
 		 */
 		if (ep->deAttributes != ATTR_WIN95
 		    && offset != pdep->de_fndoffset) {
 			brelse(bp);
 			break;
 		}
 		offset += sizeof(struct direntry);
 		while (1) {
 			/*
 			 * We are a bit aggressive here in that we delete any Win95
 			 * entries preceding this entry, not just the ones we "own".
 			 * Since these presumably aren't valid anyway,
 			 * there should be no harm.
 			 */
 			offset -= sizeof(struct direntry);
 			ep--->deName[0] = SLOT_DELETED;
 			if ((pmp->pm_flags & MSDOSFSMNT_NOWIN95)
 			    || !(offset & pmp->pm_crbomask)
 			    || ep->deAttributes != ATTR_WIN95)
 				break;
 		}
 		if (DOINGASYNC(DETOV(pdep)))
 			bdwrite(bp);
 		else if ((error = bwrite(bp)) != 0)
 			return error;
 	} while (!(pmp->pm_flags & MSDOSFSMNT_NOWIN95)
 	    && !(offset & pmp->pm_crbomask)
 	    && offset);
 	return 0;
 }
 
 /*
  * Create a unique DOS name in dvp
  */
 int
 uniqdosname(struct denode *dep, struct componentname *cnp, u_char *cp)
 {
 	struct msdosfsmount *pmp = dep->de_pmp;
 	struct direntry *dentp;
 	int gen;
 	int blsize;
 	u_long cn;
 	daddr_t bn;
 	struct buf *bp;
 	int error;
 
 	if (pmp->pm_flags & MSDOSFSMNT_SHORTNAME)
 		return (unix2dosfn((const u_char *)cnp->cn_nameptr, cp,
 		    cnp->cn_namelen, 0, pmp) ? 0 : EINVAL);
 
 	for (gen = 1;; gen++) {
 		/*
 		 * Generate DOS name with generation number
 		 */
 		if (!unix2dosfn((const u_char *)cnp->cn_nameptr, cp,
 		    cnp->cn_namelen, gen, pmp))
 			return gen == 1 ? EINVAL : EEXIST;
 
 		/*
 		 * Now look for a dir entry with this exact name
 		 */
 		for (cn = error = 0; !error; cn++) {
 			if ((error = pcbmap(dep, cn, &bn, 0, &blsize)) != 0) {
 				if (error == E2BIG)	/* EOF reached and not found */
 					return 0;
 				return error;
 			}
 			error = bread(pmp->pm_devvp, bn, blsize, NOCRED, &bp);
 			if (error) {
 				return error;
 			}
 			for (dentp = (struct direntry *)bp->b_data;
 			     (char *)dentp < bp->b_data + blsize;
 			     dentp++) {
 				if (dentp->deName[0] == SLOT_EMPTY) {
 					/*
 					 * Last used entry and not found
 					 */
 					brelse(bp);
 					return 0;
 				}
 				/*
 				 * Ignore volume labels and Win95 entries
 				 */
 				if (dentp->deAttributes & ATTR_VOLUME)
 					continue;
 				if (!bcmp(dentp->deName, cp, 11)) {
 					error = EEXIST;
 					break;
 				}
 			}
 			brelse(bp);
 		}
 	}
 }