/* 
 * fslclLookup.c --
 *
 *	The routines in the module manage the directory structure.
 *	The top level loop is in FslclLookup, and it is the workhorse
 *	of the Local Domain that is called by procedures like FslclOpen.
 *	Files and directories are also created, deleted, and renamed
 *	directly (or indirectly) through FslclLookup.
 *
 *	Support for heterogenous systems is done here by expanding "$MACHINE"
 *	in pathnames to a string like "sun3" or "spur".
 *
 * Copyright 1987 Regents of the University of California
 * All rights reserved.
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any purpose and without
 * fee is hereby granted, provided that the above copyright
 * notice appear in all copies.  The University of California
 * makes no representations about the suitability of this
 * software for any purpose.  It is provided "as is" without
 * express or implied warranty.
 */

#ifndef lint
static char rcsid[] = "$Header: /cdrom/src/kernel/Cvsroot/kernel/fslcl/fslclLookup.c,v 9.27 92/08/10 17:28:30 mgbaker Exp $ SPRITE (Berkeley)";
#endif not lint


#include <sprite.h>
#include <fs.h>
#include <fsconsist.h>
#include <fsutil.h>
#include <fsNameOps.h>
#include <fsprefix.h>
#include <fsdm.h>
#include <fslclInt.h>
#include <fslcl.h>
#include <fslclNameHash.h>
#include <fscache.h>
#include <fsStat.h>
#include <net.h>
#include <vm.h>
#include <string.h>
#include <proc.h>
#include <dbg.h>
#include <fsrecov.h>
#include <rpc.h>
#include <recov.h>

int fsCompacts;		/* The number of times a directory block was so
			 * fragmented that we could have compacted it to
			 * make room for a new entry in the directory */
/*
 * A cache of recently seen pathname components is kept in a hash table.
 * The hash table gets initialized in Fsdm_AttachDisk after the first disk
 * gets attached.
 * The name caching can be disabled by setting the fslclNameCaching flag to FALSE.
 */

FslclHashTable fslclNameTable;
FslclHashTable *fslclNameTablePtr = (FslclHashTable *)NIL;
Boolean fslclNameCaching = TRUE;
int fslclNameHashSize = FSLCL_NAME_HASH_SIZE;

/*
 * Forward Declarations.
 */

static ReturnStatus FindComponent _ARGS_((Fsio_FileIOHandle *parentHandlePtr, 
		char *component, int compLen, Boolean isDotDot, 
		Fsio_FileIOHandle **curHandlePtrPtr, int *dirOffsetPtr));
static ReturnStatus InsertComponent _ARGS_((Fsio_FileIOHandle *curHandlePtr, 
		char *component, int compLen, int fileNumber,
		int *dirOffsetPtr));
static ReturnStatus DeleteComponent _ARGS_((Fsio_FileIOHandle *parentHandlePtr,
		char *component, int compLen, int *dirOffsetPtr));
static ReturnStatus ExpandLink _ARGS_((Fsio_FileIOHandle *curHandlePtr, 
		char *curCharPtr, int offset, char nameBuffer[]));
static ReturnStatus GetHandle _ARGS_((int fileNumber, 
		Fsdm_FileDescriptor *newDescPtr, 
		Fsio_FileIOHandle *curHandlePtr, char *name, 
		Fsio_FileIOHandle **newHandlePtrPtr));
static ReturnStatus CreateFile _ARGS_((Fsdm_Domain *domainPtr, 
		Fsio_FileIOHandle *parentHandlePtr, char *component, 
		int compLen, int fileNumber, int type, int permissions, 
		Fs_UserIDs *idPtr, Fsio_FileIOHandle **curHandlePtrPtr, 
		int *dirOffsetPtr));
static ReturnStatus WriteNewDirectory _ARGS_((Fsio_FileIOHandle *curHandlePtr,
		Fsio_FileIOHandle *parentHandlePtr));
static ReturnStatus LinkFile _ARGS_((Fsio_FileIOHandle *parentHandlePtr,
		char *component, int compLen, int fileNumber, int logOp, 
		Fsio_FileIOHandle **curHandlePtrPtr, int clientID));
static ReturnStatus OkToMoveDirectory _ARGS_((
		Fsio_FileIOHandle *newParentHandlePtr, 
		Fsio_FileIOHandle *curHandlePtr));
static ReturnStatus MoveDirectory _ARGS_((Time *modTimePtr, 
		Fsio_FileIOHandle *newParentHandlePtr, 
		Fsio_FileIOHandle *curHandlePtr));
static ReturnStatus GetParentNumber _ARGS_((Fsio_FileIOHandle *curHandlePtr,
		int *parentNumberPtr));
static ReturnStatus SetParentNumber _ARGS_((Fsio_FileIOHandle *curHandlePtr, 
		int newParentNumber));
static ReturnStatus DeleteFileName _ARGS_((Fsio_FileIOHandle *parentHandlePtr,
		Fsio_FileIOHandle *curHandlePtr, char *component, int compLen,
		int forRename, Fs_UserIDs *idPtr, int logOp, int clientID));
static void	CloseDeletedFile _ARGS_((Fsio_FileIOHandle **parentHandlePtrPtr,
					Fsio_FileIOHandle **curHandlePtrPtr));
static Boolean DirectoryEmpty _ARGS_((Fsio_FileIOHandle *handlePtr));
static ReturnStatus CheckPermissions _ARGS_((Fsio_FileIOHandle *handlePtr, 
		int useFlags, Fs_UserIDs *idPtr, int type));
static ReturnStatus CacheDirBlockWrite _ARGS_((Fsio_FileIOHandle *handlePtr, 
		Fscache_Block *blockPtr, int blockNum, int length));


/*
 *----------------------------------------------------------------------
 *
 * FslclLookup --
 *
 *	The guts of local file name lookup.  This does a recursive
 *	directory lookup of a file pathname.  The success of the lookup
 *	depends on useFlags and the type.  The process needs to
 *	have read permission along the path, and other permissions on
 *	the target file itself according to useFlags. The type of the
 *	target file has to agree with the type parameter.
 *
 *	The major and minor fields of the Fs_FileID for local files correspond
 *	to the domain and fileNumber, respectively, of a file.  The domain
 *	is an index into the set of active domains (disks), and the fileNumber
 *	is an index into the array of file descriptors on disk.
 *
 * Results:
 *	If SUCCESS is returned then *handlePtrPtr contains a valid file
 *	handle.  This handle should be released with FsLocalClose.
 *	If FS_LOOKUP_REDIRECT is returned then **newNameInfoPtrPtr contains
 *	the new file name that the client takes back to its prefix table.
 *
 * Side effects:
 *	After a successful lookup the returned handle is locked and has
 *	another reference to it.  Also, the domain in which the file was
 *	found has an extra reference that needs to be released with
 *	Fsdm_DomainRelease as soon as our caller is finished with the handle.
 *
 *----------------------------------------------------------------------
 */
ReturnStatus
FslclLookup(prefixHdrPtr, relativeName, rootIDPtr, useFlags, type, clientID,
	    idPtr, permissions, fileNumber, handlePtrPtr, newNameInfoPtrPtr)
    Fs_HandleHeader *prefixHdrPtr;	/* Handle from the prefix table or
					 * the current working directory */
    char *relativeName;			/* Name to lookup relative to the
					 * file indicated by prefixHandlePtr */
    Fs_FileID *rootIDPtr;		/* File ID of the root of the domain */
    int useFlags;			/* FS_READ|FS_WRITE|FS_EXECUTE,
					 * FS_CREATE|FS_EXCLUSIVE, FS_OWNER,
					 * FS_LINK, FS_FOLLOW (links) */
    int type;				/* File type which to succeed on.  If
					 * this is FS_FILE, then any type will
					 * work. */
    int clientID;			/* Host ID of the client doing the open.
					 * Require to properly expand $MACHINE
					 * in pathnames */
    Fs_UserIDs *idPtr;			/* User and group IDs */
    int permissions;			/* Permission bits to use on a newly
					 * created file. */
    int fileNumber;			/* File number to link to if FS_LINK
					 * useFlag is present */
    Fsio_FileIOHandle **handlePtrPtr;	/* Result, the handle for the file.
					 * This is returned locked.  Also,
					 * its domain has a reference which
					 * needs to be released. */
    Fs_RedirectInfo **newNameInfoPtrPtr;	/* Redirect Result, the pathname left
					 * after it leaves our domain */
{
    register char 	*curCharPtr;	/* Pointer into the path name */
    Fsio_FileIOHandle *parentHandlePtr; /* Handle for parent dir. */
    register char	*compPtr;	/* Pointer into component. */
    register ReturnStatus status = SUCCESS;
    register int 	compLen = 0;	/* The length of component */
    Fsio_FileIOHandle	*curHandlePtr;	/* Handle for the current spot in
					 * the directory structure */
    Fsdm_Domain *domainPtr;		/* Domain of the lookup */
    char component[FS_MAX_NAME_LENGTH]; /* One component of the path name */
    char *newNameBuffer;		/* Extra buffer used after a symbolic
					 * link has been expanded */
    int numLinks = 0;			/* The number of links that have been
					 * expanded. Used to check against
					 * loops. */
    int	logOp;				/* Dir log operation. */
    int dirFileNumber;			/* File number od directory being 
					 * operated on. */
    int	dirOffset;			/* Offset of directory entry in the
					 * directory being operated on. */
    ClientData	logClientData;		/* Client data for directory change
					 * logging. */
    ClientData	recovLogClientData = (ClientData) 0;
					/* Client data for directory change
					 * logging in the recovery system. */
    /*
     * Get a handle on the domain of the file.  This is needed for disk I/O.
     * Remember that the <major> field of the fileID is a domain number.
     */
    domainPtr = Fsdm_DomainFetch(prefixHdrPtr->fileID.major, FALSE);
    if (domainPtr == (Fsdm_Domain *)NIL) {
	return(FS_DOMAIN_UNAVAILABLE);
    }

    if (prefixHdrPtr->fileID.type != FSIO_LCL_FILE_STREAM) {
	printf("FslclLookup, bad prefix handle type <%d> for {%s}%s\n",
	    prefixHdrPtr->fileID.type,
	    Fsutil_HandleName(prefixHdrPtr), relativeName);
	return(FS_DOMAIN_UNAVAILABLE);
    }
    /*
     * Duplicate the prefixHandle into the handle for the current point
     * in the directory.  This locks and ups the reference count on the handle.
     */
    curCharPtr = relativeName;
    curHandlePtr = Fsutil_HandleDupType(Fsio_FileIOHandle, prefixHdrPtr);
    parentHandlePtr = (Fsio_FileIOHandle *)NIL;
    newNameBuffer = (char *)NIL;
    /*
     * Loop through the pathname expanding links and checking permissions.
     * Creations and deletions are handled after this loop.
     */
    fs_Stats.lookup.number++;
    while (*curCharPtr != '\0' && status == SUCCESS) {
	status = CheckPermissions(curHandlePtr, FS_READ, idPtr, FS_DIRECTORY);
	if (status != SUCCESS) {
	    break;
	}
	/*
	 * Get the next component.  We make a special check here
	 * for "$MACHINE" embedded in the pathname.  This gets expanded
	 * to a machine type string, i.e. "sun3" or "spur", sort of like
	 * a symbolic link.  The value is dependent on the ID of the client
	 * doing the open.  For the local host we use a compiled in string so
	 * we can bootstrap ok, and for other clients we get the machine
	 * type string from the net module.  (why net?  why not...
	 * Net_InstallRoute installs a host's name and machine type.)
	 */
#define SPECIAL		"$MACHINE"
#define SPECIAL_LEN	8
	compPtr = component;
	while (*curCharPtr != '/' && *curCharPtr != '\0') {
	    if (*curCharPtr == '$' &&
		(strncmp(curCharPtr, SPECIAL, SPECIAL_LEN) == 0)) {
		char machTypeBuffer[32];
		char *machType;

		fs_Stats.lookup.numSpecial++;
		if (clientID == rpc_SpriteID) {
		    /*
		     * Can't count on the net stuff being setup for ourselves
		     * as that is done via a user program way after bootting.
		     * Instead, use a compiled in string.  This is important
		     * when opening "/initSprite", which is a link to 
		     * "/initSprite.$MACHINE", when running on the root server.
		     */
		    machType = mach_MachineType;
		} else {
		    Net_SpriteIDToMachType(clientID, 32, machTypeBuffer);
		    if (*machTypeBuffer == '\0') {
			printf(
			 "FslclLookup, no machine type for client %d\n",
				clientID);
			machType = "unknown";
		    } else {
			machType = machTypeBuffer;
		    }
		}
		while (*machType != '\0') {
		    *compPtr++ = *machType++;
		    if (compPtr - component >= FS_MAX_NAME_LENGTH) {
			status = FS_INVALID_ARG;
			goto endScan;
		    }
		}
		curCharPtr += SPECIAL_LEN;
#undef SPECIAL
#undef SPECIAL_LEN
	    } else {
		*compPtr++ = *curCharPtr++;
	    }
	    if (compPtr - component >= FS_MAX_NAME_LENGTH) {
		status = FS_INVALID_ARG;
		goto endScan;
	    }
	}
	fs_Stats.lookup.numComponents++;
	*compPtr = '\0';
	compLen = compPtr - component;
	/*
	 * Skip intermediate and trailing slashes so that *curCharPtr
	 * is Null when 'component' has the last component of the name.
	 */
	while (*curCharPtr == '/') {
	    curCharPtr++;
	}
	/*
	 * There are three cases for what the next component is:
	 * a sub-directory (or file), dot, and dot-dot.
	 */
	if ((compLen == 2) && component[0] == '.' && component[1] == '.') {
	    /* 
	     * Going to the parent directory ".."
	     */
	    if (curHandlePtr->hdr.fileID.minor == rootIDPtr->minor) {
		/*
		 * We are falling off the top of the domain.  Make the
		 * remaining part of the filename, including "../",
		 * available to our caller so it can go back to the prefix
		 * table.  Setting the prefixLength to zero indicates
		 * there is no prefix information in this LOOKUP_REDIRECT
		 */
		*newNameInfoPtrPtr = mnew(Fs_RedirectInfo);
		(*newNameInfoPtrPtr)->prefixLength = 0;
		(void)strcpy((*newNameInfoPtrPtr)->fileName, "../");
		(void)strcat((*newNameInfoPtrPtr)->fileName, curCharPtr);
		fs_Stats.lookup.parent++;
		status = FS_LOOKUP_REDIRECT;
	    } else {
		/*
		 * Advance curHandlePtr to the parent, "..".
		 * FindComponent will unlock its parentHandlePtr (the directory
		 * containing "..") after scanning it but before grabbing
		 * the handle for "..".  This prevents deadlock with another
		 * process descending the other way along the path.  
		 * We then nuke our own "parent" because it really isn't a
		 * parent anymore.
		 */
		if (parentHandlePtr != (Fsio_FileIOHandle *)NIL) {
		    Fsutil_HandleRelease(parentHandlePtr, TRUE);
		}
		parentHandlePtr = curHandlePtr;
		status = FindComponent(parentHandlePtr, component, compLen,
			    TRUE, &curHandlePtr, &dirOffset);
		/*
		 * Release the handle while being careful about its lock.
		 * The parent handle is normally aready unlocked by
		 * FindComponent, unless the directory is corrupted.
		 */
		Fsutil_HandleRelease(parentHandlePtr, (status != SUCCESS));
		parentHandlePtr = (Fsio_FileIOHandle *)NIL;
	    }
	} else if ((compLen == 1) && component[0] == '.') {
	    /*
	     * Just hang tight with . (dot) because we already
	     * have a locked handle for it.
	     */
	} else {
	    /*
	     * Advance to the next component and keep the handle on
	     * the parent locked so we can do deletes and creates.
	     */
	    if (parentHandlePtr != (Fsio_FileIOHandle *)NIL) {
		Fsutil_HandleRelease(parentHandlePtr, TRUE);
	    }
	    parentHandlePtr = curHandlePtr;
	    status = FindComponent(parentHandlePtr, component, compLen, FALSE,
			&curHandlePtr, &dirOffset);
	}
	/*
	 * At this point we have a locked handle on the current point
	 * in the lookup, and perhaps have a locked handle on the parent.
	 * Links are expanded now so we know whether or not the
	 * lookup is completed.  On the last component, we only
	 * expand the link if the FS_FOLLOW flag is present.
	 */
	if ((status == SUCCESS) &&
	    ((*curCharPtr != '\0') || (useFlags & FS_FOLLOW)) &&
	    ((curHandlePtr->descPtr->fileType == FS_SYMBOLIC_LINK ||
		curHandlePtr->descPtr->fileType == FS_REMOTE_LINK))) {
	    numLinks++;
	    fs_Stats.lookup.symlinks++;
	    if (numLinks > FS_MAX_LINKS) {
		status = FS_NAME_LOOP;
	    } else {
		/*
		 * An extra buffer is used because the caller probably only
		 * has a buffer just big enough for the name.
		 */
		int 	offset;		/* Distance of existing name from
					 * the start of its buffer */
		if (newNameBuffer == (char *)NIL) {
		    offset = (int)curCharPtr - (int)relativeName;
		    newNameBuffer = (char *)malloc(FS_MAX_PATH_NAME_LENGTH);
		} else {
		    offset = (int)curCharPtr - (int)newNameBuffer;
		}
		status = ExpandLink(curHandlePtr, curCharPtr, offset,
						    newNameBuffer);
		if (status == FS_FILE_NOT_FOUND) {
		    printf( "FslclLookup, empty link \"%s\"\n",
				relativeName);
		}
		curCharPtr = newNameBuffer;
	    }
	    if (status == SUCCESS) {
		/*
		 * (Note: One could enforce permissions on links here.)
		 * If the link is back to the root we have to REDIRECT,
		 * otherwise retreat the current point in the lookup to
		 * the parent directory before continuing.
		 */
		if (*curCharPtr == '/') {
		    *newNameInfoPtrPtr = mnew(Fs_RedirectInfo);
		    (void)strcpy((*newNameInfoPtrPtr)->fileName, curCharPtr);
		    status = FS_LOOKUP_REDIRECT;
		    /*
		     * Return the length of the prefix indicated by
		     * a remote link, zero means no prefix.
		     */
		    if (curHandlePtr->descPtr->fileType == FS_REMOTE_LINK) {
			fs_Stats.lookup.remote++;
			(*newNameInfoPtrPtr)->prefixLength = 
					    curHandlePtr->descPtr->lastByte;
		    } else {
			fs_Stats.lookup.redirect++;
			(*newNameInfoPtrPtr)->prefixLength = 0;
		    }
		} else if (parentHandlePtr != (Fsio_FileIOHandle *)NIL) {
		    Fsutil_HandleRelease(curHandlePtr, TRUE);
		    curHandlePtr = parentHandlePtr;
		    parentHandlePtr = (Fsio_FileIOHandle *)NIL;
		    status = SUCCESS;
		} else {
		    panic( "No parent after link");
		    status = FS_INVALID_ARG;
		}
	    }
	}
    }
endScan:
    if ((status == SUCCESS) ||
	((status == FS_FILE_NOT_FOUND) && (*curCharPtr == '\0'))) {
	/*
	 * Done with the lookup.  Determine the type of the file once
	 * we have a handle for it if its type is not already set from the
	 * file descriptor. Process creates, links, and deletes.
	 */
	switch(useFlags & (FS_CREATE|FS_DELETE|FS_LINK)) {
	    case 0:
		if (status == SUCCESS) {
		    /*
		     * Normal lookup completion.
		     */
		    status = CheckPermissions(curHandlePtr, useFlags, idPtr,
						type);
		}
		break;
	    case FS_CREATE:
		fs_Stats.lookup.forCreate++;
		if (status == SUCCESS && (useFlags & FS_EXCLUSIVE)) {
		    /*
		     * FS_EXCLUSIVE and FS_CREATE means that the file
		     * cannot already exist.
		     */
		    status = FS_FILE_EXISTS;
		} else if (status == FS_FILE_NOT_FOUND) {
		    /*
		     * 'component' is the last part of a pathname for a
		     * file we need to create and that doesn't exist.
		     * Check for write permission in the parent directory,
		     * choose a fileNumber for the new file, and then
		     * create the file itself.
		     */
		    int 	newFileNumber;
		    int 	nearbyFile;

		    status = CheckPermissions(parentHandlePtr, FS_WRITE, idPtr,
						    FS_DIRECTORY);
		    if (status == SUCCESS) {
			int logOp;
			dirFileNumber = parentHandlePtr->hdr.fileID.minor;
			newFileNumber = -1;
			dirOffset = -1;
			if (type == FS_DIRECTORY) {
			    logOp = FSDM_LOG_CREATE|FSDM_LOG_IS_DIRECTORY;
			    nearbyFile = -1;
			} else {
			    logOp = FSDM_LOG_CREATE;
			    nearbyFile = dirFileNumber;
			}
			logClientData = Fsdm_DirOpStart(logOp,
					  parentHandlePtr, dirOffset,
					  component, compLen,
					  newFileNumber, type,
					  (Fsdm_FileDescriptor *) NIL);
			if (recov_Transparent && clientID != rpc_SpriteID) {
			    recovLogClientData = Fsrecov_DirOpStart(logOp,
					      parentHandlePtr, dirOffset,
					      component, compLen,
					      newFileNumber, type,
					      (Fsdm_FileDescriptor *) NIL);
			}
			status = Fsdm_GetNewFileNumber(domainPtr, nearbyFile,
							     &newFileNumber);
			if (status == SUCCESS) {
			    status = CreateFile(domainPtr, parentHandlePtr,
				     component, compLen, newFileNumber, type,
				     permissions, idPtr, &curHandlePtr,
				     &dirOffset);
			    if (status != SUCCESS) {
				(void)Fsdm_FreeFileNumber(domainPtr,
						newFileNumber);
			    } 
			}
			if (status == SUCCESS) { 
			    Fsdm_DirOpEnd(logOp, 
				    parentHandlePtr, dirOffset,
				    component, compLen, newFileNumber, type,
				    curHandlePtr->descPtr, logClientData, 
				    status);
			    if (recov_Transparent && clientID != rpc_SpriteID) {
				Fsrecov_DirOpEnd(logOp, 
					parentHandlePtr, dirOffset,
					component, compLen, newFileNumber, type,
					curHandlePtr->descPtr,
					recovLogClientData, status);
			    }
			} else {
			    Fsdm_DirOpEnd(logOp, 
				     parentHandlePtr, dirOffset,
				     component, compLen, newFileNumber, type,
				     (Fsdm_FileDescriptor *) NIL, 
				     logClientData, status);
			    if (recov_Transparent && clientID != rpc_SpriteID) {
				Fsrecov_DirOpEnd(logOp, 
					 parentHandlePtr, dirOffset,
					 component, compLen, newFileNumber,
					 type, (Fsdm_FileDescriptor *) NIL, 
					 recovLogClientData, status);
			    }
			}
		    }
		} else {
		    /*
		     * If the file exists, it's like a normal lookup completion
		     * and we have to check permissions.
		     */
		    status = CheckPermissions(curHandlePtr, useFlags, idPtr,
						type);
		}
		if (status == SUCCESS) {
		    (void)Fsdm_FileDescStore(curHandlePtr, FALSE);
		}
		break;
	    case FS_LINK: {
		Boolean 		fileDeleted = FALSE;
		Fsio_FileIOHandle	*deletedHandlePtr;
		/*
		 * The presence of FS_LINK means that curHandlePtr references
		 * a file that is being linked to.  If the file already exists
		 * it is deleted first.  Then link is made with LinkFile.
		 */
		if (useFlags & FS_RENAME) {
		    logOp = FSDM_LOG_RENAME_LINK;
		    fs_Stats.lookup.forRename++;
		} else {
		    logOp = FSDM_LOG_LINK;
		    fs_Stats.lookup.forLink++;
		}
		dirFileNumber = parentHandlePtr->hdr.fileID.minor;
		if (status == SUCCESS) {
		    /*
		     * Linking to an existing file.
		     * This can only be in preparation for a rename.
		     */
		    if ((useFlags & FS_RENAME) &&
			(curHandlePtr->descPtr->fileType == type)) {

			/*
			 * Make sure the two files aren't actually the
			 * same.  If they are then just return SUCCESS,
			 * otherwise we would deadlock when we try to lock
			 * both file handles, unaware that they are the
			 * same handle.
			 */
			if (fileNumber == curHandlePtr->hdr.fileID.minor) {
			    break;
			}
			/*
			 * Try the delete, this fails on non-empty directories.
			 */
			deletedHandlePtr = curHandlePtr;
			status = DeleteFileName(parentHandlePtr,
			      curHandlePtr, component, compLen, FALSE, idPtr,
			      FSDM_LOG_RENAME_DELETE, clientID);
			if (status == SUCCESS) {
			    fileDeleted = TRUE;
			}
		    } else {
			/*
			 * Not ok to link to an existing file.
			 */
			status = FS_FILE_EXISTS;
		    }
		} else if (status == FS_FILE_NOT_FOUND) {
		    /*
		     * The file does not already exist.  Check write permission
		     * in the parent before making the link.
		     */
		    status = CheckPermissions(parentHandlePtr, FS_WRITE, idPtr,
						    FS_DIRECTORY);
		}
		if (status == SUCCESS) {
		    status = LinkFile(parentHandlePtr,
				component, compLen, fileNumber, logOp,
				&curHandlePtr, clientID);
		    if (status == SUCCESS) {
			(void)Fsdm_FileDescStore(curHandlePtr, FALSE);
		    }
		}
		if (fileDeleted) {
		    CloseDeletedFile(&parentHandlePtr, &deletedHandlePtr);
		}
		break;
	    }
	    case FS_DELETE:
		fs_Stats.lookup.forDelete++;
		if (status == SUCCESS) {
		    if ((curHandlePtr->descPtr->fileType != type) &&
			(type != FS_FILE)) {
			status = (type == FS_DIRECTORY) ? FS_NOT_DIRECTORY :
							  FS_WRONG_TYPE;
		    } else {
			logOp = (useFlags&FS_RENAME) ? FSDM_LOG_RENAME_UNLINK :
						       FSDM_LOG_UNLINK;
			status = DeleteFileName(parentHandlePtr, curHandlePtr,
				component, compLen,
				(int) (useFlags & FS_RENAME), idPtr, logOp,
				clientID);
			if (status == SUCCESS) {
			    CloseDeletedFile(&parentHandlePtr,
					&curHandlePtr);
			}
		    }
		}
		break;
	}
    }

    /*
     * Clean up state and return a fileHandle to our caller.
     */
    if (newNameBuffer != (char *)NIL) {
	free(newNameBuffer);
    }
    if (parentHandlePtr != (Fsio_FileIOHandle *)NIL) {
	Fsutil_HandleRelease(parentHandlePtr, TRUE);
    }
    if (curHandlePtr != (Fsio_FileIOHandle *)NIL) {
	if (status != SUCCESS) {
	    Fsutil_HandleRelease(curHandlePtr, TRUE);
	    curHandlePtr = (Fsio_FileIOHandle *)NIL;
	} 
    }
    if (handlePtrPtr != (Fsio_FileIOHandle **)NIL) {
	/*
	 * Return a locked handle that has had its reference count bumped.
	 */
	*handlePtrPtr = curHandlePtr;
    } else if (curHandlePtr != (Fsio_FileIOHandle *)NIL) {
	printf( "FslclLookup: caller didn't want handle\n");
	Fsutil_HandleRelease(curHandlePtr, TRUE);
    }
    if ((status != SUCCESS) ||
	(handlePtrPtr == (Fsio_FileIOHandle **)NIL)) {
	Fsdm_DomainRelease(prefixHdrPtr->fileID.major);
    }
    if (status == FS_FILE_NOT_FOUND) {
	fs_Stats.lookup.notFound++;
    }
    return(status);
}


/*
 *----------------------------------------------------------------------
 *
 * FindComponent --
 *
 *	Look for a pathname component in a directory.  This returns a locked
 *	file handle that has its reference count incremented.
 *
 * Results:
 *	SUCCESS or FS_FILE_NOT_FOUND
 *
 * Side effects:
 *	Disk I/O, installing and locking the handle.
 *
 *----------------------------------------------------------------------
 */
static ReturnStatus
FindComponent(parentHandlePtr, component, compLen, isDotDot, curHandlePtrPtr,
	      dirOffsetPtr)
    Fsio_FileIOHandle	*parentHandlePtr;	/* Locked handle of current 
						 * directory */
    register char 	*component;		/* Name of path component to 
						 * find */
    register int 	compLen;		/* The number of characters in 
						 * component */
    Boolean		isDotDot;		/* TRUE if component is "..".
						 * In this case the handle for
						 * the parent is returned
						 * UNLOCKED. */
    Fsio_FileIOHandle	**curHandlePtrPtr;	/* Return, locked handle */
    int			*dirOffsetPtr;		/* OUT: Offset into directory
						 * off component. */
{
    register Fslcl_DirEntry *dirEntryPtr;	/* Reference to directory entry */
    register char	*s1;		/* Pointers into components used */
    register char	*s2;		/*   for fast in-line string compare */
    register int 	blockOffset;	/* Offset within the directory */
    ReturnStatus 	status;
    Fscache_Block	*cacheBlockPtr;	/* Cache block */
    int 		dirBlockNum;	/* Block number within directory */
    int 		length;		/* Length variable for read call */
    FslclHashEntry		*entryPtr;	/* Name cache entry */
    Fs_FileID		fileID;		/* Used when fetching handles */

    /*
     * Check in system-wide name cache here before scanning
     * the directory's data blocks.
     */
    entryPtr = FSLCL_HASH_LOOK_ONLY(fslclNameTablePtr, component, parentHandlePtr);
    if (entryPtr != (FslclHashEntry *)NIL) {
	if (entryPtr->hdrPtr->fileID.type != FSIO_LCL_FILE_STREAM) {
	    panic(
		      "FindComponent: got trashy handle from cache");
	} else {
	    if (isDotDot) {
		/*
		 * Unlock this directory before grabbing the handle for "..".
		 * This prevents deadlock with another lookup that is
		 * descending from our parent ("..") into this directory.
		 */
		Fsutil_HandleUnlock(parentHandlePtr);
	    }
	    *curHandlePtrPtr = Fsutil_HandleDupType(Fsio_FileIOHandle,
						entryPtr->hdrPtr);
	    return(SUCCESS);
	}
    }

    dirBlockNum = 0;
    do {
	status = Fscache_BlockRead(&parentHandlePtr->cacheInfo, dirBlockNum,
			&cacheBlockPtr, &length, FSCACHE_DIR_BLOCK, FALSE);
	if (status != SUCCESS || length == 0) {
	    *curHandlePtrPtr = (Fsio_FileIOHandle *)NIL;
	    return(FS_FILE_NOT_FOUND);
	}
	dirEntryPtr = (Fslcl_DirEntry *)cacheBlockPtr->blockAddr;
	blockOffset = 0;
	while (blockOffset < length) {
	    if (dirEntryPtr->recordLength <= 0) {
		printf("Corrupted directory?");
		printf(" File ID <%d, %d, %d>",
				 parentHandlePtr->hdr.fileID.serverID,
				 parentHandlePtr->hdr.fileID.major,
				 parentHandlePtr->hdr.fileID.minor);
		printf(" dirBlockNum <%d>, blockOffset <%d>",
			     dirBlockNum, blockOffset);
		printf("\n");
		Fscache_UnlockBlock(cacheBlockPtr, (time_t) 0,
		                    -1, 0, FSCACHE_CLEAR_READ_AHEAD);
		*curHandlePtrPtr = (Fsio_FileIOHandle *)NIL;
		return(FS_FILE_NOT_FOUND);
	    }

	    if (dirEntryPtr->fileNumber != 0) {
		/*
		 * A valid directory record.  If component and the directory
		 * entry are the same length then compare them for a match.
		 * This String Compare is in-lined for efficiency.
		 */
		if ((dirEntryPtr->nameLength == compLen)) {
		    s1 = component;
		    s2 = dirEntryPtr->fileName;
		    do {
			if (*s1 == '\0') {
			    /*
			     * The strings are the same length so hitting
			     * the end of component indicates a match.
			     */
			    if (isDotDot) {
				/*
				 * Unlock this directory before grabbing the
				 * handle for "..".  This prevents deadlock
				 * with another lookup that is descending
				 * from our parent ("..") into this directory.
				 */
				Fsutil_HandleUnlock(parentHandlePtr);
			    }
			    *dirOffsetPtr = blockOffset + 
					dirBlockNum * FS_BLOCK_SIZE;
			    /*
			     * Inlined call to GetHandle().
			     */
			    fileID.type = FSIO_LCL_FILE_STREAM;
			    fileID.serverID = rpc_SpriteID;
			    fileID.major = parentHandlePtr->hdr.fileID.major;
			    fileID.minor = dirEntryPtr->fileNumber;
			    status = Fsio_LocalFileHandleInit(&fileID, component,
					    (Fsdm_FileDescriptor *) NIL, FALSE,
					    curHandlePtrPtr);

			    Fscache_UnlockBlock(cacheBlockPtr, (time_t) 0,
				            -1, 0, FSCACHE_CLEAR_READ_AHEAD);
			    if (status == SUCCESS) {
				FSLCL_HASH_INSERT(fslclNameTablePtr, component,
					     parentHandlePtr, *curHandlePtrPtr);
			    } else {
				/*
				 * It is possible that the file doesn't 
				 * exist anymore.  This happens when
				 * the parent directory of an open
				 * directory is deleted.  The ".." entry
				 * points to a deleted file.
				 */
				if (status == FS_FILE_REMOVED) {
				    status = FS_FILE_NOT_FOUND;
				} else {

				    printf(
		"FindComponent, no handle <0x%x> for \"%s\" fileNumber %d\n",
				    status, component, dirEntryPtr->fileNumber);
				}
				if (isDotDot) {
				    /*
				     * If we have an error, relock the handle
				     * because your caller will assume that it
				     * is locked.
				     */
				    Fsutil_HandleLock(parentHandlePtr);
				}
			    }
			    goto exit;	/* to quiet lint... */
#ifndef lint
			} else if (*s1++ != *s2++) {
			    break;
#else
			} else {
			    if (*s1 != *s2) {
				break;
			    }
			    s1++;
			    s2++;
#endif
			}
		    } while (TRUE);
		}
	    }
	    blockOffset += dirEntryPtr->recordLength;
	    dirEntryPtr = (Fslcl_DirEntry *)((int)dirEntryPtr +
					 dirEntryPtr->recordLength);
	}
	dirBlockNum++;
	Fscache_UnlockBlock(cacheBlockPtr, (time_t) 0, -1, 0, 0);
    } while(TRUE);
exit:
    return(status);
}


/*
 *----------------------------------------------------------------------
 *
 * InsertComponent --
 *	Add a name to a directory.
 *
 * Results:
 *	SUCCESS or an error code.
 *
 * Side effects:
 *	Adding the name to the directory, writing the modified directory block.
 *
 *----------------------------------------------------------------------
 */
static ReturnStatus
InsertComponent(curHandlePtr, component, compLen, fileNumber, dirOffsetPtr)
    Fsio_FileIOHandle *curHandlePtr;	/* Locked handle of current directory */
    char *component;			/* Name of path component to insert */
    int compLen;			/* The length of component */
    int fileNumber;			/* File Number of inserted name */
    int	*dirOffsetPtr;  /* OUT: directory offset of component's entry.*/
{
    ReturnStatus 	status;
    int			dirBlockNum;	/* Directory block index */
    int 		blockOffset;	/* Offset within a directory block. */
    Fslcl_DirEntry 		*dirEntryPtr;	/* Reference to directory entry. */
    int 		length;		/* Length variable for read call. */
    int 		recordLength;	/* Length of directory entry for 
					 * component. */
    int 		freeSpace;	/* Total amount of free bytes in a 
					 * directory block. */
    int 		extraBytes;	/* The number of free bytes attached to
				 	 * a directory entry. */
    Fscache_Block	*cacheBlockPtr;	/* Cache block. */

    length = FS_BLOCK_SIZE;
    recordLength = Fslcl_DirRecLength(compLen);
    /*
     * Loop through the directory blocks looking for space of at least
     * recordLength in which to insert the new directory record.
     */
    dirBlockNum = 0;
    do {
	/*
	 * Read in a full data block.
	 */
	status = Fscache_BlockRead(&curHandlePtr->cacheInfo, dirBlockNum,
			&cacheBlockPtr, &length, FSCACHE_DIR_BLOCK, TRUE);
	if (status != SUCCESS) {
	    printf( "InsertComponent: Read failed\n");
	    return(status);
	} else if (length == 0) {
	    /*
	     * No more space, have to grow the directory.  First we
	     * need another cache block.
	     */
	    Boolean found = FALSE;

	    Fscache_FetchBlock(&curHandlePtr->cacheInfo, dirBlockNum,
				FSCACHE_DIR_BLOCK, &cacheBlockPtr, &found);
	    if (found) {
		panic( "InsertComponent found new dir block");
	    }
	    bzero(cacheBlockPtr->blockAddr, FS_BLOCK_SIZE);
	}

	dirEntryPtr = (Fslcl_DirEntry *)cacheBlockPtr->blockAddr;
	blockOffset = 0;
	freeSpace = 0;
	while (blockOffset < length) {
	    /*
	     * Scan a data block looking for deleted entries that are
	     * large enough to use, or for entries that contain enough extra
	     * bytes for the record we have to insert.  The amount of fragmented
	     * space is accumulated in freeSpace but compaction is not 
	     * implemented.
	     */
	    if (dirEntryPtr->fileNumber != 0) {
		/*
		 * A valid directory record.
		 * Check the left-over bytes attached to this record.
		 */
		extraBytes = dirEntryPtr->recordLength -
			     Fslcl_DirRecLength(dirEntryPtr->nameLength);
		if (extraBytes >= recordLength) {
		    /*
		     * Can fit new entry in the space left over.
		     */ 
		    goto haveASlot;
		}
		/*
		 * Count bytes that occur in fragments of 4 bytes or more.
		 */
		freeSpace += extraBytes & ~(FSLCL_REC_LEN_GRAIN-1);
	    } else {
		/*
		 * A deleted name in the directory.
		 */
		if (dirEntryPtr->recordLength >= recordLength) {
		    goto haveASlot;
		}
		freeSpace += dirEntryPtr->recordLength;
	    }
	    blockOffset += dirEntryPtr->recordLength;
	    dirEntryPtr = (Fslcl_DirEntry *)((int)dirEntryPtr +
					 dirEntryPtr->recordLength);
	}
	/*
	 * Finished scanning a directory block.  Note if the accumulated
	 * free bytes in the block could be used for a directory entry.
	 * (We aren't implementing compaction for a while yet...)
	 */
	if (freeSpace >= recordLength) {
	    fsCompacts++;
	}
	if (length < FS_BLOCK_SIZE) {
	    /*
	     * Scanned up to the end of the last directory data block.  Need
	     * to append to the end of the directory.
	     */
	    bzero(cacheBlockPtr->blockAddr + length, FSLCL_DIR_BLOCK_SIZE);
	    dirEntryPtr->recordLength = FSLCL_DIR_BLOCK_SIZE;
	    length += FSLCL_DIR_BLOCK_SIZE;
	    break;
	}

	dirBlockNum++;
	Fscache_UnlockBlock(cacheBlockPtr,
	                    (time_t) 0, -1, 0, FSCACHE_CLEAR_READ_AHEAD);
    } while(TRUE);

haveASlot:

    /*
     * At this point dirEntryPtr references the slot we have to either re-use
     * or that has enough free bytes for us to use.
     */
    if (dirEntryPtr->fileNumber != 0) {
	/*
	 * Have to take space away from the end of a valid directory entry.
	 */
	int newRecordLength;	/* New length of the existing valid entry */
	Fslcl_DirEntry *tmpDirEntryPtr;	/* Pointer to new slot */

	newRecordLength = Fslcl_DirRecLength(dirEntryPtr->nameLength);
	tmpDirEntryPtr = (Fslcl_DirEntry *)((int)dirEntryPtr + newRecordLength);
	tmpDirEntryPtr->recordLength = dirEntryPtr->recordLength -
				       newRecordLength;
	dirEntryPtr->recordLength = newRecordLength;
	dirEntryPtr = tmpDirEntryPtr;
    }
    dirEntryPtr->fileNumber = fileNumber;
    dirEntryPtr->nameLength = compLen;
    (void)strcpy(dirEntryPtr->fileName, component);

    blockOffset = (((char *)dirEntryPtr) - (char *)(cacheBlockPtr->blockAddr));
    *dirOffsetPtr = dirBlockNum * FS_BLOCK_SIZE + blockOffset;
    status = CacheDirBlockWrite(curHandlePtr,cacheBlockPtr,dirBlockNum,length);
    return(status);
}


/*
 *----------------------------------------------------------------------
 *
 * DeleteComponent --
 *
 *	Delete a name from a directory.  The file descriptor itself
 *	is not modified.  It is assumed that the handle for the file that
 *	is being removed is already locked.
 *
 * Results:
 *	SUCCESS or FS_FILE_NOT_FOUND
 *
 * Side effects:
 *	Sets the fileNumber for the component to Zero (0) to mark as deleted.
 *
 *----------------------------------------------------------------------
 */
static ReturnStatus
DeleteComponent(parentHandlePtr, component, compLen, dirOffsetPtr)
    Fsio_FileIOHandle	*parentHandlePtr;/* Locked handle of current dir. */
    char 		*component;	/* Name to delete */
    int 		compLen;	/* Length of the name */
    int			*dirOffsetPtr;	/* OUT: Directory offset of component.*/
{
    ReturnStatus	status;
    int 		blockOffset;	/* Offset within a directory block */
    Fslcl_DirEntry 	*dirEntryPtr;	/* Reference to directory entry */
    Fslcl_DirEntry 	*lastDirEntryPtr;/* Back pointer used when merging 
					  * adjacent entries after the delete */
    int 		length;		/* Length variable for read call */
    Fscache_Block	*cacheBlockPtr;	/* Cache block. */
    int			dirBlockNum;

    dirBlockNum = 0;
    do {
	status = Fscache_BlockRead(&parentHandlePtr->cacheInfo, dirBlockNum,
			  &cacheBlockPtr, &length, FSCACHE_DIR_BLOCK, FALSE);
	if (status != SUCCESS || length == 0) {
	    return(FS_FILE_NOT_FOUND);
	}
	blockOffset = 0;
	lastDirEntryPtr = (Fslcl_DirEntry *)NIL;
	dirEntryPtr = (Fslcl_DirEntry *)cacheBlockPtr->blockAddr;
	while (blockOffset < length) {
	    if ((dirEntryPtr->fileNumber != 0) &&
		(dirEntryPtr->nameLength == compLen) &&
		(strcmp(component, dirEntryPtr->fileName) == 0)) {
		/*
		 * Delete the entry from the name cache.
		 */
		*dirOffsetPtr = blockOffset + FS_BLOCK_SIZE*dirBlockNum;
		FSLCL_HASH_DELETE(fslclNameTablePtr, component,parentHandlePtr);
		dirEntryPtr->fileNumber = 0;
		if (lastDirEntryPtr != (Fslcl_DirEntry *)NIL) {
		    /*
		     * Grow the previous record so that it now includes
		     * this one.
		     */
		    lastDirEntryPtr->recordLength += dirEntryPtr->recordLength;
		}
		/*
		 * Write out the modified directory block.
		 */
		status = CacheDirBlockWrite(parentHandlePtr, cacheBlockPtr, 
					   dirBlockNum, length);
		return(status);
	    }
	    blockOffset += dirEntryPtr->recordLength;
	    if ((blockOffset & (FSLCL_DIR_BLOCK_SIZE - 1)) == 0) {
		 lastDirEntryPtr = (Fslcl_DirEntry *) NIL;
	    } else {
		 lastDirEntryPtr = dirEntryPtr;
	    }
	    dirEntryPtr = 
		(Fslcl_DirEntry *)((int)dirEntryPtr + dirEntryPtr->recordLength);
	}
	dirBlockNum++;
	Fscache_UnlockBlock(cacheBlockPtr, (time_t) 0,
	                    -1, 0, FSCACHE_CLEAR_READ_AHEAD);
    } while(TRUE);
    /*NOTREACHED*/
}


/*
 *----------------------------------------------------------------------
 *
 * ExpandLink --
 *	Expand a link by shifting the remaining pathname over and
 *	inserting the contents of the link file.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Expands the link into nameBuffer.
 *
 *----------------------------------------------------------------------
 */
static ReturnStatus
ExpandLink(curHandlePtr, curCharPtr, offset, nameBuffer)
    Fsio_FileIOHandle	*curHandlePtr;	/* Handle on the link file */
    char	*curCharPtr;		/* Points to beginning of the remaining
					 * name that has to be shifted */
    int		offset;			/* Offset of curCharPtr within its 
					 * buffer */
    char 	nameBuffer[];		/* New buffer for the expanded name */
{
    ReturnStatus 	status;
    int 		linkNameLength;	/* The length of the name in the
					 * link NOT including the Null */
    register char 	*srcPtr;
    register char 	*dstPtr;

    linkNameLength = curHandlePtr->descPtr->lastByte;	/* + 1 */
    if (linkNameLength < 0) {				/* <= */
	return(FS_FILE_NOT_FOUND);
    }
    if (*curCharPtr == '\0') {
	/*
	 * There is no pathname, just make sure the new name is Null terminated
	 */
	nameBuffer[linkNameLength] = '\0';		/* still ok */
    } else {
	/*
	 * Set up srcPtr to point to the start of the remaining pathname.
	 * Set up dstPtr to point to just after where the link will be,
	 * ie. just where the '/' is that separates the link value from
	 * the remaining pathname.
	 */
	srcPtr = curCharPtr;
	dstPtr = &nameBuffer[linkNameLength];
	if (linkNameLength < offset) {
	    /*
	     * The remaining name has to be shifted left.
	     * For example, /first is a link to /usr (or /users).  With the
	     * filename /first/abc, curCharPtr will point to the 'a' in "abc".
	     * dstPtr points to the '\0' after "/usr".
	     *
	     *  / f i r s t / a b c \0		{ current name, offset = 7 }
	     *  / u s r \0			{ link name, len = 4 }
	     *  / u s e r s \0			{   or len = 6 }
	     *  / u s r / a b c			{ final result }
	     *
	     * Insert the separating '/' first, then start at the beginning
	     * of the remaining name and copy it into the new buffer.
	     */
	    *dstPtr = '/';
	    dstPtr++;
	    for( ; ; ) {
		*dstPtr = *srcPtr;
	        if (*srcPtr == '\0') {
		    break;
		}
		dstPtr++;
		srcPtr++;
	    }
	} else {
	    /*
	     * The remaining name has to be shifted right.
	     * For example, /first is a link to /usr/tmp.  With the filename
	     * /first/abc, curCharPtr will point to the 'a' in "abc".
	     * dstPtr points to the '\0' after "/usr/tmp".
	     *
	     * / f i r s t / a b c \0		{ current name, offset = 7 }
	     * / u s r / t m p \0		{ link name, len = 8 }
	     * / u s r / t m p / a b c		{ final result }
	     * 
	     * Zoom to the end of the name (adjusting dstPtr to account
	     * for where the '/' will go) and then shift the name right.
	     */
	    dstPtr++;
	    while (*srcPtr != '\0') {
		srcPtr++;
		dstPtr++;
	    }
	    while (srcPtr >= curCharPtr) {
		*dstPtr = *srcPtr;
		dstPtr--;
		srcPtr--;
	    }
	    *dstPtr = '/';
	}
    }
    /*
     * Read and insert the link name in front of the remaining name
     * that was just shifted over.
     */
    status = Fscache_Read(&curHandlePtr->cacheInfo, 0, nameBuffer, 0,
		    &linkNameLength, (Sync_RemoteWaiter *)NIL);
    if ((status == SUCCESS) || (linkNameLength > 0)) { 
	(void)Fsdm_UpdateDescAttr(curHandlePtr, &curHandlePtr->cacheInfo.attr, 
			FSDM_FD_ACCESSTIME_DIRTY);
    }

    /*
     * FIX HERE to handle old sprite links that include a null.
     */
#ifdef notdef
    if (nameBuffer[linkNameLength-1] == '\0') {
	/*
	 * Old Sprite link with trailing NULL.  Shift remaining name over
	 * to the left one place.
	 */
	dstPtr = &nameBuffer[linkNameLength-1];
	srcPtr = dstPtr + 1;
	while (*srcPtr != '\0') {
	    *dstPtr++ = *srcPtr++;
	}
	*dstPtr = '\0'
    }
#endif /* notdef */
    return status;
}

/*
 *----------------------------------------------------------------------
 *
 * GetHandle --
 *
 *	Given a file number and the handle on the parent directotry,
 *	this routine returns a locked handle for the file.  This is a
 *	small layer on top of Fsio_LocalFileHandleInit that is oriented
 *	towards the needs of the lookup routines.
 *
 * Results:
 *	A return code.  If SUCCESS the returned handle is locked.
 *
 * Side effects:
 *	Calls Fsio_LocalFileHandleInit to set up the handle.
 *
 *----------------------------------------------------------------------
 */

static ReturnStatus
GetHandle(fileNumber, newDescPtr, curHandlePtr, name, newHandlePtrPtr)
    int		fileNumber;		/* Number of file to get handle for */
    Fsdm_FileDescriptor *newDescPtr;
    Fsio_FileIOHandle	*curHandlePtr;	/* Handle on file in the same domain */
    char	*name;			/* File name for error msgs */
    Fsio_FileIOHandle	**newHandlePtrPtr;/* Return, ref. to installed handle */
{
    register ReturnStatus status;
    Fs_FileID fileID;

    fileID.type = FSIO_LCL_FILE_STREAM;
    fileID.serverID = rpc_SpriteID;
    fileID.major = curHandlePtr->hdr.fileID.major;
    fileID.minor = fileNumber;
    status = Fsio_LocalFileHandleInit(&fileID, name, newDescPtr, FALSE,
		newHandlePtrPtr);
    return(status);
}

/*
 *----------------------------------------------------------------------
 *
 * CreateFile --
 *      Create a file by adding it do a directory and and initializing its
 *      file descriptor.  The caller has already allocated the fileNumber
 *      for the file.  The file descriptor is written to disk before the
 *	name is inserted into the directory.
 *
 * Results:
 *	SUCCESS or an error code.
 *
 * Side effects:
 *	Calls InsertComponent, Fsdm_FileDescInit.
 *
 *----------------------------------------------------------------------
 */
static ReturnStatus
CreateFile(domainPtr, parentHandlePtr, component, compLen, fileNumber, type,
	   permissions, idPtr, curHandlePtrPtr, dirOffsetPtr)
    Fsdm_Domain	*domainPtr;		/* Domain of the file */
    Fsio_FileIOHandle	*parentHandlePtr;/* Handle of directory in which to add 
					 * file. */
    char	*component;		/* Name of the file */
    int		compLen;		/* The length of component */
    int		fileNumber;		/* Domain relative file number */
    int		type;			/* Type of the file */
    int		permissions;		/* Permission bits on the file */
    Fs_UserIDs	*idPtr;			/* User ID of calling process */
    Fsio_FileIOHandle	**curHandlePtrPtr;/* Return, handle for the new file */
    int		*dirOffsetPtr;  /* OUT: directory offset of component's entry.*/
{
    ReturnStatus	status;
    Fsdm_FileDescriptor	*parentDescPtr;	/* Descriptor for the parent */
    Fsdm_FileDescriptor	*newDescPtr;	/* Descriptor for the new file */

    /*
     * Set up the file descriptor using the group ID from the parent directory.
     */
    parentDescPtr = parentHandlePtr->descPtr;
    newDescPtr = (Fsdm_FileDescriptor *)malloc(sizeof(Fsdm_FileDescriptor));
    status = Fsdm_FileDescInit(domainPtr, fileNumber, type, permissions,
			    idPtr->user, parentDescPtr->gid, newDescPtr);
    if (status == SUCCESS) {
	if (type == FS_DIRECTORY) {
	    /*
	     * Both the parent and the directory itself reference it.
	     */
	    newDescPtr->numLinks = 2;
	    newDescPtr->flags |= FSDM_FD_LINKS_DIRTY;
	}
	/*
	 * GetHandle does extra work because we already have the
	 * file descriptor all set up...
	 */
	status = GetHandle(fileNumber, newDescPtr, parentHandlePtr, component,
			    curHandlePtrPtr);
	if (status != SUCCESS) {
	    /*
	     * GetHandle shouldn't fail because we just wrote out
	     * the file descriptor.
	     */
	    panic( "CreateFile: GetHandle failed\n");
	} else {
	    if (type == FS_DIRECTORY) {
		status = WriteNewDirectory(*curHandlePtrPtr,
					    parentHandlePtr);
	    }
	    if (status == SUCCESS) {
		/*
		 * Commit by adding the name to the directory.  If we crash
		 * after this the file will show up and have a good
		 * descriptor for itself on disk.
		 */
		status = InsertComponent(parentHandlePtr, component,
					  compLen, fileNumber, dirOffsetPtr);
		if (type == FS_DIRECTORY) {
		    if (status == SUCCESS) {
			/*
			 * Update the parent directory to reflect
			 * the addition of a new sub-directory.
			 */
			parentDescPtr->numLinks++;
			parentDescPtr->descModifyTime = Fsutil_TimeInSeconds();
			parentDescPtr->flags |= FSDM_FD_LINKS_DIRTY;
		       (void)Fsdm_FileDescStore(parentHandlePtr, FALSE);
		    }
		}
	    }
	    if (status != SUCCESS) {
		/*
		 * Couldn't add to the directory, no disk space perhaps.
		 * Unwind by marking the file descriptor as free and
		 * releasing the handle we've created.
		 */
		panic("CreateFile: aborting create of %d (%s) in %d\n",
			fileNumber, component, 
			parentHandlePtr->hdr.fileID.minor);
		newDescPtr->flags = FSDM_FD_FREE;
		Fsutil_HandleRelease(*curHandlePtrPtr, TRUE);
		*curHandlePtrPtr = (Fsio_FileIOHandle *)NIL;
	    }
	}
    }
    if (status != SUCCESS)  { 
	free((Address) newDescPtr);
    } else {
       (void)Fsdm_FileDescStore(parentHandlePtr, FALSE);
    }
    return(status);
}


/*
 *----------------------------------------------------------------------
 *
 * WriteNewDirectory --
 *      Write a new sub-directory.  Set the file numbers on the canned image
 *      of a new directory and write the block to the directory.
 *
 * Results:
 *	SUCCESS or an error code.
 *
 * Side effects:
 *	Disk I/O to write the directory data block.
 *
 *----------------------------------------------------------------------
 */
static	ReturnStatus
WriteNewDirectory(curHandlePtr, parentHandlePtr)
    Fsio_FileIOHandle *curHandlePtr;		/* Handle of file to delete */
    Fsio_FileIOHandle *parentHandlePtr;	/* Handle of directory in which
						 * to delete file*/
{
    ReturnStatus	status;
    int			offset;
    int			length;
    register Fslcl_DirEntry *dirEntryPtr;
    char		*dirBlock;

    /*
     * The malloc and Byte_Copy could be avoided by puting this routine
     * into its own monitor so that it could write directly onto fslclEmptyDirBlock
     * fslclEmptyDirBlock is already set up with ".", "..", and the correct
     * nameLengths and recordLengths.
     */
    dirBlock = (char *)malloc(FSLCL_DIR_BLOCK_SIZE);
    bcopy((Address)fslclEmptyDirBlock, (Address)dirBlock, FSLCL_DIR_BLOCK_SIZE);
    dirEntryPtr = (Fslcl_DirEntry *)dirBlock;
    dirEntryPtr->fileNumber = curHandlePtr->hdr.fileID.minor;
    dirEntryPtr = (Fslcl_DirEntry *)((int)dirEntryPtr +
				 dirEntryPtr->recordLength);
    dirEntryPtr->fileNumber = parentHandlePtr->hdr.fileID.minor;
    offset = 0;
    length = FSLCL_DIR_BLOCK_SIZE;
    status = Fscache_Write(&curHandlePtr->cacheInfo, 0, (Address)dirBlock,
		offset, &length, (Sync_RemoteWaiter *)NIL);
    if (status == SUCCESS) {
	(void)Fsdm_UpdateDescAttr(curHandlePtr, &curHandlePtr->cacheInfo.attr, 
			FSDM_FD_MODTIME_DIRTY);
    }
    free(dirBlock);
    return(status);
}


/*
 *----------------------------------------------------------------------
 *
 * LinkFile --
 *	Create an entry in a directory that references an existing file.
 *	This understands that links to directories are made as part of
 *	a rename and checks that no circularities in the directory structure
 *	result from moving a directory.  Other than that it is left to
 *	the caller to check permissions.
 *
 * Results:
 *	SUCCESS or an error code.
 *
 * Side effects:
 *	Calls InsertComponent, bumps the link count on the existing file.
 *	Calls MoveDirectory in the case of directories.
 *
 *----------------------------------------------------------------------
 */
static ReturnStatus
LinkFile(parentHandlePtr, component, compLen, fileNumber, logOp,curHandlePtrPtr,
	clientID)
    Fsio_FileIOHandle	*parentHandlePtr;/* Handle of directory in which to add 
					 * file. */
    char	*component;		/* Name of the file */
    int		compLen;		/* The length of component */
    int		fileNumber;		/* Domain relative file number */
    int		logOp;
    Fsio_FileIOHandle	**curHandlePtrPtr;/* Return, handle for the new file */
    int		clientID;		/* ID of requesting client. */
{
    ReturnStatus	status;
    Fsdm_FileDescriptor	*linkDescPtr;	/* Descriptor for the existing file */
    Time 		modTime;	/* Descriptors are modified */
    ClientData		logClientData;
    ClientData		recovLogClientData = (ClientData) 0;
    int			dirOffset;

    if (fileNumber == parentHandlePtr->hdr.fileID.minor) {
	/*
	 * Trying to move a directory into itself, ie. % mv subdir subdir
	 */
	return(GEN_INVALID_ARG);
    }
    status = GetHandle(fileNumber, (Fsdm_FileDescriptor *) NIL,
			parentHandlePtr, component, curHandlePtrPtr);
    if (status != SUCCESS) {
	printf( "LinkFile: can't get existing file handle\n");
	return(FAILURE);
    } else if ((*curHandlePtrPtr)->descPtr->fileType == FS_DIRECTORY) {
	logOp |= FSDM_LOG_IS_DIRECTORY;
	status = OkToMoveDirectory(parentHandlePtr, *curHandlePtrPtr);
    }
    if (status == SUCCESS) {
	linkDescPtr = (*curHandlePtrPtr)->descPtr;
	logClientData = Fsdm_DirOpStart(logOp, parentHandlePtr, -1,
				component, compLen, fileNumber, 
					linkDescPtr->fileType,
					linkDescPtr);
	if (recov_Transparent && clientID != rpc_SpriteID) {
	    recovLogClientData = Fsrecov_DirOpStart(logOp, parentHandlePtr, -1,
		    component, compLen, fileNumber, 
		    linkDescPtr->fileType, linkDescPtr);
	}
	linkDescPtr->numLinks++;
	linkDescPtr->descModifyTime = Fsutil_TimeInSeconds();
	linkDescPtr->flags |= FSDM_FD_LINKS_DIRTY;
	status = Fsdm_FileDescStore(*curHandlePtrPtr, FALSE);
	if (status == SUCCESS) {
	    /*
	     * Commit by adding the name to the directory.
	     */
	    status = InsertComponent(parentHandlePtr, component, compLen,
						fileNumber, &dirOffset);
	    if ((*curHandlePtrPtr)->descPtr->fileType == FS_DIRECTORY &&
		status == SUCCESS) {
		/*
		 * This link is part of a rename (links to directories are
		 * only allowed at this time), and the ".." entry in the
		 * directory may have to be fixed.
		 */
		modTime.seconds = Fsutil_TimeInSeconds();
		status = MoveDirectory(&modTime, parentHandlePtr,
						    *curHandlePtrPtr);
		if (status != SUCCESS) {
		    (void)DeleteComponent(parentHandlePtr, component,
						    compLen, &dirOffset);
		}
	    }
	    if (status != SUCCESS) {
		/*
		 * Couldn't add to the directory because of I/O probably.
		 * Unwind by reducing the link count.  This gets the cache
		 * consistent with the cached directory image anyway.
		 */
		linkDescPtr->numLinks--;
		linkDescPtr->flags |= FSDM_FD_LINKS_DIRTY;
		(void)Fsdm_FileDescStore(*curHandlePtrPtr, FALSE);
		Fsutil_HandleRelease(*curHandlePtrPtr, TRUE);
		*curHandlePtrPtr = (Fsio_FileIOHandle *)NIL;
	    } else {
		(void)Fsdm_FileDescStore(parentHandlePtr, FALSE);
	    }
	} else {
	    linkDescPtr->numLinks--;
	    linkDescPtr->flags &= ~FSDM_FD_LINKS_DIRTY;
	}
	Fsdm_DirOpEnd(logOp, parentHandlePtr, dirOffset, 
		  component, compLen, fileNumber, linkDescPtr->fileType,
		  linkDescPtr, logClientData, status);
	if (recov_Transparent && clientID != rpc_SpriteID) {
		Fsrecov_DirOpEnd(logOp, parentHandlePtr, dirOffset, 
			component, compLen, fileNumber, linkDescPtr->fileType,
			linkDescPtr, recovLogClientData, status);
	}
    }
    return(status);
}


/*
 *----------------------------------------------------------------------
 *
 * OkToMoveDirectory --
 *
 *	Check that it is ok to move a directory.  Dis-connected trees and
 *	loops in directory structure are prevented here.
 *
 * Results:
 *	A return code.
 *
 * Side effects:
 *	File handles are momentarily locked on the route from the new
 *	position in the hierarchy up to the root to check against illegal moves.
 *	This action might cause deadlock with another process desending along
 *	same route, descenders hold a lock on the parent while they try for
 *	a lock on the next sub-directory.
 *
 *----------------------------------------------------------------------
 */
static ReturnStatus
OkToMoveDirectory(newParentHandlePtr, curHandlePtr)
    Fsio_FileIOHandle *newParentHandlePtr;	/* New parent directory for
						 * curHandlePtr */
    Fsio_FileIOHandle *curHandlePtr;		/* Directory being moved */
{
    ReturnStatus	status;
    int 		oldParentFileNumber;	/* File number of original 
						 * parent directory. */
    int			newParentNumber;	/* File number of new 
						 * parent directory. */

    status = GetParentNumber(curHandlePtr, &oldParentFileNumber);
    if (status != SUCCESS) {
	return(status);
    }
    newParentNumber = newParentHandlePtr->hdr.fileID.minor;
    if (oldParentFileNumber == newParentNumber) {
	/*
	 * ".." entry is ok because the rename is within the same directory.
	 */
	status = SUCCESS;
    } else {
	/*
	 * Have to trace up from the new parent to make sure we don't find
	 * the current file.  If we let that happen then you create
	 * dis-connected loops in the directory structure.
	 */
	Fsio_FileIOHandle *parentHandlePtr;
	int parentNumber;

	for (parentNumber = newParentNumber; status == SUCCESS; ) {
	    if (parentNumber == curHandlePtr->hdr.fileID.minor) {
		status = FS_INVALID_ARG;
	    } else if (parentNumber == FSDM_ROOT_FILE_NUMBER ||
		       parentNumber == oldParentFileNumber) {
		break;
	    } else {
		/*
		 * Advance parentNumber upwards towards the root.  The
		 * handles are locked because of the I/O required to get
		 * the parent file number.  We have to be careful when
		 * locking the parents because the first parent
		 * (newParentHandlePtr) is already locked by us.
		 *
		 * It still seems like there is a possibility of deadlock if
		 * someone is desending into newParentHandlePtr and has a lock
		 * on the directory above that.  FIX ME
		 */
		if (parentNumber != newParentNumber) {
		    status = GetHandle(parentNumber,
		       (Fsdm_FileDescriptor *) NIL,   curHandlePtr, (char *)NIL,
					&parentHandlePtr);
		} else {
		    parentHandlePtr = newParentHandlePtr;
		}
		if (status == SUCCESS) {
		    status = GetParentNumber(parentHandlePtr, &parentNumber);
		}
		if (parentHandlePtr != newParentHandlePtr) {
		    (void)Fsutil_HandleRelease(parentHandlePtr, TRUE);
		}
	    }
	}
    }
    return(status);
}


/*
 *----------------------------------------------------------------------
 *
 * MoveDirectory --
 *
 *	Complete the moving of a directory by updating its reference to
 *	its parent and incrementing the link count on its new parent.
 *
 * Results:
 *	A return code.
 *
 * Side effects:
 *	Sets the directory's parent entry to reference the parent passed in.
 *	Increments the link count on the new parent.
 *
 *----------------------------------------------------------------------
 */
static ReturnStatus
MoveDirectory( modTimePtr, newParentHandlePtr, curHandlePtr)
    Time	*modTimePtr;			/* Modify time for parent */
    Fsio_FileIOHandle	*newParentHandlePtr;	/* New parent directory for 
						 * curHandlePtr */
    Fsio_FileIOHandle	*curHandlePtr;		/* Directory being moved */
{
    ReturnStatus	status;
    int			oldParentFileNumber;	/* File number of original 
						 * parent directory */
    int			newParentNumber;	/* File number of new 
						 * parent directory */

    status = GetParentNumber(curHandlePtr, &oldParentFileNumber);
    if (status != SUCCESS) {
	printf(
		  "MoveDirectory: Can't get parent's file number\n");
    } else {
	newParentNumber = newParentHandlePtr->hdr.fileID.minor;
	if (oldParentFileNumber != newParentNumber) {
	    /*
	     * Patch the directory entry for ".."
	     */
	    FSLCL_HASH_DELETE(fslclNameTablePtr, "..", curHandlePtr);
	    status = SetParentNumber(curHandlePtr, newParentNumber);
	}
	if (status == SUCCESS) {
	    /*
	     * Increment the link count on the new parent.  The old parent's
	     * link count gets decremented when the orignial instance of the
	     * directory is removed.  This is done even if the move is within
	     * the same directory because deleting the original name later will
	     * reduce the link count.
	     */
	    register Fsdm_FileDescriptor *parentDescPtr;

	    parentDescPtr = newParentHandlePtr->descPtr;
	    parentDescPtr->numLinks++;
	    parentDescPtr->descModifyTime = modTimePtr->seconds;
	    parentDescPtr->flags |= FSDM_FD_LINKS_DIRTY;
	    (void)Fsdm_FileDescStore(newParentHandlePtr, FALSE);
	}
    }
    return(status);
}


/*
 *----------------------------------------------------------------------
 *
 * GetParentNumber --
 *	Read a directory to determine the file number of the parent.
 *
 * Results:
 *	SUCCESS or an error code, sets *parentNumberPtr to be the file number
 *	from the ".." entry in the directory.
 *
 * Side effects:
 *	None, except for the I/O to read the directory block.
 *
 *----------------------------------------------------------------------
 */
static ReturnStatus
GetParentNumber(curHandlePtr, parentNumberPtr)
    Fsio_FileIOHandle	*curHandlePtr;	/* Handle for current directory */
    int		*parentNumberPtr;	/* Result, the file number of the parent
					 * of curHandlePtr */
{
    ReturnStatus 	status;
    int 		length;
    register Fslcl_DirEntry *dirEntryPtr;
    Fscache_Block	*cacheBlockPtr;

    status = Fscache_BlockRead(&curHandlePtr->cacheInfo, 0, &cacheBlockPtr,
				&length, FSCACHE_DIR_BLOCK, FALSE);
    if (status != SUCCESS) {
	return(status);
    } else if (length == 0) {
	return(FAILURE);
    }

    dirEntryPtr = (Fslcl_DirEntry *)cacheBlockPtr->blockAddr;
    if (dirEntryPtr->nameLength != 1 ||
	dirEntryPtr->fileName[0] != '.' ||
	dirEntryPtr->fileName[1] != '\0') {
	printf(
		  "GetParentNumber: \".\", corrupted directory\n");
	status = FAILURE;
    } else {
	dirEntryPtr = 
		(Fslcl_DirEntry *)((int)dirEntryPtr + dirEntryPtr->recordLength);
	if (dirEntryPtr->nameLength != 2 ||
	    dirEntryPtr->fileName[0] != '.' ||
	    dirEntryPtr->fileName[1] != '.' ||
	    dirEntryPtr->fileName[2] != '\0') {
	    printf(
		      "GetParentNumber: \"..\", corrupted directory\n");
	    status = FAILURE;
	} else {
	    *parentNumberPtr = dirEntryPtr->fileNumber;
	}
    }
    Fscache_UnlockBlock(cacheBlockPtr, (time_t) 0,
	                -1, 0, FSCACHE_CLEAR_READ_AHEAD);

    return(status);
}


/*
 *----------------------------------------------------------------------
 *
 * SetParentNumber --
 *	Patch the file number for the ".." entry of a directory.
 *
 * Results:
 *	SUCCESS or an error code from the I/O.
 *
 * Side effects:
 *	Changes the parent file number of the ".." entry.
 *
 *----------------------------------------------------------------------
 */
static ReturnStatus
SetParentNumber(curHandlePtr, newParentNumber)
    Fsio_FileIOHandle	*curHandlePtr;	/* Handle for current directory */
    int 		newParentNumber;/* The new file number of the parent
					 * of curHandlePtr */
{
    ReturnStatus	status;
    int 		length;
    register Fslcl_DirEntry *dirEntryPtr;
    Fscache_Block	*cacheBlockPtr;

    status = Fscache_BlockRead(&curHandlePtr->cacheInfo, 0, &cacheBlockPtr,
				&length, FSCACHE_DIR_BLOCK, FALSE);
    if (status != SUCCESS) {
	return(status);
    } else if (length == 0) {
	return(FAILURE);
    }
    dirEntryPtr = (Fslcl_DirEntry *)cacheBlockPtr->blockAddr;
    if (dirEntryPtr->nameLength != 1 ||
	dirEntryPtr->fileName[0] != '.' ||
	dirEntryPtr->fileName[1] != '\0') {
	printf(
		  "SetParentNumber: \".\", corrupted directory\n");
	Fscache_UnlockBlock(cacheBlockPtr, (time_t) 0,
	                    -1, 0, FSCACHE_CLEAR_READ_AHEAD);
	return(FAILURE);
    }
    dirEntryPtr = (Fslcl_DirEntry *)((int)dirEntryPtr + dirEntryPtr->recordLength);
    if (dirEntryPtr->nameLength != 2 ||
	dirEntryPtr->fileName[0] != '.' ||
	dirEntryPtr->fileName[1] != '.' ||
	dirEntryPtr->fileName[2] != '\0') {
	printf("SetParentNumber: \"..\", corrupted directory\n");
	Fscache_UnlockBlock(cacheBlockPtr, (time_t) 0,
	                    -1, 0, FSCACHE_CLEAR_READ_AHEAD);
	return(FAILURE);
    }
    dirEntryPtr->fileNumber = newParentNumber;
    status = CacheDirBlockWrite(curHandlePtr, cacheBlockPtr, 0, length);
    return(status);
}


/*
 *----------------------------------------------------------------------
 *
 * DeleteFileName --
 *
 *      Delete a file by clearing its fileNumber in the directory and
 *	reducing its link count.  
 *
 * Results:
 *	SUCCESS or an error code.
 *
 * Side effects:
 *	Log entry may be written.
 *
 *----------------------------------------------------------------------
 */
static ReturnStatus
DeleteFileName(parentHandlePtr, curHandlePtr, component,
	     compLen, forRename, idPtr, logOp, clientID)
    Fsio_FileIOHandle *parentHandlePtr;	/* Handle of directory in
						 * which to delete file*/
    Fsio_FileIOHandle *curHandlePtr;	/* Handle of file to delete */
    char *component;				/* Name of the file to delte */
    int compLen;				/* The length of component */
    int forRename;		/* if FS_RENAME, then the file being delted
				 * is being renamed.  This allows non-empty
				 * directories to be deleted */
    Fs_UserIDs *idPtr;		/* User and group IDs */
    int		logOp;		/* Directory log operation.; */
    int		clientID;
{
    ReturnStatus status;
    Fsdm_FileDescriptor *parentDescPtr;	/* Descriptor for parent */
    Fsdm_FileDescriptor *curDescPtr;	/* Descriptor for the file to delete */
    int type;				/* Type of the file */
    int	fileNumber;			/* Number of file being deleted. */
    ClientData	logClientData;		/* ClientData returned from 
					 * DirOpStart. */
    ClientData	recovLogClientData = (ClientData) 0;
					/* ClientData returned from 
					 * DirOpStart, for recovery system. */
    int dirOffset;

    type = curHandlePtr->descPtr->fileType;
    if (parentHandlePtr == (Fsio_FileIOHandle *)NIL) {
	/*
	 * There is no handle on the parent because we have just
	 * gone up via "..".  You can't delete the parent.
	 */
	status = FS_NO_ACCESS;
    } else if ((compLen == 1) && (component[0] == '.')) {
	/*
	 * Disallow removing dot.
	 */
	status = FS_NO_ACCESS;
    } else if (type == FS_DIRECTORY && (!forRename) &&
		!DirectoryEmpty(curHandlePtr)) {
	status = FS_DIR_NOT_EMPTY;
    } else {
	/*
	 * One needs write permission in the parent to do the delete.
	 */
	status = CheckPermissions(parentHandlePtr, FS_WRITE, idPtr,
					FS_DIRECTORY);
    }
    if (status != SUCCESS) {
	return(status);
    }
    curDescPtr = curHandlePtr->descPtr;
    parentDescPtr = parentHandlePtr->descPtr;
    fileNumber = curHandlePtr->hdr.fileID.minor;
    dirOffset = -1;

    if (type == FS_DIRECTORY) {
	logOp |= FSDM_LOG_IS_DIRECTORY;
    }
    logClientData = Fsdm_DirOpStart(logOp, parentHandlePtr, dirOffset, 
		    component, compLen, fileNumber, type,
		    curHandlePtr->descPtr);
    if (recov_Transparent && clientID != rpc_SpriteID) {
	recovLogClientData = Fsrecov_DirOpStart(logOp, parentHandlePtr,
		dirOffset, component, compLen, fileNumber, type,
		curHandlePtr->descPtr);
    }
    /*
     * Remove the name from the directory first.
     */
    status = DeleteComponent(parentHandlePtr, component, compLen, &dirOffset);
    if (status == SUCCESS) {
	curDescPtr->numLinks--;
	curDescPtr->flags |= FSDM_FD_LINKS_DIRTY;
	if (type == FS_DIRECTORY) {
	    /*
	     * The directory might have been known in the hash table as
	     * someone's "..". Besure that this entry is gone.
	     */
	    FSLCL_HASH_DELETE(fslclNameTablePtr, "..", curHandlePtr);
	    if (!forRename) {
		/*
		 * Directories have an extra link because they reference 
		 * themselves.
		 */
		curDescPtr->numLinks--;
		if (curDescPtr->numLinks > 0) {
		    printf("DeleteFileName: extra links on directory\n");
		}
	    }
	}
	curDescPtr->descModifyTime = Fsutil_TimeInSeconds();
	status = Fsdm_FileDescStore(curHandlePtr, FALSE);
	if (status != SUCCESS) {
	    printf("DeleteFileName: (1) Couldn't store descriptor\n");
#ifdef notdef
	    return(status);
#endif
	}
	if (type == FS_DIRECTORY) {
	    /*
	     * A directory's link count reflects the number of subdirectories
	     * it has (they each have a ".." that references it.)  Here
	     * it is decremented because the subdirectory is going away.
	     */
	    parentDescPtr->numLinks--;
	    parentDescPtr->descModifyTime = Fsutil_TimeInSeconds();
	    parentDescPtr->flags |= FSDM_FD_LINKS_DIRTY;
	}
	status = Fsdm_FileDescStore(parentHandlePtr, FALSE);
	if (status != SUCCESS) {
	    printf("DeleteFileName: (2) Couldn't store descriptor\n");
#ifdef notdef
	    return(status);
#endif
	}
	if ((curDescPtr->numLinks <= 0) && (curHandlePtr->use.ref != 0)) {
	    logOp |= FSDM_LOG_STILL_OPEN;
	}
    } 
    Fsdm_DirOpEnd(logOp, parentHandlePtr, dirOffset,
		        component, compLen, fileNumber, type,
			curDescPtr, logClientData, status);
    if (recov_Transparent && clientID != rpc_SpriteID) {
	Fsrecov_DirOpEnd(logOp, parentHandlePtr, dirOffset,
		component, compLen, fileNumber, type,
		curDescPtr, recovLogClientData, status);
    }
    return(status);
}

/*
 *----------------------------------------------------------------------
 *
 * CloseDeletedFile --
 *
 *	This routine is called to close a deleted file.
 *	If there are no links left then the
 *	file's handle is marked as deleted.  Finally, if this routine
 *	has the last reference on the handle then the file's data blocks
 *	are truncated away and the file descriptor is marked as free.
 *
 *	This is a separate routine from DeleteFileName because the
 *	file cannot be closed unless the parent handle is unlocked
 *	(to prevent deadlock while doing the consistency callback),
 *	and the code to do a rename deletes the existing file, then
 *	does a link.  This would break if the parent handle was
 *	released during the delete.
 *
 * Results:
 *	SUCCESS or error code.
 *
 * Side effects:
 *	The parent and/or current handles may be released and set to NIL.
 *
 *----------------------------------------------------------------------
 */

static void
CloseDeletedFile(parentHandlePtrPtr, curHandlePtrPtr)
    Fsio_FileIOHandle **parentHandlePtrPtr;	/* Handle of parent of
						 * deleted file. */
    Fsio_FileIOHandle **curHandlePtrPtr;	/* Handle of deleted file. */

{
    register Fsio_FileIOHandle *curHandlePtr;	/* Local copy */

    curHandlePtr = *curHandlePtrPtr;
    if (curHandlePtr->descPtr->numLinks <= 0) {
	/*
	 * At this point curHandlePtr is potentially the last reference
	 * to the file.  If there are no users then do the delete, otherwise
	 * mark the handle as deleted and Fsio_FileClose will take care of 
	 * it.
	 */
	curHandlePtr->flags |= FSIO_FILE_NAME_DELETED;
	if (curHandlePtr->use.ref == 0) {
	    /*
	     * Handle the deletion and clean up the handle.
	     * We set the the clientID to us and specify client
	     * call-backs so that any other clients will be notified.
	     */
	    Fsutil_HandleRelease(*parentHandlePtrPtr, TRUE);
	    *parentHandlePtrPtr = (Fsio_FileIOHandle *)NIL;
	    (void)Fsio_FileCloseInt(curHandlePtr, 0, 0, 0, rpc_SpriteID, 
		    TRUE);
	    *curHandlePtrPtr = (Fsio_FileIOHandle *)NIL;
	} else {
	    Fsutil_HandleRelease(curHandlePtr, TRUE);
	    *curHandlePtrPtr = (Fsio_FileIOHandle *)NIL;
	}
    } else {
	Fsutil_HandleRelease(curHandlePtr, TRUE);
	*curHandlePtrPtr = (Fsio_FileIOHandle *)NIL;
    }
}


/*
 *----------------------------------------------------------------------
 *
 * Fslcl_DeleteFileDesc --
 *	Delete a file from disk given its file handle.  It is assumed that the
 *	file's name has already been deleted from the directory structure.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Delete the data blocks and mark file descriptor as free.  The
 *	handle remains locked during this call.  The DESCRIPTOR referenced
 *	by the handle is FREED because the file handle is going to
 *	be removed.
 *
 *----------------------------------------------------------------------
 */

ReturnStatus
Fslcl_DeleteFileDesc(handlePtr)
    Fsio_FileIOHandle *handlePtr;
{
    ReturnStatus status;
    Fsdm_Domain *domainPtr;

    if (handlePtr->descPtr->fileType == FS_DIRECTORY) {
	/*
	 * Remove .. from the name cache so we don't end up with
	 * a bad cache entry later when this directory is re-created.
	 */
	FSLCL_HASH_DELETE(fslclNameTablePtr, "..", handlePtr);
    }

    domainPtr = Fsdm_DomainFetch(handlePtr->hdr.fileID.major, FALSE);
    if (domainPtr == (Fsdm_Domain *)NIL) {
	return(FS_DOMAIN_UNAVAILABLE);
    }
    /*
     * The ordering of the deletion is as follows:
     * 1. Mark the descriptor on disk as free so if we crash the
     *		disk scavenger will free the blocks for us.
     * 2. Truncate the blocks out of the cache and from the descriptor.
     * 3. Mark the file descriptor as available in the bitmask.
     */
    handlePtr->descPtr->flags = FSDM_FD_FREE | FSDM_FD_DIRTY;
    status = Fsdm_FileDescStore(handlePtr,TRUE);
    if (status != SUCCESS) {
	printf("Fslcl_DeleteFileDesc: Can't mark descriptor as free\n");
    } else {
	status = Fsio_FileTrunc(handlePtr, 0, FSCACHE_TRUNC_DELETE);
	if (status != SUCCESS) {
	    printf("Fslcl_DeleteFileDesc: Can't truncate file <%d,%d> \"%s\"\n",
		    handlePtr->hdr.fileID.major, handlePtr->hdr.fileID.minor,
		    Fsutil_HandleName(handlePtr));
	} else {
	    (void)Fsdm_FreeFileNumber(domainPtr, handlePtr->hdr.fileID.minor);
	}
	free((Address)handlePtr->descPtr);
	handlePtr->descPtr = (Fsdm_FileDescriptor *)NIL;
    }
    Fsdm_DomainRelease(handlePtr->hdr.fileID.major);
    return(status);
}

/*
 *----------------------------------------------------------------------
 *
 * DirectoryEmpty --
 *	Scan a directory and determine if there are any files left
 *	in it other than "." and "..".
 *
 * Results:
 *	TRUE if the directory is empty, FALSE otherwise.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static Boolean
DirectoryEmpty(handlePtr)
    Fsio_FileIOHandle *handlePtr;	/* Handle of directory to check */
{
    ReturnStatus	status;
    int 		blockOffset;	/* Offset within a directory block */
    Fslcl_DirEntry 		*dirEntryPtr;	/* Reference to directory entry */
    int 		length;		/* Length for read call */
    int			dirBlockNum;
    Fscache_Block	*cacheBlockPtr;

    dirBlockNum = 0;
    do {
	status = Fscache_BlockRead(&handlePtr->cacheInfo, dirBlockNum,
		      &cacheBlockPtr, &length, FSCACHE_DIR_BLOCK, FALSE);
	if (status != SUCCESS || length == 0) {
	    /*
	     * Have run out of the directory and not found anything.
	     */
	    return(TRUE);
	}
	blockOffset = 0;
	dirEntryPtr = (Fslcl_DirEntry *)cacheBlockPtr->blockAddr;
	while (blockOffset < length) {
	    if (dirEntryPtr->fileNumber != 0) {
		/*
		 * A valid directory record.
		 */
		if ((strcmp(".", dirEntryPtr->fileName) == 0) ||
		    (strcmp("..", dirEntryPtr->fileName) == 0)) {
		    /*
		     * "." and ".." are ok
		     */
		} else {
		    Fscache_UnlockBlock(cacheBlockPtr, (time_t) 0,
			                -1, 0, FSCACHE_CLEAR_READ_AHEAD);
		    return(FALSE);
		}
	    }
	    blockOffset += dirEntryPtr->recordLength;
	    dirEntryPtr = 
		(Fslcl_DirEntry *)((int)dirEntryPtr + dirEntryPtr->recordLength);
	}
	dirBlockNum++;
	Fscache_UnlockBlock(cacheBlockPtr, (time_t) 0,
	                    -1, 0, FSCACHE_CLEAR_READ_AHEAD);
    } while(TRUE);
    /*NOTREACHED*/
}

/*
 *----------------------------------------------------------------------
 *
 * CheckPermissions --
 *
 *	Check permissions on a file during lookup.  This just looks
 *	at the uid, groupIDs, and the permission bits on the file.
 *	
 *	Some semantic checking is done:
 *		type indicates what kind of file to accept.
 *		Execution of files with no execute bit is prevented for uid = 0
 *		Execution of directories is prevented.
 *	Some semantic checking is not done:
 *		Doesn't check against writing to directories.  This is
 *		done later in the FileSrvOpen routine.
 *
 * Results:
 *	FS_NO_ACCESS if the useFlags include a permission that does
 *	not fit with the uid/groupIDs of the file.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */
static ReturnStatus
CheckPermissions(handlePtr, useFlags, idPtr, type)
    Fsio_FileIOHandle		*handlePtr;
    register int		useFlags;
    register Fs_UserIDs		*idPtr;
    int 			type;
{
    register Fsdm_FileDescriptor	*descPtr;
    register int		*groupPtr;
    register unsigned int 	permBits;
    register int 		index;
    register int		uid = idPtr->user;
    ReturnStatus 		status;

    if (handlePtr->hdr.fileID.type != FSIO_LCL_FILE_STREAM) {
	panic( "CheckPermissions on non-local file\n");
	return(FAILURE);
    }
    descPtr = handlePtr->descPtr;
    /*
     * Make sure the file type matches.  FS_FILE means any type, otherwise
     * it should match exactly.
     */
    if (type != FS_FILE && type != descPtr->fileType) {
	/*
	 * Patch around the fact that FS_REMOTE_LINK and FS_SYMBOLIC_LINK
	 * get or'ed together, but they are not-proper bit fields.
	 * (They equal 3 and 2, respectively.)
	 * Hence we allow a regular symbolic link to satisfy a
	 * request for a remote link.
	 */
	if ((type == (FS_REMOTE_LINK|FS_SYMBOLIC_LINK)) &&
	    ((descPtr->fileType == FS_SYMBOLIC_LINK) ||
	     (descPtr->fileType == FS_REMOTE_LINK))) {
/*	    printf( "Allowing a symlink for a remote link\n");*/
	} else {
	    switch(type) {
		case FS_DIRECTORY:
		    return(FS_NOT_DIRECTORY);
		default:
		    return(FS_WRONG_TYPE);
	    }
	}
    }
    /*
     * Dis-allow execution of directories...
     */
    if ((type == FS_FILE) && (useFlags & FS_EXECUTE) &&
	(descPtr->fileType != FS_FILE)) {
	return(FS_WRONG_TYPE);
    }
    /*
     * Reset SETUID/SETGID bit when writing a file.
     */
    if ((useFlags & FS_WRITE) && (descPtr->fileType == FS_FILE) &&
	(descPtr->permissions & (FS_SET_UID|FS_SET_GID))) {
	descPtr->permissions &= ~(FS_SET_UID|FS_SET_GID);
	descPtr->flags |= FSDM_FD_PERMISSIONS_DIRTY;
    }
    /*
     * Check for ownership permission.  This probably redundant with
     * respect to the checking done by FslclSetAttr.
     */
#ifdef notdef
    if (useFlags & FS_OWNERSHIP) {
	if ((uid != descPtr->uid) && (uid != 0)) {
	    return(FS_NOT_OWNER);
	}
    }
#endif notdef
    /*
     * Check read/write/exec permissions against one of the owner bits,
     * the group bits, or the world bits.  'permBits' is set to
     * be the corresponding bits from the file descriptor and then
     * shifted over so the comparisions are against the WORLD bits.
     */
    if (uid == 0) {
	/*
	 * For normal files, only check for execute permission.  This
	 * prevents root from being able to execute ordinary files by
	 * accident.  However, root has complete access to directories.
	 */
	if (descPtr->fileType == FS_DIRECTORY) {
	    return(SUCCESS);
	}
	useFlags &= FS_EXECUTE;
    }
    if (uid == descPtr->uid) {
	permBits = (descPtr->permissions >> 6) & 07;
    } else {
	for (index = idPtr->numGroupIDs, groupPtr = idPtr->group;
	     index > 0;
	     index--, groupPtr++) {
	    if (*groupPtr == descPtr->gid) {
		permBits = (descPtr->permissions >> 3) & 07;
		goto havePermBits;
	    }
	}
	permBits = descPtr->permissions & 07;
    }
havePermBits:
    if (((useFlags & FS_READ) && ((permBits & FS_WORLD_READ) == 0)) ||
	((useFlags & FS_WRITE) && ((permBits & FS_WORLD_WRITE) == 0)) ||
	((useFlags & FS_EXECUTE) && ((permBits & FS_WORLD_EXEC) == 0))) {
	/*
	 * The file's permission don't include what is needed.
	 */
	status = FS_NO_ACCESS;
    } else {
	status = SUCCESS;
    }
    return(status);
}


/*
 *----------------------------------------------------------------------
 *
 * CacheDirBlockWrite --
 *
 *	Write into a cache block returned from Fscache_BlockRead.  Used only
 *	for writing directories.
 *
 * Results:
 *	SUCCESS unless error when allocating disk space.
 *
 * Side effects:
 *	The cache block is unlocked.  It is deleted if the offset is the
 *	beginning of the block and the disk allocation failed.
 *
 *----------------------------------------------------------------------
 */

static ReturnStatus
CacheDirBlockWrite(handlePtr, blockPtr, blockNum, length)
    register	Fsio_FileIOHandle	*handlePtr;	/* Handle for file. */
    register	Fscache_Block	*blockPtr;	/* Cache block. */
    int				blockNum;	/* Block number. */
    int				length;		/* Number of valid bytes in
						 * the block. */
{
    ReturnStatus	status = SUCCESS;
    int			blockAddr = FSDM_NIL_INDEX;
    Boolean		newBlock;
    int			flags = FSCACHE_CLEAR_READ_AHEAD;
    int			offset;
    int			newLastByte;
    int			blockSize;

    offset =  blockNum * FS_BLOCK_SIZE;
    newLastByte = offset + length - 1;
    (void) (handlePtr->cacheInfo.backendPtr->ioProcs.allocate)
	((Fs_HandleHeader *)handlePtr, offset, length, 0,
		&blockAddr, &newBlock);
#ifdef lint
    (void) Fsdm_BlockAllocate((Fs_HandleHeader *)handlePtr, offset, length,
			0, &blockAddr, &newBlock);
    (void) FsrmtFileBlockAllocate((Fs_HandleHeader *)handlePtr, offset, length,
			0, &blockAddr, &newBlock);
#endif /* lint */
    if (blockAddr == FSDM_NIL_INDEX) {
	status = FS_NO_DISK_SPACE;
	printf("CacheDirBlockWrite: out of space for %s.\n",
	       Fsutil_HandleName(handlePtr));	/* DEBUG */
	if (handlePtr->descPtr->lastByte + 1 < offset) {
	    /*
	     * Delete the block if are appending and this was a new cache
	     * block.
	     */
	    flags = FSCACHE_DELETE_BLOCK;
	}
    }
    Fscache_UpdateDirSize(&handlePtr->cacheInfo, newLastByte);
    handlePtr->descPtr->dataModifyTime = Fsutil_TimeInSeconds();
    handlePtr->descPtr->descModifyTime = handlePtr->descPtr->dataModifyTime;
    handlePtr->descPtr->flags |= FSDM_FD_MODTIME_DIRTY;
    fs_Stats.blockCache.dirBytesWritten += FSLCL_DIR_BLOCK_SIZE;
    fs_Stats.blockCache.dirBlockWrites++;
    blockSize = handlePtr->descPtr->lastByte + 1 - (blockNum * FS_BLOCK_SIZE);
    if (blockSize > FS_BLOCK_SIZE) {
	blockSize = FS_BLOCK_SIZE;
    }
    Fscache_UnlockBlock(blockPtr, (time_t) Fsutil_TimeInSeconds(), blockAddr,
			blockSize, flags);
    return(status);
}


/*
 *----------------------------------------------------------------------
 *
 * Fslcl_CheckDirLog --
 *
 *	Check the directory op log against the contents of a dir.  If there's
 *	a discrepancy, modify the directory to contain the correct files
 *	and directories.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Modify the directory to contain the correct files.
 *
 *----------------------------------------------------------------------
 */
void
Fslcl_CheckDirLog(parentHandlePtr, dirLogList)
    Fsio_FileIOHandle	*parentHandlePtr;
    List_Links		*dirLogList;
{
    List_Links		*itemPtr;
    char		*component;
    int			compLen;
    FslclHashEntry	*entryPtr;	/* Name cache entry */
    Fsio_FileIOHandle	*handlePtr;
    Boolean		found;
    int			dirBlockNum;
    ReturnStatus	status;
    Fscache_Block	*cacheBlockPtr;	/* Cache block */
    int			length;
    int			blockOffset;
    Fslcl_DirEntry	*dirEntryPtr;	/* Reference to directory entry */
    char		buf[256];


    if (!recov_Transparent) {
	panic("Fslcl_CheckDirLog: shouldn't have been called.\n");
    }
    LIST_FORALL(dirLogList, itemPtr) {
	/* Try cached lookup first. */

	found = FALSE;
	Fsrecov_GetComponent(itemPtr, &component, &compLen);
	strncpy(buf, component, compLen);
	buf[compLen] = '\0';
	entryPtr =
	    FSLCL_HASH_LOOK_ONLY(fslclNameTablePtr, component, parentHandlePtr);

	if (entryPtr != (FslclHashEntry *)NIL) {
	    if (entryPtr->hdrPtr->fileID.type != FSIO_LCL_FILE_STREAM) {
		panic("Fslcl_CheckDirLog: got trashy handle from cache");
	    }
	    handlePtr = (Fsio_FileIOHandle *) entryPtr->hdrPtr;
	    found = TRUE;
	} else {
	    dirBlockNum = 0;
	    do {
		status = Fscache_BlockRead(&parentHandlePtr->cacheInfo,
			dirBlockNum, &cacheBlockPtr, &length, FSCACHE_DIR_BLOCK,
			FALSE);
		if (status != SUCCESS || length == 0) {
		    break;
		}
		dirEntryPtr = (Fslcl_DirEntry *)cacheBlockPtr->blockAddr;
		blockOffset = 0;
		while (blockOffset < length) {
		    if (dirEntryPtr->recordLength <= 0) {
			printf(" File ID <%d, %d, %d>",
					 parentHandlePtr->hdr.fileID.serverID,
					 parentHandlePtr->hdr.fileID.major,
					 parentHandlePtr->hdr.fileID.minor);
			printf(" dirBlockNum <%d>, blockOffset <%d>",
				     dirBlockNum, blockOffset);
			printf("\n");
			    Fscache_UnlockBlock(cacheBlockPtr, (time_t) 0,
					    -1, 0, FSCACHE_CLEAR_READ_AHEAD);
			panic("Fslcl_CheckDirLog: Corrupted directory?");
		    }

		    if (dirEntryPtr->fileNumber != 0) {
		    /*
		     * A valid directory record.  If component and the directory
		     * entry are the same length then compare them for a match.
		     * This String Compare is in-lined for efficiency.
		     */
			if ((dirEntryPtr->nameLength == compLen)) {
			    if (strncmp(component, dirEntryPtr->fileName,
				    compLen) == 0) {
				found = TRUE;
				break;
			    }
			}
		    } else {
			printf("Uh oh, dirEntryPtr->fileNumber is 0\n");
		    }
		    blockOffset += dirEntryPtr->recordLength;
		    dirEntryPtr = (Fslcl_DirEntry *)((int)dirEntryPtr +
						 dirEntryPtr->recordLength);
		}
		dirBlockNum++;
		Fscache_UnlockBlock(cacheBlockPtr, (time_t) 0, -1, 0, 0);
	    } while(!found);
	}
	/* It's been found or not.  Now deal with the result. */
	if (found) {
	    printf("Found %s\n", component);
	} else {
	    printf("Didn't find %s\n", component);
	}
    }
    return;
}
