/*
 * wblaunch.c - WBLaunch main routines
 * part of WBLaunch - emulates WB startup of programs from the Shell
 * Copyright (C) 1998, 1999 Stephen Williams <sw@fysh.org>
 * wbstart.library is copyright (C) Stefan Becker
 *
 * This program 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 of the License, or
 * (at your option) any later version.
 *
 * This program 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 <exec/memory.h>
#include <dos/dos.h>
#include <libraries/wbstart.h>
#include <workbench/startup.h>

#include <clib/dos_protos.h>
#include <clib/exec_protos.h>
#include <clib/wbstart_protos.h>

#ifdef VBCC
#include <inline/dos_protos.h>
#include <inline/exec_protos.h>
#endif

#include <string.h>


/* Library bases */
extern struct DosLibrary *	DOSBase;
extern struct Library *		SysBase;
struct Library *		WBStartBase;


/* Constants for my readargs array */
#define	ARG_PROGRAM	0
#define	ARG_ARGUMENTS	1
#define	ARG_PRI		2
#define	ARG_VERBOSE	3


/* String for c:Version */
const char Version[] = "\0$VER: WBLaunch 1.1 (14.2.99)";

/* WBStart library name */
const char WBStartName[] = "wbstart.library";

/* Text for extended help */
const char Verbose[] =
  "Copyright © 1998, 1999 Stephen Williams <sw@fysh.org>\n"
  "wbstart.library is copyright © Stefan Becker\n"
  "See WBLaunch.readme for terms of distribution";


/*
 * Functions
 */


/*
 * NAME:
 *	findFile - find directory in path containing a given file
 *
 * SYNOPSIS:
 *	fileLock = findFile(filename)
 *	BPTR findFile(char *)
 *
 * FUNCTION:
 *	This routine searches the path for the given filename.  The
 *	current directory is searched first.  If the file is not
 *	found there, the path is searched.
 *
 * INPUTS:
 *	filename - pointer to a null-terminated string
 *
 * RESULTS:
 *	fileLock - BCPL pointer to a lock on the directory containing
 *	the file, or 0 for failure
 *
 * NOTE:
 *	It is your responsibility to unlock fileLock when you have
 *	finished with it.
 *
 */

BPTR findFile(char * fileName)
{
  BPTR		fileLock;
  BPTR *	pathPtr;
  BPTR		prevDir;

  /* Try current directory */
  if(fileLock = Lock(fileName, SHARED_LOCK)) {
    #if DEBUG
    Printf("Found \"%s\" in current directory\n", fileName);
    #endif
    UnLock(fileLock);
    return DupLock(((struct Process *)FindTask(NULL))->pr_CurrentDir);
  }

  /*
   * Not there?  Look in the path.  This path traversal code is
   * Deep Magic.  Don't blame me if you don't understand it.
   * Neither do I any more ;-)  Future versions of this program
   * will use dospath.library instead
   */
  for(pathPtr = (BPTR *)(((struct CommandLineInterface *)(((struct Process *)FindTask(NULL))->pr_CLI << 2))->cli_CommandDir << 2);
                    pathPtr != NULL; pathPtr = (BPTR *)(pathPtr[0] << 2)) {
    prevDir = CurrentDir(pathPtr[1]);
    fileLock = Lock(fileName, SHARED_LOCK);
    (void)CurrentDir(prevDir);
    if(fileLock) {
      #if DEBUG
      Printf("Found \"%s\" in path\n", fileName);
      #endif
      UnLock(fileLock);
      return DupLock(pathPtr[1]);
    }
  }

  return 0L;
}


/*
 * Entry point
 *
 * Called from custom startup code.
 *
 * INPUTS:
 *	None.
 *
 * RESULT:
 *	AmigaDOS error code (0=OK, 5=WARN, 10=ERROR or 20=FAIL).
 *
 */

int shellmain(void)
{
  struct RDArgs *	anchor;
  int			argCount = 0;
  char **		arguments;
  char *		argv0 = (char *)((((struct CommandLineInterface *)(((struct Process *)FindTask(NULL))->pr_CLI << 2))->cli_CommandName << 2) + 1);
  char *		name;
  int			rc = 20;
  LONG			rdargs[4] = {0L, 0L, 0L, 0L};

  /* ReadArgs parsing */
  anchor = ReadArgs("PROGRAM/A,ARGS/M,PRI=PRIORITY/N/K,VERBOSE/S",
		    &rdargs[0], NULL);
  if(anchor) {
    BPTR	fileOpen;
    BPTR	newDirectory = 0L;
    BPTR	oldDirectory =
      ((struct Process *)FindTask(NULL))->pr_CurrentDir;
    int		pathLen;
    char *	pathStr = 0L;
    
    /* Got appropriate arguments */
    name = (char *)rdargs[ARG_PROGRAM];
    
    /* Count arguments passed as ARGS/M */
    for(arguments = (char **)rdargs[ARG_ARGUMENTS];
	(arguments != NULL) && (*arguments != NULL);
	arguments++)
      argCount++;
    arguments = (char **)rdargs[ARG_ARGUMENTS];
    
    /* See if the filename has no directory part.  We'll need to change
       directory if this is the case */
    if(!(pathLen = (int)FilePart(name) - (int)name)) {
      /* Find the file in the current directory or path */
      newDirectory = findFile(name);
    } else {
      /* Got a path part; change dir to there */
      if(pathStr = AllocVec(pathLen + 4, MEMF_CLEAR | MEMF_REVERSE)) {
	strncat(pathStr, name, pathLen);
#ifdef DEBUG
	Printf("Locking directory \"%s\", ", pathStr);
#endif
	newDirectory = Lock(pathStr, SHARED_LOCK);
	/*oldDirectory = CurrentDir(newDirectory);*/
#ifdef DEBUG
	Printf("lock = %lx\n", newDirectory);
#endif
      }
    }
    
    /* Make sure file exists */
    (void)CurrentDir(newDirectory);
    if(fileOpen = Open(FilePart(name), MODE_OLDFILE)) {
      ULONG	firstLong = 0;
      
      /* Read first longword */
      (void)Read(fileOpen, &firstLong, 4);
      Close(fileOpen);
      (void)CurrentDir(oldDirectory);
      
      /*
       * Make sure we got an executable file header.
       * NB: This will FAIL for binaries not in hunk format (e.g. ELF);
       * this needs fixing in some way!
       *
       * I could just not bother with the check, but then wbstart.library
       * might choke if someone passes a text file or something in.
       *
       * Maybe I should check for ELF headers as well?
       * Or maybe ELF users should all change to WarpUp ;-)
       */
      if(firstLong == 0x000003f3) {
	struct Library * privWBStartBase;
	
	/* Open library */
	if(privWBStartBase = OpenLibrary((char *)WBStartName,
					 (ULONG)WBSTART_VERSION)) {
	  int		goodArgs = 0;
	  int		count;
	  struct WBArg *wbArgs = NULL;
	  
	  /* Do Workbench argument parse */
	  if(argCount > 0) {
	    if(wbArgs = AllocVec(argCount * sizeof(struct WBArg),
				 MEMF_ANY | MEMF_REVERSE)) {
	      char *		argDir;
	      char *		fname;
	      struct WBArg *	nextArg = wbArgs;
	      /* Process arguments one at a time */
#ifdef DEBUG
	      Printf("Received %ld arguments\n", (ULONG)argCount);
#endif
	      for(count = 0; count < argCount; count++) {
		/* Go back to starting directory */
		BPTR tempFile = Lock(arguments[count], SHARED_LOCK);
#ifdef DEBUG
		Printf("Processing arg #%ld, \"%s\":\n",
		       (ULONG)(count + 1),
		       arguments[count]);
#endif
		/* Try to open the file */
		if(tempFile) {
		  UnLock(tempFile);
		  /* Allocate temp space */
		  if(argDir =
		     AllocVec(strlen(arguments[count]),
			      MEMF_REVERSE | MEMF_CLEAR)) {
		    fname = FilePart(arguments[count]);
		    /* See if there's a directory component */
		    if(fname == arguments[count]) {
		      /* No, it's relative to start directory */
		      /* Need to DupLock() so we can safely UnLock() */
		      nextArg->wa_Lock = DupLock(oldDirectory);
		      nextArg->wa_Name = arguments[count];
		      nextArg++;
		      goodArgs++;
#ifdef DEBUG
		      Printf("  Located relative to current directory\n");
#endif
		    } else {
		      BPTR argLock;
		      strncat(argDir, arguments[count],
			      (int)(fname - arguments[count]));
		      argLock = Lock(argDir, SHARED_LOCK);
		      /* Attempt lock on directory */
		      if(argLock) {
			/* Got it; use that lock and the
			   filename component */
			nextArg->wa_Lock = argLock;
			nextArg->wa_Name = fname;
			nextArg++;
			goodArgs++;
#ifdef DEBUG
			Printf("  Located in directory \"%s\"\n", argDir);
#endif
		      } else {
#ifdef DEBUG
			Printf("  Couldn't lock this argument\n");
#endif
		      }
		    }
		    FreeVec(argDir);
		  }
		} else {
		  Printf("%s warning: couldn't locate argument \"%s\"\n",
			 argv0, arguments[count]);
#ifdef DEBUG
		  Printf("  Couldn't locate this argument\n");
#endif
		}
	      }
	    } else Printf("%s warning: couldn't allocate memory for arguments\n", argv0);
	  }
	  
	  /* Start the process */
	  if(newDirectory) {
	    char * argString;
#ifdef DEBUG
	    Printf("Starting process with %ld arguments\n", goodArgs);
#endif
	    
	    /* Verbose? */
	    if(rdargs[ARG_VERBOSE] &&
	       (argString = AllocVec(512L, MEMF_ANY | MEMF_REVERSE))) {
	      Printf("\n\033[1m%s\033[0m\n%s\n\n",
		     &Version[7], &Verbose[0]);
	      NameFromLock(newDirectory, argString, 511L);
	      AddPart(argString, FilePart(name), 511L);
	      Printf("Launching \"%s\" with %ld arguments%s", argString,
		     goodArgs, goodArgs ? ":\n" : "\n");
	      for(count = 0; count < goodArgs; count++) {
		NameFromLock(wbArgs[count].wa_Lock, argString, 511);
		Printf("  Argument #%ld: \"%s\", directory \"%s\"\n",
		       (ULONG)(count + 1),
		       wbArgs[count].wa_Name,
		       argString);
		
	      }
	      Printf("\n");
	      FreeVec(argString);
	    }
	    
	    WBStartBase = privWBStartBase;
	    rc = !WBStartTags(
			      WBStart_Priority,	rdargs[ARG_PRI] ? *(ULONG *)rdargs[ARG_PRI] : 0,
			      WBStart_ArgumentCount,	goodArgs,
			      WBStart_ArgumentList,	goodArgs ? wbArgs : NULL,
			      WBStart_Name,		FilePart(name),
			      WBStart_DirectoryLock,	newDirectory,
			      0
			      ) ? 0 : 10;
#ifdef DEBUG
	    Printf("Process started\n");
#endif
	  } else Printf("%s: initialization failed\n", argv0);
	  
	  /* Close things */
	  if(wbArgs) {
	    register int count;
	    /* Unlock all the directories we locked */
	    for(count = 0; count < goodArgs; count++)
	      UnLock(wbArgs[count].wa_Lock);
	    FreeVec(wbArgs);
	  }
	  CloseLibrary(privWBStartBase);
	  
	} else Printf("%s: couldn't open %s v%ld+\n", argv0, WBStartName,
		      (ULONG)WBSTART_VERSION);
	
      } else PrintFault(ERROR_OBJECT_WRONG_TYPE, argv0);
      
    } else {
      Printf("%s: couldn't open \"%s\"\n", argv0, name);
      (void)CurrentDir(oldDirectory); /* changed prior to Open() */
    }
    
    if(pathStr) FreeVec(pathStr);
    /* Change directory back to where we started */
    if(newDirectory) {
      /*(void)CurrentDir(oldDirectory);*/
      UnLock(newDirectory);
    }
    /* Free argument list */
    FreeArgs(anchor);
    
  } else PrintFault(IoErr(), argv0); /* ReadArgs() failed */

  return rc;
}
