/* Directory lookup and modification
   Copyright (C) 1991, 1992 Free Software Foundation

This file is part of the GNU Hurd.

The GNU Hurd is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

The GNU Hurd is distributed in the hope that it will be useful, 
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with the GNU Hurd; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* Heavily modified from UCB by Michael I. Bushnell.  */

/*
 * Copyright (c) 1989 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution is only permitted until one year after the first shipment
 * of 4.4BSD by the Regents.  Otherwise, redistribution and use in source and
 * binary forms are permitted provided that: (1) source distributions retain
 * this entire copyright notice and comment, and (2) distributions including
 * binaries display the following acknowledgement:  This product includes
 * software developed by the University of California, Berkeley and its
 * contributors'' in the documentation or other materials provided with the
 * distribution and in all advertising materials mentioning features or use
 * of this software.  Neither the name of the University nor the names of
 * its contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 *	@(#)ufs_lookup.c	7.23 (Berkeley) 6/28/90
 */

#include "ufs.h"
#include "fs.h"
#include "dir.h"
#include "dinode.h"
#include "inode.h"
#include <errno.h>
#include <string.h>
#include <stdio.h>

/*struct	nchstats nchstats;*/
int	dirchk = 1;

static int dirbadentry (struct direct *, int);

/*
 * Convert a component of a pathname into a pointer to a locked inode.
 * This is a very central and rather complicated routine.
 * If the file system is not maintained in a strict tree hierarchy,
 * this can result in a deadlock situation (see comments in code below).
 *
 * The dp argument is the directory to search.  It must be locked.
 *
 * The name argument is the name to look for.
 *
 * The flag argument is LOOKUP, CREATE, RENAME, or DELETE depending on
 * whether the name is to be looked up, created, renamed, or deleted.
 * When CREATE, RENAME, or DELETE is specified, information usable in
 * creating, renaming, or deleting a directory entry may be calculated.
 *
 * The newnode argument (if nonzero) is set to the node if found,
 * locked.  If we are looking up `.', the newnode argument has an
 * additional reference created, and "rides" on the existing lock
 * of dp.
 *
 * The ds argument is a buffer provided for all but LOOKUP in which 
 * information is cached for a later commit operation.
 *
 * The cred argument is used for permission checking. 
 *
 * Overall outline of ufs_lookup:
 *
 *	check accessibility of directory
 *	look for name in cache, if found, then if at end of path
 *	  and deleting or creating, drop it, else return name
 *	search for name in directory, to found or notfound
 * notfound:
 *	if creating, return locked directory, leaving info on available slots
 *	else return error
 * found:
 *	if at end of path and deleting, return information to allow delete
 *	if at end of path and rewriting (RENAME and LOCKPARENT), lock target
 *	  inode and return info to allow rewrite
 *	if not at end, add name to cache; if at end and neither creating
 *	  nor deleting, add name to cache
 */
error_t
lookup(struct inode *dp,
       char *name,
       enum lookup_type type,
       struct inode **newnode,
       struct dirstat *ds,
       struct protid *cred)
{
  struct direct *ep;		/* the current directory entry */
  char *buf = 0;		/* holds the current directory buffer */
  int entryoffsetinblock;	/* offset of ep in buf */
  off_t offset;			/* current offset in directory */
  volatile enum {NONE, COMPACT, FOUND} slotstatus;
  volatile int slotoffset = -1;	/* offset of area with free space */
  volatile int slotsize = 0;	/* size of area at slotoffset */
  volatile int slotfreespace;	/* amount of space free in slot */
  volatile int slotneeded;	/* size of the entry we're seeking */
  int numdirpasses;		/* strategy for directory search */
  int endsearch;		/* offset to end directory search */
  volatile int prevoff = -1;	/* offset of previous entry */
  struct inode *tdp;		/* returned by iget */
  off_t enduseful;		/* pointer past last used dir slot */
  int error;
  memory_object_t dirmem;	/* mapping the directory */
  int isdotdot;
  int namelen;

  if (error = pathnamecheck (name))
    return error;
  namelen = strlen (name);

  /* Check for zero length pathname (which refers to the current
     directory) */
  /* By observation, this works even if we can't search the directory. */
  if (*name == '\0')
    {
      dp->i_refcnt++;
      *newnode = dp;
      return 0;
    }

  /*
   * Check accessiblity of directory.
   */
  if (!(error = catch_exception ()))
    {
      if ((DI_MODE (dp->di) & IFMT) != IFDIR)
	error = ENOTDIR;
      else 
	error = ufs_access(dp, IEXEC, cred);
      end_catch_exception ();
    }
  if (error)
    return error;


  isdotdot = (namelen == 2 && name[0] == '.' && name[1] == '.');
  if (isdotdot && dp->i_number == ROOTINO && dotdot_file != MACH_PORT_NULL)
    return EAGAIN;
  
  if (type != LOOKUP && !ds)
    panic ("ufs_lookup dirstat not provided");

  dirmem = get_filemap (dp);

#if 0
  /*
   * We now have a segment name to search for, and a directory to search.
   *
   * Before tediously performing a linear scan of the directory,
   * check the name cache to see if the directory/name pair
   * we are looking for is known already.
   */
  if (error = cache_lookup(ndp))
    {
      int vpid;	/* capability number of vnode */
      
      if (error == ENOENT)
	return (error);
#if 0 /* XXX */
      if (vdp == ndp->ni_rdir && ndp->ni_isdotdot)
	panic("ufs_lookup: .. through root");
#endif
      
      /*
       * Get the next vnode in the path.
       * See comment below starting `Step through' for
       * an explaination of the locking protocol.
       */
      pdp = dp;
      dp = VTOI(ndp->ni_vp);
      vdp = ndp->ni_vp;
      vpid = vdp->v_id;
      if (pdp == dp)
	{
	  VREF(vdp);
	  error = 0;
	} 
      else if (ndp->ni_isdotdot)
	{
	  IUNLOCK(pdp);
	  error = vget(vdp);
	}
      else
	{
	  error = vget(vdp);
	  IUNLOCK(pdp);
	}
      /*
       * Check that the capability number did not change
       * while we were waiting for the lock.
       */
      if (!error)
	{
	  if (vpid == vdp->v_id)
	    return (0);
	  else
	    iput(dp);
	}
      ILOCK(pdp);
      dp = pdp;
      vdp = ITOV(dp);
      ndp->ni_vp = NULL;
    }
  
#endif

  /*
   * Suppress search for slots unless creating
   * file and at end of pathname, in which case
   * we watch for a place to put the new file in
   * case it doesn't already exist.
   */
  slotstatus = FOUND;
  if (type == CREATE || type == RENAME)
    {
      slotstatus = NONE;
      slotfreespace = 0;
      slotneeded = DIRSIZ(namelen);
    }
  else
    {
      slotfreespace = 0;
      slotneeded = 0;
    }
  
  
  if (error = catch_exception ())
    goto exception_error;

  /*
   * If there is cached information on a previous search of
   * this directory, pick up where we last left off.
   * We cache only lookups as these are the most common
   * and have the greatest payoff. Caching CREATE has little
   * benefit as it usually must search the entire directory
   * to determine that the entry does not exist. Caching the
   * location of the last DELETE or RENAME has not reduced
   * profiling time and hence has been removed in the interest
   * of simplicity.
   */
  
  if (type != LOOKUP || dp->i_diroff == 0 || dp->i_diroff > dp->di->di_size)
    {
      offset = 0;
      entryoffsetinblock = 0;	/* just to shut up gcc */
      numdirpasses = 1;
    }
  else
    {
      offset = dp->i_diroff;
      entryoffsetinblock = blkoff(offset);
      if (entryoffsetinblock != 0)
	{
	  buf = 0;
	  error = vm_map (mach_task_self (), (u_int *)&buf, sblock->fs_bsize,
			  0, 1, dirmem, lblkno (offset), 0,
			  VM_PROT_READ|VM_PROT_WRITE,
			  VM_PROT_READ|VM_PROT_WRITE, VM_INHERIT_NONE);
	  if (error)
	    panic_with_error ("lookup map", error);
	  register_memory_fault_area (buf, sblock->fs_bsize);
	}
      numdirpasses = 2;
/*      nchstats.ncs_2passes++;*/
    }
  endsearch = roundup(dp->di->di_size, DIRBLKSIZ);
  enduseful = 0;
  
 searchloop:
  while (offset < endsearch)
    {
      /*
       * If offset is on a block boundary,
       * read the next directory block.
       * Release previous if it exists.
       */
      if (blkoff(offset) == 0)
	{
	  if (buf)
	    {
	      unregister_memory_fault_area (buf, sblock->fs_bsize);
	      vm_deallocate (mach_task_self (), (int) buf, sblock->fs_bsize);
	    }
	  buf = 0;
	  vm_map (mach_task_self (), (u_int *)&buf, sblock->fs_bsize, 0, 1,
		  dirmem, offset, 0, VM_PROT_READ|VM_PROT_WRITE,
		  VM_PROT_READ|VM_PROT_WRITE, VM_INHERIT_NONE);
	  register_memory_fault_area (buf, sblock->fs_bsize);
	  entryoffsetinblock = 0;
	}
      /*
       * If still looking for a slot, and at a DIRBLKSIZE
       * boundary, have to start looking for free space again.
       */
      if (slotstatus == NONE &&
	  (entryoffsetinblock & (DIRBLKSIZ - 1)) == 0)
	{
	  slotoffset = -1;
	  slotfreespace = 0;
	}
      /*
       * Get pointer to next entry.
       * Full validation checks are slow, so we only check
       * enough to insure forward progress through the
       * directory. Complete checks can be run by patching
       * "dirchk" to be true.
       */
      ep = (struct direct *)(buf + entryoffsetinblock);
      if (ep->d_reclen == 0 ||
	  (dirchk && dirbadentry(ep, entryoffsetinblock)))
	{
	  int i;
	  
	  dirbad(dp, offset, "mangled entry");
	  i = DIRBLKSIZ - (entryoffsetinblock & (DIRBLKSIZ - 1));
	  offset += i;
	  entryoffsetinblock += i;
	  continue;
	}
      
      /*
       * If an appropriate sized slot has not yet been found,
       * check to see if one is available. Also accumulate space
       * in the current block so that we can determine if
       * compaction is viable.
       */
      if (slotstatus != FOUND)
	{
	  int size = ep->d_reclen;
	  
	  if (ep->d_ino != 0)
	    size -= DIRSIZ(ep->d_namlen);
	  if (size > 0)
	    {
	      if (size >= slotneeded)
		{
		  slotstatus = FOUND;
		  slotoffset = offset;
		  slotsize = ep->d_reclen;
		}
	      else if (slotstatus == NONE)
		{
		  slotfreespace += size;
		  if (slotoffset == -1)
		    slotoffset = offset;
		  if (slotfreespace >= slotneeded)
		    {
		      slotstatus = COMPACT;
		      slotsize = offset + ep->d_reclen - slotoffset;
		    }
		}
	    }
	}
      /*
       * Check for a name match.
       */
      if (ep->d_ino && ep->d_namlen == namelen
	  && !bcmp(name, ep->d_name, (unsigned)ep->d_namlen))
	goto found;

      prevoff = offset;
      offset += ep->d_reclen;
      entryoffsetinblock += ep->d_reclen;
      if (ep->d_ino)
	enduseful = offset;
    }
  /* notfound: */
  /*
   * If we started in the middle of the directory and failed
   * to find our target, we must check the beginning as well.
   */
  if (numdirpasses == 2)
    {
      numdirpasses--;
      offset = 0;
      endsearch = dp->i_diroff;
      goto searchloop;
    }
  /*
   * If creating, and at end of pathname and current
   * directory has not been removed, then can consider
   * allowing file to be created.
   */
  if (type == CREATE || type == RENAME)
    {
      /*
       * Access for write is interpreted as allowing
       * creation of files in the directory.  Also, if the directory
       * has no links then it has been deleted and files cannot be created
       * in it.
       */
      error = ufs_access (dp, IWRITE, cred);
      if (!error && !dp->di->di_nlink)
	error = EINVAL;
      if (error)
	{
	  unregister_memory_fault_area (buf, sblock->fs_bsize);
	  vm_deallocate (mach_task_self (), (u_int) buf, sblock->fs_bsize);
	  mach_port_deallocate (mach_task_self (), dirmem);
	  end_catch_exception ();
	  return (error);
	}
      /*
       * Return an indication of where the new directory
       * entry should be put.  If we didn't find a slot,
       * then set ndp->ni_count to 0 indicating that the new
       * slot belongs at the end of the directory. If we found
       * a slot, then the new entry can be put in the range
       * [ndp->ni_offset .. ndp->ni_offset + ndp->ni_count)
       */
      ds->type = CREATE;
      if (slotstatus == NONE)
	{
	  ds->count = 0;
	  /* Free the buffer and save the memory object for direnter's use */
	  unregister_memory_fault_area (buf, sblock->fs_bsize);
	  vm_deallocate (mach_task_self (), (u_int) buf, sblock->fs_bsize);
	  ds->dirmem = dirmem;
	}
      else 
	{
	  /* Find what buffer we need, set it up for direnter, and release
	     the memory object */
	  if (lblkno (offset) != lblkno (slotoffset))
	    {
	      unregister_memory_fault_area (buf, sblock->fs_bsize);
	      vm_deallocate (mach_task_self (), (u_int) buf, sblock->fs_bsize);
	      vm_map (mach_task_self (), (u_int *)&buf, sblock->fs_bsize, 0, 1,
		      dirmem, lblkno (slotoffset) * sblock->fs_bsize, 0,
		      VM_PROT_READ|VM_PROT_WRITE, VM_PROT_READ|VM_PROT_WRITE,
		      VM_INHERIT_NONE);
	      register_memory_fault_area (buf, sblock->fs_bsize);
	    }
	  ds->ep = (struct direct *)
	    (slotoffset - lblkno (slotoffset) * sblock->fs_bsize);
	  mach_port_deallocate (mach_task_self (), dirmem);
	  ds->count = slotsize;
	  ds->buffer = buf;
	}
    }
  else
    {
      /* Lookup or deletion doesn't save info.  Force ds->type to be LOOKUP
	 so the caller doesn't mistakenly do dirremove.  */
      if (ds)
	ds->type = LOOKUP;
      unregister_memory_fault_area (buf, sblock->fs_bsize);
      vm_deallocate (mach_task_self (), (u_int)buf, sblock->fs_bsize);
      mach_port_deallocate (mach_task_self (), dirmem);
    }
  *newnode = 0;
#if 0
  /*
   * Insert name into cache (as non-existent) if appropriate.
   */
  if (ndp->ni_makeentry)
    cache_enter(ndp);
#endif
  end_catch_exception ();
  return (ENOENT);
  
 found:
  mach_port_deallocate (mach_task_self (), dirmem);
/*  if (numdirpasses == 2)
    nchstats.ncs_pass2++;*/
  /*
   * Check that directory length properly reflects presence
   * of this entry.
   */
  if (entryoffsetinblock + DIRSIZ(ep->d_namlen) > dp->di->di_size)
    {
      dirbad(dp, offset, "i_size too small");
      dp->di->di_size = entryoffsetinblock + DIRSIZ(ep->d_namlen);
    }
  
  /*
   * Found component in pathname.
   * If the final component of path name, save information
   * in the cache as to where the entry was found.
   */
  if (type == LOOKUP)
    dp->i_diroff = offset &~ (DIRBLKSIZ - 1);
  
  /*
   * If deleting, and at end of pathname, return
   * parameters which can be used to remove file.
   * If the wantparent type isn't set, we return only
   * the directory (in ndp->ni_dvp), otherwise we go
   * on and lock the inode, being careful with ".".
   */
  if (type == REMOVE)
    {
      /*
       * Write access to directory required to delete files.
       */
      if (error = ufs_access(dp, IWRITE, cred))
	{
	  vm_deallocate (mach_task_self (), (u_int) buf, sblock->fs_bsize);
	  unregister_memory_fault_area (buf, sblock->fs_bsize);
	  end_catch_exception ();
	  return (error);
	}
      /*
       * Return pointer to current entry in ndp->ni_offset,
       * and distance past previous entry (if there
       * is a previous entry in this block) in ndp->ni_count.
       * Save directory inode pointer in ndp->ni_dvp for dirremove().
       */
      ds->ep = ep;
      if ((offset&(DIRBLKSIZ-1)) == 0)
	ds->prevep = 0;
      else
	{
	  if (prevoff == -1)
	    panic ("lookup prevoff");
	  ds->prevep = (struct direct *)((int)ep - (offset - prevoff));
	}
      ds->buffer = buf;
      ds->type = REMOVE;
    }
  /*
   * If rewriting (RENAME), return the inode and the
   * information required to rewrite the present directory
   * Must get inode of directory entry to verify it's a
   * regular file, or empty directory.
   */
  if (type == RENAME)
    {
      if (error = ufs_access(dp, IWRITE, cred))
	{
	  unregister_memory_fault_area (buf, sblock->fs_bsize);
	  vm_deallocate (mach_task_self (), (int) buf, sblock->fs_bsize);
	  end_catch_exception ();
	  return (error);
	}
      ds->ep = ep;
      ds->buffer = buf;
      ds->type = RENAME;
    }
  
  /*
   * Step through the translation in the name.  We do not `iput' the
   * directory because we may need it again if a symbolic link
   * is relative to the current directory.  Instead we save it
   * unlocked as "pdp".  We must get the target inode before unlocking
   * the directory to insure that the inode will not be removed
   * before we get it.  We prevent deadlock by always fetching
   * inodes from the root, moving down the directory tree. Thus
   * when following backward pointers ".." we must unlock the
   * parent directory before getting the requested directory.
   * There is a potential race condition here if both the current
   * and parent directories are removed before the `iget' for the
   * inode associated with ".." returns.  We hope that this occurs
   * infrequently since we cannot avoid this race condition without
   * implementing a sophisticated deadlock detection algorithm.
   * Note also that this simple deadlock detection scheme will not
   * work if the file system has any hard links other than ".."
   * that point backwards in the directory structure.
   */
  if (dp->i_number == ep->d_ino)
    {
      tdp = dp;
      dp->i_refcnt++;
    }
  else if (isdotdot)
    {
      dp->i_nodelete = 1;
      mutex_unlock (&dp->i_toplock);
      if (error = iget (ep->d_ino, &tdp))
	{
	  if (ds->type != LOOKUP)
	    dsrelease (ds);
	  end_catch_exception ();
	  return error;
	}	
      mutex_lock (&dp->i_toplock);
      dp->i_nodelete = 0;
    }
  else
    {
      if (error = iget (ep->d_ino, &tdp))
	{
	  if (ds->type != LOOKUP)
	    dsrelease (ds);
	  end_catch_exception ();
	  return error;
	}
    }

  if (type == REMOVE || type == RENAME)
    {
      /*
       * If directory is "sticky", then user must own
       * the directory, or the file in it, else she
       * may not delete it (unless she's root). This
       * implements append-only directories.
       */
      if ((DI_MODE (dp->di) & ISVTX) != 0
	  && isowner (dp, cred) != 0
	  && isowner (tdp, cred) != 0)
	{
	  if (dp->i_number == tdp->i_number)
	    irele (tdp);
	  else
	    iput(tdp);
	  dsrelease (ds);
	  end_catch_exception ();
	  return (EPERM);
	}
    }
  *newnode = tdp;
  end_catch_exception ();
  return (0);


 exception_error:
  if (ds->type != LOOKUP)
    dsrelease (ds);
  else
    {
      if (buf)
	{
	  vm_deallocate (mach_task_self (), (u_int) buf, sblock->fs_bsize);
	  unregister_memory_fault_area (buf, sblock->fs_bsize);
	}
      if (dirmem)
	mach_port_deallocate (mach_task_self (), dirmem);
    }
  return error;
#if 0  
  /*
   * Insert name into cache if appropriate.
   */
  if (ndp->ni_makeentry)
    cache_enter(ndp);
#endif
}


void
dirbad(struct inode *ip,
       off_t offset,
       char *how)
{
	printf("bad dir ino %d at offset %d: %s\n",
	       ip->i_number, offset, how);
	panic("bad dir");
}

/*
 * Do consistency checking on a directory entry:
 *	record length must be multiple of 4
 *	entry must fit in rest of its DIRBLKSIZ block
 *	record must be large enough to contain entry
 *	name is not longer than MAXNAMLEN
 *	name must be as long as advertised, and null terminated
 */
static int
dirbadentry(struct direct *ep,
	    int entryoffsetinblock)
{
  register int i;
  
  if ((ep->d_reclen & 0x3) != 0 ||
      ep->d_reclen > DIRBLKSIZ - (entryoffsetinblock & (DIRBLKSIZ - 1)) ||
      ep->d_reclen < DIRSIZ(ep->d_namlen) || ep->d_namlen > MAXNAMLEN)
    return (1);
  for (i = 0; i < ep->d_namlen; i++)
    if (ep->d_name[i] == '\0')
      return (1);
  return (ep->d_name[i]);
}

/*
 * Write a directory entry after a call to namei, using the parameters
 * which it left in nameidata.  The argument ip is the inode which the
 * new directory entry will refer to.  The nameidata field ndp->ni_dvp
 * is a pointer to the directory to be written, which was left locked by
 * namei.  Remaining parameters (ndp->ni_offset, ndp->ni_count) indicate
 * how the space for the new entry is to be gotten.
 */
error_t
direnter(struct inode *dp,
	 char *name,
	 struct inode *ip,
	 struct dirstat *ds,
	 struct protid *cred)
{
  struct direct *nep;
  struct direct *volatile ep = ds->ep;
  int loc, spacefree;
  error_t error = 0;
  volatile u_int dsize;
  int newentrysize;
  
  if (ds->type != CREATE)
    panic ("direnter: bad type");
  
  newentrysize = DIRSIZ(strlen (name));
  if (ds->count == 0) 
    {
      /*
       * If ds->count is 0, then lookup could find no space in the
       * directory. In this case we will write the new entry into a fresh
       * block.
       */
      ds->buffer = 0;

      /* Grow the file */
      dsize = 0;		/* to shut gcc up. */
      if (!(error = catch_exception ()))
	{
	  while (dp->di->di_size + DIRBLKSIZ > dp->i_allocsize)
	    if (error = file_extend (dp, dp->di->di_size, 
				     dp->di->di_size + DIRBLKSIZ, cred))
	      break;
	  if (!error)
	    {
	      dsize = dp->di->di_size;
	      dp->di->di_size += DIRBLKSIZ;
	      dp->di->di_ctime = time->seconds;
	      dp->di->di_mtime = time->seconds;
	    }
	  end_catch_exception ();
	}
      if (error)
	{
	  mach_port_deallocate (mach_task_self (), ds->dirmem);
	  return error;
	}

      ds->buffer = 0;
      vm_map (mach_task_self (), (u_int *)&ds->buffer, sblock->fs_bsize, 0, 1, 
	      ds->dirmem, dsize, 0, VM_PROT_READ|VM_PROT_WRITE,
	      VM_PROT_READ|VM_PROT_WRITE, VM_INHERIT_NONE);
      register_memory_fault_area (ds->buffer, sblock->fs_bsize);
      mach_port_deallocate (mach_task_self (), ds->dirmem);
      ds->ep = (struct direct *)(ds->buffer);
      if (!(error = catch_exception ()))
	{
	  ds->ep->d_ino = 0;
	  ds->ep->d_reclen = DIRBLKSIZ;
	  ds->count = DIRBLKSIZ;
	  end_catch_exception ();
	}
      else
	{
	  vm_deallocate (mach_task_self (), (u_int) ds->buffer, 
			 sblock->fs_bsize);
	  unregister_memory_fault_area (ds->buffer, sblock->fs_bsize);
	  return error;
	}
    }
  
  /* At time point, we are guaranteed that we have space from
     ds->ep through (char *)ds->ep + ds->count.  To use this space,
     we may have to compact the entries located there, by copying them 
     together towards the beginning of the block, leaving the free space
     in one usable chunk at the end. */
    

#if 0  
  /*
   * Increase size of directory if entry eats into new space.
   * This should never push the size past a new multiple of
   * DIRBLKSIZE.
   *
   * N.B. - THIS IS AN ARTIFACT OF 4.2 AND SHOULD NEVER HAPPEN.
   */
  if (ndp->ni_offset + ndp->ni_count > dp->i_size)
    dp->i_size = ndp->ni_offset + ndp->ni_count;
#endif

  if (error = catch_exception ())
    {
      vm_deallocate (mach_task_self (), (u_int) ds->buffer, sblock->fs_bsize);
      unregister_memory_fault_area (ds->buffer, sblock->fs_bsize);
      return error;
    }

  dsize = DIRSIZ(ep->d_namlen);
  spacefree = ep->d_reclen - dsize;
  for (loc = ep->d_reclen; loc < ds->count; )
    {
      nep = (struct direct *)((char *)ep + loc);
      if (ep->d_ino)
	{
	  /* trim the existing slot */
	  ep->d_reclen = dsize;
	  ep = (struct direct *)((char *)ep + dsize);
	}
      else
	{
	  /* overwrite; nothing there; header is ours */
	  spacefree += dsize;
	}
      dsize = DIRSIZ(nep->d_namlen);
      spacefree += nep->d_reclen - dsize;
      loc += nep->d_reclen;
      bcopy((caddr_t)nep, (caddr_t)ep, dsize);
    }
  /*
   * Update the pointer fields in the previous entry (if any),
   * copy in the new entry, and write out the block.
   */
  if (ep->d_ino == 0)
    {
      if (spacefree + dsize < newentrysize)
	panic("direnter: compact1");
      ep->d_reclen = spacefree + dsize;
    }
  else 
    {
      if (spacefree < newentrysize)
	panic("direnter: compact2");
      ep->d_reclen = dsize;
      ep = (struct direct *)((char *)ep + dsize);
      ep->d_reclen = spacefree;
    }
  ep->d_ino = ip->i_number;
  ep->d_namlen = strlen (name);
  strcpy (ep->d_name, name);
  dp->di->di_mtime = time->seconds;

  unregister_memory_fault_area (ds->buffer, sblock->fs_bsize);
  vm_deallocate (mach_task_self (), (u_int) ds->buffer, sblock->fs_bsize);
  ds->type = LOOKUP;
  end_catch_exception ();

#if 0
  if (!error && ndp->ni_endoff && ndp->ni_endoff < dp->i_size)
    error = itrunc(dp, (u_long)ndp->ni_endoff, IO_SYNC);
#endif
  
  file_update (dp, 1);
  return (error);
}

/*
 * Remove a directory entry after a call to namei, using
 * the parameters which it left in nameidata. The entry
 * ni_offset contains the offset into the directory of the
 * entry to be eliminated.  The ni_count field contains the
 * size of the previous record in the directory.  If this
 * is 0, the first entry is being deleted, so we need only
 * zero the inode number to mark the entry as free.  If the
 * entry isn't the first in the directory, we must reclaim
 * the space of the now empty record by adding the record size
 * to the size of the previous entry.
 */
error_t
dirremove(struct inode *dp,
	  struct dirstat *ds)
{
  error_t error;
  
  if (ds->type != REMOVE)
    panic ("dirremove bad type");

  if (!(error = catch_exception ()))
    {
      if (ds->prevep)
	/* Coalesce into previous entry */
	ds->prevep->d_reclen += ds->ep->d_reclen;
      else
	/* First entry in block, set d_ino to zero. */
	ds->ep->d_ino = 0;

      dp->di->di_mtime = time->seconds;
      end_catch_exception ();
    }
  unregister_memory_fault_area (ds->buffer, sblock->fs_bsize);
  vm_deallocate (mach_task_self (), (u_int) ds->buffer, sblock->fs_bsize);
  ds->type = LOOKUP;
  file_update (dp, 1);
  return error;
}

/*
 * Rewrite an existing directory entry to point at the inode
 * supplied.  The parameters describing the directory entry are
 * set up by a call to namei.
 */
error_t
dirrewrite(struct inode *dp, 
	   struct inode *ip,
	   struct dirstat *ds)
{
  error_t error;

  if (ds->type != RENAME)
    panic ("dirrewrite bad type");
  
  if (!(error = catch_exception ()))
    {
      ds->ep->d_ino = ip->i_number;
      dp->di->di_mtime = time->seconds;
      end_catch_exception ();
    }
  vm_deallocate (mach_task_self (), (u_int) ds->buffer, sblock->fs_bsize);
  unregister_memory_fault_area (ds->buffer, sblock->fs_bsize);
  ds->type = LOOKUP;
  file_update (dp, 1);
  return error;
}

/*
 * Check if a directory is empty or not.
 * Inode supplied must be locked.
 *
 * Using a struct dirtemplate here is not precisely
 * what we want, but better than using a struct direct.
 *
 * NB: does not handle corrupted directories.
 */
/* This routine must be called from inside a catch_exception ().  */
int
dirempty(struct inode *ip,
	 ino_t parentino,
	 struct protid *cred)
{
  register off_t off;
  struct dirtemplate dbuf;
  struct direct *dp = (struct direct *)&dbuf;
  int error;
#define	MINDIRSIZ (sizeof (struct dirtemplate) / 2)

  /* If someone is walking back .., we can't delete the directory
     or they will get lost. */
  if (ip->i_nodelete)
    return 0;
  
  for (off = 0; off < ip->di->di_size; off += dp->d_reclen)
    {
      error = fs_rdwr (ip, (char *)dp, off, MINDIRSIZ, 0, cred);
      if (error)
	return (0);
      /* avoid infinite loops */
      if (dp->d_reclen == 0)
	return (0);
      /* skip empty entries */
      if (dp->d_ino == 0)
	continue;
      /* accept only "." and ".." */
      if (dp->d_namlen > 2)
	return (0);
      if (dp->d_name[0] != '.')
	return (0);
      /*
       * At this point d_namlen must be 1 or 2.
       * 1 implies ".", 2 implies ".." if second
       * char is also "."
       */
      if (dp->d_namlen == 1)
	continue;
      if (dp->d_name[1] == '.' && dp->d_ino == parentino)
	continue;
      return (0);
    }
  return (1);
}

/*
 * Check if source directory is in the path of the target directory.
 * We get target locked, source unlocked.  When we return, source is
 * unlocked and target is locked.  This routine assumes that no renames
 * will happen while it is running; as a result, ufs_rename serializes
 * all renames of directories.
 */
int
checkpath(struct inode *source,
	  struct inode *target,
	  struct protid *cred)
{
  struct inode *ip;
  struct inode *newip;
  int error = 0;
  ino_t sourceinum;
  
  sourceinum = source->i_number;
  mutex_unlock (&source->i_toplock);
  
  ip = target;
  mutex_lock (&ip->i_toplock);
  ip->i_refcnt++;
  if (ip->i_number == sourceinum)
    goto out;

  for (;;)
    {
      if (ip->i_number == ROOTINO)
	break;
      if (ip->i_number == sourceinum)
	{
	  error = EINVAL;
	  break;
	}
      
      error = lookup (ip, "..", LOOKUP, &newip, 0, cred);
      if (error)
	break;
      iput (ip);
      ip = newip;
    }
  
 out:
  if (error == ENOTDIR)
    printf("checkpath: .. not a directory\n");
  iput (ip);
  if (!error)
    mutex_lock (&target->i_toplock);
  return (error);
}

error_t
pathnamecheck (char *name)
{
  char *p;
  
  for (p = name; *p; p++)
    if (*p == '/')
      return EINVAL;
  return 0;
}

void
dsrelease (struct dirstat *ds)
{
  switch (ds->type)
    {
    case LOOKUP:
    default:
      panic ("dsrelease");
    case REMOVE:
    case RENAME:
      unregister_memory_fault_area (ds->buffer, sblock->fs_bsize);
      vm_deallocate (mach_task_self (), (u_int) ds->buffer, sblock->fs_bsize);
      break;
    case CREATE:
      if (ds->count == 0)
	  mach_port_deallocate (mach_task_self (), ds->dirmem);
      else
	{
	  unregister_memory_fault_area (ds->buffer, sblock->fs_bsize);
	  vm_deallocate (mach_task_self (), (u_int) ds->buffer,
			 sblock->fs_bsize);
	}
      break;
    }
  ds->type = LOOKUP;
}


error_t
ufs_access (struct inode *ip,
	    mode_t type,
	    struct protid *cred)
{
  volatile mode_t mode = DI_MODE (ip->di);
  int test;
  error_t error;
  
  if (isuid (0, cred))
    return 0;
  
  if (error = catch_exception ())
    return error;
  
  if (isuid (DI_UID (ip->di), cred))
    test = (mode & IOWNER) >> 6;
  else if (groupmember (DI_GID (ip->di), cred))
    test = (mode & IGROUP) >> 3;
  else if (cred->nuids || !(mode & IUSEUNK))
    test = mode & IKNOWN;
  else
    test = (mode & IUNKNOWN) >> 18;
  end_catch_exception ();
  return ((test & (type >> 6)) ? 0 : EACCES);
}

int
groupmember (gid_t grp,
	     struct protid *cred)
{
  int i;
  gid_t *idp;
  
  for (i = 0, idp = cred->gids->ids; i < cred->ngids;
       i++, idp++)
    if (grp == *idp)
      return 1;
  return 0;
}

/* This means "is U.I.D.", not "inode set U.I.D." */
int
isuid (uid_t uid,
       struct protid *cred)
{
  int i;
  uid_t *idp;
  
  for (i = 0, idp = cred->uids->ids; i < cred->nuids;
       i++, idp++)
    if (uid == *idp)
      return 1;
  return 0;
}

/* Test if the user can do owner-only modifications */
int 
isowner (struct inode *ip,
	 struct protid *cred)
{
  int i;
  uid_t *idp;
  uid_t owner;
  gid_t group;
  error_t error;

  if (error = catch_exception ())
    return error;
  owner = DI_UID (ip->di);
  group = DI_GID (ip->di);
  end_catch_exception ();

  for (i = 0, idp = cred->uids->ids; i < cred->nuids; i++, idp++)
    {
      if (*idp == 0 || *idp == owner)
	return 0;
      if (*idp == group && groupmember (group, cred))
	return 0;
    }
  return EPERM;
}

  
 
