/* $NetBSD: msdosfs_denode.c,v 1.28 1998/02/10 14:10:00 mrg Exp $ */12/*-3* SPDX-License-Identifier: BSD-4-Clause4*5* Copyright (C) 1994, 1995, 1997 Wolfgang Solfrank.6* Copyright (C) 1994, 1995, 1997 TooLs GmbH.7* All rights reserved.8* Original code by Paul Popelka ([email protected]) (see below).9*10* Redistribution and use in source and binary forms, with or without11* modification, are permitted provided that the following conditions12* are met:13* 1. Redistributions of source code must retain the above copyright14* notice, this list of conditions and the following disclaimer.15* 2. Redistributions in binary form must reproduce the above copyright16* notice, this list of conditions and the following disclaimer in the17* documentation and/or other materials provided with the distribution.18* 3. All advertising materials mentioning features or use of this software19* must display the following acknowledgement:20* This product includes software developed by TooLs GmbH.21* 4. The name of TooLs GmbH may not be used to endorse or promote products22* derived from this software without specific prior written permission.23*24* THIS SOFTWARE IS PROVIDED BY TOOLS GMBH ``AS IS'' AND ANY EXPRESS OR25* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES26* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.27* IN NO EVENT SHALL TOOLS GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,28* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,29* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;30* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,31* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR32* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF33* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.34*/35/*-36* Written by Paul Popelka ([email protected])37*38* You can do anything you want with this software, just don't say you wrote39* it, and don't remove this notice.40*41* This software is provided "as is".42*43* The author supplies this software to be publicly redistributed on the44* understanding that the author is not responsible for the correct45* functioning of this software in any circumstances and is not liable for46* any damages caused by this software.47*48* October 199249*/5051#include <sys/param.h>52#include <sys/systm.h>53#include <sys/buf.h>54#include <sys/clock.h>55#include <sys/kernel.h>56#include <sys/malloc.h>57#include <sys/mount.h>58#include <sys/vmmeter.h>59#include <sys/vnode.h>6061#include <vm/vm.h>62#include <vm/vm_extern.h>6364#include <fs/msdosfs/bpb.h>65#include <fs/msdosfs/direntry.h>66#include <fs/msdosfs/denode.h>67#include <fs/msdosfs/fat.h>68#include <fs/msdosfs/msdosfsmount.h>6970static MALLOC_DEFINE(M_MSDOSFSNODE, "msdosfs_node", "MSDOSFS vnode private part");7172static int73de_vncmpf(struct vnode *vp, void *arg)74{75struct denode *de;76uint64_t *a;7778a = arg;79de = VTODE(vp);80return (de->de_inode != *a) || (de->de_refcnt <= 0);81}8283/*84* If deget() succeeds it returns with the gotten denode locked().85*86* pmp - address of msdosfsmount structure of the filesystem containing87* the denode of interest. The address of88* the msdosfsmount structure are used.89* dirclust - which cluster bp contains, if dirclust is 0 (root directory)90* diroffset is relative to the beginning of the root directory,91* otherwise it is cluster relative.92* diroffset - offset past begin of cluster of denode we want93* lkflags - locking flags (LK_NOWAIT)94* depp - returns the address of the gotten denode.95*/96int97deget(struct msdosfsmount *pmp, u_long dirclust, u_long diroffset,98int lkflags, struct denode **depp)99{100int error;101uint64_t inode;102struct mount *mntp = pmp->pm_mountp;103struct direntry *direntptr;104struct denode *ldep;105struct vnode *nvp, *xvp;106struct buf *bp;107108#ifdef MSDOSFS_DEBUG109printf("deget(pmp %p, dirclust %lu, diroffset %lx, flags %#x, "110"depp %p)\n",111pmp, dirclust, diroffset, lkflags, depp);112#endif113MPASS((lkflags & LK_TYPE_MASK) == LK_EXCLUSIVE);114115/*116* On FAT32 filesystems, root is a (more or less) normal117* directory118*/119if (FAT32(pmp) && dirclust == MSDOSFSROOT)120dirclust = pmp->pm_rootdirblk;121122/*123* See if the denode is in the denode cache. Use the location of124* the directory entry to compute the hash value. For subdir use125* address of "." entry. For root dir (if not FAT32) use cluster126* MSDOSFSROOT, offset MSDOSFSROOT_OFS127*128* NOTE: de_vncmpf will explicitly skip any denodes that do not have129* a de_refcnt > 0. This insures that we do not attempt to use130* a denode that represents an unlinked but still open file.131* These files are not to be accessible even when the directory132* entry that represented the file happens to be reused while the133* deleted file is still open.134*/135inode = DETOI(pmp, dirclust, diroffset);136137error = vfs_hash_get(mntp, inode, lkflags, curthread, &nvp,138de_vncmpf, &inode);139#ifdef MSDOSFS_DEBUG140printf("vfs_hash_get(inode %lu) error %d\n", inode, error);141#endif142if (error)143return (error);144if (nvp != NULL) {145*depp = VTODE(nvp);146if ((*depp)->de_dirclust != dirclust) {147printf("%s: wrong dir cluster %lu %lu\n",148pmp->pm_mountp->mnt_stat.f_mntonname,149(*depp)->de_dirclust, dirclust);150goto badoff;151}152if ((*depp)->de_diroffset != diroffset) {153printf("%s: wrong dir offset %lu %lu\n",154pmp->pm_mountp->mnt_stat.f_mntonname,155(*depp)->de_diroffset, diroffset);156goto badoff;157}158return (0);159badoff:160vgone(nvp);161vput(nvp);162msdosfs_integrity_error(pmp);163return (EBADF);164}165ldep = malloc(sizeof(struct denode), M_MSDOSFSNODE, M_WAITOK | M_ZERO);166167/*168* Directory entry was not in cache, have to create a vnode and169* copy it from the passed disk buffer.170*/171/* getnewvnode() does a VREF() on the vnode */172error = getnewvnode("msdosfs", mntp, &msdosfs_vnodeops, &nvp);173if (error) {174*depp = NULL;175free(ldep, M_MSDOSFSNODE);176return error;177}178nvp->v_data = ldep;179ldep->de_vnode = nvp;180ldep->de_flag = 0;181ldep->de_dirclust = dirclust;182ldep->de_diroffset = diroffset;183ldep->de_inode = inode;184cluster_init_vn(&ldep->de_clusterw);185lockmgr(nvp->v_vnlock, LK_EXCLUSIVE | LK_NOWITNESS, NULL);186VN_LOCK_AREC(nvp); /* for doscheckpath */187fc_purge(ldep, 0); /* init the FAT cache for this denode */188error = insmntque(nvp, mntp);189if (error != 0) {190free(ldep, M_MSDOSFSNODE);191*depp = NULL;192return (error);193}194error = vfs_hash_insert(nvp, inode, lkflags, curthread, &xvp,195de_vncmpf, &inode);196#ifdef MSDOSFS_DEBUG197printf("vfs_hash_insert(inode %lu) error %d\n", inode, error);198#endif199if (error) {200*depp = NULL;201return (error);202}203if (xvp != NULL) {204*depp = xvp->v_data;205return (0);206}207208ldep->de_pmp = pmp;209ldep->de_refcnt = 1;210/*211* Copy the directory entry into the denode area of the vnode.212*/213if ((dirclust == MSDOSFSROOT ||214(FAT32(pmp) && dirclust == pmp->pm_rootdirblk)) &&215diroffset == MSDOSFSROOT_OFS) {216/*217* Directory entry for the root directory. There isn't one,218* so we manufacture one. We should probably rummage219* through the root directory and find a label entry (if it220* exists), and then use the time and date from that entry221* as the time and date for the root denode.222*/223nvp->v_vflag |= VV_ROOT; /* should be further down XXX */224225ldep->de_Attributes = ATTR_DIRECTORY;226ldep->de_LowerCase = 0;227if (FAT32(pmp))228ldep->de_StartCluster = pmp->pm_rootdirblk;229/* de_FileSize will be filled in further down */230else {231ldep->de_StartCluster = MSDOSFSROOT;232ldep->de_FileSize = pmp->pm_rootdirsize * DEV_BSIZE;233}234/*235* fill in time and date so that fattime2timespec() doesn't236* spit up when called from msdosfs_getattr() with root237* denode238*/239ldep->de_CHun = 0;240ldep->de_CTime = 0x0000; /* 00:00:00 */241ldep->de_CDate = (0 << DD_YEAR_SHIFT) | (1 << DD_MONTH_SHIFT)242| (1 << DD_DAY_SHIFT);243/* Jan 1, 1980 */244ldep->de_ADate = ldep->de_CDate;245ldep->de_MTime = ldep->de_CTime;246ldep->de_MDate = ldep->de_CDate;247/* leave the other fields as garbage */248} else {249error = readep(pmp, dirclust, diroffset, &bp, &direntptr);250if (error) {251/*252* The denode does not contain anything useful, so253* it would be wrong to leave it on its hash chain.254* Arrange for vput() to just forget about it.255*/256ldep->de_Name[0] = SLOT_DELETED;257vgone(nvp);258vput(nvp);259*depp = NULL;260return (error);261}262(void)DE_INTERNALIZE(ldep, direntptr);263brelse(bp);264}265266/*267* Fill in a few fields of the vnode and finish filling in the268* denode. Then return the address of the found denode.269*/270if (ldep->de_Attributes & ATTR_DIRECTORY) {271/*272* Since DOS directory entries that describe directories273* have 0 in the filesize field, we take this opportunity274* to find out the length of the directory and plug it into275* the denode structure.276*/277u_long size;278279/*280* XXX it sometimes happens that the "." entry has cluster281* number 0 when it shouldn't. Use the actual cluster number282* instead of what is written in directory entry.283*/284if (diroffset == 0 && ldep->de_StartCluster != dirclust) {285#ifdef MSDOSFS_DEBUG286printf("deget(): \".\" entry at clust %lu != %lu\n",287dirclust, ldep->de_StartCluster);288#endif289ldep->de_StartCluster = dirclust;290}291292nvp->v_type = VDIR;293if (ldep->de_StartCluster != MSDOSFSROOT) {294error = pcbmap(ldep, 0xffff, 0, &size, 0);295if (error == E2BIG) {296ldep->de_FileSize = de_cn2off(pmp, size);297error = 0;298} else {299#ifdef MSDOSFS_DEBUG300printf("deget(): pcbmap returned %d\n", error);301#endif302}303}304} else305nvp->v_type = VREG;306vn_set_state(nvp, VSTATE_CONSTRUCTED);307ldep->de_modrev = init_va_filerev();308*depp = ldep;309return (0);310}311312int313deupdat(struct denode *dep, int waitfor)314{315struct direntry dir;316struct timespec ts;317struct buf *bp;318struct direntry *dirp;319int error;320321if (DETOV(dep)->v_mount->mnt_flag & MNT_RDONLY) {322dep->de_flag &= ~(DE_UPDATE | DE_CREATE | DE_ACCESS |323DE_MODIFIED);324return (0);325}326vfs_timestamp(&ts);327DETIMES(dep, &ts, &ts, &ts);328if ((dep->de_flag & DE_MODIFIED) == 0 && waitfor == 0)329return (0);330dep->de_flag &= ~DE_MODIFIED;331if (DETOV(dep)->v_vflag & VV_ROOT)332return (EINVAL);333if (dep->de_refcnt <= 0)334return (0);335error = readde(dep, &bp, &dirp);336if (error)337return (error);338DE_EXTERNALIZE(&dir, dep);339if (bcmp(dirp, &dir, sizeof(dir)) == 0) {340if (waitfor == 0 || (bp->b_flags & B_DELWRI) == 0) {341brelse(bp);342return (0);343}344} else345*dirp = dir;346if ((DETOV(dep)->v_mount->mnt_flag & MNT_NOCLUSTERW) == 0)347bp->b_flags |= B_CLUSTEROK;348if (waitfor)349error = bwrite(bp);350else if (vm_page_count_severe() || buf_dirty_count_severe())351bawrite(bp);352else353bdwrite(bp);354return (error);355}356357/*358* Truncate the file described by dep to the length specified by length.359*/360int361detrunc(struct denode *dep, u_long length, int flags, struct ucred *cred)362{363int error;364int allerror;365u_long eofentry;366u_long chaintofree;367daddr_t bn;368int boff;369int isadir = dep->de_Attributes & ATTR_DIRECTORY;370struct buf *bp;371struct msdosfsmount *pmp = dep->de_pmp;372373#ifdef MSDOSFS_DEBUG374printf("detrunc(): file %s, length %lu, flags %x\n", dep->de_Name, length, flags);375#endif376377/*378* Disallow attempts to truncate the root directory since it is of379* fixed size. That's just the way dos filesystems are. We use380* the VROOT bit in the vnode because checking for the directory381* bit and a startcluster of 0 in the denode is not adequate to382* recognize the root directory at this point in a file or383* directory's life.384*/385if ((DETOV(dep)->v_vflag & VV_ROOT) && !FAT32(pmp)) {386#ifdef MSDOSFS_DEBUG387printf("detrunc(): can't truncate root directory, clust %ld, offset %ld\n",388dep->de_dirclust, dep->de_diroffset);389#endif390return (EINVAL);391}392393if (dep->de_FileSize < length)394return (deextend(dep, length, cred));395396/*397* If the desired length is 0 then remember the starting cluster of398* the file and set the StartCluster field in the directory entry399* to 0. If the desired length is not zero, then get the number of400* the last cluster in the shortened file. Then get the number of401* the first cluster in the part of the file that is to be freed.402* Then set the next cluster pointer in the last cluster of the403* file to CLUST_EOFE.404*/405if (length == 0) {406chaintofree = dep->de_StartCluster;407dep->de_StartCluster = 0;408eofentry = ~0;409} else {410error = pcbmap(dep, de_clcount(pmp, length) - 1, 0,411&eofentry, 0);412if (error) {413#ifdef MSDOSFS_DEBUG414printf("detrunc(): pcbmap fails %d\n", error);415#endif416return (error);417}418}419420fc_purge(dep, de_clcount(pmp, length));421422/*423* If the new length is not a multiple of the cluster size then we424* must zero the tail end of the new last cluster in case it425* becomes part of the file again because of a seek.426*/427if ((boff = length & pmp->pm_crbomask) != 0) {428if (isadir) {429bn = cntobn(pmp, eofentry);430error = bread(pmp->pm_devvp, bn, pmp->pm_bpcluster,431NOCRED, &bp);432} else {433error = bread(DETOV(dep), de_cluster(pmp, length),434pmp->pm_bpcluster, cred, &bp);435}436if (error) {437#ifdef MSDOSFS_DEBUG438printf("detrunc(): bread fails %d\n", error);439#endif440return (error);441}442memset(bp->b_data + boff, 0, pmp->pm_bpcluster - boff);443if ((flags & IO_SYNC) != 0)444bwrite(bp);445else446bdwrite(bp);447}448449/*450* Write out the updated directory entry. Even if the update fails451* we free the trailing clusters.452*/453dep->de_FileSize = length;454if (!isadir)455dep->de_flag |= DE_UPDATE | DE_MODIFIED;456allerror = vtruncbuf(DETOV(dep), length, pmp->pm_bpcluster);457#ifdef MSDOSFS_DEBUG458if (allerror)459printf("detrunc(): vtruncbuf error %d\n", allerror);460#endif461error = deupdat(dep, !DOINGASYNC((DETOV(dep))));462if (error != 0 && allerror == 0)463allerror = error;464#ifdef MSDOSFS_DEBUG465printf("detrunc(): allerror %d, eofentry %lu\n",466allerror, eofentry);467#endif468469/*470* If we need to break the cluster chain for the file then do it471* now.472*/473if (eofentry != ~0) {474error = fatentry(FAT_GET_AND_SET, pmp, eofentry,475&chaintofree, CLUST_EOFE);476if (error) {477#ifdef MSDOSFS_DEBUG478printf("detrunc(): fatentry errors %d\n", error);479#endif480return (error);481}482fc_setcache(dep, FC_LASTFC, de_cluster(pmp, length - 1),483eofentry);484}485486/*487* Now free the clusters removed from the file because of the488* truncation.489*/490if (chaintofree != 0 && !MSDOSFSEOF(pmp, chaintofree))491freeclusterchain(pmp, chaintofree);492493return (allerror);494}495496/*497* Extend the file described by dep to length specified by length.498*/499int500deextend(struct denode *dep, u_long length, struct ucred *cred)501{502struct msdosfsmount *pmp = dep->de_pmp;503struct vnode *vp = DETOV(dep);504struct buf *bp;505off_t eof_clusteroff;506u_long count;507int error;508509/*510* The root of a DOS filesystem cannot be extended.511*/512if ((vp->v_vflag & VV_ROOT) != 0 && !FAT32(pmp))513return (EINVAL);514515/*516* Directories cannot be extended.517*/518if (dep->de_Attributes & ATTR_DIRECTORY)519return (EISDIR);520521if (length <= dep->de_FileSize)522panic("deextend: file too large");523524/*525* Compute the number of clusters to allocate.526*/527count = de_clcount(pmp, length) - de_clcount(pmp, dep->de_FileSize);528if (count > 0) {529if (count > pmp->pm_freeclustercount)530return (ENOSPC);531error = extendfile(dep, count, NULL, NULL, DE_CLEAR);532if (error != 0)533goto rewind;534}535536/*537* For the case of cluster size larger than the page size, we538* need to ensure that the possibly dirty partial buffer at539* the old end of file is not filled with invalid pages by540* extension. Otherwise it has a contradictory state of541* B_CACHE | B_DELWRI but with invalid pages, and cannot be542* neither written out nor validated.543*544* Fix it by proactively clearing extended pages. Need to do545* both vfs_bio_clrbuf() to mark pages valid, and to zero546* actual buffer content which might exist in the tail of the547* already valid cluster.548*/549error = bread(vp, de_cluster(pmp, dep->de_FileSize), pmp->pm_bpcluster,550NOCRED, &bp);551if (error != 0)552goto rewind;553vfs_bio_clrbuf(bp);554eof_clusteroff = de_cn2off(pmp, de_cluster(pmp, dep->de_FileSize));555vfs_bio_bzero_buf(bp, dep->de_FileSize - eof_clusteroff,556pmp->pm_bpcluster - dep->de_FileSize + eof_clusteroff);557if (!DOINGASYNC(vp))558(void)bwrite(bp);559else if (vm_page_count_severe() || buf_dirty_count_severe())560bawrite(bp);561else562bdwrite(bp);563564vnode_pager_setsize(vp, length);565dep->de_FileSize = length;566dep->de_flag |= DE_UPDATE | DE_MODIFIED;567return (deupdat(dep, !DOINGASYNC(vp)));568569rewind:570/* truncate the added clusters away again */571(void)detrunc(dep, dep->de_FileSize, 0, cred);572return (error);573}574575/*576* Move a denode to its correct hash queue after the file it represents has577* been moved to a new directory.578*/579void580reinsert(struct denode *dep)581{582struct vnode *vp;583584/*585* Fix up the denode cache. If the denode is for a directory,586* there is nothing to do since the hash is based on the starting587* cluster of the directory file and that hasn't changed. If for a588* file the hash is based on the location of the directory entry,589* so we must remove it from the cache and re-enter it with the590* hash based on the new location of the directory entry.591*/592#if 0593if (dep->de_Attributes & ATTR_DIRECTORY)594return;595#endif596vp = DETOV(dep);597dep->de_inode = DETOI(dep->de_pmp, dep->de_dirclust, dep->de_diroffset);598#ifdef MSDOSFS_DEBUG599printf("vfs_hash_rehash(inode %lu, refcnt %lu, vp %p)\n",600dep->de_inode, dep->de_refcnt, vp);601#endif602vfs_hash_rehash(vp, dep->de_inode);603}604605int606msdosfs_reclaim(struct vop_reclaim_args *ap)607{608struct vnode *vp = ap->a_vp;609struct denode *dep = VTODE(vp);610611#ifdef MSDOSFS_DEBUG612printf("msdosfs_reclaim(): dep %p, file %s, refcnt %ld\n",613dep, dep->de_Name, dep->de_refcnt);614#endif615616/*617* Remove the denode from its hash chain.618*/619#ifdef MSDOSFS_DEBUG620printf("vfs_hash_remove(inode %lu, refcnt %lu, vp %p)\n",621dep->de_inode, dep->de_refcnt, vp);622#endif623vfs_hash_remove(vp);624/*625* Purge old data structures associated with the denode.626*/627#if 0 /* XXX */628dep->de_flag = 0;629#endif630free(dep, M_MSDOSFSNODE);631vp->v_data = NULL;632633return (0);634}635636int637msdosfs_inactive(struct vop_inactive_args *ap)638{639struct vnode *vp = ap->a_vp;640struct denode *dep = VTODE(vp);641int error = 0;642643#ifdef MSDOSFS_DEBUG644printf("msdosfs_inactive(): dep %p, de_Name[0] %x\n", dep, dep->de_Name[0]);645#endif646647/*648* Ignore denodes related to stale file handles.649*/650if (dep->de_Name[0] == SLOT_DELETED || dep->de_Name[0] == SLOT_EMPTY)651goto out;652653/*654* If the file has been deleted and it is on a read/write655* filesystem, then truncate the file, and mark the directory slot656* as empty. (This may not be necessary for the dos filesystem.)657*/658#ifdef MSDOSFS_DEBUG659printf("msdosfs_inactive(): dep %p, refcnt %ld, mntflag %llx, MNT_RDONLY %llx\n",660dep, dep->de_refcnt, (unsigned long long)vp->v_mount->mnt_flag,661(unsigned long long)MNT_RDONLY);662#endif663if (dep->de_refcnt <= 0 && (vp->v_mount->mnt_flag & MNT_RDONLY) == 0) {664error = detrunc(dep, (u_long) 0, 0, NOCRED);665dep->de_flag |= DE_UPDATE;666dep->de_Name[0] = SLOT_DELETED;667}668deupdat(dep, 0);669670out:671/*672* If we are done with the denode, reclaim it673* so that it can be reused immediately.674*/675#ifdef MSDOSFS_DEBUG676printf("msdosfs_inactive(): v_usecount %d, de_Name[0] %x\n",677vrefcnt(vp), dep->de_Name[0]);678#endif679if (dep->de_Name[0] == SLOT_DELETED || dep->de_Name[0] == SLOT_EMPTY)680vrecycle(vp);681return (error);682}683684685