/* Default treefs_s_dir_lookup hook

   Copyright (C) 1992, 1993, 1994, 1995 Free Software Foundation, Inc.

   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 this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */

#include <fcntl.h>
#include <string.h>

#include <hurd/fsys.h>

#include "treefs.h"
#include "treefs-s-hooks.h"

/* Default dir_lookup hook.  This code was originally copied from diskfs.  */ 
error_t
_treefs_s_dir_lookup (struct treefs_handle *h,
		      char *path, int flags, mode_t mode,
		      enum retry_type *retry, char *retry_name,
		      file_t *result, mach_msg_type_name_t *result_type)
{
  struct treefs_node *dir;
  struct treefs_node *node;
  unsigned symlink_expansions = 0;
  error_t err = 0;
  char *path_buf = 0;
  int path_buf_len = 0;
  int lastcomp = 0;
  int mustbedir = 0;

  flags &= O_HURD;
  mode &= ~S_IFMT;
  
  /* Skip leading slashes */
  while (path[0] == '/')
    path++;

  *result_type = MACH_MSG_TYPE_MAKE_SEND;
  *retry = FS_RETRY_NORMAL;
  retry_name[0] = '\0';

  if (path[0] == '\0')
    {
      mustbedir = 1;

      /* Set things up in the state expected by the code from gotit: on. */
      dir = 0;
      node = h->po->node;
      mutex_lock (&node->lock);
      treefs_node_ref (node);
      goto gotit;
    }

  dir = h->po->node;
  mutex_lock (&dir->lock);
  node = 0;

  treefs_node_ref (dir);	/* acquire a ref for later node_release */

  do
    {
      char *nextname;

      assert (!lastcomp);
      
      /* Find the name of the next pathname component */
      nextname = index (path, '/');

      if (nextname)
	{
	  *nextname++ = '\0';
	  while (*nextname == '/')
	    nextname++;
	  if (*nextname == '\0')
	    {
	      /* These are the rules for filenames ending in /. */
	      nextname = 0;
	      lastcomp = 1;
	      mustbedir = 1;
	      
	    }
	  else
	    lastcomp = 0;
	}
      else
	lastcomp = 1;
	  
      node = 0;

      /* Lookup the next pathname component.  */
      if (!lastcomp)
	err = treefs_dir_lookup (dir, path, h->auth, 0, 0, &node);
      else
	/* ... and in this case, the last.  Note that the S_IFREG only
	   applies in the case of O_CREAT, which is turned off for
	   directories anyway.  */
	err =
	  treefs_dir_lookup (dir, path, h->auth, flags, mode | S_IFREG, &node);

      /* If we get an error we're done */
      if (err == EAGAIN)
	{
	  if (h->po->parent_port != MACH_PORT_NULL)
	    {
	      *retry = FS_RETRY_REAUTH;
	      *result = h->po->parent_port;
	      *result_type = MACH_MSG_TYPE_COPY_SEND;
	      if (!lastcomp)
		strcpy (retry_name, nextname);
	      err = 0;
	      goto out;
	    }
	  else
	    /* The global filesystem root...  .. == .  */
	    {
	      err = 0;
	      node = dir;
	      treefs_node_ref (node);
	    }
	}
      
      if (err)
	goto out;

      /* If this is translated, start the translator (if necessary)
	 and return.  */
      /* The check for `node != dir' simplifies this code a great
	 deal.  Such a translator should already have been started,
	 so there's no lossage in doing it this way. */
      if ((!lastcomp || !(flags & O_NOTRANS))
	  && node != dir)
	{
	  file_t dir_port = MACH_PORT_NULL, child_fsys;

	  /* Be very careful not to hold an inode lock while fetching
	     a translator lock and vice versa.  */

	  mutex_unlock (&node->lock);
	  mutex_unlock (&dir->lock);

	  do
	    {
	      err =
		treefs_node_get_active_trans (node, dir, h->po->parent_port,
					      &dir_port, &child_fsys);
	      if (err == 0 && child_fsys != MACH_PORT_NULL)
		{
		  err =
		    fsys_getroot (child_fsys, dir_port,
				  MACH_MSG_TYPE_COPY_SEND,
				  h->auth->uids, h->auth->nuids,
				  h->auth->gids, h->auth->ngids,
				  lastcomp ? flags : 0,
				  retry, retry_name, result);
		  /* If we got MACH_SEND_INVALID_DEST or MIG_SERVER_DIED, then
		     the server is dead.  Zero out the old control port and try
		     everything again.  */
		  if (err == MACH_SEND_INVALID_DEST || err == EMIG_SERVER_DIED)
		    treefs_node_drop_active_trans (node, child_fsys);
		}
	    }
	  while (err == MACH_SEND_INVALID_DEST || err == EMIG_SERVER_DIED);

	  if (err || child_fsys)
	    {
	      /* We're done; return to the user.  If there are more
		 components after this name, be sure to append them to the
		 user's retry path. */
	      if (!err && !lastcomp)
		{
		  strcat (retry_name, "/");
		  strcat (retry_name, nextname);
		}

	      *result_type = MACH_MSG_TYPE_MOVE_SEND;

	      treefs_node_unref (dir);
	      treefs_node_unref (node);
	      if (dir_port)
		mach_port_deallocate (mach_task_self (), dir_port);

	      return err;
	    }
	  
	  /* We're here if we tried the translator check, and it
	     failed.   Lock everything back, and make sure we do it
	     in the right order. */
	  if (strcmp (path, "..") != 0)
	    {
	      mutex_unlock (&node->lock);
	      mutex_lock (&dir->lock);
	      mutex_lock (&node->lock);
	    }
	  else
	    mutex_lock (&dir->lock);
	}
      
      if (treefs_node_type (node) == S_IFLNK
	  && !(lastcomp && (flags & (O_NOLINK|O_NOTRANS))))
	/* Handle symlink interpretation */
	{
	  unsigned nextname_len = nextname ? strlen (nextname) + 1 : 0;
	  /* max space we currently have for the sym link */
	  unsigned sym_len = path_buf_len - nextname_len - 1;

	  if (symlink_expansions++ > node->fsys->max_symlinks)
	    {
	      err = ELOOP;
	      goto out;
	    }
	
	  err = treefs_node_get_symlink (node, path_buf, &sym_len);
	  if (err == E2BIG)
	    /* Symlink contents + extra path won't fit in our buffer, so
	       reallocate it and try again.  */
	    {
	      path_buf_len = sym_len + nextname_len + 1;
	      path_buf = alloca (path_buf_len);
	      err = treefs_node_get_symlink (node, path_buf, &sym_len);
	    }
	  if (err)
	    goto out;

	  if (nextname)
	    {
	      path_buf[sym_len] = '/';
	      bcopy (nextname, path_buf + sym_len + 1, nextname_len - 1);
	    }
	  path_buf[nextname_len + sym_len] = '\0';

	  if (path_buf[0] == '/')
	    {
	      /* Punt to the caller.  */
	      *retry = FS_RETRY_MAGICAL;
	      *result = MACH_PORT_NULL;
	      strcpy (retry_name, path_buf);
	      goto out;
	    }
	  
	  path = path_buf;
	  if (lastcomp)
	    {
	      lastcomp = 0;
	      /* Symlinks to nonexistent files aren't allowed to cause
		 creation, so clear the flag here. */
	      flags &= ~O_CREAT;
	    }
	  treefs_node_release (node);
	  node = 0;
	}
      else
	{
	  /* Handle normal nodes */
	  path = nextname;
	  if (node == dir)
	    treefs_node_unref (dir);
	  else
	    treefs_node_release (dir);
	  if (!lastcomp)
	    {
	      dir = node;
	      node = 0;
	    }
	  else
	    dir = 0;
	}
    } while (path && *path);
  
 gotit:
  /* At this point, node is the node to return.  */

  if (mustbedir && !treefs_node_isdir (node))
    err = ENOTDIR;
  if (err)
    goto out;

  err = treefs_node_create_right (node, flags, h->po->parent_port, h->auth,
				  result);

 out:
  if (node)
    {
      if (dir == node)
	treefs_node_unref (node);
      else
	treefs_node_release (node);
    }
  if (dir)
    treefs_node_release (dir);
  return err;  
}