/*
 *  This file is part of ixemul.library for the Amiga.
 *  Copyright (C) 1991, 1992  Markus M. Wild
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * Lock() and LLock() emulation. Takes care of expanding paths that contain
 * symlinks. 
 * Call __plock() if you need a lock to the parent directory as used in
 * other packets, that way you always get the "right" thing
 */

#define _KERNEL
#include "ixemul.h"
#include "kprintf.h"

#include <stdlib.h>
#include <string.h>

static struct DevProc	*get_device_proc (char *, struct DevProc *, int *);
static int		unslashify (char *);

static void lock(struct MsgPort *handler, struct StandardPacket *sp, BPTR lock, char *s)
{
  sp->sp_Pkt.dp_Type = ACTION_LOCATE_OBJECT;
  sp->sp_Pkt.dp_Arg1 = lock;
  sp->sp_Pkt.dp_Arg2 = CTOBPTR(s);
  sp->sp_Pkt.dp_Arg3 = ACCESS_READ;

  PutPacket(handler, sp);
  __wait_sync_packet(sp);
}

static void unlock(struct MsgPort *handler, struct StandardPacket *sp, BPTR lock)
{
  sp->sp_Pkt.dp_Port = __srwport;
  sp->sp_Pkt.dp_Type = ACTION_FREE_LOCK;
  sp->sp_Pkt.dp_Arg1 = lock;

  PutPacket(handler, sp);
  __wait_sync_packet(sp);
}

static void readlink(struct MsgPort *handler, struct StandardPacket *sp, BPTR lock, char *s)
{
  sp->sp_Pkt.dp_Port = __srwport;
  sp->sp_Pkt.dp_Type = ACTION_READ_LINK;
  sp->sp_Pkt.dp_Arg1 = lock;
  sp->sp_Pkt.dp_Arg2 = (long)s; /* read as cstr */
  sp->sp_Pkt.dp_Arg3 = (long)s; /* write as cstr, same place */
  sp->sp_Pkt.dp_Arg4 = 255; /* what a BSTR can address */

  PutPacket(handler, sp);
  __wait_sync_packet(sp);
}

int is_pseudoterminal(char *name)
{
  int i = 1;

  if (!memcmp(name, "/dev/", 5) || !(i = memcmp(name, "dev:", 4)))
    {
      if (i)
        name++;
      if ((name[4] == 'p' || name[4] == 't') && name[5] == 't'
          && name[6] == 'y' && name[7] >= 'p' && name[7] <= 'u'
          && strchr("0123456789abcdef", name[8]) && !name[9])
        return i + 4;
    }
  return 0;
}

BPTR
__plock (const char *file_name, int (*last_func)(), void *last_arg)
{
  BPTR parent_lock;
  struct MsgPort *handler;
  struct StandardPacket *sp;
  unsigned char *bstr;
  char *sep, *next = NULL, *cp;
  int len;
  /* true when we're processing the last element of name */
  int is_last;
  /* true after first pass, we should unlock all locks except the one
   * we get as a sideeffect of DeviceProc */
  int unlock_parent;
  int link_levels;
  int no_error;
  BPTR result;
  int res_res2;
  int omask;
  int is_fs;
  short is_root = u.u_is_root;
  struct DevProc *dp;
  char *orig_name, *name;

  KPRINTF (("__plock: file_name = %s, last_func = $%lx\n", 
	    file_name ? file_name : "(none)", last_func));

  /* ``fix'' until I find the real problem in pdksh.. */
  if (! file_name)
    return 0;

  /* need to operate on a backup of the passed name, so I can do
   * /sys -> sys: conversion in place */
  name = alloca (strlen (file_name) + 2);
  strcpy (name + 1, file_name);
  if (is_root)
    name[0] = '/';
  else
    name++;
  orig_name = name;

  /* now get a LONG aligned packet */
  sp = alloca (sizeof(*sp)+2);
  sp = LONG_ALIGN (sp);
  __init_std_packet(sp);

  /* allocate one BSTR-buffer. A length of 255 is enough, since a bstr
   * can't address any more ;-) */
  bstr = alloca (256 + 2);
  bstr = LONG_ALIGN (bstr);
  
  /* NOTE: although we don't use any DOS calls here, we have to block
   * any signals, since the locks we obtain in this function have to
   * be freed before anything else can be done. This function is
   * *not* reentrant in the sense that it can be interrupted without
   * being finished */
  omask = syscall (SYS_sigsetmask, ~0);

  dp = 0;

retry_multi_assign:

  name = orig_name;
  if (ix.ix_flags & ix_translate_slash)
    unslashify (name);

  if (!strcmp(name, ":"))
    {
      result = 0;
      res_res2 = 6262;  /* another special special (the root directory) */
      dp = 0;
      goto do_return;
    }
  if (!strcasecmp(name, "nil:") || !strcasecmp(name, "/nil") ||
      !strcmp(name, "/dev/null") || !strcmp(name, "dev:null"))
    {
	result = 0;
        res_res2 = 4242;	/* special special ;-)))) */
        dp = 0;
        goto do_return;
    }
  if (is_pseudoterminal(name))
    {
      result = 0;
      res_res2 = 5252;	/* special special ;-)))) */
      dp = 0;
      goto do_return;
    }
  if (!strcmp(name, "*") || !strcasecmp(name, "console:") ||
      !strcmp(name, "/dev/tty"))
    {
      handler = (struct MsgPort *)(((struct Process *)FindTask (0))->pr_ConsoleTask);
      parent_lock = 0;
      name = "*";  /* Apparently use of CONSOLE: gave problems with
                      Emacs, so we continue to use "*" instead. */
      FreeDeviceProc (dp);
      is_fs = 0;
      dp = 0;
    }
  else
    {
      dp = get_device_proc (name, dp, &is_fs);

      handler = dp ? dp->dvp_Port : 0;
      parent_lock = dp ? dp->dvp_Lock : 0;
    }

  is_last = 0;
  unlock_parent = 0;
  link_levels = 0;
  result = 0;

  if (! handler) 
    {
      res_res2 = ERROR_OBJECT_NOT_FOUND;
      goto do_return;
    }

  link_levels = 0;

  /* this seems logical, doesn't it? don't know ;-)) */
  sep = index (name, ':');
  if (sep) name = sep+1;

  do
    {
      KPRINTF (("__plock: solving for %s.\n", name));

      if (is_fs)
        {
	  /* fetch the first part of "name", thus stopping at either a : or a / 
	   * next points at the start of the next directory component to be
	   * processed in the next run
	   */

	  sep = index (name, ':');
	  if (sep) 
	    {
	      sep++; /* the : is part of the filename */
	      next = sep;
	    }
	  else
	    {
	      sep = index (name, '/');
	      
	      /* map foo/bar/ into foo/bar, but keep foo/bar// */
	      if (sep && sep[1]==0)
	        {
	          is_last = 1;
	          next = sep;
	        }
	      else if (! sep)
	        {
	          sep = name + strlen (name);
	          next = sep;
	          is_last = 1;
	        }
	      else
	        {
	          if (ix.ix_flags & ix_translate_slash)
		    for (next = sep + 1; *next == '/'; next ++) ;
		  else
		    next = sep + 1;
		
	          /* if the slash is the first character, it means "parent",
	           * so we have to pass it literally to Lock() */
	          if (sep == name) sep = next;
	        }
	    }
	}
      else
	{
	  sep = name + strlen (name);
	  is_last = 1;
	}

      len = sep - name;
      if (len) bcopy (name, bstr + 1, len);
      *bstr = len;

      /* turn a ".." into a "/", and a "." into a "" */

      if (bcmp (bstr, "\2..", 3) == 0)
        {
	  bstr[0] = 1; bstr[1] = '/';
        }
      else if (bcmp (bstr, "\1.", 2) == 0)
        bstr[0] = 0;

      do
	{
	  int res = 1;

          sp->sp_Pkt.dp_Port = __srwport;
	  if (! is_last)
	    {
	      lock(handler, sp, parent_lock, bstr);
	      no_error = sp->sp_Pkt.dp_Res1 > 0;
	    }
	  else
	    res = (*last_func)(sp, handler, parent_lock, CTOBPTR (bstr),
                               last_arg, &no_error);

          is_root = 0;
	  /* if no error, fine */
          if (no_error)
            break;
          if (is_fs && sp->sp_Pkt.dp_Res2 == ERROR_OBJECT_NOT_FOUND && !bcmp(bstr, "\1/", 2))
            {
              is_root = 1;
              orig_name = next - 1;
              *orig_name = '/';
              FreeDeviceProc (dp);
              dp = 0;
              if (unlock_parent)
                unlock(handler, sp, parent_lock);
              goto retry_multi_assign;
            }
          if (!res)
            break;

	  /* else check whether ordinary error or really symlink */
          if (sp->sp_Pkt.dp_Res2 != ERROR_IS_SOFT_LINK) break;

	  /* read the link. temporarily use our bstr as a cstr, thus setting
           * a terminating zero byte and skipping the length byte */
	  bstr[*bstr + 1] = 0;

	  readlink(handler, sp, parent_lock, bstr + 1);

	  /* error (no matter which...) couldn't read the link */
	  if (sp->sp_Pkt.dp_Res1 <= 0)
	    {
	      /* this is our error-"lock", so make sure it is really zero */
	      sp->sp_Pkt.dp_Res1 = 0;
	      break;
            }

	  /* okay, new name. Set up as bstr and retry to lock it */
	  *bstr = sp->sp_Pkt.dp_Res1;

	  /* if the read name is absolute, we may have to change the
	   * handler and the parent lock. Check for this */
	  bstr[*bstr + 1] = 0;
	  
	  if ((cp = index ((char *)bstr + 1, ':')))
	    {
              if (unlock_parent)
                unlock(handler, sp, parent_lock);

	      /* if this is ":foobar", then the handler stays the same, and the
	       * parent_lock gets zero. Don't need get_device_proc() to find
	       * this out ;-) */
	      if (cp == (char *)bstr+1)
		parent_lock = 0;
	      else
	        {
	          /*
	           * NOTE: Multiassigns are currently only supported as the first
	           *       part of a path name. I don't like the idea of setting up
	           *       another recursion level here just to parse them...
	           *
	           * This approach also makes symbolic links to non-fs devices
	           * limited, which is bad. I'll HAVE to think something up
	           * (so you can't for example have dev:tty -> con:0/0/640/100/tty)
	           */
                  handler = DeviceProc (bstr + 1);	/* XXX fix !!! */
                  parent_lock = IoErr ();
                }
              unlock_parent = 0;
              
              if (! handler)
                {
                  /* interesting bug.. long not noticed... */

		  sp->sp_Pkt.dp_Res1 = 0;
		  if (! strcasecmp (bstr + 1, "nil:"))
		    sp->sp_Pkt.dp_Res2 = 4242;
		  else
		    sp->sp_Pkt.dp_Res2 = ERROR_OBJECT_NOT_FOUND;
		  break;
		}
	    }

	  ++link_levels;
	}
      while (link_levels < MAXSYMLINKS);

      if (link_levels == MAXSYMLINKS)
	{
	  result = 0;
	  res_res2 = ERROR_TOO_MANY_LEVELS;
	}
      else
	{
	  result = sp->sp_Pkt.dp_Res1;
	  res_res2 = sp->sp_Pkt.dp_Res2;
	}

      if (unlock_parent)
        unlock(handler, sp, parent_lock);
      else
	unlock_parent = (result != 0);

      parent_lock = result;
      name = next;
    }
  while (no_error && ! is_last);

  /* yes I know it's ugly ... */
  if (!no_error && res_res2 == ERROR_OBJECT_NOT_FOUND 
      && dp && (dp->dvp_Flags & DVPF_ASSIGN))
    goto retry_multi_assign;

do_return:
  FreeDeviceProc (dp);

  /* set up Result2 so that the IoErr() works */
  ((struct Process *)FindTask (0))->pr_Result2 = res_res2;

  syscall (SYS_sigsetmask, omask);

  KPRINTF (("__plock: returning %ld, res2 = %ld.\n", result, res_res2));

  return result;
}

/*
 * this is somewhat similar to DeviceProc(), but with the difference that it's
 * strictly passive, it won't start the handler, if it doesn't exist. This is
 * vital to be able to deal with stubborn console handlers, that don't answer
 * packets until they're really open..
 * Returns:
 *	HAN_UNDEF	device doesn't exist
 *	HAN_CLONE_DEV	device exists, handler zero
 *	HAN_FS_DEV	device exists, handler non-zero
 *	HAN_NOT_A_DEV	exists, but is not a device
 */

#define HAN_UNDEF	0
#define HAN_CLONE_DEV	1
#define HAN_FS_DEV	2
#define	HAN_NOT_A_DEV	3

static int
find_handler (char *bstr)
{
  struct RootNode *rn;
  struct DosInfo *di;
  struct DevInfo *dv;
  int res = HAN_UNDEF;
  
  /* could probably use less drastic measures under 2.0... */
  Forbid ();
  rn = (struct RootNode *) DOSBase->dl_Root;
  di = BTOCPTR (rn->rn_Info);
  for (dv = BTOCPTR (di->di_DevInfo); dv; dv = BTOCPTR (dv->dvi_Next))
    {
      if (! strncasecmp (bstr, BTOCPTR (dv->dvi_Name), bstr[0]+1))
        {
          if (dv->dvi_Type == DLT_DEVICE)
            res = dv->dvi_Task ? HAN_FS_DEV : HAN_CLONE_DEV;
          else
	    res = HAN_NOT_A_DEV;
	  break;
	}
    }
  Permit ();
  return res;
}

/*
 * feels very much like GetDeviceProc(), but works under 1.3 as well. Under
 * 2.0, we're using the dos-library GetDeviceProc(), under 1.3 that is
 * emulated with own structures.
 * is_fs is filled out with a best guess approach, since we can't use the
 * proper packet on a handler that isn't yet fully operating (as after just
 * calling DevProc)
 * if calling with prev!=0, is_fs is not touched.
 */

static struct DevProc *
get_device_proc (char *name, struct DevProc *prev, int *is_fs)
{
  char *cp, *f = NULL;
  int len;
  struct DevProc *dp;
  int han = HAN_UNDEF;
  struct Process *this_proc = NULL;
  APTR oldwin = NULL;

  if (! prev)
    {
      /* have to prove the opposite */
      *is_fs = 1;

      cp = index (name, ':');
      if (cp && cp != name)	/* ":..." has to be a filesystem */
        {
          len = cp - name;
          f = alloca (len + 1);
          f[0] = len;
          bcopy (name, f + 1, len);

          /* try to find it */
          han = find_handler (f);

	  KPRINTF (("  find_handler(%s) = %ld\n", name, han));

	  /* this might be wrong, we'll know more after GetDeviceProc() */
          if (han == HAN_CLONE_DEV) 
            *is_fs = 0;
        }
    }

  if (ix.ix_flags & ix_no_insert_disk_requester)
    {
      this_proc = (struct Process *)FindTask (0);
      oldwin = this_proc->pr_WindowPtr;
      this_proc->pr_WindowPtr = (APTR)-1;
    }
  dp = GetDeviceProc (name, prev);  
  if (ix.ix_flags & ix_no_insert_disk_requester)
    this_proc->pr_WindowPtr = oldwin;

  /* Second approach to verify, if a handler probably is a file system or not.
   * If the device is a filesystem, but didn't contain a volume before, then
   * the GetDeviceProc() call will have popped up the `please insert a disk'
   * requester. If the user obeyed, the handler will now exist. On the other
   * hand, if the device really is a clone device, it will still be one (ie. have
   * its task field zero), so check the device list again. */

  /* only for possible clone devices */
  if (han == HAN_CLONE_DEV)
    *is_fs = find_handler (f) != HAN_CLONE_DEV;
  
  return dp;
}

static int
unslashify (char *name)
{
  char *oname = name;

  if (index (name, ':'))
    return 0;

  while (oname[0] == '/' &&
         (oname[1] == '/' || !memcmp(oname + 1, "./", 2) || !memcmp(oname + 1, "../", 3)))
    while (*++oname == '.') ;

  if (!strcmp(oname, "/.") || !strcmp(oname, "/.."))
    oname[1] = '\0';

  /* don't (!) use strcpy () here, this is an overlapping copy ! */
  if (oname > name)
    bcopy (oname, name, strlen (oname) + 1);
    
  /* root directory */
  if (name[0] == '/' && name[1] == 0)
    {
      name[0] = ':';
      return 0;
    }

  if (name[0] == '/')
    {
      /* get the delimiter */
      char *cp = index (name + 1, '/');
      int shift = 0;

      /* if there is a separating (and not terminating) slash, shift a bit ;-) */
      if (cp)
	while (*cp == '/')
          {
            shift ++;
	    cp ++;
	  }

      /* is it a terminator (then discard it) or a separator ? */
      if (! cp || !*cp)
        {
	  /* terminator */
          cp = name + strlen (name);
          bcopy (name + 1, name, cp - name);
          cp[-1-shift] = ':';
          cp[-shift] = 0;
	}
      else
	{
	  /* separator */
	  bcopy (name + 1, name, strlen (name) + 1);
	  cp --;
	  bcopy (cp, cp - (shift - 1), strlen (cp) + 1);
	  cp[-shift] = ':';
	}
    }
  return 0;
}
