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

#define KERNEL
#include "ixemul.h"
#include <ctype.h>
#include <string.h>

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

/* until sksh is fixed... */
#define BIG_KLUDGE

#define BASE_EXT_DECL
#define BASE_PAR_DECL	
#define BASE_PAR_DECL0	
#define BASE_NAME	ix.ix_dos_base
__inline static LONG NameFromLock(BASE_PAR_DECL BPTR lock, UBYTE* buffer, long int len)
{
	BASE_EXT_DECL
	register LONG res __asm("d0");
	register void *a6 __asm ("a6");
	register BPTR d1 __asm("d1");
	register UBYTE* d2 __asm("d2");
	register long int d3 __asm("d3");

	a6 = BASE_NAME;
	d1 = lock;
	d2 = buffer;
	d3 = len;
	__asm volatile ("
	jsr a6@(-0x192)"
	: "=r" (res)
	: "r" (a6), "r" (d1), "r" (d2), "r" (d3)
	: "d0", "d1", "a0", "a1", "d2", "d3");
	*(u_char *)d2=*(u_char *)d2;
	return res;
}

static BPTR try_load_seg (BPTR lock, char *name, char **args);


/*
 * This function does what LoadSeg() does, and a little bit more ;-)
 * Besides walking the PATH of the user, we try to do interpreter expansion as
 * well. But, well, we do it a little bit different then a usual Amiga-shell.
 * We check the magic cookies `#!' and `;!', and if found, run the interpreter
 * specified on this first line of text. This does *not* depend on any script
 * bit set!
 * If this check is negative, the script bit is tested. If set, the special
 * BPTR (-2) is returned, this is the hint to ssystem() to go and invoke
 * system() with the command line. In this case *NO* interpreter expansion
 * takes place, as the expansion must have already failed before.
 */

/*
 * IMPORTANT: only call this function with all signals masked!!! 
 */

/*
 * name:        the name of the command to load. Can be relative to installed PATH
 * args:        if set, a string to the first part of an expanded command is stored
 */

BPTR
__load_seg (char *name, char **args)
{
  BPTR lock, parent_lock, seg;
  char *base_name;
  
  /* perhaps the name is vanilla enough, so that even LoadSeg() groks it? */
  if (args) *args = 0;
  seg = LoadSeg (name);
  if (seg)
    return seg;

  /* try to lock the file (using __lock() provides full path-parsing ;-)) */

  seg = 0;
#if 0
  lock = __lock2 (name, ACCESS_READ, &parent_lock, &base_name);
  DP(("__load_seg: __lock2 (%s) = $%lx, $%lx%s", name, lock, parent_lock,
      lock ? "-> " : "\n"));
  if (lock)
    {
      seg = try_load_seg (parent_lock, base_name, args);
      DP(("$%lx\n", seg));

      if (parent_lock) UnLock (parent_lock);

      __unlock (lock);
      syscall (SYS_free, base_name);
    }
#else
  lock = __lock (name, ACCESS_READ);
  if (lock)
    {
      /* this is tricky.. it is legal to CurrentDir() to a file. This is what
       * we do here, we try to LoadSeg("") afterwards ;-)) */
      seg = try_load_seg (lock, "", args);

      __unlock (lock);
    }
#endif  

  /* now we may have a valid segment */
  if (seg)
    return seg;

  /* if the command was specified with some kind of path, for example with a
   * device or a directory in it, we don't run it thru the PATH expander
   */
  if (strpbrk (name, ":/"))
    return 0;

  /* so the command is not directly addressable, but perhaps it's in our PATH? */
  {
    struct Process *me = (struct Process *)((*(struct ExecBase **)4)->ThisTask);
    struct CommandLineInterface *cli;

    /* but we need a valid CLI then */
    if (cli = BTOCPTR (me->pr_CLI))
      {
	struct path_element {
	  BPTR	next;
	  BPTR 	lock;
	} *lock_list;

	for (lock_list = BTOCPTR (cli->cli_CommandDir);
	     lock_list;
	     lock_list = BTOCPTR (lock_list->next))
	  {
#if 0
            DP(("__load_seg: trying PATH component: next = $%lx, lock = $%lx\n",
                lock_list->next, lock_list->lock));
#endif

	    if (seg = try_load_seg (lock_list->lock, name, args))
	      break;
	  }
      }
  }
  
  return seg;
}

static BPTR
try_load_seg (BPTR lock, char *name, char **args)
{
  BPTR ocd;
  BPTR seg;

  if (args) *args = 0;
  
  ocd = CurrentDir (lock);
  
  seg = LoadSeg (name);

  /* try to do interpreter - expansion, but only if args is non-zero */
  if (! seg && args)
    {
      int fd;
      char magic[2];
      struct stat stb;
      
      if (syscall (SYS_stat, name, &stb) == 0 && S_ISREG (stb.st_mode))
	{
	  if ((fd = syscall (SYS_open, name, 0)) >= 0)
            {
	      if (syscall (SYS_read, fd, magic, 2) == 2 && 
	          (((magic[0] == '#' || magic[0] == ';') && magic[1] == '!')
#ifdef BIG_KLUDGE
		  /* I can't currently use #! expansion with sksh, since the shell
		   * does the same expansion again, and then doesn't seem to get
		   * it right anymore... this is the so far unused hidden bit,
		   * set it with PROTECT +h
		   */
		  || (stb.st_amode & (1 << 7))
#endif
										))
	        {
	          char interp[MAXINTERP + 1];
	          int n;
	      
	          if ((n = syscall (SYS_read, fd, interp, MAXINTERP)) > 0)
		    {
		      char *cp;

#ifdef BIG_KLUDGE
		      if (stb.st_amode & (1<<7))
		        {
			  strcpy (interp, "sksh -c");
			  n = strlen (interp);
			}
#endif

		      /* oky.. got one.. terminate with 0 and try to find end of it */
		      interp[n] = 0;
		      for (cp = interp; cp < interp+n; cp++)
		        if (*cp == 0 || isspace (*cp)) break;
		      *cp++ = 0;

		      /* okay, lets try to load this instead. Call us recursively,
		       * but leave out the argument-argument, so we can't get
		       * into infinite recursion. Interpreter-Expansion is only
		       * done the first time __load_seg() is called from
		       * ssystem()
		       */
		      seg = __load_seg (interp, 0);
		  
		      if (seg)
		        {
			  char *interp_path;
			  int len;

		          /* in this case, set the argument as well. 
		           */
		          for (;cp < interp+n; cp++) 
			    if (! isspace (*cp) || *cp == '\n') break;

			  /* we read a certain amount of bytes, but we 
			   * unconditionally stop when we hit newline
			   */
			  if (*cp == '\n')
			    n = cp - interp;

		          if (cp < interp+n) 
			    *args = cp;

		          /* crop any further arguments, only ONE argument
		           * is supported
			   */
		          for (;cp < interp+n; cp++) 
			    if (isspace (*cp)) 
			      break;
		          if (cp < interp+n) 
			    *cp = 0;

			  /* okay, now get a reasonably long buffer for the
			   * system to convert our lock into a path-name
			   */
			  if (! strpbrk (name, ":/"))
			    {
			      interp_path = alloca (MAXPATHLEN);
			      if (NameFromLock (lock, interp_path, MAXPATHLEN) == -1)
				len = strlen (interp_path);
			      else
				len = 0;
			    }
			  else
			    len = 0;

			  /* now build the argument string */
			  if (! *args) *args = "";
			  cp = syscall (SYS_malloc, len + 1 + strlen (name) + 1
					+ strlen (*args) + 2);
			  *cp = 0;
			  if (**args)
			    {
			      strcat (cp, *args);
			      strcat (cp, " ");
			    }

#ifdef BIG_KLUDGE
		          if (stb.st_amode & (1<<7))
			    strcat (cp, "\"");
#endif

			  if (len)
			    {
			      strcat (cp, interp_path);
			      if (! index (":/", cp[strlen (cp) - 1]) && *name)
				strcat (cp, "/");
			    }
			  strcat (cp, name);

			  *args = cp;
#ifdef BIG_KLUDGE
		          if (stb.st_amode & (1<<7))
		            {
			      /* tell ssystem() that it should add the closing
			       * quote... shudder.. I want a newer sksh !
			       */
		              *args = (char *)(-(int)cp);
		            }
#endif
		        }
		    }
	        }
	      syscall (SYS_close, fd);
	    }

	  /* check to see if script bit is set, no matter whether we could
	   * actually open the file or not. If set, set seg to magic BPTR */
	  if (! seg && (stb.st_amode & FIBF_SCRIPT))
	    seg = (BPTR) -2;
        }
    }
  
  CurrentDir (ocd);
  
  return seg;
}
