/*
 *  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.
 */

/* 20-jan-92	-mw-	revamped to emulate GetDeviceProc() under 1.3
 *			new way to find out whether IsFileSystem(name)
 */

/*
 * 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 <stdlib.h>
#include <string.h>

#ifdef DEBUG
#define DP(a) kprintf a
#else
#define DP(a)
#endif

#if __GNUC__ != 2
#define alloca __builtin_alloca
#endif

#ifndef ACTION_READ_LINK
#define ACTION_READ_LINK 1024
#endif

#ifndef ACTION_IS_FILESYSTEM
#define ACTION_IS_FILESYSTEM 1027
#endif


#ifndef ERROR_IS_SOFT_LINK
#define ERROR_IS_SOFT_LINK  233
#endif

#ifndef DVPF_ASSIGN
/* structure return by GetDeviceProc() */
struct DevProc {
	struct MsgPort *dvp_Port;
	BPTR		dvp_Lock;
	ULONG		dvp_Flags;
	struct DosList *dvp_DevNode;	/* DON'T TOUCH OR USE! */
};

/* definitions for dvp_Flags */
#define DVPB_UNLOCK	0
#define DVPF_UNLOCK	(1L << DVPB_UNLOCK)
#define DVPB_ASSIGN	1
#define DVPF_ASSIGN	(1L << DVPB_ASSIGN)

#define BASE_EXT_DECL
#define BASE_PAR_DECL	
#define BASE_PAR_DECL0	
#define BASE_NAME	ix.ix_dos_base
__inline static struct DevProc* GetDeviceProc(BASE_PAR_DECL UBYTE* name, struct DevProc* dp)
{
	BASE_EXT_DECL
	register struct DevProc* res __asm("d0");
	register void *a6 __asm ("a6");
	register UBYTE* d1 __asm("d1");
	register struct DevProc* d2 __asm("d2");

	a6 = BASE_NAME;
	d1 = name;
	d2 = dp;
	__asm volatile ("
	jsr a6@(-0x282)"
	: "=r" (res)
	: "r" (a6), "r" (d1), "r" (d2)
	: "d0", "d1", "a0", "a1", "d2");
	*(char*)d2=*(char *)d2;
	return res;
}
__inline static void FreeDeviceProc(BASE_PAR_DECL struct DevProc* dp)
{
	BASE_EXT_DECL
	register void *a6 __asm ("a6");
	register struct DevProc* d1 __asm("d1");

	a6 = BASE_NAME;
	d1 = dp;
	__asm volatile ("
	jsr a6@(-0x288)"
	: /* no output */
	: "r" (a6), "r" (d1)
	: "d0", "d1", "a0", "a1");
 	*(char*)d1=*(char *)d1;	
}
#endif

static struct DevProc	*get_device_proc (char *, struct DevProc *, int *);
static void 		free_device_proc (struct DevProc *);

extern int _dos20;

BPTR
__plock (char *name, int (*last_func)(), void *last_arg)
{
  BPTR parent_lock;
  struct MsgPort *handler;
  struct StandardPacket *sp;
  unsigned char *bstr;
  char *sep, *next, *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 split_name;
  struct DevProc *dp;
  char *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 (! strcmp (name, "*") || ! strcasecmp (name, "console:"))
    {
      handler = (struct MsgPort *)(((struct Process *)FindTask (0))->pr_ConsoleTask);
      parent_lock = 0;
      DP(("__plock: console override, handler $%lx.\n", handler));
      name = "*"; /* supports console: under 1.3 */
      if (dp) free_device_proc (dp);
      split_name = 0;
      dp = 0;
    }
  else
    {
      dp = get_device_proc (name, dp, &split_name);
      DP(("__plock: gdp(%s) -> $%lx",name,dp));

      handler = dp ? dp->dvp_Port : 0;
      parent_lock = dp ? dp->dvp_Lock : 0;
      if (handler)
        DP(("  handler = $%lx, parent_lock = $%lx, is_fs = %ld\n", handler, parent_lock, split_name));
      else
        DP(("\n"));
    }

  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
    {
DP(("__plock: solving for %s.\n", name));

      if (split_name)
        {
	  /* 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
	        {
	          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++;
	        }
	    }
	}
      else
	{
	  sep = name + strlen (name);
	  is_last = 1;
	}

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

      if (ix.ix_translate_dots)
	{
          /* 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
	{
          sp->sp_Pkt.dp_Port = __rwport;
	  if (! is_last)
	    {
              sp->sp_Pkt.dp_Type = ACTION_LOCATE_OBJECT;
              sp->sp_Pkt.dp_Arg1 = parent_lock;
              sp->sp_Pkt.dp_Arg2 = CTOBPTR(bstr);
              sp->sp_Pkt.dp_Arg3 = ACCESS_READ;

              PutPacket(handler, sp);
              __wait_packet(sp);

	      no_error = sp->sp_Pkt.dp_Res1 > 0;
	    }
	  else
	    if (! (*last_func)(sp, handler, parent_lock, CTOBPTR (bstr),
                               last_arg, &no_error)) break;

	  /* if no error, fine */
          if (no_error) 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;

          sp->sp_Pkt.dp_Port = __rwport;
          sp->sp_Pkt.dp_Type = ACTION_READ_LINK;
          sp->sp_Pkt.dp_Arg1 = parent_lock;
          sp->sp_Pkt.dp_Arg2 = (long) (bstr + 1); /* read as cstr */
          sp->sp_Pkt.dp_Arg3 = (long) (bstr + 1); /* write as cstr, same place */
          sp->sp_Pkt.dp_Arg4 = 255; /* what a BSTR can address */

          PutPacket(handler, sp);
          __wait_packet(sp);

	  /* 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;
            }

	  /* oky, 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)
	        {
                  sp->sp_Pkt.dp_Port = __rwport;
                  sp->sp_Pkt.dp_Type = ACTION_FREE_LOCK;
                  sp->sp_Pkt.dp_Arg1 = parent_lock;

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

	      /* 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;
	    }

	  ++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)
	{
          sp->sp_Pkt.dp_Port = __rwport;
          sp->sp_Pkt.dp_Type = ACTION_FREE_LOCK;
          sp->sp_Pkt.dp_Arg1 = parent_lock;

          PutPacket(handler, sp);
          __wait_packet(sp);
	}
      else
	unlock_parent = 1;

      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:
  free_device_proc (dp);

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

  syscall (SYS_sigsetmask, omask);

DP(("__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 DosLibrary *dl;
  struct RootNode *rn;
  struct DosInfo *di;
  struct DevInfo *dv;
  extern struct ixemul_base *ixemulbase;
  int res = HAN_UNDEF;
  
  /* could probably use less drastic measures under 2.0... */
  Forbid ();
  dl = (struct DosLibrary *) ixemulbase->ix_dos_base;
  rn = (struct RootNode *) dl->dl_Root;
  di = BTOCPTR (rn->rn_Info);
  for (dv = BTOCPTR (di->di_DevInfo); dv; dv = BTOCPTR (dv->dvi_Next))
    {
#if 0
/* quite verbose output... */
      {
        char buf[255];
        
        bcopy (BTOCPTR (dv->dvi_Name)+1, buf, *(char *)BTOCPTR (dv->dvi_Name));
        buf[*(char *)BTOCPTR (dv->dvi_Name)] = 0;
        DP(("\t%s(%ld,$%lx)", buf, dv->dvi_Type, dv->dvi_Task));
      }
#endif

      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 ();
/*  DP(("\n")); */
  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;
  int len;
  struct DevProc *dp;
  int han = HAN_UNDEF;

  if (! prev)
    {
      /* have to prove the oposite */
      *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);

DP(("  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;
        }
    }
  else
    if (! _dos20)
      {
	/* 1.3 doesn't support multiassigns, and this would be the only reason
	 * to call here more than once. */
	free (prev);
	return 0;
      }

  if (_dos20)
    dp = GetDeviceProc (name, prev);  
  else
    {
      dp = (struct DevProc *) syscall (SYS_malloc, sizeof (*dp));
      dp->dvp_Port = DeviceProc (name);
      dp->dvp_Lock = IoErr ();
      dp->dvp_Flags = 0;	/* just don't set DVPF_ASSIGN ;-)) */
      dp->dvp_DevNode = 0;	/* is private anyway */
    }

  /* 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 void
free_device_proc (struct DevProc *dp)
{
  if (_dos20)
    FreeDeviceProc (dp);
  else
    free (dp);
}
