*BSD News Article 7856


Return to BSD News archive

Xref: sserve comp.unix.bsd:7907 alt.sources:4518
Path: sserve!manuel.anu.edu.au!munnari.oz.au!spool.mu.edu!uwm.edu!linac!pacific.mps.ohio-state.edu!zaphod.mps.ohio-state.edu!cs.utexas.edu!sun-barr!news2me.EBay.Sun.COM!exodus.Eng.Sun.COM!sun!amdahl!paulp
From: paulp@uts.amdahl.com (Paul Popelka)
Newsgroups: comp.unix.bsd,alt.sources
Subject: [386bsd] mountable DOS filesystem code, Part 3/5
Message-ID: <bc0H03vfbbM800@amdahl.uts.amdahl.com>
Date: 17 Nov 92 17:26:49 GMT
Followup-To: comp.unix.bsd
Organization: Amdahl Corporation, Sunnyvale CA
Lines: 1659


# This is a shell archive.  Save it in a file, remove anything before
# this line, and then unpack it by entering "sh file".  Note, it may
# create directories; files and directories will be owned by you and
# have default permissions.
#
# This archive contains:
#
#	/sys/pcfs/pcfs_lookup.c
#	/sys/pcfs/pcfs_fat.c
#
echo x - /sys/pcfs/pcfs_lookup.c
sed 's/^X//' >/sys/pcfs/pcfs_lookup.c << 'END-of-/sys/pcfs/pcfs_lookup.c'
X/*
X *  Written by Paul Popelka (paulp@uts.amdahl.com)
X *
X *  You can do anything you want with this software,
X *    just don't say you wrote it,
X *    and don't remove this notice.
X *
X *  This software is provided "as is".
X *
X *  The author supplies this software to be publicly
X *  redistributed on the understanding that the author
X *  is not responsible for the correct functioning of
X *  this software in any circumstances and is not liable
X *  for any damages caused by this software.
X *
X *  October 1992
X */
X
X#include "param.h"
X#include "namei.h"
X#include "buf.h"
X#include "vnode.h"
X#include "mount.h"
X
X#include "bpb.h"
X#include "direntry.h"
X#include "denode.h"
X#include "pcfsmount.h"
X#include "fat.h"
X
X/*
X *  When we search a directory the blocks containing directory
X *  entries are read and examined.  The directory entries
X *  contain information that would normally be in the inode
X *  of a unix filesystem.  This means that some of a directory's
X *  contents may also be in memory resident denodes (sort of
X *  an inode).  This can cause problems if we are searching
X *  while some other process is modifying a directory.  To
X *  prevent one process from accessing incompletely modified
X *  directory information we depend upon being the soul owner
X *  of a directory block.  bread/brelse provide this service.
X *  This being the case, when a process modifies a directory
X *  it must first acquire the disk block that contains the
X *  directory entry to be modified.  Then update the disk
X *  block and the denode, and then write the disk block out
X *  to disk.  This way disk blocks containing directory
X *  entries and in memory denode's will be in synch.
X */
Xint
Xpcfs_lookup(vdp, ndp, p)
X	struct vnode *vdp;	/* vnode of directory to search		*/
X	struct nameidata *ndp;
X	struct proc *p;
X{
X	daddr_t bn;
X	int flag;
X	int error;
X	int lockparent;
X	int wantparent;
X	int slotstatus;
X#define	NONE	0
X#define	FOUND	1
X	int slotoffset;
X	int slotcluster;
X	int frcn;
X	unsigned long cluster;
X	int rootreloff;
X	int diroff;
X	int isadir;		/* ~0 if found direntry is a directory	*/
X	unsigned long scn;	/* starting cluster number		*/
X	struct denode *dp;
X	struct denode *pdp;
X	struct denode *tdp;
X	struct pcfsmount *pmp;
X	struct buf *bp = 0;
X	struct direntry *dep;
X	unsigned char dosfilename[12];
X
X#if defined(PCFSDEBUG)
Xprintf("pcfs_lookup(): looking for %s\n", ndp->ni_ptr);
X#endif /* defined(PCFSDEBUG) */
X	ndp->ni_dvp = vdp;
X	ndp->ni_vp  = NULL;
X	dp = VTODE(vdp);
X	pmp = dp->de_pmp;
X	lockparent = ndp->ni_nameiop & LOCKPARENT;
X	flag = ndp->ni_nameiop & OPMASK;
X	wantparent = ndp->ni_nameiop & (LOCKPARENT | WANTPARENT);
X#if defined(PCFSDEBUG)
Xprintf("pcfs_lookup(): vdp %08x, dp %08x, Attr %02x\n",
X	vdp, dp, dp->de_Attributes);
X#endif /* defined(PCFSDEBUG) */
X
X/*
X *  Be sure vdp is a directory.  Since dos filesystems
X *  don't have the concept of execute permission anybody
X *  can search a directory.
X */
X	if ((dp->de_Attributes & ATTR_DIRECTORY) == 0)
X		return ENOTDIR;
X
X/*
X *  See if the component of the pathname we are looking for
X *  is in the directory cache.  If so then do a few things
X *  and return.
X */
X	if (error = cache_lookup(ndp)) {
X		int vpid;
X
X		if (error == ENOENT)
X			return error;
X#ifdef PARANOID
X		if (vdp == ndp->ni_rdir  &&  ndp->ni_isdotdot)
X			panic("pcfs_lookup: .. thru root");
X#endif /* PARANOID */
X		pdp = dp;
X		vdp = ndp->ni_vp;
X		dp  = VTODE(vdp);
X		vpid = vdp->v_id;
X		if (pdp == dp) {
X			VREF(vdp);
X			error = 0;
X		} else if (ndp->ni_isdotdot) {
X			DEUNLOCK(pdp);
X			error = vget(vdp);
X			if (!error && lockparent && *ndp->ni_next == '\0')
X				DELOCK(pdp);
X		} else {
X			error = vget(vdp);
X			if (!lockparent || error || *ndp->ni_next != '\0')
X				DEUNLOCK(pdp);
X		}
X
X		if (!error) {
X			if (vpid == vdp->v_id) {
X#if defined(PCFSDEBUG)
Xprintf("pcfs_lookup(): cache hit, vnode %08x, file %s\n", vdp, dp->de_Name);
X#endif /* defined(PCFSDEBUG) */
X				return 0;
X			}
X			deput(dp);
X			if (lockparent && pdp != dp && *ndp->ni_next == '\0')
X				DEUNLOCK(pdp);
X		}
X		DELOCK(pdp);
X		dp = pdp;
X		vdp = DETOV(dp);
X		ndp->ni_vp = NULL;
X	}
X
X/*
X *  If they are going after the . or .. entry in the
X *  root directory, they won't find it.  DOS filesystems
X *  don't have them in the root directory.  So, we fake it.
X *  deget() is in on this scam too.
X */
X	if ((vdp->v_flag & VROOT)  &&  ndp->ni_ptr[0] == '.'  &&
X	    (ndp->ni_namelen == 1  ||
X	     (ndp->ni_namelen == 2  &&  ndp->ni_ptr[1] == '.'))) {
X		isadir = ATTR_DIRECTORY;
X		scn = PCFSROOT;
X		diroff = -1;
X		cluster = ~0;
X#if defined(PCFSDEBUG)
Xprintf("pcfs_lookup(): looking for . or .. in root directory\n");
X#endif /* defined(PCFSDEBUG) */
X		goto foundroot;
X	}
X
X/*
X *  Don't search for free slots unless we are creating
X *  a filename and we are at the end of the pathname.
X */
X	slotstatus = FOUND;
X	if ((flag == CREATE || flag == RENAME) && *ndp->ni_next == '\0') {
X		slotstatus = NONE;
X		slotoffset = -1;
X	}
X
X	unix2dosfn((unsigned char *)ndp->ni_ptr, dosfilename, ndp->ni_namelen);
X	dosfilename[11] = 0;
X#if defined(PCFSDEBUG)
Xprintf("pcfs_lookup(): dos version of filename %s, length %d\n",
X	dosfilename, ndp->ni_namelen);
X#endif /* defined(PCFSDEBUG) */
X/*
X *  Search the directory pointed at by vdp for the
X *  name pointed at by ndp->ni_ptr.
X */
X	tdp = NULL;
X/*
X *  The outer loop ranges over the clusters that make
X *  up the directory.  Note that the root directory is
X *  different from all other directories.  It has a
X *  fixed number of blocks that are not part of the
X *  pool of allocatable clusters.  So, we treat it a
X *  little differently.
X *  The root directory starts at "cluster" 0.
X */
X	rootreloff = 0;
X	for (frcn = 0; ; frcn++) {
X		if (error = pcbmap(dp, frcn, &bn, &cluster)) {
X			if (error == E2BIG)
X				break;
X			return error;
X		}
X		if (error = bread(pmp->pm_devvp, bn, pmp->pm_bpcluster, NOCRED, &bp)) {
X			brelse(bp);
X			return error;
X		}
X		for (diroff = 0; diroff < pmp->pm_depclust; diroff++) {
X			dep = (struct direntry *)bp->b_un.b_addr + diroff;
X
X/*
X *  If the slot is empty and we are still looking for
X *  an empty then remember this one.  If the slot is
X *  not empty then check to see if it matches what we
X *  are looking for.  If the slot has never been filled
X *  with anything, then the remainder of the directory
X *  has never been used, so there is no point in searching
X *  it.
X */
X			if (dep->deName[0] == SLOT_EMPTY   ||
X			    dep->deName[0] == SLOT_DELETED) {
X				if (slotstatus != FOUND) {
X					slotstatus  = FOUND;
X					if (cluster == PCFSROOT)
X						slotoffset = rootreloff;
X					else
X						slotoffset = diroff;
X					slotcluster = cluster;
X				}
X				if (dep->deName[0] == SLOT_EMPTY) {
X					brelse(bp);
X					goto notfound;
X				}
X			} else {
X				/* Ignore volume labels (anywhere, not just
X				 * the root directory). */
X				if ((dep->deAttributes & ATTR_VOLUME) == 0  &&
X				    bcmp(dosfilename, dep->deName, 11) == 0) {
X#if defined(PCFSDEBUG)
Xprintf("pcfs_lookup(): match diroff %d, rootreloff %d\n", diroff, rootreloff);
X#endif /* defined(PCFSDEBUG) */
X/*
X *  Remember where this directory entry came from
X *  for whoever did this lookup.
X *  If this is the root directory we are interested
X *  in the offset relative to the beginning of the
X *  directory (not the beginning of the cluster).
X */
X					if (cluster == PCFSROOT)
X						diroff = rootreloff;
X					ndp->ni_pcfs.pcfs_offset = diroff;
X					ndp->ni_pcfs.pcfs_cluster = cluster;
X					goto found;
X				}
X			}
X			rootreloff++;
X		}			/* for (diroff = 0; .... */
X/*
X *  Release the buffer holding the directory cluster
X *  just searched.
X */
X		brelse(bp);
X	}			/* for (frcn = 0; ; frcn++) */
Xnotfound:;
X/*
X *  We hold no disk buffers at this point.
X */
X
X/*
X *  If we get here we didn't find the entry we were looking
X *  for.  But that's ok if we are creating or renaming and
X *  are at the end of the pathname and the directory hasn't
X *  been removed.
X */
X#if defined(PCFSDEBUG)
Xprintf("pcfs_lookup(): flag %d, refcnt %d, slotstatus %d\n",
X	flag, dp->de_refcnt, slotstatus);
Xprintf("               slotoffset %d, slotcluster %d\n",
X	slotoffset, slotcluster);
X#endif /* defined(PCFSDEBUG) */
X	if ((flag == CREATE || flag == RENAME)  &&
X	    *ndp->ni_next == '\0' && dp->de_refcnt != 0) {
X		if (slotstatus == NONE) {
X			ndp->ni_pcfs.pcfs_offset  = 0;
X			ndp->ni_pcfs.pcfs_cluster = 0;
X			ndp->ni_pcfs.pcfs_count   = 0;
X		} else {
X#if defined(PCFSDEBUG)
Xprintf("pcfs_lookup(): saving empty slot location\n");
X#endif /* defined(PCFSDEBUG) */
X			ndp->ni_pcfs.pcfs_offset  = slotoffset;
X			ndp->ni_pcfs.pcfs_cluster = slotcluster;
X			ndp->ni_pcfs.pcfs_count   = 1;
X		}
X/*		dp->de_flag |= DEUPD; /* never update dos directories */
X		ndp->ni_nameiop |= SAVENAME;
X		if (!lockparent)	/* leave searched dir locked?	*/
X			DEUNLOCK(dp);
X	}
X/*
X *  Insert name in cache as non-existant if not
X *  trying to create it.
X */
X	if (ndp->ni_makeentry && flag != CREATE)
X		cache_enter(ndp);
X	return ENOENT;
X
Xfound:;
X/*
X *  NOTE:  We still have the buffer with matched
X *  directory entry at this point.
X */
X	isadir = dep->deAttributes & ATTR_DIRECTORY;
X	scn = dep->deStartCluster;
X
Xfoundroot:;
X/*
X *  If we entered at foundroot, then we are looking
X *  for the . or .. entry of the filesystems root
X *  directory.  isadir and scn were setup before
X *  jumping here.  And, bp is null.  There is no buf header.
X */
X
X/*
X *  If deleting and at the end of the path, then
X *  if we matched on "." then don't deget() we would
X *  probably panic().  Otherwise deget() the directory
X *  entry.
X */
X	if (flag == DELETE && ndp->ni_next == '\0') {
X		if (dp->de_StartCluster == scn  &&
X		    isadir) { /* "." */
X			VREF(vdp);
X			ndp->ni_vp = vdp;
X			if (bp) brelse(bp);
X			return 0;
X		}
X		error = deget(pmp, isadir,
X			cluster, diroff, scn, bp, &tdp);
X		if (error) {
X			if (bp) brelse(bp);
X			return error;
X		}
X		ndp->ni_vp = DETOV(tdp);
X		if (!lockparent)
X			DEUNLOCK(dp);
X		if (bp) brelse(bp);
X		return 0;
X	}
X
X/*
X *  If renaming.
X */
X	if (flag == RENAME && wantparent && *ndp->ni_next == '\0') {
X		if (dp->de_StartCluster == scn  &&
X		    isadir) {
X			if (bp) brelse(bp);
X			return EISDIR;
X		}
X		error = deget(pmp, isadir, cluster, diroff,
X			scn, bp, &tdp);
X		if (error) {
X			if (bp) brelse(bp);
X			return error;
X		}
X		ndp->ni_vp = DETOV(tdp);
X		ndp->ni_nameiop |= SAVENAME;
X		if (!lockparent)
X			DEUNLOCK(dp);
X		if (bp) brelse(bp);
X		return 0;
X	}
X
X/*
X *  ?
X */
X	pdp = dp;
X	if (ndp->ni_isdotdot) {
X		DEUNLOCK(pdp);
X		error = deget(pmp, isadir, cluster, diroff,
X			scn, bp, &tdp);
X		if (error) {
X			DELOCK(pdp);
X			if (bp) brelse(bp);
X			return error;
X		}
X		if (lockparent && *ndp->ni_next == '\0')
X			DELOCK(pdp);
X		ndp->ni_vp = DETOV(tdp);
X	} else if (dp->de_StartCluster == scn  &&
X		   isadir) { /* "." */
X		VREF(vdp);
X		ndp->ni_vp = vdp;
X	} else {
X		error = deget(pmp, isadir, cluster, diroff,
X			scn, bp, &tdp);
X		if (error) {
X			if (bp) brelse(bp);
X			return error;
X		}
X		if (!lockparent || *ndp->ni_next != '\0')
X			DEUNLOCK(pdp);
X		ndp->ni_vp = DETOV(tdp);
X	}
X	if (bp) brelse(bp);
X
X/*
X *  Insert name in cache if wanted.
X */
X	if (ndp->ni_makeentry)
X		cache_enter(ndp);
X	return 0;
X}
X
X/*
X *  dep - directory to copy into the directory
X *  ndp - nameidata structure containing info on
X *    where to put the directory entry in the directory.
X *  depp - return the address of the denode for the
X *    created directory entry if depp != 0
X */
Xint
Xcreatede(dep, ndp, depp)
X	struct denode *dep;
X	struct nameidata *ndp;
X	struct denode **depp;
X{
X	int bn;
X	int error;
X	int theoff;
X	int isadir = dep->de_Attributes & ATTR_DIRECTORY;
X	unsigned long newcluster;
X	struct direntry *ndep;
X	struct denode *ddep = VTODE(ndp->ni_dvp);	/* directory to add to */
X	struct pcfsmount *pmp = dep->de_pmp;
X	struct buf *bp;
X#if defined(PCFSDEBUG)
Xprintf("createde(dep %08x, ndp %08x, depp %08x)\n", dep, ndp, depp);
X#endif /* defined(PCFSDEBUG) */
X
X/*
X *  If no space left in the directory then allocate
X *  another cluster and chain it onto the end of the
X *  file.  There is one exception to this.  That is,
X *  if the root directory has no more space it can NOT
X *  be expanded.  extendfile() checks for and fails attempts to
X *  extend the root directory.  We just return an error
X *  in that case.
X */
X	if (ndp->ni_pcfs.pcfs_count == 0) {
X		if (error = extendfile(ddep, &bp, &newcluster))
X			return error;
X		ndep = (struct direntry *)bp->b_un.b_addr;
X		*ndep = dep->de_de;
X/*
X *  If they want us to return with the denode gotten.
X */
X		if (depp) {
X			error = deget(pmp, isadir, newcluster, 0,
X				dep->de_StartCluster, bp, depp);
X			if (error) {
X				return error;
X			}
X		}
X
X		if (error = bwrite(bp)) {
X/*deput()?*/
X			return error;
X		}
X/*
X *  Let caller know where we put the directory entry.
X */
X		ndp->ni_pcfs.pcfs_cluster = newcluster;
X		ndp->ni_pcfs.pcfs_offset  = 0;
X		return 0;
X	}
X
X/*
X *  There is space in the existing directory.  So,
X *  we just read in the cluster with space.  Copy
X *  the new directory entry in.  Then write it to
X *  disk.
X *  NOTE:  DOS directories do not get smaller as
X *  clusters are emptied.
X */
X	if (ndp->ni_pcfs.pcfs_cluster == PCFSROOT) {
X		bn = pmp->pm_rootdirblk +
X			(ndp->ni_pcfs.pcfs_offset / pmp->pm_depclust);
X		theoff = ndp->ni_pcfs.pcfs_offset % pmp->pm_depclust;
X	} else {
X		bn = cntobn(pmp, ndp->ni_pcfs.pcfs_cluster);
X		theoff = ndp->ni_pcfs.pcfs_offset;
X	}
X	if (error = bread(pmp->pm_devvp, bn, pmp->pm_bpcluster,
X	    NOCRED, &bp)) {
X		brelse(bp);
X		return error;
X	}
X	ndep = (struct direntry *)(bp->b_un.b_addr) + theoff;
X	*ndep = dep->de_de;
X/*
X *  If they want us to return with the denode gotten.
X */
X	if (depp) {
X		error = deget(pmp, isadir, ndp->ni_pcfs.pcfs_cluster,
X			ndp->ni_pcfs.pcfs_offset,
X			dep->de_StartCluster, bp, depp);
X		if (error) {
X			return error;
X		}
X	}
X	if (error = bwrite(bp))
X/*deput()?*/
X		return error;		/* do a brelse() if error ? */
X	return 0;
X}
X
X/*
X *  Read in a directory entry and mark it as being deleted.
X */
Xint
Xmarkdeleted(pmp, dirclust, diroffset)
X	struct pcfsmount *pmp;
X	unsigned long dirclust;
X	unsigned long diroffset;
X{
X	int offset;
X	int error;
X	daddr_t bn;
X	struct direntry *ep;
X	struct buf *bp;
X
X	if (dirclust == PCFSROOT) {
X		bn = pmp->pm_rootdirblk + (diroffset / pmp->pm_depclust);
X		offset = diroffset % pmp->pm_depclust;
X	} else {
X		bn = cntobn(pmp, dirclust);
X		offset = diroffset;
X	}
X/*printf("markdeleted(): bread()ing block %d\n", bn);*/
X	if (error = bread(pmp->pm_devvp, bn, pmp->pm_bpcluster, NOCRED, &bp)) {
X		brelse(bp);
X		return error;
X	}
X
X	ep = (struct direntry *)bp->b_un.b_addr + offset;
X	ep->deName[0] = SLOT_DELETED;
X
X	return bwrite(bp);
X}
X
X/*
X *  Remove a directory entry.
X *  At this point the file represented by the directory
X *  entry to be removed is still full length until no
X *  one has it open.  When the file no longer being
X *  used pcfs_inactive() is called and will truncate
X *  the file to 0 length.  When the vnode containing
X *  the denode is needed for some other purpose by 
X *  VFS it will call pcfs_reclaim() which will remove
X *  the denode from the denode cache.
X */
Xint
Xremovede(ndp)
X	struct nameidata *ndp;
X{
X	struct denode *dep = VTODE(ndp->ni_vp);	/* the file being removed */
X	struct pcfsmount *pmp = dep->de_pmp;
X	int error;
X
X#if defined(PCFSDEBUG)
X/*printf("removede(): filename %s\n", dep->de_Name);
Xprintf("rmde(): dep %08x, ndpcluster %d, ndpoffset %d\n",
X	dep, ndp->ni_pcfs.pcfs_cluster, ndp->ni_pcfs.pcfs_offset);*/
X#endif /* defined(PCFSDEBUG) */
X
X/*
X *  Read the directory block containing the directory
X *  entry we are to make free.  The nameidata structure
X *  holds the cluster number and directory entry index
X *  number of the entry to free.
X */
X	error = markdeleted(pmp, ndp->ni_pcfs.pcfs_cluster,
X		ndp->ni_pcfs.pcfs_offset);
X
X	dep->de_refcnt--;
X	return error;
X}
X
X/*
X *  Be sure a directory is empty except for "." and "..".
X *  Return 1 if empty, return 0 if not empty or error.
X */
Xint
Xdosdirempty(dep)
X	struct denode *dep;
X{
X	int dei;
X	int error;
X	unsigned long cn;
X	daddr_t bn;
X	struct buf *bp;
X	struct pcfsmount *pmp = dep->de_pmp;
X	struct direntry *dentp;
X
X/*
X *  Since the filesize field in directory entries for a directory
X *  is zero, we just have to feel our way through the directory
X *  until we hit end of file.
X */
X	for (cn = 0;; cn++) {
X		error = pcbmap(dep, cn, &bn, 0);
X		if (error == E2BIG)
X			return 1;	/* it's empty */
X		error = bread(pmp->pm_devvp, bn, pmp->pm_bpcluster, NOCRED,
X			&bp);
X		if (error) {
X			brelse(bp);
X			return error;
X		}
X		dentp = (struct direntry *)bp->b_un.b_addr;
X		for (dei = 0; dei < pmp->pm_depclust; dei++) {
X			if (dentp->deName[0] != SLOT_DELETED) {
X/*
X *  In dos directories an entry whose name starts with SLOT_EMPTY (0)
X *  starts the beginning of the unused part of the directory, so we
X *  can just return that it is empty.
X */
X				if (dentp->deName[0] == SLOT_EMPTY) {
X					brelse(bp);
X					return 1;
X				}
X/*
X *  Any names other than "." and ".." in a directory mean
X *  it is not empty.
X */
X				if (bcmp(dentp->deName, ".          ", 11)  &&
X				    bcmp(dentp->deName, "..         ", 11)) {
X					brelse(bp);
X#if defined(PCFSDEBUG)
Xprintf("dosdirempty(): entry %d found %02x, %02x\n", dei, dentp->deName[0],
X	dentp->deName[1]);
X#endif /* defined(PCFSDEBUG) */
X					return 0;	/* not empty */
X				}
X			}
X			dentp++;
X		}
X		brelse(bp);
X	}
X	/*NOTREACHED*/
X}
X
X/*
X *  Check to see if the directory described by target is
X *  in some subdirectory of source.  This prevents something
X *  like the following from succeeding and leaving a bunch
X *  or files and directories orphaned.
X *	mv /a/b/c /a/b/c/d/e/f
X *  Where c and f are directories.
X *  source - the inode for /a/b/c
X *  target - the inode for /a/b/c/d/e/f
X *  Returns 0 if target is NOT a subdirectory of source.
X *  Otherwise returns a non-zero error number.
X *  The target inode is always unlocked on return.
X */
Xint
Xdoscheckpath(source, target)
X	struct denode *source;
X	struct denode *target;
X{
X	daddr_t scn;
X	struct denode dummy;
X	struct pcfsmount *pmp;
X	struct direntry *ep;
X	struct denode *dep;
X	struct buf *bp = NULL;
X	int error = 0;
X
X	dep = target;
X	if ((target->de_Attributes & ATTR_DIRECTORY) == 0  ||
X	    (source->de_Attributes & ATTR_DIRECTORY) == 0) {
X		error = ENOTDIR;
X		goto out;
X	}
X	if (dep->de_StartCluster == source->de_StartCluster) {
X		error = EEXIST;
X		goto out;
X	}
X	if (dep->de_StartCluster == PCFSROOT)
X		goto out;
X	for (;;) {
X		if ((dep->de_Attributes & ATTR_DIRECTORY) == 0) {
X			error = ENOTDIR;
X			goto out;
X		}
X		pmp = dep->de_pmp;
X		scn = dep->de_StartCluster;
X		error = bread(pmp->pm_devvp, cntobn(pmp, scn),
X			pmp->pm_bpcluster, NOCRED, &bp);
X		if (error) {
X			break;
X		}
X		ep = (struct direntry *)bp->b_un.b_addr + 1;
X		if ((ep->deAttributes & ATTR_DIRECTORY) == 0  ||
X		    bcmp(ep->deName, "..         ", 11) != 0) {
X			error = ENOTDIR;
X			break;
X		}
X		if (ep->deStartCluster == source->de_StartCluster) {
X			error = EINVAL;
X			break;
X		}
X		if (ep->deStartCluster == PCFSROOT)
X			break;
X		deput(dep);
X		/* NOTE: deget() clears dep on error */
X		error = deget(pmp, ATTR_DIRECTORY, scn, 1,
X			ep->deStartCluster, bp, &dep);
X		brelse(bp);
X		bp = NULL;
X		if (error)
X			break;
X	}
Xout:;
X	if (bp)
X		brelse(bp);
X	if (error == ENOTDIR)
X		printf("doscheckpath(): .. not a directory?\n");
X	if (dep != NULL)
X		deput(dep);
X	return error;
X}
X
X/*
X *  Read in the disk block containing the directory entry
X *  dep came from and return the address of the buf header,
X *  and the address of the directory entry within the block.
X */
Xint
Xreadde(dep, bpp, epp)
X	struct denode *dep;
X	struct buf **bpp;
X	struct direntry **epp;
X{
X	int error;
X	daddr_t bn;
X	unsigned long theoff;
X	struct pcfsmount *pmp = dep->de_pmp;
X
X	if (dep->de_dirclust == PCFSROOT) {
X		bn = pmp->pm_rootdirblk +
X			(dep->de_diroffset / pmp->pm_depclust);
X		theoff = dep->de_diroffset % pmp->pm_depclust;
X	} else {
X		bn = cntobn(pmp, dep->de_dirclust);
X		theoff = dep->de_diroffset;
X	}
X	if (error = bread(pmp->pm_devvp, bn, pmp->pm_bpcluster, NOCRED, bpp)) {
X		brelse(*bpp);
X		*bpp = NULL;
X		return error;
X	}
X	if (epp)
X		*epp = (struct direntry *)((*bpp)->b_un.b_addr) + theoff;
X	return 0;
X}
END-of-/sys/pcfs/pcfs_lookup.c
echo x - /sys/pcfs/pcfs_fat.c
sed 's/^X//' >/sys/pcfs/pcfs_fat.c << 'END-of-/sys/pcfs/pcfs_fat.c'
X/*
X *  Written by Paul Popelka (paulp@uts.amdahl.com)
X *
X *  You can do anything you want with this software,
X *    just don't say you wrote it,
X *    and don't remove this notice.
X *
X *  This software is provided "as is".
X *
X *  The author supplies this software to be publicly
X *  redistributed on the understanding that the author
X *  is not responsible for the correct functioning of
X *  this software in any circumstances and is not liable
X *  for any damages caused by this software.
X *
X *  October 1992
X */
X
X/*
X *  kernel include files.
X */
X#include "param.h"
X#include "systm.h"
X#include "buf.h"
X#include "file.h"
X#include "namei.h"
X#include "mount.h"	/* to define statfs structure */
X#include "vnode.h"	/* to define vattr structure */
X#include "errno.h"
X
X/*
X *  pcfs include files.
X */
X#include "bpb.h"
X#include "pcfsmount.h"
X#include "direntry.h"
X#include "denode.h"
X#include "fat.h"
X
X/*
X *  Fat cache stats.
X */
Xint fc_fileextends       = 0;	/* # of file extends			*/
Xint fc_lfcempty          = 0;	/* # of time last file cluster cache entry
X				 * was empty */
Xint fc_bmapcalls         = 0;	/* # of times pcbmap was called		*/
X#define	LMMAX	20
Xint fc_lmdistance[LMMAX];	/* counters for how far off the last cluster
X				 * mapped entry was. */
Xint fc_largedistance     = 0;	/* off by more than LMMAX		*/
X
X/*
X *  Map the logical cluster number of a file into
X *  a physical disk sector that is filesystem relative.
X *  dep - address of denode representing the file of interest
X *  findcn - file relative cluster whose filesystem relative
X *    cluster number and/or block number are/is to be found
X *  bnp - address of where to place the file system relative
X *    block number.  If this pointer is null then don't return
X *    this quantity.
X *  cnp - address of where to place the file system relative
X *    cluster number.  If this pointer is null then don't return
X *    this quantity.
X *  NOTE:
X *    Either bnp or cnp must be non-null.
X *    This function has one side effect.  If the requested
X *    file relative cluster is beyond the end of file, then
X *    the actual number of clusters in the file is returned
X *    in *cnp.  This is useful for determining how long a
X *    directory is.  If cnp is null, nothing is returned.
X */
Xint
Xpcbmap(dep, findcn, bnp, cnp)
X	struct denode *dep;
X	unsigned long findcn;	/* file relative cluster to get		*/
X	daddr_t *bnp;		/* returned filesys relative blk number	*/
X	unsigned long *cnp;	/* returned cluster number		*/
X{
X	int error;
X	u_long i;
X	u_long cn;
X	u_long prevcn;
X	u_long byteoffset;
X	u_long bn;
X	u_long bo;
X	struct buf *bp0 = 0;
X	u_long bp0_bn = -1;
X	struct buf *bp1 = 0;
X	struct pcfsmount *pmp = dep->de_pmp;
X	union fattwiddle x;
X
X	fc_bmapcalls++;
X
X/*
X *  If they don't give us someplace to return a value
X *  then don't bother doing anything.
X */
X	if (bnp == NULL  &&  cnp == NULL)
X		return 0;
X
X	i = 0;
X	cn = dep->de_StartCluster;
X/*
X *  The "file" that makes up the root directory is contiguous,
X *  permanently allocated, of fixed size, and is not made up
X *  of clusters.  If the cluster number is beyond the end of
X *  the root directory, then return the number of clusters in
X *  the file.
X */
X	if (cn == PCFSROOT) {
X		if (dep->de_Attributes & ATTR_DIRECTORY) {
X			if (findcn * pmp->pm_SectPerClust > pmp->pm_rootdirsize) {
X				if (cnp)
X					*cnp = pmp->pm_rootdirsize / pmp->pm_SectPerClust;
X				return E2BIG;
X			}
X			if (bnp)
X				*bnp = pmp->pm_rootdirblk + (findcn * pmp->pm_SectPerClust);
X			if (cnp)
X				*cnp = PCFSROOT;
X			return 0;
X		} else {	/* just an empty file */
X			if (cnp)
X				*cnp = 0;
X			return E2BIG;
X		}
X	}
X
X/*
X *  Rummage around in the fat cache, maybe we can avoid
X *  tromping thru every fat entry for the file.
X *  And, keep track of how far off the cache was from
X *  where we wanted to be.
X */
X	fc_lookup(dep, findcn, &i, &cn);
X	if ((bn = findcn - i) >= LMMAX)
X		fc_largedistance++;
X	else
X		fc_lmdistance[bn]++;
X
X/*
X *  Handle all other files or directories the normal way.
X */
X	if (FAT12(pmp)) {	/* 12 bit fat	*/
X		for (; i < findcn; i++) {
X			if (PCFSEOF(cn)) {
X				goto hiteof;
X			}
X			byteoffset = cn + (cn >> 1);
X			bn = (byteoffset >> pmp->pm_bnshift) + pmp->pm_fatblk;
X			bo = byteoffset &  pmp->pm_brbomask;
X			if (bn != bp0_bn) {
X				if (bp0)
X					brelse(bp0);
X				if (error = bread(pmp->pm_devvp, bn,
X				    pmp->pm_BytesPerSec, NOCRED, &bp0)) {
X					brelse(bp0);
X					return error;
X				}
X				bp0_bn = bn;
X			}
X			x.byte[0] = bp0->b_un.b_addr[bo];
X/*
X *  If the first byte of the fat entry was the last byte
X *  in the block, then we must read the next block in the
X *  fat.  We hang on to the first block we read to insure
X *  that some other process doesn't update it while we
X *  are waiting for the 2nd block.
X *  Note that we free bp1 even though the next iteration of
X *  the loop might need it.
X */
X			if (bo == pmp->pm_BytesPerSec-1) {
X				if (error = bread(pmp->pm_devvp, bn+1,
X				    pmp->pm_BytesPerSec, NOCRED, &bp1)) {
X					brelse(bp0);
X					return error;
X				}
X				x.byte[1] = bp1->b_un.b_addr[0];
X				brelse(bp1);
X			} else {
X				x.byte[1] = bp0->b_un.b_addr[bo+1];
X			}
X			if (cn & 1)
X				x.word >>= 4;
X			prevcn = cn;
X			cn = x.word & 0x0fff;
X/*
X *  Force the special cluster numbers in the range
X *  0x0ff0-0x0fff to be the same as for 16 bit cluster
X *  numbers to let the rest of pcfs think it is always
X *  dealing with 16 bit fats.
X */
X			if ((cn & 0x0ff0) == 0x0ff0)
X				cn |= 0xf000;
X		}
X	} else {				/* 16 bit fat	*/
X		for (; i < findcn; i++) {
X			if (PCFSEOF(cn)) {
X				goto hiteof;
X			}
X			byteoffset = cn << 1;
X			bn = (byteoffset >> pmp->pm_bnshift) + pmp->pm_fatblk;
X			bo = byteoffset &  pmp->pm_brbomask;
X			if (bn != bp0_bn) {
X				if (bp0)
X					brelse(bp0);
X				if (error = bread(pmp->pm_devvp, bn,
X				    pmp->pm_BytesPerSec, NOCRED, &bp0)) {
X					brelse(bp0);
X					return error;
X				}
X				bp0_bn = bn;
X			}
X			prevcn = cn;
X			cn = *(u_short *)(bp0->b_un.b_addr+bo);
X		}
X	}
X
X	if (!PCFSEOF(cn)) {
X		if (bp0)
X			brelse(bp0);
X		if (bnp)
X			*bnp = cntobn(pmp, cn);
X		if (cnp)
X			*cnp = cn;
X		fc_setcache(dep, FC_LASTMAP, i, cn);
X		return 0;
X	}
X
Xhiteof:;
X	if (cnp)
X		*cnp = i;
X	if (bp0)
X		brelse(bp0);
X	/* update last file cluster entry in the fat cache */
X	fc_setcache(dep, FC_LASTFC, i-1, prevcn);
X	return E2BIG;
X}
X
X/*
X *  Find the closest entry in the fat cache to the
X *  cluster we are looking for.
X */
Xfc_lookup(dep, findcn, frcnp, fsrcnp)
X	struct denode *dep;
X	unsigned long findcn;
X	unsigned long *frcnp;
X	unsigned long *fsrcnp;
X{
X	int i;
X	unsigned long cn;
X	struct fatcache *closest = 0;
X
X	for (i = 0; i < FC_SIZE; i++) {
X		cn = dep->de_fc[i].fc_frcn;
X		if (cn != FCE_EMPTY  &&  cn <= findcn) {
X			if (closest == 0  ||  cn > closest->fc_frcn)
X				closest = &dep->de_fc[i];
X		}
X	}
X	if (closest) {
X		*frcnp  = closest->fc_frcn;
X		*fsrcnp = closest->fc_fsrcn;
X	}
X}
X
X/*
X *  Purge the fat cache in denode dep of all entries
X *  relating to file relative cluster frcn and beyond.
X */
Xfc_purge(dep, frcn)
X	struct denode *dep;
X	unsigned int frcn;
X{
X	int i;
X	struct fatcache *fcp;
X
X	fcp = dep->de_fc;
X	for (i = 0; i < FC_SIZE; i++) {
X		if (fcp->fc_frcn != FCE_EMPTY  &&  fcp->fc_frcn >= frcn) {
X			fcp->fc_frcn = FCE_EMPTY;
X		}
X		fcp++;
X	}
X}
X
X/*
X *  Once the first fat is updated the other copies of
X *  the fat must also be updated.  This function does
X *  this.
X *  pmp - pcfsmount structure for filesystem to update
X *  bp0 - addr of modified fat block
X *  bp1 - addr of 2nd modified fat block (0 if not needed)
X *  fatbn - block number relative to begin of filesystem
X *    of the modified fat block.
X */
Xvoid
Xupdateotherfats(pmp, bp0, bp1, fatbn)
X	struct pcfsmount *pmp;
X	struct buf *bp0;
X	struct buf *bp1;
X	u_long fatbn;
X{
X	int i;
X	struct buf *bpn0;
X	struct buf *bpn1;
X
X#if defined(PCFSDEBUG)
Xprintf("updateotherfats(pmp %08x, bp0 %08x, bp1 %08x, fatbn %d)\n",
X	pmp, bp0, bp1, fatbn);
X#endif /* defined(PCFSDEBUG) */
X
X/*
X *  Now copy the block(s) of the modified fat to the other
X *  copies of the fat and write them out.  This is faster
X *  than reading in the other fats and then writing them
X *  back out.  This could tie up the fat for quite a while.
X *  Preventing others from accessing it.  To prevent us
X *  from going after the fat quite so much we use delayed
X *  writes, unless they specfied "synchronous" when the
X *  filesystem was mounted.  If synch is asked for then
X *  use bwrite()'s and really slow things down.
X */
X	for (i = 1; i < pmp->pm_FATs; i++) {
X		fatbn += pmp->pm_FATsecs;
X		/* getblk() never fails */
X		bpn0 = getblk(pmp->pm_devvp, fatbn, pmp->pm_BytesPerSec);
X		bcopy(bp0->b_un.b_addr, bpn0->b_un.b_addr,
X			pmp->pm_BytesPerSec);
X		if (pmp->pm_waitonfat)
X			bwrite(bpn0);
X		else
X			bdwrite(bpn0);
X		if (bp1) {
X			/* getblk() never fails */
X			bpn1 = getblk(pmp->pm_devvp, fatbn+1,
X			    pmp->pm_BytesPerSec);
X			bcopy(bp1->b_un.b_addr, bpn1->b_un.b_addr,
X				pmp->pm_BytesPerSec);
X			if (pmp->pm_waitonfat)
X				bwrite(bpn1);
X			else
X				bdwrite(bpn1);
X		}
X	}
X}
X
X/*
X *  Updating entries in 12 bit fats is a pain in the butt.
X *  So, we have a function to hide this ugliness.
X *
X *  The following picture shows where nibbles go when
X *  moving from a 12 bit cluster number into the appropriate
X *  bytes in the FAT.
X *
X *      byte m        byte m+1      byte m+2
X *    +----+----+   +----+----+   +----+----+
X *    |  0    1 |   |  2    3 |   |  4    5 |   FAT bytes
X *    +----+----+   +----+----+   +----+----+
X *
X *       +----+----+----+ +----+----+----+
X *       |  3    0    1 | |  4    5    2 |
X *       +----+----+----+ +----+----+----+
X *         cluster n        cluster n+1
X *
X *    Where n is even.
X *    m = n + (n >> 2)
X *
X *  This function is written for little endian machines.
X *  least significant byte stored into lowest address.
X */
Xvoid
Xsetfat12slot(bp0, bp1, oddcluster, byteoffset, newvalue)
X	struct buf *bp0;
X	struct buf *bp1;
X	int oddcluster;
X	u_int byteoffset;
X	u_short newvalue;
X{
X	unsigned char *b0;
X	unsigned char *b1;
X	union fattwiddle x;
X
X/*
X *  If we have a 2nd buf header and the byte offset is not the
X *  last byte in the buffer, then something is fishy.  Better
X *  tell someone.
X */
X	if (bp1  &&  byteoffset != 511)
X		printf("setfat12slot(): bp1 %08x, byteoffset %d shouldn't happen\n",
X			bp1, byteoffset);
X
X/*
X *  Get address of 1st byte and 2nd byte setup
X *  so we don't worry about which buf header to
X *  be poking around in.
X */
X	b0 = (unsigned char *)&bp0->b_un.b_addr[byteoffset];
X	if (bp1)
X		b1 = (unsigned char *)&bp1->b_un.b_addr[0];
X	else
X		b1 = b0 + 1;
X
X/*printf("setfat12(): offset %d, old %02x%02x, new %04x\n", byteoffset, *b0, *b1, newvalue); */
X
X	if (oddcluster) {
X		x.word = newvalue << 4;
X		*b0 = (*b0 & 0x0f) | x.byte[0];
X		*b1 = x.byte[1];
X	} else {
X		x.word = newvalue & 0x0fff;
X		*b0 = x.byte[0];
X		*b1 = (*b1 & 0xf0) | x.byte[1];
X	}
X/*printf("setfat12(): result %02x%02x\n", *b0, *b1); */
X}
X
Xint
Xclusterfree(pmp, cluster)
X	struct pcfsmount *pmp;
X	unsigned long cluster;
X{
X	int error;
X
X	error = fatentry(FAT_SET, pmp, cluster, 0, PCFSFREE);
X	if (error == 0) {
X/*
X *  If the cluster was successfully marked free, then update the count of
X *  free clusters, and turn off the "allocated" bit in the
X *  "in use" cluster bit map.
X */
X		pmp->pm_freeclustercount++;
X		pmp->pm_inusemap[cluster >> 3] &= ~(1 << (cluster & 0x07));
X	}
X	return error;
X}
X
X/*
X *  Get or Set or 'Get and Set' the cluster'th entry in the
X *  fat.
X *  function - whether to get or set a fat entry
X *  pmp - address of the pcfsmount structure for the
X *    filesystem whose fat is to be manipulated.
X *  cluster - which cluster is of interest
X *  oldcontents - address of a word that is to receive
X *    the contents of the cluster'th entry if this is
X *    a get function
X *  newcontents - the new value to be written into the
X *    cluster'th element of the fat if this is a set
X *    function.
X *
X *  This function can also be used to free a cluster
X *  by setting the fat entry for a cluster to 0.
X *
X *  All copies of the fat are updated if this is a set
X *  function.
X *  NOTE:
X *    If fatentry() marks a cluster as free it does not
X *    update the inusemap in the pcfsmount structure.
X *    This is left to the caller.
X */
Xint
Xfatentry(function, pmp, cluster, oldcontents, newcontents)
X	int function;
X	struct pcfsmount *pmp;
X	u_long cluster;
X	u_long *oldcontents;
X	u_long newcontents;
X{
X	int error;
X	u_long whichbyte;
X	u_long whichblk;
X	struct buf *bp0 = 0;
X	struct buf *bp1 = 0;
X	union fattwiddle x;
X/*printf("fatentry(func %d, pmp %08x, clust %d, oldcon %08x, newcon %d)\n",
X	function, pmp, cluster, oldcontents, newcontents);*/
X
X/*
X *  Be sure they asked us to do something.
X */
X	if ((function & (FAT_SET | FAT_GET)) == 0) {
X		printf("fatentry(): function code doesn't specify get or set\n");
X		return EINVAL;
X	}
X
X/*
X *  If they asked us to return a cluster number
X *  but didn't tell us where to put it, give them
X *  an error.
X */
X	if ((function & FAT_GET)  &&  oldcontents == NULL) {
X		printf("fatentry(): get function with no place to put result\n");
X		return EINVAL;
X	}
X
X/*
X *  Be sure the requested cluster is in the filesystem.
X */
X	if (cluster < CLUST_FIRST || cluster > pmp->pm_maxcluster)
X		return EINVAL;
X
X	if (FAT12(pmp)) {
X		whichbyte = cluster + (cluster >> 1);
X		whichblk  = (whichbyte >> pmp->pm_bnshift) + pmp->pm_fatblk;
X		whichbyte &= pmp->pm_brbomask;
X/*
X *  Read in the fat block containing the entry of interest.
X *  If the entry spans 2 blocks, read both blocks.
X */
X		error = bread(pmp->pm_devvp, whichblk,
X		    pmp->pm_BytesPerSec, NOCRED, &bp0);
X		if (error) {
X			brelse(bp0);
X			return error;
X		}
X		if (whichbyte == (pmp->pm_BytesPerSec-1)) {
X			error = bread(pmp->pm_devvp, whichblk+1,
X			    pmp->pm_BytesPerSec, NOCRED, &bp1);
X			if (error) {
X				brelse(bp0);
X				return error;
X			}
X		}
X		if (function & FAT_GET) {
X			x.byte[0] = bp0->b_un.b_addr[whichbyte];
X			x.byte[1] = bp1 ? bp1->b_un.b_addr[0] :
X					  bp0->b_un.b_addr[whichbyte+1];
X			if (cluster & 1)
X				x.word >>= 4;
X			x.word &= 0x0fff;
X			/* map certain 12 bit fat entries to 16 bit */
X			if ((x.word & 0x0ff0) == 0x0ff0)
X				x.word |= 0xf000;
X			*oldcontents = x.word;
X		}
X		if (function & FAT_SET) {
X			setfat12slot(bp0, bp1, cluster & 1, whichbyte,
X				newcontents);
X			updateotherfats(pmp, bp0, bp1, whichblk);
X
X/*
X *  Write out the first fat last.
X */
X			if (pmp->pm_waitonfat)
X				bwrite(bp0);
X			else
X				bdwrite(bp0);
X			bp0 = NULL;
X			if (bp1) {
X				if (pmp->pm_waitonfat)
X					bwrite(bp1);
X				else
X					bdwrite(bp1);
X				bp1 = NULL;
X			}
X			pmp->pm_fmod++;
X		}
X	} else {	/* fat16 */
X		whichbyte = cluster << 1;
X		whichblk  = (whichbyte >> pmp->pm_bnshift) + pmp->pm_fatblk;
X		whichbyte &= pmp->pm_brbomask;
X		error = bread(pmp->pm_devvp, whichblk,
X		    pmp->pm_BytesPerSec, NOCRED, &bp0);
X		if (error) {
X			brelse(bp0);
X			return error;
X		}
X		if (function & FAT_GET) {
X			*oldcontents = *((u_short *)(bp0->b_un.b_addr +
X				whichbyte));
X		}
X		if (function & FAT_SET) {
X			*(u_short *)(bp0->b_un.b_addr+whichbyte) = newcontents;
X			updateotherfats(pmp, bp0, 0, whichblk);
X			if (pmp->pm_waitonfat)
X				bwrite(bp0);	/* write out blk from the 1st fat */
X			else
X				bdwrite(bp0);
X			bp0 = NULL;
X			pmp->pm_fmod++;
X		}
X	}
X	if (bp0)
X		brelse(bp0);
X	if (bp1)
X		brelse(bp1);
X	return 0;
X}
X
X/*
X *  Allocate a free cluster.
X *  pmp - 
X *  retcluster - put the allocated cluster's number here.
X *  fillwith - put this value into the fat entry for the
X *     allocated cluster.
X */
Xint
Xclusteralloc(pmp, retcluster, fillwith)
X	struct pcfsmount *pmp;
X	unsigned long *retcluster;
X	unsigned long fillwith;
X{
X	int error;
X	u_long cn;
X	u_long end_cn;
X
X	/* This for loop really needs to start from 0. */
X	for (cn = 0; cn <= pmp->pm_maxcluster; cn += 8) {
X		if (pmp->pm_inusemap[cn >> 3] != 0xff) {
X			end_cn = cn | 0x07;
X			for (;cn <= end_cn; cn++) {
X				if ((pmp->pm_inusemap[cn >> 3] & (1 << (cn & 0x07))) == 0)
X					goto found_one;
X			}
X			printf("clusteralloc(): this shouldn't happen\n");
X		}
X	}
X	return ENOSPC;
X
Xfound_one:;
X	error = fatentry(FAT_SET, pmp, cn, 0, fillwith);
X	if (error == 0) {
X		pmp->pm_inusemap[cn >> 3] |= 1 << (cn & 0x07);
X		pmp->pm_freeclustercount--;
X		pmp->pm_fmod++;
X		*retcluster = cn;
X	}
X#if defined(PCFSDEBUG)
Xprintf("clusteralloc(): allocated cluster %d\n", cn);
X#endif /* defined(PCFSDEBUG) */
X	return error;
X}
X
X/*
X *  Free a chain of clusters.
X *  pmp - address of the pcfs mount structure for the
X *    filesystem containing the cluster chain to be freed.
X *  startcluster - number of the 1st cluster in the chain
X *    of clusters to be freed.
X */
Xint
Xfreeclusterchain(pmp, startcluster)
X	struct pcfsmount *pmp;
X	unsigned long startcluster;
X{
X	unsigned long nextcluster;
X	int error = 0;
X
X	while (startcluster >= CLUST_FIRST  &&  startcluster <= pmp->pm_maxcluster) {
X		error = fatentry(FAT_GET_AND_SET, pmp, startcluster,
X			&nextcluster, PCFSFREE);
X		if (error) {
X			printf("freeclusterchain(): free failed, cluster %d\n",
X				startcluster);
X			break;
X		}
X/*
X *  If the cluster was successfully marked free, then update the count of
X *  free clusters, and turn off the "allocated" bit in the
X *  "in use" cluster bit map.
X */
X		pmp->pm_freeclustercount++;
X		pmp->pm_inusemap[startcluster >> 3] &=
X			~(1 << (startcluster & 0x07));
X		startcluster = nextcluster;
X		pmp->pm_fmod++;
X	}
X	return error;
X}
X
X/*
X *  Read in fat blocks looking for free clusters.
X *  For every free cluster found turn off its
X *  corresponding bit in the pm_inusemap.
X */
Xint
Xfillinusemap(pmp)
X	struct pcfsmount *pmp;
X{
X	struct buf *bp0 = 0;
X	daddr_t bp0_blk = -1;
X	struct buf *bp1 = 0;
X	daddr_t cn;
X	daddr_t whichblk;
X	int whichbyte;
X	int error;
X	union fattwiddle x;
X
X/*
X *  Mark all clusters in use, we mark the free ones in the
X *  fat scan loop further down.
X */
X	for (cn = 0; cn < (pmp->pm_maxcluster >> 3) + 1; cn++)
X		pmp->pm_inusemap[cn] = 0xff;
X
X/*
X *  Figure how many free clusters are in the filesystem
X *  by ripping thougth the fat counting the number of
X *  entries whose content is zero.  These represent free
X *  clusters.
X */
X	pmp->pm_freeclustercount = 0;
X	pmp->pm_lookhere = -1;
X	for (cn = CLUST_FIRST; cn <= pmp->pm_maxcluster; cn++) {
X		if (FAT12(pmp)) {
X			whichbyte = cn + (cn >> 1);
X			whichblk  = (whichbyte >> pmp->pm_bnshift) + pmp->pm_fatblk;
X			whichbyte = whichbyte & pmp->pm_brbomask;
X			if (whichblk != bp0_blk) {
X				if (bp0)
X					brelse(bp0);
X				error = bread(pmp->pm_devvp, whichblk,
X					pmp->pm_BytesPerSec, NOCRED, &bp0);
X				if (error) {
X					goto error_exit;
X				}
X				bp0_blk = whichblk;
X			}
X			x.byte[0] = bp0->b_un.b_addr[whichbyte];
X			if (whichbyte == (pmp->pm_BytesPerSec-1)) {
X				error = bread(pmp->pm_devvp, whichblk+1,
X					pmp->pm_BytesPerSec, NOCRED, &bp1);
X				if (error)
X					goto error_exit;
X				x.byte[1] = bp1->b_un.b_addr[0];
X				brelse(bp0);
X				bp0 = bp1;
X				bp1 = NULL;
X				bp0_blk++;
X			} else {
X				x.byte[1] = bp0->b_un.b_addr[whichbyte + 1];
X			}
X			if (cn & 1)
X				x.word >>= 4;
X			x.word &= 0x0fff;
X		} else {	/* 16 bit fat	*/
X			whichbyte = cn << 1;
X			whichblk  = (whichbyte >> pmp->pm_bnshift) + pmp->pm_fatblk;
X			whichbyte = whichbyte & pmp->pm_brbomask;
X			if (whichblk != bp0_blk) {
X				if (bp0)
X					brelse(bp0);
X				error = bread(pmp->pm_devvp, whichblk,
X					pmp->pm_BytesPerSec, NOCRED, &bp0);
X				if (error)
X					goto error_exit;
X				bp0_blk = whichblk;
X			}
X			x.byte[0] = bp0->b_un.b_addr[whichbyte];
X			x.byte[1] = bp0->b_un.b_addr[whichbyte+1];
X		}
X		if (x.word == 0) {
X			pmp->pm_freeclustercount++;
X			pmp->pm_inusemap[cn >> 3] &= ~(1 << (cn & 0x07));
X			if (pmp->pm_lookhere < 0)
X				pmp->pm_lookhere = cn;
X		}
X	}
X	brelse(bp0);
X	return 0;
X
Xerror_exit:;
X	if (bp0)
X		brelse(bp0);
X	if (bp1)
X		brelse(bp1);
X	return error;
X}
X
X/*
X *  Allocate a new cluster and chain it onto the end of the
X *  file.
X *  dep - the file to extend
X *  bpp - where to return the address of the buf header for the
X *        new file block
X *  ncp - where to put cluster number of the newly allocated file block
X *        If this pointer is 0, do not return the cluster number.
X *
X *  NOTE:
X *   This function is not responsible for turning on the DEUPD
X *   bit if the de_flag field of the denode and it does not
X *   change the de_FileSize field.  This is left for the caller
X *   to do.
X */
Xint
Xextendfile(dep, bpp, ncp)
X	struct denode *dep;
X	struct buf **bpp;
X	unsigned int *ncp;
X{
X	int error = 0;
X	unsigned long frcn;
X	unsigned long cn;
X	struct pcfsmount *pmp = dep->de_pmp;
X
X/*
X *  Don't try to extend the root directory
X */
X	if (DETOV(dep)->v_flag & VROOT) {
X		printf("extendfile(): attempt to extend root directory\n");
X		return ENOSPC;
X	}
X
X/*
X *  If the "file's last cluster" cache entry is empty,
X *  and the file is not empty,
X *  then fill the cache entry by calling pcbmap().
X */
X	fc_fileextends++;
X	if (dep->de_fc[FC_LASTFC].fc_frcn == FCE_EMPTY  &&
X	    dep->de_StartCluster != 0) {
X		fc_lfcempty++;
X		error = pcbmap(dep, 0xffff, 0, &cn);
X		/* we expect it to return E2BIG */
X		if (error != E2BIG)
X			return error;
X		error = 0;
X	}
X
X/*
X *  Allocate another cluster and chain onto the end of the file.
X *  If the file is empty we make de_StartCluster point to the
X *  new block.  Note that de_StartCluster being 0 is sufficient
X *  to be sure the file is empty since we exclude attempts to
X *  extend the root directory above, and the root dir is the
X *  only file with a startcluster of 0 that has blocks allocated
X *  (sort of).
X */
X	if (error = clusteralloc(pmp, &cn, CLUST_EOFE))
X		return error;
X	if (dep->de_StartCluster == 0) {
X		dep->de_StartCluster = cn;
X		frcn = 0;
X	} else {
X		error = fatentry(FAT_SET, pmp, dep->de_fc[FC_LASTFC].fc_fsrcn,
X			0, cn);
X		if (error) {
X			clusterfree(pmp, cn);
X			return error;
X		}
X
X		frcn = dep->de_fc[FC_LASTFC].fc_frcn + 1;
X	}
X
X/*
X *  Update the "last cluster of the file" entry in the denode's
X *  fat cache.
X */
X	fc_setcache(dep, FC_LASTFC, frcn, cn);
X
X/*
X *  Get the buf header for the new block of the file.
X */
X	if (dep->de_Attributes & ATTR_DIRECTORY) {
X		*bpp = getblk(pmp->pm_devvp, cntobn(pmp, cn),
X			pmp->pm_bpcluster);
X	} else {
X		*bpp = getblk(DETOV(dep), frcn,
X			pmp->pm_bpcluster);
X	}
X	clrbuf(*bpp);
X
X/*
X *  Give them the filesystem relative cluster number
X *  if they want it.
X */
X	if (ncp)
X		*ncp = cn;
X	return 0;
X}
END-of-/sys/pcfs/pcfs_fat.c
exit