/*
 * 5799-WZQ (C) COPYRIGHT = NONE
 * LICENSED MATERIALS - PROPERTY OF IBM
 */
/* $Header:dfs_vnodeops.c 12.1$ */
/* $ACIS:dfs_vnodeops.c 12.1$ */
/* $Source: /ibm/acis/usr/sys/dfs/RCS/dfs_vnodeops.c,v $ */

#ifndef lint
static char *rcsid = "$Header:dfs_vnodeops.c 12.1$";
#endif

/* @(#)dfs_vnodeops.c	1.5 87/09/11 3.2/4.3DFSSRC */
/*
 * Copyright (c) 1986 by Sun Microsystems, Inc.
 */

#ifdef DFS
#include "param.h"
#include "systm.h"
#include "dir.h"
#include "user.h"
#include "vnode.h"
#include "vfs.h"
#include "file.h"
#include "uio.h"
#include "buf.h"
#include "kernel.h"
#include "cmap.h"
#include "proc.h"
#include "dfs.h"
#ifdef	USE_NAME_LOOKUP
#include "dnlc.h"
#endif
#include "../machine/clock.h"
#include "../ufs/fs.h"	/* for ROOTINO define (sigh) */

#ifdef DEBUG
extern int dfs_debug;
#define DEBUGF(x,y)	if (x) (y)
#else
#define DEBUGF(x,y)
#endif

struct vnode *dfs_make_vnode();
#ifdef	USE_NAME_LOOKUP
struct vnode *dnlc_lookup();
#endif
struct dnode *dfs_table[DFS_HASH_SIZE] = { 0 };
struct dnode *dfs_find();
u_char dfs_map_attributes();
u_short dfs_unmap_attributes();
char *dfs_map_name();
char *dfs_unmap_name();
u_long dfs_get_unix_time();
u_long dfs_get_dos_time();
u_short dfs_map_fid();
u_long	exchl();
u_short exchw();

/*
 * These are the vnode ops routines which implement the vnode interface to
 * the dos file system.  
 */

int dfs_open();
int dfs_close();
int dfs_rdwr();
int dfs_ioctl();
int dfs_select();
int dfs_getattr();
int dfs_setattr();
int dfs_access();
int dfs_lookup();
int dfs_create();
int dfs_remove();
int dfs_link();
int dfs_rename();
int dfs_mkdir();
int dfs_rmdir();
int dfs_readdir();
int dfs_symlink();
int dfs_readlink();
int dfs_fsync();
int dfs_inactive();
int dfs_bmap();
int dfs_bread();
int dfs_brelse();
int dfs_fid();
int dfs_badop();
int dfs_lockctl();
int dfs_noop();

struct vnodeops dfs_vnodeops = {
	dfs_open,
	dfs_close,
	dfs_rdwr,
	dfs_ioctl,
	dfs_select,
	dfs_getattr,
	dfs_setattr,
	dfs_access,
	dfs_lookup,
	dfs_create,
	dfs_remove,
	dfs_link,
	dfs_rename,
	dfs_mkdir,
	dfs_rmdir,
	dfs_readdir,
	dfs_symlink,
	dfs_readlink,
	dfs_fsync,
	dfs_inactive,
	dfs_bmap,
	dfs_badop,
	dfs_bread,
	dfs_brelse,
	dfs_lockctl,
	dfs_fid,
	dfs_badop
};

/*ARGSUSED*/
int
dfs_open(vpp, flag, cred)
	register struct vnode **vpp;
	int flag;
	struct ucred *cred;
{
	return (0);
}

/*ARGSUSED*/
int
dfs_close(vp, flag, cred)
	struct vnode *vp;
	int flag;
	struct ucred *cred;
{

	return(0);
}

int
dfs_rdwr(vp, uio, rw, ioflag, cred)
	register struct vnode *vp;
	struct uio *uio;
	enum uio_rw rw;
	int ioflag;
	struct ucred *cred;
{
	int error = 0,size;
	struct dfs_dir *ddp = VTODir(vp);
	DEBUGF(dfs_debug & 1,printf("dfs_rdwr called\n"));

	if (dfs_is_directory(ddp)) {
		return (EISDIR);
	}

	if (rw == UIO_READ && uio->uio_resid == 0) {
		return (0);
	}
	if (uio->uio_offset < 0 || (uio->uio_offset + uio->uio_resid) < 0) {
		return (EINVAL);
	}
	if (rw == UIO_WRITE && uio->uio_offset + uio->uio_resid >
				      u.u_rlimit[RLIMIT_FSIZE].rlim_cur) {
		psignal(u.u_procp, SIGXFSZ);
		return (EFBIG);
	}
	
	dlock(vp);
	if ((ioflag & IO_APPEND) && (rw == UIO_WRITE)) {
		uio->uio_offset = dfs_get_size(ddp);
	}

	if (rw == UIO_READ && (uio->uio_offset + uio->uio_resid) >
						 	dfs_get_size(ddp)) {
		size = dfs_get_size(ddp) - uio->uio_offset;
	} else {
		size = uio->uio_resid;
	}
		

	if (rw == UIO_READ) {
		error =	dfs_read(vp,uio->uio_offset,size,uio,1);
	} else {
#ifdef DFS_RO
		panic("dfs_rdwr:write on RO fs");
#endif
		error =	dfs_write(vp,uio->uio_offset,size,uio,1);
	}
	dunlock(vp);
	return (error);
}


/*ARGSUSED*/
int
dfs_ioctl(vp, com, data, flag, cred)
	struct vnode *vp;
	int com;
	caddr_t data;
	int flag;
	struct ucred *cred;
{

	return (EINVAL);
}

/*ARGSUSED*/
int
dfs_select(vp, which, cred)
	struct vnode *vp;
	int which;
	struct ucred *cred;
{

	return (EINVAL);
}


int
dfs_getattr(vp, vap, cred)
	struct vnode *vp;
	struct vattr *vap;
	struct ucred *cred;
{
	struct  dfs_vfs_info *dfs = VFSTODFS(vp->v_vfsp);
	int	mnt_flags = dfs_get_mnt_flags(vp->v_vfsp);
	struct dfs_dir *ddp = VTODir(vp);

	DEBUGF(dfs_debug & 1,printf("dfs_getattr called\n"));

#ifdef DEBUG
	if (dfs_debug & 0x10) {
		printf("vnode values->\n");
		printf(" v_flag = %x\n",vp->v_flag);
		printf(" v_count = %d\n",vp->v_count);
		printf(" v_shlockc = %d\n",vp->v_shlockc);
		printf(" v_exlockc = %d\n",vp->v_exlockc);
		printf(" v_type = %d\n",vp->v_type);
		printf(" v_rdev = %d\n",vp->v_rdev);
	}
#endif

	if (dfs_is_directory(ddp)) {
		vap->va_type = VDIR;
		vap->va_size =dfs_get_dir_size(vp,ddp);
		vap->va_blocks = vap->va_size / DFS_BSIZE;
	} else {
		vap->va_type = VREG;
		vap->va_size = dfs_get_size(ddp);
		if (vap->va_size) {
			int resid;
			vap->va_blocks = (vap->va_size + DFS_BSIZE - 1)
								   /DFS_BSIZE;
			if (resid = 
				vap->va_blocks % dfs_cluster_size(vp->v_vfsp)) {
				vap->va_blocks += dfs_cluster_size(vp->v_vfsp)
								       - resid;
			}
		} else {
			vap->va_blocks = dfs_cluster_size(vp->v_vfsp);
		}
	}
	vap->va_mode = dfs_unmap_attributes(mnt_flags,
				ddp->dfs_attributes,dfs_is_directory(ddp));
	vap->va_uid = dfs->dfs_vuid;
	vap->va_gid = dfs->dfs_vgid;
	vap->va_fsid = vp->v_rdev | (VFSTODFS(vp->v_vfsp)->dfs_vext) << 16;
	vap->va_nodeid = dfs_map_fid(ddp->dfs_start);
	vap->va_nlink = 1;
	vap->va_atime.tv_sec = vap->va_mtime.tv_sec = vap->va_ctime.tv_sec =
					dfs_get_unix_time(mnt_flags,ddp);
	vap->va_rdev = vp->v_rdev;
	vap->va_blocksize = DFS_BSIZE;

#ifdef DEBUG
	if (dfs_debug & 0x10) {
		printf("vap values ->\n");
		printf(" va_type = %d\n",vap->va_type);
		printf(" va_mode = %x\n",vap->va_mode);
		printf(" va_uid = %d\n",vap->va_uid);
		printf(" va_gid = %d\n",vap->va_gid);
		printf(" va_fsid = %d\n",vap->va_fsid);
		printf(" va_nodeid = %d\n",vap->va_nodeid);
		printf(" va_nlink = %d\n",vap->va_nlink);
		printf(" va_size = %d\n",vap->va_size);
		printf(" va_blocksize = %d\n",vap->va_blocksize);
		printf(" va_rdev = %x\n",vap->va_rdev);
		printf(" va_blocks = %d\n",vap->va_blocks);
	}
#endif
	DEBUGF(dfs_debug & 1,printf("dfs_getattr done\n"));
	return (0);
}

int
dfs_setattr(vp, vap, cred)
	register struct vnode *vp;
	register struct vattr *vap;
	struct ucred *cred;
{
	struct dfs_dir *ddp = VTODir(vp);
	u_char	attributes;
	u_long	set_time;
	int	mnt_flags = dfs_get_mnt_flags(vp->v_vfsp);
	int	error;

#ifdef DFS_RO
		panic("dfs_setattr:write on RO fs");
#endif

	DEBUGF(dfs_debug & 1,printf("dfs_setattr called\n"));
	if ((vap->va_nlink != -1) || (vap->va_blocksize != -1) ||
	    (vap->va_rdev != -1) || (vap->va_blocks != -1) ||
	    (vap->va_fsid != -1) || (vap->va_nodeid != -1) ||
	    (vap->va_type != VINIT)) {
		return (EINVAL);
	}

	if (mnt_flags & DFS_CHANGE_USER) {
		if (vap->va_uid != -1) {
			if (cred->cr_uid)
				return(EPERM);
			VFSTODFS(vp->v_vfsp)->dfs_vuid = vap->va_uid;
		}
	}
	if (mnt_flags & DFS_CHANGE_GROUP) {
		if (vap->va_gid != -1) {
			if (cred->cr_uid)
				return(EPERM);
			VFSTODFS(vp->v_vfsp)->dfs_vgid = vap->va_gid;
		}
	}
	/* don't actually change directory attributes */
	if (dfs_is_directory(VTODir(vp))) {
		return(0);
	}
	dlock(vp);
	if (vap->va_mtime.tv_sec != -1) {
		set_time = vap->va_mtime.tv_sec;
	} else {
		set_time = 0;
	}
	if (vap->va_mode != (u_short)-1) {
		attributes = dfs_map_attributes(mnt_flags,vap->va_mode);
		if (attributes == DFS_BAD_ATTRIBUTES) {
			return(EPERM);
		}
		ddp->dfs_attributes = attributes;
	}
	error = dfs_write_dir(vp,set_time);
	dunlock(vp);
	DEBUGF(dfs_debug & 1,printf("dfs_setattr complete\n"));
	return (error);
}

int
dfs_access(vp, mode, cred)
	struct vnode *vp;
	int mode;
	struct ucred *cred;
{
	int	dos_att = VTODir(vp)->dfs_attributes;
	struct	dfs_vfs_info *dfs = VFSTODFS(vp->v_vfsp);
	int	i,mnt_flags = dfs->dfs_vmnt;

	u.u_error = EACCES;
	/* allow writes only if read only is off */
	if ((dos_att & DFS_READ_ONLY) && (mode & (VWRITE >> 6))) {
		return (EACCES);
	}
	/* allow execute only if noexec if off or the vnode is a directory */
	if ((mnt_flags & DFS_NOEXECUTE) &&
		 ((dos_att & DFS_DIRECTORY) == 0) && (mode & (VEXEC >> 6))) {
		return (EACCES);
	}
	u.u_error = 0;
	/* now make sure we even have access to the file */
	if ((mnt_flags & (DFS_USER|DFS_GROUP)) == 0) {
		/* every one has access */
		return(0);
	}
	if ((mnt_flags & DFS_USER) && (cred->cr_uid == dfs->dfs_vuid)) {
		/* user has access and we are that user */
		return(0);
	}
	if (mnt_flags & DFS_GROUP) {
		for (i=0; i < NGROUPS; i++) {
			if (cred->cr_groups[i] == NOGROUP)
				break;
			/* group has access and we are in that group */
			if (cred->cr_groups[i] == dfs->dfs_vgid)
				return(0);
		}
	}
	/*
	 * group or user only has access and we are not that user or in that
	 * group.
	 */
	u.u_error = EACCES;
	return (EACCES);
}

int
dfs_readlink(vp, uiop, cred)
	struct vnode *vp;
	struct uio *uiop;
	struct ucred *cred;
{
	return(ENXIO);
}

/*ARGSUSED*/
int
dfs_fsync(vp, cred)
	struct vnode *vp;
	struct ucred *cred;
{
	struct  dfs_vfs_info *dfs = VFSTODFS(vp->v_vfsp);
	int	cluster_size = dfs->dfs_vcluster_size*DFS_BSIZE;
	int	cluster;

	DEBUGF(dfs_debug & 1,printf("dfs_fsync called\n"));
	if (vp->v_flag & VROOT) {
		int	end_cluster = dfs->dfs_vdir_size/DFS_BSIZE;
		for (cluster = 0; cluster < end_cluster; cluster++) {
			blkflush(dfs->dfs_vdevvp, dfs->dfs_vdir_start+cluster,
								    DFS_BSIZE);
		}
		return(0);
	}
	
	for (cluster = dfs_get_start(VTODir(vp)); !DFS_IS_EOF(dfs,cluster) ;
				cluster = dfs_get_cluster(dfs,cluster) ) {
		blkflush(dfs->dfs_vdevvp, CLTOBLKS(dfs,cluster), cluster_size);
	}
	return(0);
}

/*ARGSUSED*/
int
dfs_inactive(vp, cred)
	struct vnode *vp;
	struct ucred *cred;
{
#ifndef DFS_RO
	register struct dfs_dir *ddp = VTODir(vp);
	int	error;
	unsigned long pages[FAT_PAGES];

	DEBUGF(dfs_debug & 1,printf("dfs_inactive called\n"));
	if (ddp->dfs_flag == DFS_DIR_ERASED) {
		bzero(pages,sizeof(pages));
		dfs_free_links(vp->v_vfsp,ddp,pages);
		dfs_lock_fat(vp->v_vfsp,pages);
		error = dfs_write_fat(vp->v_vfsp,pages);
		if (error) {
			return(error);
		}
	}
#else
	DEBUGF(dfs_debug & 1,printf("dfs_inactive called\n"));
#endif
	VFSTODFS(vp->v_vfsp)->dfs_vrefcnt--;
	dfs_free(vp);
	return (0);
}

/*
 * look up a dos file. Note: Unix names and DOS names have to be cross mapped.
 * How these names are mapped is a function of the options to mount. Notice 
 * that the map is done before the vnode lookup.
 */

#ifdef DEBUG
char *
dfs_mkstring(s)
	u_char	*s;
{
	static	char buf[DFS_MAX_NAME_LENGTH+1];

	bcopy(s,buf,DFS_MAX_NAME_LENGTH);
	buf[DFS_MAX_NAME_LENGTH] = 0;
	return(buf);
}
#endif DEBUG

dfs_lookup(dvp, nm, vpp, cred)
	struct vnode *dvp;
	char *nm;
	struct vnode **vpp;
	struct ucred *cred;
{
	int error;
	struct dfs_dir *ddp = VTODir(dvp);
	int	cluster_size = VFSTODFS(dvp->v_vfsp)->dfs_vcluster_size
								*DFS_BSIZE;
	struct buf *bp;
	struct dfs_dir *lpd;
	char	*name;
	char	nb[DFS_MAX_NAME_LENGTH+1];
	int	count,offset;

	/*
	 * make sure we are a directory.
	 */
	DEBUGF(dfs_debug & 0x400,
		printf("dfs_lookup called for <%s> in dir <%s>..",
			nm,dfs_mkstring(ddp->dfs_name)));
	if (!dfs_is_directory(ddp)) {
		DEBUGF(dfs_debug & 0x400,
			printf("but <%s> is not a directory\n",
						 dfs_mkstring(ddp->dfs_name)));
		return(ENOTDIR);
	}

	/*
	 * check for '.' and '..'
	 */
	if (*nm == '.') {
		if (nm[1] == 0) {
			*vpp = dvp;
			VN_HOLD(dvp);
			DEBUGF(dfs_debug & 0x400,
				printf("which is itself <%s>\n",
					dfs_mkstring(VTODir(*vpp)->dfs_name)));
			return(0);
		} else if ((nm[1] == '.')  && (nm[2] == 0)) {
			if (VTOD(dvp)->dfs_parent) {
				*vpp = VTOD(dvp)->dfs_parent;
				VN_HOLD(*vpp);
				DEBUGF(dfs_debug & 0x400,
					printf("which is its parent <%s>\n",
					dfs_mkstring(VTODir(*vpp)->dfs_name)));
				return(0);
			} else {
				DEBUGF(dfs_debug & 0x400,
					printf("but root has no parent\n"));
				return(ENOENT);
			}
		}
	}

	/*
	 * if we can't map unix name into a dos file, then we can't possibly
	 * find it.
 	 */
	if ((name = dfs_map_name(dfs_get_mnt_flags(dvp->v_vfsp),nm,nb))
								 == NULL) {
		DEBUGF(dfs_debug & 0x400, 
				 printf("but <%s> doesn't map to dos\n",nm));
		return(EPERM);
	}

	/*
	 * look the name up in the name cache
	 */
#ifdef	USE_NAME_LOOKUP
	*vpp = dnlc_lookup(dvp, name, NOCRED);
	if (*vpp) {
		VN_HOLD(*vpp);
		DEBUGF(dfs_debug & 0x400,
			 printf("and found <%s> in name cache\n",
					dfs_mkstring(VTODir(*vpp)->dfs_name)));
		return(0);
	}
#endif

	dlock(dvp);
	/*
	 * read the directory in
	 */
	count = dfs_get_dir_size(dvp,ddp);
	if (dvp->v_flag & VROOT) {
		cluster_size = DFS_BSIZE;
	}
	for (offset = 0; offset < count; offset += cluster_size) {
		int 	i;

		bp = geteblk(cluster_size);
		error = dfs_read(dvp,offset,cluster_size,bp->b_un.b_addr,0);
		if (error) {
			brelse(bp);
			DEBUGF(dfs_debug & 0x400,
				 printf("but I can't read the directory\n"));
			dunlock(dvp);
			return(error);
		}

		/*
		 * search for the desired entry
		 */	
		for (lpd = (struct dfs_dir *)bp->b_un.b_addr, i = cluster_size;
			 (i > 0) && lpd->dfs_flag; lpd++,
					 i -= sizeof(struct dfs_dir)) {
			if (dfs_is_volume(lpd))
				continue;
			if ((*name == lpd->dfs_name[0]) && 
			 (bcmp(name,lpd->dfs_name, DFS_MAX_NAME_LENGTH) == 0)) {
				*vpp = dfs_make_vnode(dvp,lpd,lpd->dfs_start,
				       (caddr_t)lpd - bp->b_un.b_addr + offset);
#ifdef	USE_NAME_LOOKUP
				dnlc_enter(dvp, name, *vpp, NOCRED);
#endif
				DEBUGF(dfs_debug & 0x400,
				 printf("and found <%s> in directory\n",
					dfs_mkstring(VTODir(*vpp)->dfs_name)));
				brelse(bp);
				dunlock(dvp);
				return(0);
			}
		}
		brelse(bp);
		if ( i > 0 ) {
			break;
		}

	}
	DEBUGF(dfs_debug & 0x400,
			 printf("but I can't find <%s> in directory\n",name));
	dunlock(dvp);
	return(ENOENT);
}


/*
 * create a new dos file
 */
/*ARGSUSED*/
dfs_create(dvp, nm, va, exclusive, mode, vpp, cred)
	struct vnode *dvp;
	char *nm;
	struct vattr *va;
	enum vcexcl exclusive;
	int mode;
	struct vnode **vpp;
	struct ucred *cred;
{
	int error;
	struct dfs_dir	dentry;
	char	*name;
	char	nb[DFS_MAX_NAME_LENGTH+1];
	struct	buf *bp;
	int	tcount,count;
	u_short	start;
	struct  dfs_dir *lpd;
	int	mnt_flags = dfs_get_mnt_flags(dvp->v_vfsp);
	int	clust_size = VFSTODFS(dvp->v_vfsp)->dfs_vcluster_size
								*DFS_BSIZE;
	int	offset;
	unsigned long pages[FAT_PAGES];

#ifdef DFS_RO
		panic("dfs_create:write on RO fs");
#endif

	/*
	 * map the name. don't create a file which
	 * is invalid.
	 */
	DEBUGF(dfs_debug & 1,printf("dfs_create called\n"));
	if ((name = dfs_map_name(mnt_flags,nm,nb)) == 0) {
		return(EPERM);
	}

	/*
	 * don't create a new file in an erased directory
	 */
	if (VTODir(dvp)->dfs_flag == DFS_DIR_ERASED) {
		return(ENXIO);
	}

	/*
	 * first look up the file
	 */
	error = dfs_lookup(dvp, nm, vpp, cred);
	if (!error) {
		dlock(dvp);
		if (exclusive == EXCL) {
			dunlock(dvp);
			VN_RELE(*vpp);
			return (EEXIST);
		} else {
			struct dfs_dir	*ddp = VTODir(*vpp);
			u_char	attributes = dfs_map_attributes(mnt_flags,
								va->va_mode);
			unsigned long pages[FAT_PAGES];

			if (attributes == DFS_BAD_ATTRIBUTES) {
				dunlock(dvp);
				VN_RELE(*vpp);
				return(EPERM);
			}

			if (dfs_is_directory(ddp)) {
				dunlock(dvp);
				VN_RELE(*vpp);
				return(EISDIR);
			}
			bzero(pages,sizeof(pages));
			dfs_free_links(dvp->v_vfsp,ddp,pages);
			dfs_set_cluster(VFSTODFS(dvp->v_vfsp),dfs_get_start(ddp)
				,VFSTODFS(dvp->v_vfsp)->dfs_veof,pages);
			dfs_lock_fat(dvp->v_vfsp,pages);
			error = dfs_write_fat(dvp->v_vfsp,pages);
			if (error) {
				dunlock(dvp);
				VN_RELE(*vpp);
				return(error);
			}
			ddp->dfs_size = dfs_put_size(0);
			ddp->dfs_attributes = attributes;
			error = dfs_write_dir(*vpp,time.tv_sec);
			if (error) {
				dunlock(dvp);
				VN_RELE(*vpp);
				return(error);
			}
			dunlock(dvp);
			return(0);
		}
	}
	if ((dentry.dfs_attributes = dfs_map_attributes(mnt_flags,va->va_mode)) 
							== DFS_BAD_ATTRIBUTES) {
		return(EPERM);
	}
	bcopy(name,dentry.dfs_name,DFS_MAX_NAME_LENGTH);
	bzero(pages,sizeof(pages));
	error = dfs_alloc_cluster(dvp->v_vfsp,&start,pages);
	if (error) {
		return(error);
	}
	dfs_lock_fat(dvp->v_vfsp,pages);
	error = dfs_write_fat(dvp->v_vfsp,pages);
	dentry.dfs_start = dfs_put_start(start);
	dentry.dfs_size = dfs_put_size(0);
	dlock(dvp);
	tcount = dfs_get_dir_size(dvp,VTODir(dvp));
	if (dvp->v_flag & VROOT) {
		clust_size = DFS_BSIZE;
	}
	for (offset = 0; offset < tcount; offset += clust_size) {
		bp = geteblk(clust_size);
		error = dfs_read(dvp,offset,clust_size,bp->b_un.b_addr,0);
		if (error) {
			bzero(pages,sizeof(pages));
			dfs_free_cluster(dvp->v_vfsp,start,pages);
			dfs_lock_fat(dvp->v_vfsp,pages);
			dfs_write_fat(dvp->v_vfsp,pages);
			dunlock(dvp);
			brelse(bp);
			return(error);
		}

		/*
		 * search for an empty directory slot
		 */	
		for (lpd = (struct dfs_dir *)bp->b_un.b_addr,
		         count = clust_size; count > 0; lpd++,count -= 
						     sizeof(struct dfs_dir)) {
			if (dfs_is_empty(lpd)) {
				bcopy(&dentry,lpd,sizeof(struct dfs_dir));
				error = dfs_write(dvp,
				         (char *)lpd - bp->b_un.b_addr + offset,
			    			  sizeof(struct dfs_dir),lpd,0);
				if (error) {
					bzero(pages,sizeof(pages));
					dfs_free_cluster(dvp->v_vfsp,start,
								        pages);
					dfs_lock_fat(dvp->v_vfsp,pages);
					dfs_write_fat(dvp->v_vfsp,pages);
					brelse(bp);
					dunlock(dvp);
					return(error);
				}
				*vpp=dfs_make_vnode(dvp, &dentry,
			            dentry.dfs_start,
				     (caddr_t)lpd - bp->b_un.b_addr + offset);
#ifdef	USE_NAME_LOOKUP
				dnlc_enter(dvp, name, *vpp, NOCRED);
#endif
				brelse(bp);
				dunlock(dvp);
				return(0);
			}
		}

		brelse(bp);
	}

	/*
	 * no directory entries in the current directory. If the current
	 * directory is root, we're done (we can't expand the root directory),
	 * otherwise we expand the directory by one cluster.
	 */

	if (dvp->v_flag & VROOT) {
		bzero(pages,sizeof(pages));
		dfs_free_cluster(dvp->v_vfsp,start,pages);
		dfs_lock_fat(dvp->v_vfsp,pages);
		dfs_write_fat(dvp->v_vfsp,pages);
		dunlock(dvp);
		return(ENOSPC);
	}

	/*
	 * write a new cluster at the end of the directory
	 */
	bp = geteblk(clust_size);
	bzero(bp->b_un.b_addr,clust_size);
	bcopy(&dentry,bp->b_un.b_addr,sizeof(struct dfs_dir));
	error = dfs_write(dvp,tcount,clust_size,bp->b_un.b_addr,0);
	brelse(bp);
	if (error) {
		bzero(pages,sizeof(pages));
		dfs_free_cluster(dvp->v_vfsp,start,pages);
		dfs_lock_fat(dvp->v_vfsp,pages);
		dfs_write_fat(dvp->v_vfsp,pages);
		dunlock(dvp);
		return(error);
	}
	*vpp=dfs_make_vnode(dvp, &dentry, dentry.dfs_start, tcount);
	dunlock(dvp);
#ifdef	USE_NAME_LOOKUP
	dnlc_enter(dvp, name, *vpp, NOCRED);
#endif
	return (error);
}

dfs_remove(dvp, nm, cred)
	struct vnode *dvp;
	char *nm;
	struct ucred *cred;
{
	int	error;
	struct  vnode *vp;
	struct  dfs_dir *ddp,*lpd;
	struct  buf *bp;
	int	count,offset;
	int	cluster_size = VFSTODFS(dvp->v_vfsp)->dfs_vcluster_size
								*DFS_BSIZE;
	char	name[DFS_MAX_NAME_LENGTH+1];

#ifdef DFS_RO
		panic("dfs_remove:write on RO fs");
#endif
	DEBUGF(dfs_debug & 1,printf("dfs_remove called\n"));
	if (!strcmp(nm, ".") || !strcmp(nm, "..")) {
		return(EINVAL);
	} 
	dlock(dvp);
	error = dfs_lookup(dvp, nm, &vp, cred);
	if (error) {
		dunlock(dvp);
		return(error);
	}

	dlock(vp);
	ddp = VTODir(vp);
	if (dfs_is_directory(ddp)) {
		/*
		 * read the directory in
		 */
		count = dfs_get_dir_size(vp,ddp);
		if (vp->v_flag & VROOT) {
			cluster_size = DFS_BSIZE;
		}
		for (offset = 0; offset < count; offset += cluster_size) {
			int i;

			bp = geteblk(cluster_size);
			error = dfs_read(dvp,offset,cluster_size,
							bp->b_un.b_addr,0);
			if (error) {
				brelse(bp);
				dunlock(dvp);
				dunlock(vp);
				VN_RELE(vp);
				return(error);
			}
	
			/*
			 * search for the desired entry
			 */	
			for (lpd = (struct dfs_dir *)bp->b_un.b_addr, i=0;
				 (i > 0) && lpd->dfs_flag; lpd++, i -= 
						sizeof(struct dfs_dir)) {
				if (!dfs_is_empty(lpd) && (bcmp(".          ",
			 	    lpd->dfs_name, DFS_MAX_NAME_LENGTH))  &&
				     (bcmp("..         ",lpd->dfs_name,
						   DFS_MAX_NAME_LENGTH)) ) {
					brelse(bp);
					dunlock(dvp);
					dunlock(vp);
					VN_RELE(vp);
					return(ENOTEMPTY);
				}
			}
			brelse(bp);
			if (i>0)
				break;
		}
	}

	/* build a dos name to flush the name cache */
	bcopy(ddp->dfs_name,name,DFS_MAX_NAME_LENGTH);
	name[DFS_MAX_NAME_LENGTH] = 0;
#ifdef	USE_NAME_LOOKUP
	dnlc_remove(vp, name);
#endif

	ddp->dfs_flag = DFS_DIR_ERASED;
	error = dfs_write_dir(vp,time.tv_sec);
	dunlock(dvp);
	dunlock(vp);
	DEBUGF(dfs_debug & 0x200,printf("releasing vnode vp\n"));
	VN_RELE(vp);
	DEBUGF(dfs_debug & 1,printf("dfs_remove done\n"));
	return(error);
}

dfs_link(vp, tdvp, tnm, cred)
	struct vnode *vp;
	struct vnode *tdvp;
	char *tnm;
	struct ucred *cred;
{
	return (EINVAL);
}

dfs_rename(odvp, onm, ndvp, nnm, cred)
	struct vnode *odvp;
	char *onm;
	struct vnode *ndvp;
	char *nnm;
	struct ucred *cred;
{
	int error;
	struct vnode *vp;
	struct buf *bp;
	struct dfs_dir *lpd;
	struct dfs_dir dentry;
	char oname[DFS_MAX_NAME_LENGTH+1];
	char nname[DFS_MAX_NAME_LENGTH+1];
	char *np;
	int count,tcount,offset;
	int clust_size = VFSTODFS(ndvp->v_vfsp)->dfs_vcluster_size * DFS_BSIZE;
	int mnt_flags = dfs_get_mnt_flags(ndvp->v_vfsp);

#ifdef DFS_RO
		panic("dfs_rename:write on RO fs");
#endif
	/*
	 * actually we should check for DFS_DOT and DFS_DOT_DOT instead.
	 */	
	DEBUGF(dfs_debug & 1,printf("dfs_rename called\n"));
	if (!strcmp(onm, ".") || !strcmp(onm, "..") || !strcmp(nnm, ".")
	    || !strcmp (nnm, "..")) {
		return(EINVAL);
	} 

	/*
	 * first we remove the target
	 */
	dlock(ndvp);
	dlock(odvp);
	error = dfs_remove(ndvp, nnm, cred);
	if (error && (error != ENOENT)) {
		dunlock(ndvp);
		dunlock(odvp);
		return(error);
	}

	/*
	 * if we're using the same directory 
	 */
	if (ndvp == odvp) {
		error = dfs_lookup(odvp, onm, &vp, cred);
		if (error) {
			dunlock(ndvp);
			dunlock(odvp);
			return(error);
		}
		dlock(vp);
		bcopy(VTODir(vp)->dfs_name,oname,DFS_MAX_NAME_LENGTH);
		oname[DFS_MAX_NAME_LENGTH] = 0;
		np = dfs_map_name(mnt_flags,nnm,nname);
		if (np == 0) {
			dunlock(ndvp);
			dunlock(odvp);
			dunlock(vp);
			return(EPERM);
		}
		bcopy(np,VTODir(vp)->dfs_name,DFS_MAX_NAME_LENGTH);
#ifdef	USE_NAME_LOOKUP
		dnlc_remove(odvp, oname);
#endif
		error = dfs_write_dir(vp,0);
		dunlock(vp);
		dunlock(ndvp);
		dunlock(odvp);
		return(error);
	}

	/*
	 * we're renaming across directories
	 */
	np = dfs_map_name(mnt_flags,nnm,nname);
	if (np == 0) {
		dunlock(ndvp);
		dunlock(odvp);
		return(EPERM);
	}
	error = dfs_lookup(odvp, onm, &vp, cred);
	if (error) {
		dunlock(ndvp);
		dunlock(odvp);
		return(error);
	}
	dlock(vp);
	bcopy(VTODir(vp)->dfs_name,oname,DFS_MAX_NAME_LENGTH);
	oname[DFS_MAX_NAME_LENGTH] = 0;
	bcopy(VTODir(vp),&dentry,sizeof(struct dfs_dir));
	bcopy(np,dentry.dfs_name,DFS_MAX_NAME_LENGTH);
	tcount = dfs_get_dir_size(ndvp,VTODir(ndvp));
	if (ndvp->v_flag & VROOT) {
		clust_size = DFS_BSIZE;
	}
	for (offset = 0; offset < tcount; offset += clust_size) {
		bp = geteblk(clust_size);
		error = dfs_read(ndvp,offset,clust_size,bp->b_un.b_addr,0);
		if (error) {
			dunlock(ndvp);
			dunlock(odvp);
			dunlock(vp);
			VN_RELE(vp);
			brelse(bp);
			return(error);
		}

		/*
		 * search for an empty directory slot
		 */	
		for (lpd = (struct dfs_dir *)bp->b_un.b_addr, 
		     count = clust_size; count > 0; lpd++,
					   count -= sizeof(struct dfs_dir)) {
			if (dfs_is_empty(lpd)) {
				bcopy(&dentry,lpd,sizeof(struct dfs_dir));
				error = dfs_write(ndvp,
					(char *)lpd - bp->b_un.b_addr + offset,
				    		  sizeof(struct dfs_dir),lpd,0);
				brelse(bp);
				if (error) {
					dunlock(ndvp);
					dunlock(odvp);
					dunlock(vp);
					VN_RELE(vp);
					return(error);
				}
				/*
				 * mark the old directory as erased
				 */
				VTODir(vp)->dfs_flag = DFS_DIR_ERASED;
				error = dfs_write_dir(vp,0);
				if (error) {
					dunlock(vp);
					dunlock(ndvp);
					dunlock(odvp);
					VN_RELE(vp);
					return(error);
				}
				/*
				 * fix the old vnode to properly describe it's new
				 * location.
				 */
				bcopy(&dentry,VTODir(vp),sizeof(struct dfs_dir));
				VTOD(vp)->dfs_parent = ndvp;
				VTOD(vp)->dfs_dir_offset = 
				      ((char *)lpd) - bp->b_un.b_addr + offset;
				VN_HOLD(ndvp);
#ifdef	USE_NAME_LOOKUP
				dnlc_remove(odvp, oname);
#endif
				dunlock(ndvp);
				dunlock(odvp);
				dunlock(vp);
				VN_RELE(vp);
				VN_RELE(odvp);
				return(0);
			}
		}

		brelse(bp);
	}

	/*
	 * no directory entries in the current directory. If the current
	 * directory is root, we're done (we can't expand the root directory),
	 * otherwise we expand the directory by one cluster.
	 */

	if (ndvp->v_flag & VROOT) {
		dunlock(ndvp);
		dunlock(odvp);
		dunlock(vp);
		VN_RELE(vp);
		return(ENOSPC);
	}

	/*
	 * write a new cluster at the end of the directory
	 */
	bp = geteblk(clust_size);
	bzero(bp->b_un.b_addr,clust_size);
	bcopy(&dentry,bp->b_un.b_addr,sizeof(struct dfs_dir));
	error = dfs_write(ndvp,tcount,clust_size,bp->b_un.b_addr,0);
	brelse(bp);
	if (error) {
		dunlock(ndvp);
		dunlock(odvp);
		dunlock(vp);
		VN_RELE(vp);
		return(error);
	}
	VTODir(vp)->dfs_flag = DFS_DIR_ERASED;
	error = dfs_write_dir(vp,0);
	if (error) {
		dunlock(ndvp);
		dunlock(odvp);
		dunlock(vp);
		VN_RELE(vp);
		return(error);
	}
	bcopy(&dentry,VTODir(vp),sizeof(struct dfs_dir));
	VTOD(vp)->dfs_parent = ndvp;
	VTOD(vp)->dfs_dir_offset =  tcount;
	VN_HOLD(ndvp);
#ifdef	USE_NAME_LOOKUP
	dnlc_remove(odvp, oname);
#endif
	dunlock(ndvp);
	dunlock(odvp);
	dunlock(vp);
	VN_RELE(vp);
	VN_RELE(odvp);
	return(0);
}

dfs_mkdir(dvp, nm, va, vpp, cred)
	struct vnode *dvp;
	char *nm;
	register struct vattr *va;
	struct vnode **vpp;
	struct ucred *cred;
{
	int error;
	struct dfs_dir *ddp;
	struct dfs_dir *ddp2;
	struct buf *bp;
	int cluster_size;
#ifdef DFS_RO
		panic("dfs_mkdir:write on RO fs");
#endif
	DEBUGF(dfs_debug & 1,printf("dfs_mkdir called\n"));
	dlock(dvp);
	cluster_size = dfs_cluster_size(dvp->v_vfsp) * DFS_BSIZE;
 	error = dfs_create(dvp, nm, va, EXCL, 0, vpp, cred);
	if (error) {
		dunlock(dvp);
		return(error);
	}

	dlock(*vpp);
	ddp  = VTODir(*vpp);
	ddp->dfs_attributes |= DFS_DIRECTORY;
	ddp->dfs_attributes &= ~DFS_ARCHIVE;
	(*vpp)->v_type = VDIR;

	bp = geteblk(cluster_size);
	bzero(bp->b_un.b_addr,cluster_size);
	/* create '.' and '..' */
	ddp2 = (struct dfs_dir *)bp->b_un.b_addr;
	bcopy(ddp,ddp2,sizeof(struct dfs_dir));
	bcopy(".          ",ddp2->dfs_name,DFS_MAX_NAME_LENGTH);
	bcopy(VTODir(dvp),&ddp2[1],sizeof(struct dfs_dir));
	bcopy("..         ",&(ddp2[1].dfs_name),DFS_MAX_NAME_LENGTH);
	ddp2[1].dfs_time = ddp->dfs_time;
	ddp2[1].dfs_date = ddp->dfs_date;
	error = dfs_write(*vpp,0,cluster_size,bp->b_un.b_addr,0);
	if (!error) {
		error = dfs_write_dir(*vpp,0);
	}
	dunlock(*vpp);
	dunlock(dvp);
	brelse(bp);
	return (error);
}

dfs_rmdir(dvp, nm, cred)
	struct vnode *dvp;
	char *nm;
	struct ucred *cred;
{
#ifdef DFS_RO
		panic("dfs_rmdir:write on RO fs");
#endif
	DEBUGF(dfs_debug & 1,printf("dfs_rmdir called\n"));
	return(dfs_remove(dvp, nm, cred));
}

dfs_symlink(dvp, lnm, tva, tnm, cred)
	struct vnode *dvp;
	char *lnm;
	struct vattr *tva;
	char *tnm;
	struct ucred *cred;
{
	return (EINVAL);
}

/*
 * Read directory entries.
 * There are some weird things to look out for here.  The uio_offset
 * is directory index * sizeof struct direct. Ideally we would just
 * return uio_offset as index * sizeof struct dfs_dir, but the nfs_server
 * code "knows" that offsets can only be multiples of struct direct. (sigh).
 * Eventually the server code needs to be fixed, but for now we fake the offset
 * size.
 */
dfs_readdir(vp, uio, cred)
	struct vnode *vp;
	register struct uio *uio;
	struct ucred *cred;
{
	struct	direct udir;
	struct  dfs_dir ddir;
	int	error,size = dfs_get_dir_size(vp,VTODir(vp));
	int	mnt_flags = dfs_get_mnt_flags(vp->v_vfsp);
	char	name[DFS_MAX_NAME_LENGTH+1];

	DEBUGF(dfs_debug & 1,printf("dfs_readdir called\n"));

	while (uio->uio_resid) {
		if (uio->uio_offset > size) {
			return(EIO);
		}
		if (uio->uio_offset == size) {
			return(0);
		}
		if (uio->uio_resid < sizeof(struct dfs_dir)) {
			return(EIO);
		}
		error=dfs_read(vp,uio->uio_offset,sizeof(struct dfs_dir),
								      &ddir,0);
		if (error) {
			return(error);
		}

		if (ddir.dfs_flag == DFS_DIR_NOTUSED) {
			uio->uio_offset=size;
			DEBUGF(dfs_debug & 1,
				printf("last dfs readdir done, new off = %d\n",
							uio->uio_offset));
			return(0);
		}
		if ((ddir.dfs_flag == DFS_DIR_ERASED) 
						|| (dfs_is_volume(&ddir))) {
			udir.d_fileno = 0;
		} else if ((mnt_flags & DFS_HIDE_HIDDEN) 
						&& (ddir.dfs_attributes &
						    (DFS_HIDDEN|DFS_SYSTEM))) {
			udir.d_fileno = 0;
		} else {
			udir.d_fileno = dfs_map_fid(ddir.dfs_start);
		}
		udir.d_reclen = sizeof (struct dfs_dir);
		dfs_unmap_name(mnt_flags,ddir.dfs_name,name);
		DEBUGF(dfs_debug & 0x1000,printf("reading name=%s\n",name));
#ifdef DEBUG
	if (dfs_debug & 0x4000) {
		int	i = ddir.dfs_attributes;
		printf("reading dos dir ->\n");
		printf(" dfs_name = <%s>\n",name);
		printf(" dfs_attributes = 0x%x %s%s%s%s%s%s\n",i,
		   (i&DFS_READ_ONLY)?"readonly,":"",(i&DFS_HIDDEN)?"hidden,":"",
		   (i&DFS_SYSTEM)?"system,":"",(i&DFS_VOL_LABLE)?"volume,":"",
		 		(i&DFS_DIRECTORY)?"directory,":"",
						(i&DFS_ARCHIVE)?"archive,":"");
		i = dfs_get_time(&ddir);
		printf(" dfs_time = %x (%d:%d:%d)\n",i,DFS_GET_HOUR(i),
						DFS_GET_MIN(i),DFS_GET_SEC(i));
		i = dfs_get_date(&ddir);
		printf(" dfs_date = %x (%d/%d/%d)\n",i,DFS_GET_MONTH(i),
						DFS_GET_DAY(i),DFS_GET_YEAR(i));
		printf(" size = %d\n",dfs_get_size(&ddir));
		printf(" start = 0x%x (%d)\n",dfs_get_start(&ddir),
							dfs_get_start(&ddir));
	}
#endif DEBUG
		
		udir.d_namlen = strlen(name);
		strcpy(udir.d_name,name);
		error = uiomove(&udir, sizeof(struct dfs_dir), UIO_READ, uio);
		DEBUGF(dfs_debug & 1,
			printf("dfs readdir done, new off = %d, resid = %d\n",
				uio->uio_offset,uio->uio_resid));
		if (error)
			return(error);
	}
	return (0);
}

/*
 * Convert from file system blocks to device blocks
 */
int
dfs_bmap(vp, bn, vpp, bnp)
	struct vnode *vp;	/* file's vnode */
	daddr_t bn;		/* fs block number */
	struct vnode **vpp;	/* RETURN vp of device */
	daddr_t *bnp;		/* RETURN device block number */
{
	DEBUGF(dfs_debug & 1,printf("dfs_bmap called\n"));
	if (vpp)
		*vpp = VFSTODFS(vp->v_vfsp)->dfs_vdevvp;
	if (bnp) {
		struct	dfs_vfs_info *dfs = VFSTODFS(vp->v_vfsp);
		int	cluster = dfs_get_start(VTODir(vp));
		int	file_offset = bn / dfs->dfs_vcluster_size;
		int	cl_offset =  bn % dfs->dfs_vcluster_size;
		int i;

		for (i =0; i < file_offset; i++) {
			if (DFS_IS_EOF(dfs,cluster = 
					dfs_get_cluster(dfs,cluster))) {
				return(ENOSPC);
			}
		}
		*bnp = CLTOBLKS(dfs,cluster) + cl_offset;
	}
	return (0);
}

dfs_bread(vp, lbn, bpp, sizep)
	struct vnode *vp;
	daddr_t lbn;
	struct buf **bpp;
	long	*sizep;
{
	struct dfs_vfs_info *dfs= VFSTODFS(vp->v_vfsp);
	struct	buf *bp;
	int	start_cluster = lbn / dfs->dfs_vcluster_size;
	int	cl_offset = lbn % dfs->dfs_vcluster_size;
	int	cluster = dfs_get_start(VTODir(vp));
	int	i;

	DEBUGF(dfs_debug & 0x10000, printf("dfs_bread called\n"));
	if (vp->v_flag & VROOT) {
		DEBUGF(dfs_debug & 0x10000,
			   printf("dfs_bread reading root dir\n"));
		/*
		 * the root directory has no entries in the FAT
		 */
		bp = bread(dfs->dfs_vdevvp, dfs->dfs_vdir_start+lbn, DFS_BSIZE);
		if (bp->b_flags & B_ERROR) {
			brelse(bp);
			DEBUGF(dfs_debug & 0x10000,
				printf("dfs_bread error on root read\n"));
			return(EIO);
		}
		*sizep = DFS_BSIZE;
		*bpp = bp;
		DEBUGF(dfs_debug & 0x10000, printf("dfs_bread root done\n"));
		return(0);
	}

	/*
	 * map the first file cluster
	 */
	for (i = 0; i < start_cluster; i++) {
		if (DFS_IS_EOF(dfs,cluster = 
				dfs_get_cluster(dfs,cluster))) {
			DEBUGF(dfs_debug & 0x10000,
				 printf("dfs_bread eof on normal read\n"));
			return(EIO); /* should be a panic ? */
		}
	}
	bp = bread(dfs->dfs_vdevvp, CLTOBLKS(dfs,cluster)+cl_offset,DFS_BSIZE);
	if (bp->b_flags & B_ERROR) {
		DEBUGF(dfs_debug & 0x10000,
				printf("dfs_bread error on normal read\n"));
		brelse(bp);
		return(EIO);
	}
	*sizep = DFS_BSIZE;
	*bpp = bp;
	DEBUGF(dfs_debug & 0x10000, printf("dfs_bread normal read done\n"));
	return(0);
}

dfs_brelse(vp,bp)
	struct	vnode *vp;
	struct	buf *bp;
{
	DEBUGF(dfs_debug & 0x10000, printf("dfs_brelse called\n"));
	bp->b_resid = 0;
	brelse(bp);
	DEBUGF(dfs_debug & 0x10000, printf("dfs_brelse done\n"));
	return(0);
}

dfs_fid(vp, fidpp)
	struct vnode *vp;
	struct fid **fidpp;
{
	struct dfs_fid *dfid;
#ifdef USE_NFS

	/*
	 * attempts to export dfs lead to panics on the server.
	 * for now disallow exporting dfs.
	 */

	DEBUGF(dfs_debug & 0x10000, printf("dfs_fid called\n"));
	dfid = (struct dfs_fid *)kmem_alloc(sizeof(struct dfs_fid));
	dfid->dfid_len = sizeof(struct dfs_fid) -
					 (sizeof(struct fid) - MAXFIDSZ);
	dfid->dfid_start = dfs_map_fid(VTODir(vp)->dfs_start);
	*fidpp = (struct fid *) dfid;
	DEBUGF(dfs_debug & 0x10000, printf("dfs_fid done fid = %d\n",
							dfid->dfid_start));
	return(0);
#else
	return(EINVAL);
#endif
}

int
dfs_badop()
{
	panic("dfs_badop");
	return(0);
}

int
dfs_noop()
{
	return(EINVAL);
}

/*
 * Record-locking requests are passed to the local Lock-Manager daemon.
 */
int
dfs_lockctl(vp, ld, cmd, cred)
	struct vnode *vp;
	struct flock *ld;
	int cmd;
	struct ucred *cred;
{
	return(EINVAL);
}


/*
 * make a dfs vnode
 */
struct vnode *
dfs_make_vnode(pvp, ddp, fid, diri)
	struct	vnode *pvp;	/* parent vnode */
	struct	dfs_dir *ddp;	/* directory entry */
	u_short	fid;		/* file id */
	int	diri;		/* directory index */
{
	struct	dnode *dp;

	if (dp = dfs_find(fid, pvp->v_vfsp)) {
		VN_HOLD(DTOV(dp));
		return(DTOV(dp));
	}
	dp=(struct dnode *)kmem_alloc((u_int)sizeof(struct dnode));
	if (dp == 0) {
		return(0);
	}
	bzero(dp,sizeof(struct dnode));
	bcopy(ddp,&(dp->dfs_dir),sizeof(struct dfs_dir));
	dp->dfs_parent = pvp;
	VN_HOLD(pvp);
	dp->dfs_dir_offset = diri;
	VN_INIT(DTOV(dp), pvp->v_vfsp,(dfs_is_directory(ddp) ? VDIR : VREG),
								 pvp->v_rdev);
	DTOV(dp)->v_op = &dfs_vnodeops;
	VFSTODFS(pvp->v_vfsp)->dfs_vrefcnt++;
	dfs_save(dp);
	return(DTOV(dp));
}

/*
 * Lookup a dnode by fid
 */
struct dnode *
dfs_find(fid, vfsp)
        u_short fid;
        struct vfs *vfsp;
{
	register struct dnode *dp;

	dp = dfs_table[dfs_table_hash(fid)];
	while (dp != NULL) {
		if ((dfs_get_start(&(dp->dfs_dir)) == fid) &&
		    (vfsp == DTOV(dp)->v_vfsp)) {
			return (dp);
		}
		dp = dp->dfs_next;
	}
	return (NULL);
}

/*
 * Put a dnode into the hash table for dfs_find
 */
dfs_save(dp)
        struct dnode *dp;
{
	register int fid = dp->dfs_dir.dfs_start;
	register int index = dfs_table_hash(fid);
        dp->dfs_next = dfs_table[index];
        dfs_table[index] = dp;
}

/*
 * Remove a dnode from the hash table
 */
dfs_unsave(dp)
	struct dnode *dp;
{
	struct dnode *dtab;
	struct dnode *dprev = NULL;
	register int fid = dp->dfs_dir.dfs_start;

	dtab = dfs_table[dfs_table_hash(fid)];
	while (dtab != NULL) {
		if (dtab == dp) {
			if (dprev == NULL) {
				dfs_table[dfs_table_hash(fid)] = dtab->dfs_next;
			} else {
				dprev->dfs_next = dtab->dfs_next;
			}
			return;
		}
		dprev = dtab;
		dtab = dtab->dfs_next;
	}
}

/*
 * free a dnode
 */
dfs_free(dp)
	register struct dnode *dp;
{
	dfs_unsave(dp);
	if (dp->dfs_parent)
		VN_RELE(dp->dfs_parent);
	kmem_free((caddr_t)dp, sizeof(struct dnode));
}

/*
 * map unix modes to dos attributes. todo: set up mnt_flags to control
 * this mapping.
 */
u_char
dfs_map_attributes(mnt_flags,unix_att)
	int	mnt_flags;
	u_short	unix_att;
{
	u_char dos_att = DFS_ARCHIVE;
	if ((unix_att & VWRITE) == 0) {
		dos_att |= DFS_READ_ONLY;
	}
	if (unix_att & VSVTX) {
		dos_att |= DFS_SYSTEM;
	}
	if (unix_att & VSVTX) {
		dos_att |= DFS_HIDDEN;
	}
	if (unix_att & (VSUID|VSGID)) {
		return(DFS_BAD_ATTRIBUTES);
	}
	return(dos_att);
}

u_short
dfs_unmap_attributes(mnt_flags,dos_att)
	int	mnt_flags;
	u_char	dos_att;
{
	u_short	unix_att = VREAD;	/* always allow read */

	/* turn on the 'x' bit only if noexecute is not on */
	if (((mnt_flags & DFS_NOEXECUTE) == 0) || (dos_att & DFS_DIRECTORY)) {
		unix_att |= VEXEC;
	}
	/* turn on the 'w' bit only if file is read/write */
	if ((dos_att & DFS_READ_ONLY) == 0) {
		unix_att |= VWRITE;
	}

	/* now simulate group/usr/other access according to mount options */
	switch (mnt_flags & (DFS_USER|DFS_GROUP)) {
	case	0:
		DEBUGF(dfs_debug & 0x2000,printf("mapping att to all\n"));
		unix_att |= (unix_att >> 6) | (unix_att >> 3);
		break;
	case	DFS_USER:
		DEBUGF(dfs_debug & 0x2000,printf("mapping att to user\n"));
		break;
	case	DFS_GROUP:
		DEBUGF(dfs_debug & 0x2000,printf("mapping att to group\n"));
		unix_att >>= 3;
		break;
	case	DFS_GROUP|DFS_USER:
		DEBUGF(dfs_debug & 0x2000,printf("mapping att to usr/group\n"));
		unix_att |= (unix_att >> 3);
		break;
	}

	if (dos_att & (DFS_SYSTEM|DFS_HIDDEN)) {
		unix_att |=  VSVTX;
	}
	if (dos_att & DFS_DIRECTORY) {
		unix_att |= DFS_DIR_MODE;
	} else {
		unix_att |= DFS_REG_MODE;
	}
	return(unix_att);
}

/*
 * translate unix name into dos name using mnt_flags as a guide
 */
char *
dfs_map_name(mnt_flags,u_name,buffer)
	int	mnt_flags;
	char	*u_name;
	char	*buffer;
{

	char c;
	char *d_name = buffer;

	for (; (c = *u_name++) && c != '.';)
	{
		/* lower to upper.. */
		if ((c >= 'a') && (c <= 'z'))
			c = (c - 'a') + 'A';
		if (d_name < buffer + DFS_MAX_FILE_LENGTH)
			*d_name++ = c;
	}

	while (d_name < buffer + DFS_MAX_FILE_LENGTH)
		*d_name++ = ' ';

	if (c == 0) --u_name;

	for (; (c = *u_name++);)
	{
		/* lower to upper.. */
		if ((c >= 'a') && (c <= 'z'))
			c = (c - 'a') + 'A';
		if (d_name < buffer + DFS_MAX_NAME_LENGTH)
			*d_name++ = c;
	}

	while (d_name < buffer + DFS_MAX_NAME_LENGTH)
		*d_name++ = ' ';
	*d_name = 0;		/* make it a string */
	return(buffer);	/* for now we always succede */
}
		
/*
 * translate dos name into unix name using mnt_flags as a guide
 */
char *
dfs_unmap_name(mnt_flags,d_name,buffer)
	int	mnt_flags;
	char	*d_name;
	char	*buffer;
{

	char c;
	char *u_name = buffer;
	int  i;

	for (i=0; i < DFS_MAX_FILE_LENGTH; i++)
	{
		c = d_name[i];
		if (c == ' ')
			break;
		/* upper to lowerr.. */
		if (!(mnt_flags & DFS_UPPER) && (c >= 'A') && (c <= 'Z'))
			c = (c - 'A') + 'a';
		*u_name++ = c;
	}
	if (d_name[DFS_MAX_FILE_LENGTH] != ' ') {
		*u_name++ = '.';
	}

	for (i= DFS_MAX_FILE_LENGTH; i < DFS_MAX_NAME_LENGTH; i++) {
		c = d_name[i];
		if (c == ' ')
			break;
		/* lower to upper.. */
		if (!(mnt_flags & DFS_UPPER) && (c >= 'A') && (c <= 'Z'))
			c = (c - 'A') + 'a';
		*u_name++ = c;
	}
	*u_name = 0;
	return(buffer);	/* for now we always succede */
}

/*
 * translate dos time to unix time
 */
u_long
dfs_get_unix_time(mnt_flags,ddp)
	int	mnt_flags;
	struct dfs_dir *ddp;
{
	u_short	time = dfs_get_time(ddp);
	u_short date = dfs_get_date(ddp);
	u_long  total_time = 0;
	int	year = DFS_GET_YEAR(date);
	int	month = DFS_GET_MONTH(date);
	int	i;
	
	DEBUGF(dfs_debug & 0x1000, printf("year = %d, month=%d\n",year,month));
	for ( i = 70; i < year; i++)
		total_time += SECYR + (LEAPYEAR(i) ? SECDAY :0);

	for (i = 1; i < month; i++) {
		adjustmonth(i, &total_time, year,1);
	}
	total_time += SECDAY*(DFS_GET_DAY(date)-1);
	total_time += SECHR*DFS_GET_HOUR(time);
	total_time += SECMIN*DFS_GET_MIN(time);
	total_time += DFS_GET_SEC(time);

	total_time += SECMIN * tz.tz_minuteswest;
	if (mnt_flags & DFS_DST) {
		total_time -= SECHR;
	}
	return(total_time);
}

/*
 * translate unix time dos time
 */
u_long
dfs_get_dos_time(mnt_flags,time)
	int	mnt_flags;
	u_long	time;
{
	u_long dos_time = 0;
	int	i, year;

	DEBUGF(dfs_debug & 1,printf("getting dos time\n"));
	time -= SECMIN * tz.tz_minuteswest;
	if (mnt_flags & DFS_DST) {
		time += SECHR;
	}
        for (i = 70; time > SECYR + (LEAPYEAR(i) ? SECDAY : 0); i++)
              time -= SECYR + (LEAPYEAR(i) ? SECDAY : 0);

	year = i;
	i -= 80;
	DFS_PUT_YEAR(dos_time,i);
        for (i = 1; *(int *)&time > 0; i++) {
                adjustmonth(i, &time, year, 0);
        }
        /* Set the Month on time of day clock */
        --i;
	DFS_PUT_MONTH(dos_time,i);
        adjustmonth(i, &time, year, 1);

	i = (time / SECDAY) + 1;
	DFS_PUT_DAY(dos_time,i);
	time %= SECDAY;

	i = time / SECHR;
	DFS_PUT_HOUR(dos_time,i);
	time %= SECHR;

	i = time / SECMIN;
	DFS_PUT_MIN(dos_time,i);
	time %= SECMIN;

	DFS_PUT_SEC(dos_time,time);
	DEBUGF(dfs_debug & 1,printf("done getting dos time\n"));
	return(dos_time);
}

int
dfs_read(vp,b_off,size,addr,type)
	struct vnode *vp;
	u_long	b_off;
	u_long	size;
	char	*addr;
	int	type;
{
	struct dfs_dir *ddp = VTODir(vp);
	struct dfs_vfs_info *dfs= VFSTODFS(vp->v_vfsp);
	struct	buf *bp;
	int	cl_size = dfs->dfs_vcluster_size*DFS_BSIZE;
	int	start_cluster = b_off / cl_size;
	int	cl_offset = (b_off) % cl_size;
	int	cluster = dfs_get_start(ddp);
	int	rsize,i,error;

	DEBUGF(dfs_debug & 0x20,
			   printf("dfs_read: off=%d size=%d\n",b_off,size));
	if (size == 0) {
		return(0);
	}
	if (vp->v_flag & VROOT) {
		DEBUGF(dfs_debug & 0x20,
			   printf("reading root dir\n"));
		/*
		 * the root directory has no entries in the FAT
		 */
		cluster = b_off / DFS_BSIZE;
		cl_offset = b_off % DFS_BSIZE;
		rsize = cl_offset+size;
		DEBUGF(dfs_debug & 0x40,
			printf("bread(0x%x,%d,%d)--cluster=%d,cl_offset=%d\n",
				dfs->dfs_vdevvp,dfs->dfs_vdir_start+cluster,
				ROUND(rsize,DFS_BSIZE),cluster,cl_offset));
		bp = bread(dfs->dfs_vdevvp, dfs->dfs_vdir_start+cluster,
				ROUND(rsize,DFS_BSIZE));
		if (bp->b_flags & B_ERROR) {
			brelse(bp);
			return(EIO);
		}
		if (type) {
			error = uiomove(bp->b_un.b_addr+cl_offset,size,
						UIO_READ, (struct uio *)addr);
		} else {
			bcopy(bp->b_un.b_addr+cl_offset,addr,size);
			error = 0;
		}
		brelse(bp);
		DEBUGF(dfs_debug & 0x20,
			   printf("root dir read\n"));
		return(error);
	}
		
	DEBUGF(dfs_debug&0x800,
			 printf("dfs_read: off=%d size=%d\n",b_off,size));
	/*
	 * map the first file cluster
	 */
	for (i = 0; i < start_cluster; i++) {
		if (DFS_IS_EOF(dfs,cluster = 
				dfs_get_cluster(dfs,cluster))) {
			return(EIO); /* should be a panic ? */
		}
	}
	rsize = _min(size,cl_size-cl_offset);
	DEBUGF(dfs_debug&0x800,printf("normal read clust %d boff %d size %d\n",
					cluster,CLTOBLKS(dfs,cluster),rsize));
	bp = bread(dfs->dfs_vdevvp, CLTOBLKS(dfs,cluster),cl_size);
	if (bp->b_flags & B_ERROR) {
		brelse(bp);
		DEBUGF(dfs_debug & 0x800,printf("dfs_read:error1\n"));
		return(EIO);
	}
	if (type) {
		error = uiomove(bp->b_un.b_addr+cl_offset,rsize,
						UIO_READ, (struct uio *)addr);
		if (error) {
			brelse(bp);
			return(error);
		}
	} else {
		bcopy(bp->b_un.b_addr+cl_offset,addr,rsize);
		addr += rsize;
	}
	size -= rsize;
	brelse(bp);
	while (size > 0) {
		DEBUGF(dfs_debug & 0x800,
			printf("in main loop, current cluster %x(%d) "
							,cluster,cluster));
		DEBUGF(dfs_debug & 0x800, printf("next should be %x(%d)\n",
		   dfs_get_cluster(dfs,cluster),dfs_get_cluster(dfs,cluster)));
		if (DFS_IS_EOF(dfs,cluster =
				dfs_get_cluster(dfs,cluster))) {
			DEBUGF(dfs_debug & 0x800,
				printf("dfs_read:eof on cluster %x (%d)\n",
							    cluster,cluster));
			return(EIO); /* should be a panic ? */
		}
		rsize = _min(size,cl_size);
		bp = bread(dfs->dfs_vdevvp, CLTOBLKS(dfs,cluster),cl_size);
		if (bp->b_flags & B_ERROR) {
			brelse(bp);
			DEBUGF(dfs_debug & 0x800,printf("dfs_read: error2\n"));
			return(EIO);
		}
		if (type) {
			error = uiomove(bp->b_un.b_addr,rsize,
						UIO_READ, (struct uio *)addr);
			if (error) {
				brelse(bp);
				return(error);
			}
		} else {
			bcopy(bp->b_un.b_addr,addr,rsize);
			addr += rsize;
		}
		size -= rsize;
		brelse(bp);
	}
	DEBUGF(dfs_debug & 0x800,printf("dfs_read done\n"));
	return(0);
}
		
int
dfs_write(vp,b_off,size,addr,type)
	struct vnode *vp;
	u_long	b_off;
	u_long	size;
	char	*addr;
	int	type;
{
	struct dfs_dir *ddp = VTODir(vp);
	struct dfs_vfs_info *dfs= VFSTODFS(vp->v_vfsp);
	struct buf *bp;
	int	cl_size = dfs->dfs_vcluster_size*DFS_BSIZE;
	int	start_cluster = b_off / cl_size;
	int	cl_offset = (b_off) % cl_size;
	int	cluster = dfs_get_start(ddp);
	int	b_size = size;
	unsigned long pages[FAT_PAGES];
	int	error,rsize,new_size,i;

	bzero(pages,sizeof(pages));
#ifdef DFS_RO
	panic("dfs_write: writing on RO fs");
#endif
	DEBUGF(dfs_debug & 0x20,
			   printf("dfs_write: off=%d size=%d\n",b_off,size));
	if (size == 0) {
		return(0);
	}
	if (vp->v_flag & VROOT) {
		DEBUGF(dfs_debug & 0x20,
			   printf("writing root dir\n"));
		/*
		 * the root directory has no entries in the FAT
		 */
		cluster = b_off / DFS_BSIZE;
		cl_offset = b_off % DFS_BSIZE;
		rsize = cl_offset+size;
		DEBUGF(dfs_debug & 0x40,
			printf("bwrite(0x%x,%d,%d)--cluster=%d,cl_offset=%d\n",
				dfs->dfs_vdevvp,dfs->dfs_vdir_start+cluster,
				ROUND(rsize,DFS_BSIZE),cluster,cl_offset));

		bp = bread(dfs->dfs_vdevvp, dfs->dfs_vdir_start+cluster,
				ROUND(rsize,DFS_BSIZE));
		if (bp->b_flags & B_ERROR) {
			brelse(bp);
			return(EIO);
		}
		if (type) {
			error = uiomove(bp->b_un.b_addr+cl_offset,size,
						UIO_WRITE, (struct uio *)addr);
			brelse(bp);
			return(error);
		} else {
			bcopy(addr,bp->b_un.b_addr+cl_offset,size);
		}
		bwrite(bp);
		DEBUGF(dfs_debug & 0x40, printf("bwrite done\n"));
		return(0);
	}
	/*
	 * map the first file cluster
	 */
	for (i = 0; i < start_cluster; i++) {
		int	ocluster = cluster;
		if (DFS_IS_EOF(dfs,cluster = 
				dfs_get_cluster(dfs,ocluster))) {
			/*
			 * allocate new clusters
			 */
			int	new_cluster,error;

			error=dfs_alloc_cluster(vp->v_vfsp,&new_cluster,pages);
			if (error)
				return(ENOSPC);
			dfs_set_cluster(dfs,ocluster,new_cluster,pages);
			dfs_lock_fat(vp->v_vfsp,pages);
			cluster = new_cluster;
			if (i != start_cluster) { 
				bp = getblk(dfs->dfs_vdevvp,
						CLTOBLKS(dfs,cluster),cl_size);
				bzero(bp->b_un.b_addr,cl_size);
				bwrite(bp);
				if (u.u_error) {
					return(EIO);
				}
			}
		}
	}
	rsize = _min(size,cl_size-cl_offset);
	DEBUGF(dfs_debug&0x800,printf("normal write clust %d boff %d size %d\n",
					cluster,CLTOBLKS(dfs,cluster),rsize));
	if ((cl_offset == 0) && (rsize == cl_size)) {
		bp = getblk(dfs->dfs_vdevvp, CLTOBLKS(dfs,cluster),cl_size);
	} else {
		bp = bread(dfs->dfs_vdevvp, CLTOBLKS(dfs,cluster),cl_size);
		if (bp->b_flags & B_ERROR) {
			brelse(bp);
			return(EIO);
		}
	}
	if (type) {
		error = uiomove(bp->b_un.b_addr+cl_offset,rsize,
						UIO_WRITE, (struct uio *)addr);
		if (error) {
			brelse(bp);
			return(error);
		}
	} else {
		bcopy(addr,bp->b_un.b_addr+cl_offset,rsize);
		addr += rsize;
	}
	size -= rsize;
	bwrite(bp);
	if (u.u_error) {
		return(EIO);
	}
	while (size > 0) {
		int	ocluster = cluster;
		if (DFS_IS_EOF(dfs,cluster =
				dfs_get_cluster(dfs,ocluster))) {
			int	new_cluster,error;
			/*
			 * allocate new clusters
			 */
			error=dfs_alloc_cluster(vp->v_vfsp,&new_cluster,pages);
			if (error)
				return(ENOSPC);
			dfs_set_cluster(dfs,ocluster,new_cluster,pages);
			dfs_lock_fat(vp->v_vfsp,pages);
			cluster=new_cluster;
		}
		rsize = _min(size,cl_size);
		if (rsize == cl_size) {
			bp = getblk(dfs->dfs_vdevvp, CLTOBLKS(dfs,cluster),
								cl_size);
		} else {
			bp = bread(dfs->dfs_vdevvp, CLTOBLKS(dfs,cluster),
								cl_size);
			if (bp->b_flags & B_ERROR) {
				brelse(bp);
				return(EIO);
			}
		}
		if (type) {
			error = uiomove(bp->b_un.b_addr,rsize,
						UIO_WRITE, (struct uio *)addr);
			if (error) {
				brelse(bp);
				return(error);
			}
		} else {
			bcopy(addr,bp->b_un.b_addr,rsize);
			addr += rsize;
		}
		size -= rsize;
		bwrite(bp);
		if (u.u_error) {
			return(EIO);
		}
	}
	DEBUGF(dfs_debug&0x800,printf("normal write done, updating dir\n"));
	if (dfs_not_zero(pages)) {
		error = dfs_write_fat(vp->v_vfsp,pages);
		if (error) {
			return(error);
		}
	}
	if (!dfs_is_directory(ddp)) {
		new_size = _max(b_off+b_size,dfs_get_size(ddp));
		ddp->dfs_size = dfs_put_size(new_size);
		error = dfs_write_dir(vp,time.tv_sec);
	} else {
		error = 0;
	}
	return(error);
}
/*
 * write a directory entry
 */
int
dfs_write_dir(dp,time)
	struct	dnode *dp;
	u_long	time;
{
	struct	dfs_dir *ddp = &dp->dfs_dir;

#ifdef DFS_RO
	panic("dfs_write_dir: writing on RO fs");
#endif
	DEBUGF(dfs_debug & 1,printf("dfs_write_dir called, offset = %d\n",
							dp->dfs_dir_offset));
	/*
	 * don't write out the root directory entry (it doesn't exist).
	 */
	if (DTOV(dp)->v_flag & VROOT) {
		DEBUGF(dfs_debug & 1,printf("root write\n"));
		return(0);
	}
	if (!dfs_is_directory(ddp)) {
		ddp->dfs_attributes |= DFS_ARCHIVE;
	}
	if (time != 0) {
		u_long	dos_time = 
		   dfs_get_dos_time(dfs_get_mnt_flags(DTOV(dp)->v_vfsp),time);
		ddp->dfs_time = dfs_put_time(dos_time);
		ddp->dfs_date = dfs_put_date(dos_time);
	}
	if (dp->dfs_parent == 0) {
		printf("dfs:noparent!!\n");
		return(EIO);
	}
	DEBUGF(dfs_debug & 1,printf("dfs_write_dir calling dfs_write\n"));
	return(dfs_write(dp->dfs_parent,dp->dfs_dir_offset,
				sizeof(struct dfs_dir),(char *)ddp,0));
}

int
dfs_write_fat(vfs,page)
	struct 	vfs *vfs;
	unsigned *page;
{
	struct dfs_vfs_info *dfs = VFSTODFS(vfs);
	int	i,j;

	DEBUGF(dfs_debug & 1,
			printf("dfs_write_fat called for pages 0x%x\n",page));
	for (i=0 ; i < dfs->dfs_vfat_pages; i++) {
		int	pi = i / (sizeof(unsigned long)*8);
		int	si = i % (sizeof(unsigned long)*8);

		if (pi > FAT_PAGES) {
			printf("dfs_fat_write, fat_pages = %d, i=%d, pi=%d \n",
				dfs->dfs_vfat_pages,i,pi);
		}
		if (page[pi] & (1 << si)) {
#ifdef DFS_RO
	panic("dfs_write_fat: writing on RO fs");
#endif
			for (j=0 ; j < dfs->dfs_vfat_copies; j++) {
				struct buf *bp = getblk(dfs->dfs_vdevvp,
				    dfs->dfs_vfat_start+i+j*dfs->dfs_vfat_pages,
							DFS_BSIZE);
				bcopy(dfs->dfs_vfat+i*DFS_BSIZE,bp->b_un.b_addr,
							DFS_BSIZE);
				bwrite(bp);
			}
			dfs_fat_unlock(vfs,pi,1 << si);
		}
	}
	return(0);
}
		
		
/*
 * get the next free cluster in the FAT
 */
dfs_alloc_cluster(vfs,cluster,pages)
	struct vfs *vfs;
	int	*cluster;
	unsigned long *pages;
{
	/*
	 * this algorithm is not very efficient yet..
	 */
	struct	dfs_vfs_info *dfs = VFSTODFS(vfs);
	int	tcluster;

	for (tcluster = dfs->dfs_vfirst_cluster; 
			tcluster < dfs->dfs_vfat_entries; tcluster++) {
		if (dfs_get_cluster(dfs,tcluster) == DFS_FREE) {

			*cluster = tcluster;
			dfs_set_cluster(dfs,tcluster,dfs->dfs_veof,pages);
			return(0);
		}
	}
	return(ENOSPC);
}

dfs_free_cluster(vfs,cluster,pages)
	struct vfs *vfs;
	int	cluster;
	unsigned long *pages;
{
	struct	dfs_vfs_info *dfs = VFSTODFS(vfs);
	unsigned page;

	DEBUGF(dfs_debug & 0x1000,printf("free cluster\n"));
	dfs_set_cluster(dfs,cluster,DFS_FREE,pages);
	DEBUGF(dfs_debug & 0x1000,printf("writing fat pages 0x%x\n",page));
}

dfs_free_links(vfs,ddp,pages)
	struct vfs *vfs;
	struct dfs_dir *ddp;
	unsigned long *pages;
{
	struct	dfs_vfs_info *dfs = VFSTODFS(vfs);
	int	cluster = dfs_get_start(ddp);

	DEBUGF(dfs_debug & 0x1000,printf("dfs_freeing links\n"));
	while (!DFS_IS_EOF(dfs,cluster)) {
		int	new_cluster = dfs_get_cluster(dfs,cluster);
		if (cluster == DFS_FREE) {
			printf("trashed fat entry");
			return;
		}
		DEBUGF(dfs_debug & 0x1000,
					printf("freeing cluster %d\n",cluster));
 		dfs_set_cluster(dfs,cluster,DFS_FREE,pages);
		cluster = new_cluster;
	}
	DEBUGF(dfs_debug & 0x1000,printf("writing fat pages 0x%x\n",pages));
}


/*
 * dfs specific routines
 */
dfs_get_dir_size(vp,ddp)
	struct vnode *vp;
	struct dfs_dir *ddp;
{
	struct	dfs_vfs_info *dfs = VFSTODFS(vp->v_vfsp);
	int	cluster_count = 1;
	int	cluster = dfs_get_start(ddp);

	if (vp->v_flag & VROOT) {
		return(dfs->dfs_vdir_size);
	}
	while (!DFS_IS_EOF(dfs,cluster = dfs_get_cluster(dfs,cluster))) {
		cluster_count++;
	}
	return(cluster_count*dfs->dfs_vcluster_size*DFS_BSIZE);
	
}

/*
 * set the fat entry and return a page mask for dfs_write_fat
 */
dfs_set_cluster(dfs,cluster,value,pages)
	struct	dfs_vfs_info *dfs;
	int	cluster,value;
	unsigned long *pages;
{
	char	*fat = dfs->dfs_vfat;
	int	entry_size = dfs->dfs_vfat_entry_size;
	int	index =  (cluster)*entry_size /DFS_BITS_PER_BYTE;
	int	pi,si;

#ifdef DFS_RO
	panic("dfs_set_cluster: writing on RO fs");
#endif

	if ((unsigned)index > dfs->dfs_vfat_pages*DFS_BSIZE) {
		printf(
	"fat_entry = %d, fat_size =%d, cluster=%d, entry_size=%d, value = %d\n"
	   ,index,dfs->dfs_vfat_entries,cluster,dfs->dfs_vfat_entry_size,value);
		panic("dfs:FAT overrun error!!\n");
	}

	if (entry_size == 12) {
		if (cluster & 1) {
			fat[index] &=  0x0f;
			fat[index] |= (value << 4) & 0xf0;
			fat[index+1] = ((value >> 4) & 0xff);
		} else {
			fat[index] = value & 0xff;
			fat[index+1] &=  0xf0;
			fat[index+1] |= ((value >> 8) & 0x0f);
		}
	} else {
		fat[index] =  value & 0xff;
		fat[index+1] = value >> 8 & 0xff;
	}

	/*
	 * this corner case may not be possible. This is to takes care of the
	 * case where the fat entry lives on two separate pages.
	 */
	pi = (index/DFS_BSIZE);
	if ((index & (DFS_BSIZE-1)) == (DFS_BSIZE-1)) {	
		int	ppi = (pi+1) / (sizeof(unsigned long)*8);
		int	psi = (pi+1) % (sizeof(unsigned long)*8);

		if (ppi >= FAT_PAGES) {
			printf("dfs_set_cluster: second page too large\n");
		} else {
			pages[ppi] |= ((unsigned)1 << (psi));
		}
	}
	si = pi % (sizeof(unsigned long)*8);
	pi = pi / (sizeof(unsigned long)*8);
	if (pi >= FAT_PAGES) {
		printf("dfs_set_cluster: first page too large\n");
	} else {
		pages[pi] |= ((unsigned)1 << (si));
	}
}

/*
 * get fat entries
 */
int
dfs_get_cluster(dfs,cluster)
	struct	dfs_vfs_info *dfs;
	int	cluster;
{
	char	*fat = dfs->dfs_vfat;
	int	entry_size = dfs->dfs_vfat_entry_size;
	int	index =  (cluster)*entry_size /DFS_BITS_PER_BYTE;

	if (entry_size == 12) {
		if (cluster & 1) {
			return( (((int)fat[index] & 0xf0) >> 4) 
						| ((int)fat[index+1] << 4) );
		} else {
			return( ((int)fat[index]) | 
					 (((int)fat[index+1] & 0x0f) << 8) );
		}
	} 
	return((int)fat[index] | ((int)fat[index+1] << 8));
}

dfs_fat_unlock(vfs,index,fat)
	struct vfs	*vfs;
	int	index;
	unsigned fat;
{
	struct dfs_vfs_info *dfs = VFSTODFS(vfs);

	if (index >= FAT_PAGES) {
		printf("dfs_fat_unlock: fat too large index = %d\n",index);
		return;
	}
	dfs->dfs_vfat_bits[index] &= ~fat;
	if (dfs->dfs_vfat_want) {
		dfs->dfs_vfat_want =0;
		wakeup(dfs->dfs_vfat_bits);
	}
}	

dfs_lock_fat(vfs,pages)
	struct vfs *vfs;
	unsigned long *pages;
{
	struct dfs_vfs_info *dfs = VFSTODFS(vfs);
	int i;

	for (i=0; i < FAT_PAGES; i++) {
		dfs->dfs_vfat_bits[i] |= pages[i];
	}
}

int
dfs_not_zero(pages)
	unsigned long *pages;
{
	int i;
	for (i=0; i < FAT_PAGES; i++) {
		if (pages[i])
			return(1);
	}
	return(0);
}

dfs_want_fat(vfs,index,fat)
	struct vfs	*vfs;
	int	index;
	unsigned fat;
{
	struct dfs_vfs_info *dfs = VFSTODFS(vfs);

	if (index >= FAT_PAGES) {
		printf("dfs_want_fat: fat too large index = %d\n",index);
		return;
	}
	while (dfs->dfs_vfat_bits[index] & fat) {
		dfs->dfs_vfat_want = 1;
		sleep(dfs->dfs_vfat_bits);
	}
}
	

/*
 * this is to make programs more happy about
 * the dfs root being the root file system!
 */
u_short
dfs_map_fid(start)
	u_short start;
{
	if (start == dfs_put_start(DFS_ROOT_FID)) {
		return(ROOTINO);
	} else if (start == dfs_put_start(ROOTINO)) {
		return(1);
	} else {
		return(dfs_put_start(start));
	}
}

u_short
dfs_unmap_fid(fid)
	u_short fid;
{
	if (fid == ROOTINO) {
		return(dfs_put_start(DFS_ROOT_FID));
	} else if (fid == 1) {
		return(dfs_put_start(ROOTINO));
	} else if (fid == 0){
		return(0xffff);		/* this is an error */
	} else {
		return(dfs_put_start(fid));
	}
}
	
/*
 * unlike ilock and iunlock, we allow the same process to lock the same
 * dnode more than once. This prevents deadlock/race conditions on looking
 * up and locking dnode's.
 */
dlock(vp)
	register struct vnode *vp;
{
	register struct dnode *dp = VTOD(vp);

	/* if we already have it locked, increment the lock count */
	if (dp->dfs_locked == (unsigned) u.u_procp->p_pid) {
		dp->dfs_lock_cnt++;
		return ;
	}
	/* wait for it to be unlocked, the lock it */
	while (dp->dfs_locked) {
		dp->dfs_wanted = 1;
		(void) sleep((caddr_t)dp, PINOD);
	}
	dp->dfs_locked = (unsigned) u.u_procp->p_pid;
	dp->dfs_lock_cnt = 1;
}

dunlock(vp)
	register struct vnode *vp;
{
	register struct dnode *dp = VTOD(vp);

	/* sanity checks */
	if (dp->dfs_locked != (unsigned) u.u_procp->p_pid) {
		panic("dunlock: dnode is not lock by us");
	}
	if (dp->dfs_lock_cnt == 0) {
		panic("dunlock: dnode lock count is 0");
	}

	/* decrement the lock count and unlock the dnode */
	if (--(dp->dfs_lock_cnt) == 0) {
		dp->dfs_locked = 0;
		if (dp->dfs_wanted) {
			dp->dfs_wanted = 0;
			wakeup((caddr_t)dp);
		}
	}
}

dunlockchain(vp)
	register struct vnode *vp;
{
	while (vp) {
		dunlock(vp);
		vp = VTOD(vp)->dfs_parent;
	}
}
u_long
exchl(l)
	u_long	l;
{
	u_short	*sp = (u_short *)&l;
	return(dfs_get_long(sp));
}

u_short
exchw(w)
	u_short w;
{
	char *cp = (char *)&w;
	return(dfs_get_word(cp));
}
#endif DFS
