
/*
   jrd's version of the "system()" function.  
  
    system(command_line) -> return-code
  
   System deals with finding the executable you're talking about,
   and firing it off.  It also deals with any redirections.  It
   returns the completion status of the executed program.  In
   addition, it sets up the environment of the child 'task' such
   that if it's using our CRT0, it'll pick up the complete arglist.  
   The comments in CRT0 claim that it's using MWC format; maybe so, 
   I can't tell as I don't use MWC.  At any rate, it seems to work.
  
   Places we look for executables:  
    1.  See if there's an env var named what you want; it's
  	expected to be the name of the program to run.
    2.  See if there's an env var called PATH.  If so, it's
  	expected to be a list of directories.  Look thru them
  	all for an executable matching the command name.
    3.	Look in the current directory for something that 
  	matches the command name.
  
   This thing requires an entry point called "barf", for reporting
   errors.  See the code.
  
   The compile-time switch BUILTINS enables searching of a list of
   builtin command names, supplied by the application that uses this.

   The compile-time switch CLI causes it to try to use a CLI to exec
   the command, if it fails to figure out how to do it any other way.   

   Compile-time switch GULAM makes it insist on gulam.
*/

#include <stdio.h>
#include <osbind.h>
#include <ctype.h>

#define LOCAL static

#define CLI
#define GULAM

#ifdef GULAM
#define GULAM_HAS_BUGS
#endif

extern char * getenv();
extern char * malloc();

extern struct basepage * _base;
extern char ** environ;

#ifdef CLI

#define NO_CLI	-65536		/* couldn't find CLI to exec */

#define _shell_p	0x04F6

/* dereference _shell_p.  run this in super mode */

LOCAL long kludge = 0;		/* will contain *_shell_p */

void deref_shell_p()
{
  kludge = *((long * )_shell_p);
}

#ifdef GULAM
/* return the gulam magic number */
long gulam_magic()
{
  long * gulam_base;

  if (!kludge)
	Supexec(deref_shell_p);  
  if (!kludge)
	return(0);
  gulam_base = (long * )kludge;
  return(*((long * )((int )gulam_base - 10)));
}
#endif

/* dereference _shell_p if necessary */
long cli_entry_vector()
{
  long cli_base;

  if (!kludge)
	Supexec(deref_shell_p);  
  return(kludge);
}

#ifdef GULAM_HAS_BUGS
/*
   El disgusto.  Gulam has some kind of horrible bug that trashes the
   stack.  Save a copy of the stack here, and save regs.  NB!!! this 
   makes us *NOT* reentrant!!!
*/

extern int _stktop;			/* top of stack set by crt0 */
LOCAL long regs[16];			/* all regs */
LOCAL char * stack_buffer;		/* use this as stack */
LOCAL char * current_stack_pointer;
LOCAL int stack_nbytes;			/* how big the stack is */

LOCAL inline bcopy(from, to, n)
char * from;
char * to;
int n;
{
  while (n-- > 0)
	{
	*to++ = *from++;
	}
}

LOCAL inline save_context()
{
  /* clone the stack */
  asm volatile ("movel sp,d0");
  asm volatile ("movel d0,_current_stack_pointer");
  asm volatile ("movel __stktop,d1");
  asm volatile ("subl d0,d1");
  asm volatile ("movel d1,_stack_nbytes");
  stack_buffer = (char * )malloc(stack_nbytes);
  bcopy(current_stack_pointer, stack_buffer, stack_nbytes);

  /* save all registers */
  asm volatile ("moveml #0xFFFF,_regs");
  asm volatile ("clrl d0" : : : "d0");
  asm volatile ("clrl d1" : : : "d1");
  asm volatile ("clrl d2" : : : "d2");
  asm volatile ("clrl d3" : : : "d3");
  asm volatile ("clrl d4" : : : "d4");
  asm volatile ("clrl d5" : : : "d5");
  asm volatile ("clrl d6" : : : "d6");
  asm volatile ("clrl d7" : : : "d7");
  asm volatile ("movel d0,a0" : : : "a0");
  asm volatile ("movel d0,a1" : : : "a1");
  asm volatile ("movel d0,a2" : : : "a2");
  asm volatile ("movel d0,a3" : : : "a3");
  asm volatile ("movel d0,a4" : : : "a4");
  asm volatile ("movel d0,a5" : : : "a5");
}

LOCAL inline restore_context()
{
  asm volatile ("moveml _regs,#0xFFFF" : : : "d0", "d1", "d2", "d3", 
					     "d4", "d5", "d6", "d7", 
					     "a0", "a1", "a2", "a3", 
					     "a4", "a5", "a6", "sp");
  bcopy(stack_buffer, current_stack_pointer, stack_nbytes);
  free(stack_buffer);
}

LOCAL char * gulam_command_string;
LOCAL int (* callgulam)();
LOCAL int gulam_result;

/*
   this prevents compiler from outsmarting us by optimizing things
   onto the stack after we've trashed regs.  DON'T INLINE!
*/
LOCAL void call_gulam_by_hand()
{
  asm volatile ("movel _gulam_command_string,sp@-");
  asm volatile ("movel _callgulam,a0");
  asm volatile ("jsr a0@");
  asm volatile ("movel d0,_gulam_result");
}

#endif

int exec_via_cli(str)
char * str;
{
#ifndef GULAM_HAS_BUGS
  int (* callcli)();
#endif

#ifdef GULAM
  if (gulam_magic() != 0x00420135)
	return(NO_CLI);
#endif
#ifdef GULAM_HAS_BUGS
  if ((long )callgulam = cli_entry_vector())
	{
	gulam_command_string = str;
	save_context();
	call_gulam_by_hand();
	restore_context();
	return(gulam_result);
	}
#else
  if ((long)callcli = cli_entry_vector())
  	return((*callcli)(str));
#endif
    else
	return(NO_CLI);
}
#endif		/* CLI */

/* random utils */
LOCAL char * copy_until(to, from, pred)
char * to;
char * from;
int (* pred)();			/* predicate, tells us when to stop */
{
  while (*from && !(*pred)(*from))
	*to++ = *from++;
  *to = '\0';
  return(from);
}

/* predicates for above */
LOCAL int char_white_p(ch)
{
  return(isspace(ch));
}

/* stuff for resolving program to run when a command gets typed */

/* probe for a file, return T if found */
LOCAL int probe_file(name)
char * name;
{
  return((Fattrib(name, 0, 0) & 0x1F) == 0);
}

/* tack 'name' onto 'base_buf' and try adding permutations til 
   til find one that's there.  'extensions' is a string
   containing a comma_separated list of pathname extensions to
   try.  'extensions' NIL means just probe for the base name.
   return T and altered base_buf if found.  */
LOCAL int search_internal(base_buf, name, extensions)
char * base_buf;
char * name;
char * extensions;
{
  char * ext_ptr;		/* pointer into 'extensions' */
  char * tail_ptr;		/* ptr to tail part of base_buf */
  char * p;

/* see if there's a trailing backslash in the base buffer */
  if ((strlen(base_buf) > 0) &&
      (base_buf[strlen(base_buf) - 1] != '\\'))
	strcat(base_buf, "\\");	/* nope, add one */
  tail_ptr = base_buf + strlen(base_buf);
  strcat(base_buf, name);	/* add the name */

/* skip any leading dots etc in name */
  while ((*tail_ptr == '.') || (*tail_ptr == '\\'))
	tail_ptr++;

/* if no extensions specified, just probe */
  if (!extensions)
	return(probe_file(base_buf));

/* if the name's got a dot in it, just probe and go home */
  if (index(tail_ptr, '.'))		/* already has extension? */
	return(probe_file(base_buf));

/* permute base buf by extensions til find a match */
  while (*tail_ptr) tail_ptr++;		/* inc tail to eos */
  for (ext_ptr = extensions ; (ext_ptr && *ext_ptr) ; )
	{
	for (p = tail_ptr ; (*ext_ptr && (*ext_ptr != ',')) ; )
		*p++ = *ext_ptr++;
	*p = '\0';
	if (*ext_ptr == ',')
		ext_ptr++;
	if (probe_file(base_buf))
		return(1);
	}
  return(0);
}

/* handed a comma-separated list of directories, a name, and
   a comma-separated list of extensions, complete the pathname
   if possible */

LOCAL char complete_name[256];

LOCAL char * search_directories(dirs, name, extensions)
char * dirs;
char * name;
char * extensions;
{
  char * p;
  char * dir_ptr = dirs;
  
  if ((*name != '.') && 
      (*name != '\\') &&
      (name[1] != ':'))		/* not abs pathname? */
    for ( ; (dir_ptr && *dir_ptr) ; )
	{
	for (p = &complete_name[0] ; (*dir_ptr && (*dir_ptr != ',')) ; )
		*p++ = *dir_ptr++;
	*p = '\0';
	if (*dir_ptr == ',')
		dir_ptr++;
	if (search_internal(complete_name, name, extensions))
		return(&complete_name[0]);
	}

/* not found?  ok, try probing with no dir, ie current dir */
  complete_name[0] = '\0';
  if (search_internal(complete_name, name, extensions))
	return(&complete_name[0]);
  return(NULL);			/* not found anyplace */
}

/* handed the name of a command, find the executable to run */
LOCAL char * find_executable(name)
char * name;
{
  char * real_name;
  if ((name[1] != ':') &&
      (name[0] != '.') &&
      (name[0] != '\\') &&
      (real_name = getenv(name)))
	return(real_name);
  return(search_directories(getenv("PATH"), name, ".ttp,.tos,.prg,.app"));
}

/* stuff for building child environments */

LOCAL char * child_env;
LOCAL int child_env_nbytes;

LOCAL int my_env_nbytes(my_env)
char ** my_env;
{
  int nbytes = 0;
  
  for ( ; (my_env && *my_env) ; my_env++)
	nbytes += strlen(*my_env) + 1;
  return(nbytes);
}

LOCAL char * clone_my_env(my_env, child_env)
char ** my_env;
char * child_env;		/* Pexec format... */
{
  for ( ; (my_env && *my_env) ; my_env++)
	{
	strcpy(child_env, *my_env);
	child_env += strlen(child_env) + 1;
	}
  return(child_env);
}

/* parse command line and build child env out of it.  return
   remaining command line, if not all used. */
LOCAL char * make_child_env(line)
char * line;
{
  short argstart[64];	/* bytepos in line being parsed */
  short argend[64];
  char ch;
  char * p;
  int i, ii;
  int n_args = 0;
  int done = 0;

#ifdef DEBUG
  fprintf(stderr, "system: make-child-env('%s')\n", line);
#endif
  for (i = 0 ; ((!done) && line[i]) ; )
	{
	while (line[i] && isspace(line[i])) i++;
	if (line[i] == '\0')
		break;

	switch (ch = line[i])
		{
		case '\'': case '"':
			i++;
			argstart[n_args] = i;
			while (line[i] != ch) i++;
			argend[n_args] = i - 1;
			i++;
			n_args++;
			break;
		case '>': case '<': done = 1; break;
		default:
			argstart[n_args] = i;
			while (line[i] && !isspace(line[i])) i++;
			argend[n_args] = i;
			n_args++;
			break;
		}
	}

  child_env_nbytes = my_env_nbytes(environ);
#ifdef DEBUG
  fprintf(stderr, "system:   my env %d bytes\n", child_env_nbytes);
#endif
  child_env_nbytes += strlen("ARGV=here") + 1;

/* count arg lengths */
  for (ii = 0 ; ii < n_args ; ii++)
	child_env_nbytes += (argend[ii] - argstart[ii]) + 1;
  child_env_nbytes += 1;		/* final nul */

/* build the child env */
  child_env = malloc(child_env_nbytes);
#ifdef DEBUG
  fprintf(stderr, "system:   bought child env at %08X %d bytes total\n", 
  	child_env, child_env_nbytes);
#endif
  p = clone_my_env(environ, child_env);
#ifdef DEBUG
  fprintf(stderr, "system:   new ptr at %08X\n", p);
#endif
  strcpy(p, "ARGV=here");		/* magic flag */
  p += strlen("ARGV=here") + 1;
  for (ii = 0 ; ii < n_args ; ii++)
	{
#ifdef DEBUG
	fprintf(stderr, "system:   next arg at %08X '%s'\n", p, line + argstart[ii]);
#endif
	strncpy(p, line + argstart[ii], (argend[ii] - argstart[ii]));
	p += (argend[ii] - argstart[ii]) + 1;
	}
  *p = '\0';

/* if there's more of the command line unused, return it */
  if (line[i])
	return(line + i);
    else
	return(NULL);
}

/* stuff for redirecting handles 0 and 1 */

LOCAL int original_handle_0, new_handle_0, handle_0_redirected = 0;
LOCAL int original_handle_1, new_handle_1, handle_1_redirected = 0;

LOCAL redirect_0(pathname)
char * pathname;
{
  int force;

  original_handle_0 = Fdup(0);		/* save old */
  new_handle_0 = Fopen(pathname, 0);	/* open readonly */
  force = Fforce(0, new_handle_0);		/* force 0 to the new one */
  handle_0_redirected = 1;
/* zzz should probly deal with errors in here */
}

LOCAL redirect_1(pathname, append_p)
char * pathname;
int append_p;
{
  int force;

  original_handle_1 = Fdup(1);		/* save old */
  new_handle_1 = Fopen(pathname, 1);	/* try to open writable */
  if (new_handle_1 < -32)			/* lose? */
	{
	new_handle_1 = Fcreate(pathname, 0);	/* ok, create it */
	append_p = 0;			/* don't bother */
	}

/* if append, try to find file and seek to end */
  if (append_p)
	Fseek(0, new_handle_1, 2);	/* eof please */

  force = Fforce(1, new_handle_1);		/* force 1 to the new one */
  handle_1_redirected = 1;
}

LOCAL restore_redirects()
{
#ifdef DEBUG
  fprintf(stderr, "system: restore redirects\n");
#endif
  if (handle_0_redirected)
	{
	Fclose(0);
	Fclose(new_handle_0);		/* necessary? free up handle? */
	Fforce(0, original_handle_0);	/* restore old setting */
	Fclose(original_handle_0);	/* is this right??? */
	handle_0_redirected = 0;
	}
  if (handle_1_redirected)
	{
	Fclose(1);
	Fclose(new_handle_1);		/* necessary? free up handle? */
	Fforce(1, original_handle_1);	/* restore old setting */
	Fclose(original_handle_1);	/* is this right??? */
	handle_1_redirected = 0;
	}
}

LOCAL process_redirects(str)
char * str;
{
  char buf[64];
  char * p;
  int append_p = 0;

#ifdef DEBUG
  fprintf(stderr, "system: process redirects '%s'\n", str);
#endif
  while (*str)
	{
	switch (*str)
		{
		case '<': 
			str++;		/* skip it */
			while (isspace(*str)) str++;
			if (*str)
				str = copy_until(&buf, str, char_white_p);
			if (strlen(buf) > 0)
				redirect_0(buf);
			break;
		case '>': 
			str++;		/* skip it */
			if (*str == '>')
				{
				str++;
				append_p = 1;
				}
			while (isspace(*str)) str++;
			if (*str)
				str = copy_until(&buf, str, char_white_p);
			if (strlen(buf) > 0)
				redirect_1(buf, append_p);
			break;
		default: 
			str++;
			break;
		}
	}
}

/* externally visible entry points */


/* 
   execute a command line.  grok out the command name, figure out
   what it means.  construct the string passed to Pexec, and
   the child environment.  Do it, and free up everything.  return
   status from the pexec 
*/
int execute_command(line)
char * line;
{
  char pexec_string[130];
  char cmd[32];
  char * executable;
  char * redirects;
  int i, cmdstart;

  i = 0;
  while(line[i] && isspace(line[i])) i++;	/* skip leading white */
  cmdstart = i;
  while(line[i] && !isspace(line[i])) i++;	/* skip nonwhite */
  strncpy(cmd, line + cmdstart, i - cmdstart);	/* pull out command name */

/* be more forgiving....
  if (strlen(cmd) == 0)
	{
	barf("No command?!?");
	return(-999);
	}	*/

  executable = find_executable(cmd);
#ifdef DEBUG
  fprintf(stderr, "system: found executable '%s'\n", executable);
#endif
  if (!executable)
	{
#ifdef CLI
/*
   try to use a CLI to execute it
*/
	int cli_result = exec_via_cli(line);
	
	if (cli_result != NO_CLI)
		return(cli_result);
	    else
#endif
	barf("I don't grok '%s'", cmd);
	return(-999);
	}
  while(line[i] && isspace(line[i])) i++;	/* skip more whitespace */
  strncpy(pexec_string, line + i, 126);		/* make the pexec string */
  redirects = make_child_env(line);		/* in case child is smart */
#ifdef DEBUG
  fprintf(stderr, "system: made child env\n");
#endif  
  if (redirects)
	process_redirects(redirects);

#ifdef DEBUG
  fprintf(stderr, "system: Pexec('%s', '%s')\n", executable, pexec_string);
#endif
  i = Pexec(PE_LOADGO, executable, pexec_string, child_env);
  
  if (redirects)
	restore_redirects();

  free(child_env);
  child_env = NULL;
  return(i);
}

int system(command_line)
char * command_line;
{
#ifdef DEBUG
  fprintf(stderr, "system('%s')\n", command_line);
#endif
  return(execute_command(command_line));
}

#ifndef barf

barf(string, arg1, arg2, arg3)
char * string;
int arg1, arg2, arg3;
{
  fprintf(stderr, string, arg1, arg2, arg3);
}
#endif
/* that's all! */
