/* Declare expression classses.  This is -*- C++ -*-.
   Copyright (C) 1992 Per Bothner.

This file is part of Q.

Q 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, or (at your option)
any later version.

Q 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 GNU CC; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

/*
Some routines were modified from GNU Bash, the Bourne Again SHell,
Copyright (C) 1989 Free Software Foundation, Inc.
*/

#pragma implementation
#include <strstream.h>
#include <fstream.h>
#include <_G_config.h>
extern "C" {
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
/*#include <sys/fcntl.h>*/
/*#include <dirent.h>*/
#include "regex.h"
}
#include <signal.h>

#include <stdarg.h>
#include "types.h"
//#include "hash.h"
#include "gfunc.h"
#include "genfiles.h"
#include "gfiles.h"
#include "exceptions.h"
#include "mapping.h"
#include "traverse.h"
#include "modules.h"
#include "gassign.h"
#include "shell.h"
#include "gkinds.h"
#include <std.h>
#include "builtin-syms.h"
#include "tempbuf.h"
#include "glob.h"
#include "shelldefs.h"
#include "parsefile.h"
#include <pwd.h>
#ifdef USE_RX
extern "C" {
#include "rx.h"
#include "rxparse.h"
#include "rxrun.h"
}
#endif

extern "C" int glob_pattern_p (char *pattern);
extern Expression * QuoteExprList(Expr_Ptr *arg, int length, int flags,
				  TraverseData *data);
extern "C" StringList * glob_filename (char *pathname);
OArray ArgvArray(0, NULL);
EnvironMap Environ(-1);
#if 0 /*def __GNU_LIBRARY__*/
#define CurrentEnviron __environ
#else
#define CurrentEnviron environ
#endif
extern char** CurrentEnviron;
static char** OriginalEnviron = 0;
extern "C" char *tilde_expand (char*);
#ifndef savestring
#define savestring(s) strdup(s)
#endif

char * get_string_value(char *s)
{
    char *sval = getenv(s);
    if (sval == NULL) {
	// Lookup local
	Root *val;
	Symbol *sym = EnterSymbol(s);
	val = sym->sym_value();
	if (val == NULL) {
	    // Kludge (based on Identifier::traverse1) FIXME!
	    Symbol *bsym = BuiltinPackage.find_exported(sym->string(),
							sym->length());
	    if (!bsym)
		return NULL;
	    val = sym->sym_value();
	    if (val == NULL)
		return NULL;
	}

	size_t in_length;
	const StringC *in_string = NULL;
	char* in_chars = Force2String(val, &in_length, &in_string);
	sval = savestring(in_chars);
	if (!in_string) {
	    in_string = NewString(in_length, in_chars);
#ifndef DO_GC
	    delete in_chars;
#endif
	}
    }
    return sval;
}


extern "C" void* xmalloc(size_t size)
{
    void *ptr = malloc(size);
    if (ptr == NULL) {
	fprintf(stderr, "Not enough virtual memory!\n");
	exit(-1);
    }
    return ptr;
}
extern "C" void* xrealloc(void* ptr, size_t size)
{
    if (ptr == NULL)
	ptr = malloc(size);
    else
	ptr = realloc(ptr, size);
    if (ptr == NULL) {
	fprintf(stderr, "Not enough virtual memory!\n");
	exit(-1);
    }
    return ptr;
}

#if 0
extern "C" int CLOSE(int old)
{
    fprintf(stderr, "close[%d](%d)", getpid(), old);
    fflush(stderr);
    return close(old);
}
extern "C" int DUP2(int old, int n)
{
    fprintf(stderr, "dup2[%d](%d,%d)", getpid(), old, n);
    fflush(stderr);
    return dup2(old, n);
}
extern "C" int DUP(int old)
{
    int out = dup(old);
    fprintf(stderr, "dup2[%d](%d)->%d", getpid(), old, out);
    fflush(stderr);
    return out;
}
extern "C" int FORK()
{
    int pid = fork();
    if (pid) {
	fprintf(stderr, "fork=>[%d]", pid);
	fflush(stderr);
    }
    return pid;
}
extern "C" int PIPE(int *fds)
{
    int r = pipe(fds);
    fprintf(stderr, "pipe{%d,%d}", fds[0], fds[1]);
    fflush(stderr);
    return r;
}
#endif

EnvironMap::EnvironMap(int size) : env(size)
{
    if (size != -1)
	env.init();
}

Root *EnvironMap::lookup_at(const char *name, int nlen)
{
    char *val = env.get(name);
    if (val == NULL)
	return NULL;
    return NewString(strlen(val), val);
}

void EnvironMap::set_at(Root *new_val, const char *name, int nlen)
{
    if (new_val == NULL)
	env.unset(name);
    else
	env.set(name, Coerce2String(new_val)->chars());
}

size_t EnvironMap::length()
{
    register int i = 0;
    register char **ptr = env.vector;
    for (; *ptr; ptr++) i++;
    return i;
}

GenSeq* EnvironMap::keys()
{
    if (env.allocated == -1)
	((EnvironMap*)this)->env.init(); // To force it to be sorted.
    int i = EnvironMap::length();
    Vector *result = NewVector(i);
    register char** eptr = env.vector;
    register Root** rptr = result->start_addr();
    while (--i >= 0) {
	char* str = *eptr++;
	char *eq = index(str, '=');
	*rptr++ =  NewString(eq ? eq - str : strlen(str), str);
    }
    return result;
}

void EnvironMap::printon(ostream& outs) const
{
    if (env.allocated == -1)
	((EnvironMap*)this)->env.init(); // To force it to be sorted.
    char **vec = env.vector;
    for (; *vec; vec++) {
	outs << *vec << "\n";
    }
}

StringC *ReadFileDescToString(int in_file)
{
    struct stat stat_buf[1];
    int cur_size;
    if (fstat(in_file, stat_buf) < 0) {
	perror("stat failed");
	cur_size = BUFSIZ;
    }
    else if ((stat_buf->st_mode & S_IFMT) != S_IFREG)
	cur_size = BUFSIZ;
    else
	cur_size = stat_buf->st_size + 1;
#if 0
    cur_size += sizeof(StringC);
    StringC *buf = (StringC*)malloc(cur_size);
    register char *ptr = buf->chars(), *lim = (char*)buf+cur_size;
#else
    char *buf = GC_malloc_atomic(cur_size);
    register char *ptr = buf, *lim = ptr+cur_size;
#endif
    for (;;) {
	if (ptr >= lim) {
	    int new_size = cur_size + BUFSIZ;
#if 1
	    buf = (char*)GC_realloc(buf, new_size);
	    ptr = buf + cur_size;
	    lim = buf + new_size;
#else
	    buf = (StringC*)realloc(buf, new_size);
	    ptr = (char*)buf + cur_size;
	    lim = (char*)buf + new_size;
#endif
	    cur_size = new_size;
	}
	int cc = read(in_file, ptr, lim - ptr);
	if (cc < 0) {perror("read failed");
	    return NULL;
	}
	if (cc == 0) break;
	ptr = ptr + cc;
    }
#if 1
    int len = ptr - buf;
    if (ptr + 1 != lim)
	buf = (char*)GC_realloc(buf, len + 1);
    buf[len] = '\0';
#ifdef DO_GC
    StringC *result = (StringC*)GC_malloc_stubborn(sizeof(StringC));
    result->StringC::StringC(buf, len);
    GC_end_stubborn_change(result);
    return result;
#else
    return new StringC(buf, len);
#endif
#else
    int len = ptr - buf->chars();
    if (ptr + 1 != lim)
	buf = realloc(buf, len + sizeof(StringC) + 1);
    buf->chars()[len] = '\0';
    buf->StringC::StringC(len);
    return buf;
#endif
}

StringC *ReadFileDescToString(PipeResult in)
{
    StringC *buf = ReadFileDescToString(in.file_id);
    if (in.wait_pid) {
	int pid;
	do { pid = ::wait(0); } while (pid > 0 && pid != in.wait_pid);
    }
    CLOSE(in.file_id);
    return buf;
}

extern "C" int ScanBackslash(char **out_ptr,
			     const char **in_ptr, const char *in_fence);
StringC *RemoveQuotes(const char *in_string, int in_length /* = -1*/)
{
    if (in_length == -1)
	in_length = strlen(in_string);
    char buf[in_length];
    const char *in_ptr = in_string;
    const char *in_limit = in_ptr + in_length;
    char *out_ptr = buf;
    for (;;) {
	if (in_ptr >= in_limit)
	    break;
	char ch = *in_ptr++;
	if (ch == '\\' && in_ptr < in_limit)
	    *out_ptr++ = *in_ptr++;
	else if (ch == '(' || ch == ')') continue;
	else if (ch == '\"') {
#if 1
	    ScanBackslash(&out_ptr, &in_ptr, in_limit);
#else
	    // Handle quoted string. For now, just skip quote.
#endif
	    continue;
	}
	else *out_ptr++ = ch;
    }
    return NewString(out_ptr - buf, buf);
}

StringC *RemoveQuotes(const StringC *arg)
{
    return RemoveQuotes(arg->chars(), arg->leng());
}

extern "C" StringC* CopyStringC(int len, char *data)
{
    return NewString(len, data);
}

extern "C" StringList*
globlist_fix(globval *globvals, globlist *globlists)
{
  int val_count = 0;
  register globval *globval_ptr = globvals;
  for ( ; globval_ptr; globval_ptr = globval_ptr->next)
      val_count++;
  register globlist *globlist_ptr = globlists;
  for ( ; globlist_ptr; globlist_ptr = globlist_ptr->next){
      StringC **glob_ptr = StringsPtr(globlist_ptr->names);
      val_count += StringsLen(globlist_ptr->names);
  }
  if (val_count == 0)
     return &NullSequence;
  StringList *list = NewVector(val_count);
  if (list == NULL) ; /* ERROR */
  StringC **list_ptr = StringsPtr(list);
  for (globval_ptr = globvals; globval_ptr; globval_ptr = globval_ptr->next) {
      *list_ptr++ = globval_ptr->name;
  }
  for (globlist_ptr = globlists; globlist_ptr;
       globlist_ptr = globlist_ptr->next) {
      StringC **glob_ptr = StringsPtr(globlist_ptr->names);
      int count = StringsLen(globlist_ptr->names);
      while (--count >= 0) *list_ptr++ = *glob_ptr++;
#ifndef DO_GC
      delete globlist_ptr->names;
#endif
  }
  return list;
}

Vector *GlobList(Root *arg)
{
    GenSeq *seq = arg->sequence();
    if (seq == NULL)
	RaiseDomainError(NULL);
    TempPtrBuf results;
    ITERATOR(seq_iter, seq);
    for (;;) {
	Root *seq_el = seq_iter.next();
	if (seq_el == Missing)
	    break;
	const StringC *str = Coerce2String(seq_el);
	char* str_start = str->chars();
	int do_glob = glob_pattern_p(str_start);
	int do_tilde_expand = str_start[0] == '~';
	if (do_glob + do_tilde_expand == 0) // No funny characters at all.
	    results.putp(str);
	else  {
	    if (do_tilde_expand)
		str_start = tilde_expand(str_start);
	    if (do_glob > 1) {
		StringList *filenames = glob_filename (str_start);
		if (do_tilde_expand)
		    free(str_start);
		int file_count = StringsCount(filenames);
		if (file_count > 0) {
		    StringC**ptr = StringsPtr(filenames);
		    while (--file_count >= 0)
			results.putp(*ptr++);
		    continue;
		}
	    }
	    results.putp(RemoveQuotes(str_start, -1));
	    if (do_tilde_expand)
		free(str_start);
	}
    }
    return NewVector(results.count(), (Root**)results.base());
}

Root *GlobListR(Root *names) // Body of builtin :(globlist :names)
{
    return GlobList(names);
}

void DoEcho(ostream* outs, Root* args)
{
    StringList* argv = GlobList(args);
    int display_return = 1;
    int display_quoted = 0;
    StringC **ptr = StringsPtr(argv);
    int argc = StringsCount(argv);
    StringC **lim = ptr + argc;
    while (ptr < lim && ptr[0]->chars()[0] == '-') {
	if (strcmp(ptr[0]->chars(), "-n") == 0) {
	    display_return = 0;
	    ptr++;
	}
	else if (strcmp(ptr[0]->chars(), "-q") == 0) {
	    display_quoted = 1;
	    ptr++;
	}
	else
	    break;
    }
    if (ptr < lim) {
	for (; ; ) {
	    if (display_quoted)
		PrintQuotedString(ptr[0]->chars(),
				  ptr[0]->leng(),
				  *outs);
	    else
		*outs << ptr[0]->chars();
	    ptr++;
	    if (ptr == lim) break;
	    *outs << ' ';
	}
    }
    if (display_return) *outs << '\n';
}

/* Given a string containing units of information separated by colons,
   return the next one pointed to by INDEX, or NULL if there are no more.
   Advance INDEX to the character after the colon. */
/* Routine based on Bash. */
char *
extract_colon_unit (char *string, int *index)
{
  int i, start;

  i = *index;

  if ((i >= strlen (string)) || !string)
    return ((char *)NULL);

  if (string[i] == ':')
    i++;

  start = i;

  while (string[i] && string[i] != ':') i++;

  *index = i;

  if (i == start)
    {
      if (!string[i])
	return ((char *)NULL);

      (*index)++;

      return (savestring (""));
    }
  else
    {
      char *value;

      value = (char *)xmalloc (1 + (i - start));
      strncpy (value, &string[start], (i - start));
      value [i - start] = '\0';

      return (value);
    }
}

static void ReplacePattern(strstreambuf* dest, re_registers* regs,
			   register char *new_pat, int new_pat_len,
			   char* old_string_start)
{
    int star_regnum = 9; /* Next '*' inserts from this register. */
    char *new_pat_end = new_pat + new_pat_len;
    while (new_pat < new_pat_end) {
	int ch = *new_pat++;
	switch (ch) {
	  case '\"': {
#define GET_NEXT(c) \
	      c = new_pat >= new_pat_end ? -1 : *(unsigned char *)new_pat++
#define UNDO_CHAR(c) new_pat--
	      for (;;) {
		  GET_NEXT(ch);
		  if (ch == -1) break; // ERROR
		  else if (ch == '\"') break;
		  else if (ch == '\\') {
		      char* new_pat_temp = new_pat;
		      ch = GetBackslash(&new_pat_temp, new_pat_end);
		      new_pat = new_pat_temp;
		      if (ch == -1) break; /* ERROR */
		      else if (ch == -2) break; /* ERROR */
		  }
		  dest->sputc(ch);
	      }
	  }
#if 0
	  case '[':
#endif
	  case '(':
	  case ')':
	    continue;
	  case '*':
	    if (star_regnum > 0) {
		int match_start = regs->start[star_regnum];
		int match_end = regs->end[star_regnum];
		if (match_start >= 0 && match_end >= match_start)
		    dest->sputn(old_string_start+match_start,
				match_end-match_start);
		star_regnum--;
	    }
	    break;
	  case '\\':
	    if (new_pat < new_pat_end) {
		ch = *new_pat++;
		if (ch == '\n') ; /* FIXME */
	    }
	  default:
	    dest->sputc(ch);
	}
    }
}

// Coerce 'arg' to a string, returning a (char*)pointing to its characters.
// If lenp is non-NULL, set it to the strings length.
// If stringp is non-NULL, optionally set it to a StringC
// that equals the result.
// *stringp is set to NULL if the resulting string is generated
// by "printing" arg.  In that case, the caller is reponsible
// for deleting the result when done.

char* Force2String(Root* arg, size_t *lenp, const StringC **stringp)
{
    const StringC *in_string = NULL;
    int in_length;
    char* in_chars;
    if (arg->isKindOf(*StringC::desc())) {
	in_string = (StringC*)arg;
	in_chars = in_string->chars();
	in_length = in_string->leng();
    }
    else if (arg->isKindOf(*Symbol::desc())) {
	in_string = ((Symbol*)arg)->Str();
	in_chars = in_string->chars();
	in_length = in_string->leng();
    }
    else { // Last resort: Convert by printing to a string..
	long save_print_readable = print_readable;
	print_readable = 0;
	ostrstream outs;
	outs << *arg;
	in_length = outs.pcount();
	outs << ends;
	in_chars = outs.str();
	print_readable = save_print_readable;
	in_string = NULL;
    }
    if (lenp)
	*lenp = in_length;
    if (stringp)
	*stringp = in_string;
    return in_chars;
}

Root* MatchAction(Root* arg, Root* patterns)
{
    GenSeq *seq = patterns->sequence();
    if (seq == NULL)
	RaiseDomainError(NULL);
    int seq_len = seq->length();
    if (seq_len != 1 && seq_len != 2)
	RaiseDomainError(NULL);

    const StringC* old_pat = Coerce2String(seq->index(0));
    const StringC* new_pat =
	seq_len == 1 ? NULL : Coerce2String(seq->index(1));

#ifdef USE_RX
    struct rx_buf pat_buf;
    int old_syntax = re_set_syntax(RE_NO_BK_PARENS|RE_NO_BK_VBAR
				   |RE_CHAR_CLASSES);
  pat_buf.no_sub = 0;
#else
    struct re_pattern_buffer pat_buf;
    int old_syntax = re_set_syntax(RE_GLOB_STYLE|RE_NO_BK_PARENS|RE_NO_BK_VBAR
				   |RE_CHAR_CLASSES);
    pat_buf.used = 0;
#endif
    pat_buf.buffer = NULL;
    pat_buf.allocated = 0;
    pat_buf.fastmap = NULL;
    pat_buf.translate = NULL;
    struct re_registers regs;
#ifdef USE_RX
    regoff_t regs_start[10];
    regoff_t regs_end[10];
    regs.num_regs = 10;
    regs.start = regs_start;
    regs.end = regs_end;
    char* msg = rx_compile_pattern(old_pat->chars(),
				   old_pat->leng(), &pat_buf);
#else
    char* msg = re_compile_pattern(old_pat->chars(),
				   old_pat->leng(), &pat_buf);
#endif
    if (msg) {
	fprintf(stderr, "[Misformed regular expression: %s - %s]\n",
		old_pat->chars(), msg);
	RaiseDomainError(NULL);
    }
    
    // Coerce 'arg' to a string.
    size_t in_length;
    const StringC *in_string = NULL;
    char* in_chars = Force2String(arg, &in_length, &in_string);

#ifdef USE_RX
    int re_val = rx_match (&pat_buf, in_chars, in_length, 0, &regs);
#else
    int re_val = re_match (&pat_buf, in_chars, in_length, 0, &regs);
#endif
    if (re_val != in_length) {
#ifndef DO_GC
	if (!in_string)
	    delete in_chars;
#endif
	Signal(GC_NEW CompareFail("match", arg, old_pat));
    }
    if (!new_pat) {
	if (!in_string) {
	    in_string = NewString(in_length, in_chars);
#ifndef DO_GC
	    delete in_chars;
#endif
	}
	return (Root*)in_string;
    }

    strstreambuf strbuf(128);
    ReplacePattern(&strbuf, &regs, new_pat->string(), new_pat->leng(),
		   in_chars);
#ifndef DO_GC
    if (!in_string)
	delete in_chars;
#endif
    new_pat = NewString(strbuf.pcount(), strbuf.str());
    strbuf.freeze(0);
    return (Root*)new_pat;
}

extern "C" get_job_spec(char*);

ExprQuote ZeroExpr(Zero);

Root* BgMacro(Root* left, Vector* args)
{
    Expr* qargs = QuoteExprList((Expr_Ptr*)args->start_addr(),
			       args->leng(), 0, NULL);
    if (left == NULL) {
	static ExprQuote *bg_expr = NULL;
	if (bg_expr == NULL) {
	    Symbol* bg_symbol =
		BuiltinPackage.find_interned("__fg_bg");
	    Root* bg_func = bg_symbol == NULL ? NULL
		: bg_symbol->sym_function();
	    if (bg_func == NULL) {
		fprintf(stderr, "[Can't find __fg_bg builtin]\n");
		RaiseDomainError(0);
	    }
	    bg_expr = GC_NEW ExprQuote(bg_func);
	}
	ExprList *ex = GC_NEW ExprList(3);
        ex->arg[0] = bg_expr;
	ex->arg[1] = &ZeroExpr;
	ex->arg[2] = qargs;
	return ex;
    }
#if 0
    if (qargs != [empty])
	ERROR;
#endif


    RunCommandExpr* run_expr =
	GC_NEW RunCommandExpr(RUN_EVAL_ARGS|RUN_BACKGROUND);
    run_expr->left.E = NULL;
    run_expr->right_args.E = (Expr*)left;
    return run_expr;
}

extern "C" int check_job_spec(char*);
Root* FgBgAction(int foreground, Root* args)
{
    int argc = StringsCount((StringList*)args);
    char* word;
    if (argc == 0) {
	word = "%+"; // get current_job.
    }
    else {
	word = StringsPtr((StringList*)args)[0]->string();
    }
  //  BLOCK_CHILD (set, oset);
    int job = check_job_spec(word);

    if (job < 0)
	goto failure;
    
  if (start_job (job, foreground)) {
    //  UNBLOCK_CHILD (oset);
      return &NullSequence;
    } 
  failure:
//      UNBLOCK_CHILD (oset);
    return &NullSequence;
}

char * shell_name = NULL;
/* Non-zero means that at this moment, the shell is interactive. */
int interactive = 0;

/* Non-zero means that the shell was started as an interactive shell. */
int interactive_shell = 1;

int forced_interactive;

int interrupt_state = 0;

int login_shell = 0;

char *current_user_name = (char *)NULL;

char *current_host_name = (char *)NULL;

extern int doInteractive;

extern "C" void initialize_jobs ();

typedef int IntFunction();
/* From readline/readline.h:
   If non-null, this contains the address of a function to call if the
   standard meaning for expanding a tilde fails.  The function is called
   with the text (sans tilde, as in "foo"), and returns a malloc()'ed string
   which is the expansion, or a NULL pointer if there is no expansion. */
extern IntFunction *tilde_expansion_failure_hook;
extern char* my_tilde_expand(char*);

void initialize_shell()
{
    tilde_expansion_failure_hook = (IntFunction*)my_tilde_expand;

    // Initialize current_user_name and current_host_name.
    struct passwd *entry = getpwuid (getuid ());
    char hostname[256];
    
    if (gethostname (hostname, 255) < 0)
	current_host_name = "??host??";
    else
	current_host_name = savestring (hostname);
    
    if (entry)
	current_user_name = savestring (entry->pw_name);
    else
	current_user_name = savestring ("I have no name!");
    endpwent ();

    // First, let the outside world know about our interactive status.
    // A shell is interactive if the `-i' flag was given, or if all of
    // the following conditions are met:
    //   no -c command
    //   no arguments remaining or the -s flag given
    //   standard input is a terminal
    //   standard output is a terminal
    // Refer to Posix.2, the description of the `sh' utility.

    if (forced_interactive ||		/* -i flag */
	(
#if 1
	 doInteractive &&
#else
	 doInteractive &&
	 !local_pending_command &&	/* -c command */
	 ((arg_index == argc) ||		/* No remaining args or... */
	  read_from_stdin) &&		/* -s flag with args */
#endif
	 isatty (fileno (stdin)) &&	/* Input is a terminal and */
	 isatty (fileno (stdout))))	/* output is a terminal. */
	interactive = 1;
    else {
	interactive = 0;
#if 0
	extern int history_expansion;
	
	history_expansion = remember_on_history = 0;
#ifdef JOB_CONTROL
	job_control = 0;
#endif
#endif
    }
    initialize_job_signals ();
    initialize_jobs();
    
    if (interactive) {
	/* Set up for checking for presence of mail. */
#if defined (USG)
	// Under System V, we can only tell if you have mail if the
	// modification date has changed.  So remember the current
	// modification dates.
	remember_mail_dates ();
#else
	// Under 4.x, you have mail if there is something in your inbox.
	// I set the remembered mail dates to 1900.
	reset_mail_files ();
#endif /* USG */

	// If this was a login shell, then assume that /bin/login has already
	// taken care of informing the user that they have new mail.
	// Otherwise, we want to check right away.
	if (login_shell == 1) {
#if !defined (USG)
	    remember_mail_dates ();
#endif
	}
	reset_mail_timer ();
    }
}

void SetArgv(int count, char**argv)
{
    int i;
#ifndef DO_GC
    if (ArgvArray.len) {
	// delete old stuff
	for (i = 0; i < ArgvArray.len; i++)
	    delete (String*)ArgvArray.start_addr()[i];
	delete [] ArgvArray.first;
    }
#endif
    shell_name = argv[0];
    ArgvArray.len = count;
    ArgvArray.first = (Root**)GC_malloc(sizeof(Root*) * count);
    for (i = 0; i < count; i++) {
	ArgvArray.first[i] = GC_NEW String(argv[i], strlen(argv[i]));
    }

#if 0
    OriginalEnviron = CurrentEnviron = envp;
    for ( ; *envp; envp++) {
	char* env_string = *envp;
	char* equals = index(env_string, '=');
	Symbol *key = EnterSymbol(env_string, equals - env_string);
	equals++;
	String* str_value = GC_NEW String(equals, strlen(equals));
	Environ.put(key, str_value);
    }
#endif
}

void FileNode::printon(ostream& outs) const
{
    if (print_readable) {
	if (print_readable && (file_name[0] != '.' || file_name[1] != '/'))
	    outs << "./";
	outs << file_name;
    }
    else {
	GenSeq::printon(outs);
    }
}

Root * FileNode::value()
{
    int fd = ::open(file_name, 0, 0);
    if (fd < 0)
	Signal(GC_NEW BadSyscall("Cannot open file %s for reading",
			      file_name, errno));
    Root *str = ReadFileDescToString(fd);
    ::CLOSE(fd);
    return str;
}

#if 0
long StatFileLength(char* file_name)
{
    struct stat stat_buf[1];
    if (stat(file_name, stat_buf) < 0) {
	return -1;
    }
    else if ((stat_buf->st_mode & S_IFMT) != S_IFREG)
	return -1;
    else
	return stat_buf->st_size;
}
#endif

size_t FileNode::length()
{
    char *file_name = filename();
    struct stat stat_buf[1];
    if (stat(file_name, stat_buf) < 0) {
	return UnknownLength;
    }
    else if ((stat_buf->st_mode & S_IFMT) == S_IFREG)
	return stat_buf->st_size;
    else if ((stat_buf->st_mode & S_IFMT) == S_IFDIR) {
#if 1
	abort();
#else
	// Return count of files in directory.
	DIR* dir = opendir(file_name);
	int count = 0;
	if (dir == NULL)
	    return UnknownLength;
	for (;;) {
	    register struct dirent *dp = (struct dirent*)readdir(dir);
	    if (dp == NULL)
		break;
	    char *name = dp->d_name;
	    if (name[0] == '.')
		if (name[1] == 0) continue; // "."
		else if (name[1] == '.' && name[2] == 0) continue; // ".."
	    count++;
	}
	closedir(dir);
	return count;
#endif
    }
    else
	return UnknownLength;
}

GenSeq* FileNode::sequence() const
{
    if (is_directory())
	return NULL;
    return this;
}

int FileNode::is_directory() const
{
    char *file_name = filename();
    struct stat stat_buf[1];
    if (stat(file_name, stat_buf) < 0)
	return 0;
    if ((stat_buf->st_mode & S_IFMT) == S_IFDIR)
	return 1;
    return 0;
}

FileNode* ConcatFileNodes(char *name1, char* name2)
{
    if (name2[0] == '/' || name1[0] == 0)
	return GC_NEW FileNode(name2);
    if (name2[0] == 0)
	return GC_NEW FileNode(name1);
    int len1 = strlen(name1);
    int len2 = strlen(name2);
    if (len2 > 2 && name2[0] == '.' && name2[1] == '/')
	len2 -= 2, name2 += 2;
    char *new_name = (char*)GC_malloc_atomic(len1+len2+2);
    sprintf(new_name, "%s/%s", name1, name2);
    return GC_NEW FileNode(new_name);
}

Root * FileNode::prefix(Root *arg)
{
    if (is_directory())
	return dir_lookup(arg);
    else
	return GenSeq::prefix(arg);
}

Root * FileNode::dir_lookup(Root *arg)
{
    if (arg->isKindOf(*Symbol::desc()))
	return ConcatFileNodes(filename(),
			       (Symbol::castdown(arg))->string());
    if (arg->isKindOf(*FileNode::desc()))
	return ConcatFileNodes(filename(),
			       (FileNode::castdown(arg))->filename());
    if (arg->isKindOf(*StringC::desc()))
	return ConcatFileNodes(filename(),
			       (StringC::castdown(arg))->chars());
    RaiseDomainError(NULL);
}

void FileNode::xapply(void* dst, Type* dstType, ArgDesc& args)
{
  if (!is_directory())
    GenSeq::xapply(dst, dstType, args);
  else if (args.rCount == 0)
    dstType->coerceFromRoot(dst, DoCurry(this, args));
  else
    {
      Root* val = dir_lookup(args.rArgs[0]);
      if (args.lCount+args.rCount+args.nCount == 1)
	dstType->coerceFromRoot(dst, val);
      else
	{
	  ArgDesc xargs(args, 0, 1);
	  val->xapply(dst, dstType, xargs);
	}
    }
}

//Assignable* FileNodeSeq::assignable() const { return &this_file; }

Root* FileNode::index(index_t index)
{
    int fd = ::open(filename(), O_RDONLY, 0);
    if (fd < 0)
	Signal(GC_NEW BadSyscall("Cannot open file %s for reading",
			      filename(), errno));
    unsigned char ch;
    if (lseek(fd, index, 0) < 0 || read(fd, &ch, 1) != 1) {
	CLOSE(fd);
	return Missing;
    } 
    CLOSE(fd);
    return &CharTable[ch];
}

int FileNode::sizeof_file() const { return sizeof(CharFile); }

void FileNode::open(GenFile *result, OpenFlags flags=0)
{
    errno = 0;
    filebuf* file = new filebuf();
    file->open(filename(), ios::in); 
    if (!file->is_open()) {
	delete file;
	Signal(GC_NEW BadSyscall("Cannot open file %s for reading",
			      filename(), errno));
    }
    CharFile *xfile = (CharFile*)result;
    CONSTRUCT(xfile, CharFile, (*this, file));
    xfile->index = 0;
    xfile->state = 0;
    xfile->stream = file;
}

void FileNode::assign(Root *new_value)
{
#if 1
    errno = 0;
    ofstream out_file(file_name);
    if (!out_file.good())
	Signal(GC_NEW BadSyscall("Cannot open file %s for writing",
			      file_name, errno));
    new_value->printon(out_file);
    out_file.close();
#else
    FILE *out_file = fopen(file_name, "w");
    if (out_file == NULL) {
	fprintf(stderr, "Failed to open file - ");
	perror(file_name);
	RaiseDomainError(NULL);
    }
    new_value->printon(out_file);
    fclose(out_file);
#endif
}

INSERT_BUILTIN(env, Environ);
INSERT_BUILTIN(argv, ArgvArray);

// The remaining routines are mostly minor modifications
// of routines in Bash.

int disallow_filename_globbing = 0;

#ifndef SYSV
/* The number of groups (within 64) that this user is a member of. */
static int default_group_array_size = 0;
static int ngroups = 0;
static _G_gid_t *group_array = (_G_gid_t *)NULL;
#endif

/* Return non-zero if GID is one that we have in our groups list. */
int group_member (int gid)
{
#ifdef SYSV
  return ((gid == getgid ()) || (gid == geteuid ()));
#else

  register int i;

  /* getgroups () returns the number of elements that it was able to
     place into the array.  We simply continue to call getgroups ()
     until the number of elements placed into the array is smaller than
     the physical size of the array. */

  while (ngroups == default_group_array_size)
    {
      default_group_array_size += 64;

      if (!group_array)
	group_array = (_G_gid_t *)xmalloc (default_group_array_size * sizeof (_G_gid_t));
      else
	group_array =
	  (_G_gid_t *)xrealloc (group_array,
			   default_group_array_size * sizeof (_G_gid_t));

      ngroups = getgroups (default_group_array_size, group_array);
    }

  /* In case of error, the user loses. */
  if (ngroups < 0)
    return (0);

  /* Search through the list looking for GID. */
  for (i = 0; i < ngroups; i++)
    if (gid == group_array[i])
      return (1);

  return (0);
#endif  /* SYSV */
}

PipeType::PipeType() : options(0)
{
    inst_size = sizeof(PipeResult);
    kind = TextTypeKind;
}

void PipeType::printon(ostream& outs) const
{
    outs << "Type(Pipe)";
}

void PipeType::coerceFromRoot(void *dstAddr, Root *val) const
{
    PipeResult& result = *(PipeResult*)dstAddr;
    if (val == &NullSequence) {
	result.wait_pid = 0;
	result.file_id = open("/dev/null", 0, 0);
	if (result.file_id < 0)
	    Signal(GC_NEW BadSyscall("Internal error: failed to open /dev/null",
				  NULL, errno));
	return;
    }
    else if (val->isKindOf(*FileNode::desc())) {
	char *filename = ((FileNode*)val)->filename();
	result.wait_pid = 0;
	result.file_id = open(filename, 0, 0);
	if (result.file_id < 0)
	    Signal(GC_NEW BadSyscall("Cannot open file %s for reading",
				  filename, errno));
	return;
    }
    int out_pipe[2];
    if (pipe(out_pipe))
	Signal(GC_NEW BadSyscall("Internal error: failed to create pipe",
			      NULL, errno));
    fflush(stdout);
    cout.flush();
    cerr.flush();
    // Must fork if the output might be enough to fill the pipe buffer,
    // since that would cause the kernel to block this process,
    // which would lead to deadlock.
    int do_fork = 1; // For now, let's be conservative.
    if (do_fork) {
	result.wait_pid = fork();
	if (result.wait_pid < 0) ; // ERROR!
	if (result.wait_pid == 0) { // child
	    if (out_pipe[1] != 1)
		DUP2(out_pipe[1], 1);
	    CLOSE(out_pipe[0]);
	    val->printon(cout);
	    cout.flush();
	    exit(0);
	}
	(void)CLOSE(out_pipe[1]);
    }
    else {
	result.wait_pid = 0;
	ofstream outs(out_pipe[1]);
	val->printon(outs);
	outs.close();
    }
    result.file_id = out_pipe[0];
}

PipeType Pipe;

void ReadFileFrom::eval(void* dst, Type* dstType, struct DisplayEnv *env)
{
    StringList *name_list = (StringList*)filename->eval(env);
    if (StringsLen(name_list) != 1)
	Signal(GC_NEW GenericCondition("Multiple filenames after '<'"));
    StringC *str = *StringsPtr(name_list);
    char* fname = str->chars();
    int in_file = open(fname, 0, 0); /* O_RDONLY */
    if (in_file < 0)
	Signal(GC_NEW BadSyscall("Failed to open file %s", fname, errno));
    if (dstType == &Text) {
	ostream& outs = *(ostream*)dst;
	filebuf infile(in_file);
	outs << &infile;
	infile.close();
    }
    else if (dstType == &Pipe) {
	PipeResult& result = *(PipeResult*)dst;
	result.wait_pid = 0;
	result.file_id = in_file;
    }
    else {
	Root *result = ReadFileDescToString(in_file);
	CLOSE(in_file);
	if (result == NULL) result = &NullSequence;
	dstType->coerceFromRoot(dst, result);
    }
}

ReadFileFrom::ReadFileFrom(Expr *filename)
{
    clear_std_fields(ReadFileFrom_code);
    filename = filename;
}

Expr * ReadFileFrom::traverse(struct TraverseData *data)
{
    filename = filename->traverse(data);
    return this;
}


Root* RunMacro(Root* left, Vector* args)
// LEFT is the argument to pass as stdin.
// It is NULL if no redirection is wanted.
{
    Expr* qargs = QuoteExprList((Expr_Ptr*)args->start_addr(),
			       args->leng(), 0, NULL);
    RunCommandExpr* run_expr = GC_NEW RunCommandExpr(0);
    run_expr->left.E = (Expr*)left;
//    run_expr->raw_identifier = 0;
    run_expr->right_args = qargs;
    return run_expr;
}

Root* ExecMacro(Root* left, Vector* args)
{
    Expr* qargs = QuoteExprList((Expr_Ptr*)args->start_addr(),
			       args->leng(), 0, NULL);
    RunCommandExpr* run_expr = GC_NEW RunCommandExpr(RUN_REPLACE_SELF);
    run_expr->left.E = left == Zero ? NULL : (Expr*)left;
//    run_expr->raw_identifier = 0;
    run_expr->right_args = qargs;
    return run_expr;
}

extern "C" int make_child(char*, int);
extern "C" void list_jobs(int);
extern "C" int wait_for(int);
extern "C" int do_stop_pipeline (int async);

// Run the command program indicated by 'expr'.
// If 'result' is non-NULL, set *result to show where the output is.
// If 'result' is NULL, send the result to file_descriptor 'result_fd'.

static int
DoRunCommand(PipeResult *result, int result_fd,
	     RunCommandExpr* expr, DisplayEnv *env, int do_wait)
{
    int eval_args = expr->run_flags & RUN_EVAL_ARGS;
    PipeResult left_result;
    // Optimize
    if (eval_args && expr->right_args.code() == RunCommand_code
	&& expr->left.E == NULL) {
	expr = (RunCommandExpr*)expr->right_args.E;
	eval_args = expr->run_flags & RUN_EVAL_ARGS;
    }
    if (expr->left.E) {
	expr->left.E->eval(&left_result, &Pipe, env);
    }
    else {
	left_result.file_id = -1;
	left_result.wait_pid = 0;
    }
    char *name_string; /* For 'jobs' command. */
    StringList *argv_str = eval_args ? NULL
	: GlobList(expr->right_args.eval(env));
    int exec_only = expr->replaces_self();
    int i;
    int argc = eval_args ? 0 : StringsCount(argv_str);
    fflush(stdout);
    int out_pipe[2];
    if (result) {
	if (pipe(out_pipe))
	    Signal(GC_NEW BadSyscall("Failed to create a pipe", NULL, errno));
	result_fd = out_pipe[1];
    }
    fflush(stdout);
    char *argv[argc+2];
    if (eval_args)
	name_string = "<expression>";
    else {
	char **arg_ptr = argv;
	for (i = 0; i < argc; i++)
	    *arg_ptr++ = StringsPtr(argv_str)[i]->chars();
	*arg_ptr = 0;
	name_string = argv[0];
    }
#if 0
    if (eval_args) {
	expr->right_args.eval(&cout, &Text, env);
	    return -1;
    }
#endif
    cin.rdbuf()->sync();
    cout.rdbuf()->sync();
    cerr.rdbuf()->sync();
    int pid = exec_only ? 0 : make_child(strdup(name_string), !do_wait);
    if (pid < 0)
	Signal(GC_NEW BadSyscall("fork system call failed", NULL, errno));
    if (!pid) { /* child */
	if(left_result.file_id > 0) {
	    DUP2(left_result.file_id, 0);
	    CLOSE(left_result.file_id);
	}
	else if (!do_wait) {
	    int fd = ::open("/dev/null", O_RDONLY);
	    if (fd > 0) {
		DUP2(fd, 0);
		CLOSE(fd);
	    }
	}
	if (result_fd != 1 && result_fd != -1) {
	    DUP2(result_fd, 1);
	    CLOSE(result_fd);
	}
	if (result)
	    CLOSE(out_pipe[0]);

	if (eval_args) {
	    without_job_control ();
	    expr->right_args.eval(&cout, &Text, env);
	    exit (0);
	}
	signal (SIGCHLD, SIG_DFL);
	execvp(argv[0], argv);
	BadSyscall badExecCond("exec %s failed", argv[0], errno);
	cerr << badExecCond << '\n';
	cerr.flush();
	_exit(-1);
    }
    /* parent */
    if (left_result.file_id >= 0)
	CLOSE(left_result.file_id);
    if (result)
	CLOSE(out_pipe[1]);
    if (result) {
	result->wait_pid = pid;
	result->file_id = out_pipe[0];
    }
//    if (do_wait || eval_args)
//	do_stop_pipeline(!do_wait);
    delete argv_str;
    return pid;
}

void RunCommandExpr::eval(void* dst, Type* dstType, struct DisplayEnv *env)
{
    int pid;
    PipeResult result;
    if (run_flags & RUN_BACKGROUND) {
	DoRunCommand(NULL, 1, this, env, 0);
	do_stop_pipeline(1);
	return;
    }
    if (dstType == &Pipe) {
	DoRunCommand((PipeResult*)dst, -1, this, env, 0);
//	do_stop_pipeline(1);
	return;
    }
    if (dstType != &Text) {
	pid = DoRunCommand(&result, -1, this, env, 1);
	do_stop_pipeline(0);
	dstType->coerceFromRoot(dst, ReadFileDescToString(result));
    }
    else {
	ostream& outs = *(ostream*)dst;
	register streambuf* outbuf = outs.rdbuf();
	if (
#ifdef _IO_IS_FILEBUF
	    outbuf->_flags & _IO_IS_FILEBUF
#elif _S_IS_FILEBUF
	    outbuf->_flags & _S_IS_FILEBUF
#else
	    0
#endif
	    )
	  {
	    int fd = ((filebuf*)outbuf)->fd();
	    outbuf->sync();
	    pid = DoRunCommand(NULL, fd, this, env, 1);
	    do_stop_pipeline(0);
	    outbuf->sync();
	  }
	else
	  {
	    pid = DoRunCommand(&result, -1, this, env, 0);
	    do_stop_pipeline(0);
	    char buf[BUFSIZ];
	    int last = '\n';
	    for (;;) {
		int cc = ::read(result.file_id, buf, BUFSIZ);
		if (cc < 0)
		    Signal(GC_NEW BadSyscall("read system call failed",
					  NULL, errno));
		if (cc == 0)
		    break;
		outbuf->sputn(buf, cc);
		last = buf[cc-1];
	    }
	    CLOSE(result.file_id);
	  }
    }
    int retcode = wait_for(pid);
    if (retcode != 0) // FIXME: "" should be argv[0] below.
	Signal(GC_NEW ProgramExitFailure("", pid, (signed char)retcode));
}

Root *GlobString(Root *arg)
{
    const StringC *str = Coerce2String(arg);
    return glob_filename (str->chars());
}

/* environ.c -- library for manipulating environments for GNU.
   Copyright (C) 1986, 1989 Free Software Foundation, Inc.

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

#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))

ProcessEnvironment::ProcessEnvironment (int size)
{
    allocated = size;
    if (size == -1) {
	if (OriginalEnviron == NULL)
	    OriginalEnviron = CurrentEnviron;
	vector = OriginalEnviron;
    }
    else {
	vector = new char *[allocated + 1];
	vector[0] = 0;
    }
}

ProcessEnvironment::~ProcessEnvironment()
{
    if (allocated != -1) {
	register char **vec = vector;
	while (*vec)
	    delete *vec++;
	delete vector;
    }
}

static int strpcmp(const void *s1, const void* s2)
{ return strcmp(*(char**)s1, *(char**)s2); }

/* Copy the environment given to this process into E.
   Also copies all the strings in it, so we can be sure
   that all strings in these environments are safe to free.  */

void ProcessEnvironment::init()
{
  extern char **environ;
  register int i;

  for (i = 0; environ[i]; i++) /*EMPTY*/;

  if (allocated < i) {
      int new_size = max (i, allocated + 10);
      if (allocated == -1)
	  vector = (char **) xmalloc ((new_size + 1) * sizeof (char *));
      else
	  vector = (char **) xrealloc ((char *)vector,
				       (new_size + 1) * sizeof (char *));
      allocated = new_size;
    }

  memcpy (vector, environ, (i + 1) * sizeof (char *));

  qsort(vector, i, sizeof(char*), strpcmp);
  while (--i >= 0)
    {
      register int len = strlen (vector[i]);
      register char *new_str = new char[len+1];
      memcpy (new_str, vector[i], len + 1);
      vector[i] = new_str;
    }
  if (this == &Environ.env)
      CurrentEnviron = vector;
}

// Return the value in THIS environment of variable VAR.
char * ProcessEnvironment::get(const char *var)
{
  register int len = strlen (var);
  register char *s;
  register char ** vec = vector;

  for (; s = *vec; vec++)
    if (!strncmp (s, var, len)
	&& s[len] == '=')
      return &s[len + 1];

  return 0;
}

/* Store the value in E of VAR as VALUE.  */

void ProcessEnvironment::set (const char* var, const char* value)
{
    if (allocated == -1)
	init();
    register int i;
    register int len = strlen (var);
    register char *s;
    int smaller = 0; /* Count of lexically smaller entries */
    
    for (i = 0; s = vector[i]; ) {
	int delta = strncmp (s, var, len);
	if (!delta && s[len] == '=')
	    break;
	i++;
	if (delta < 0)
	    smaller = i;
    }
    
    if (s == 0) {
	if (i == allocated) {
	    allocated += 10;
	    vector = (char **) xrealloc ((char *)vector,
					 (allocated + 1) * sizeof (char *));
	    vector[i+1] = 0;
	    if (this == &Environ.env)
		CurrentEnviron = vector;
	}
	// Keep it sorted.
	for (register int j = i; j > smaller; j--)
	    vector[j+1] = vector[j];
	i = smaller;
    }
    else
	delete s;
    
    s = new char [len + strlen (value) + 2];
    strcpy (s, var);
    strcat (s, "=");
    strcat (s, value);
    vector[i] = s;
    return;
}

/* Remove the setting for variable VAR from environment E.  */

void ProcessEnvironment::unset(const char *var)
{
    if (allocated == -1)
	init();
    register int len = strlen (var);
    register char *s;
    register char **vec = vector;
    
    for (; s = *vec; vec++)
	if (!strncmp (s, var, len)
	    && s[len] == '=') {
	    free (s);
	    bcopy (vec + 1, vec,
		   (allocated - (vec - vector)) * sizeof (char *));
	    vector[allocated - 1] = 0;
	    return;
	}
}

extern "C" void programming_error(char *format, ...)
{
    va_list args;
    va_start(args, format);
    fprintf(stderr, "Internal error: ");
    vfprintf(stderr, format, args);
    fprintf(stderr, "\n");
    RaiseDomainError(NULL);
    va_end(args);
}
extern "C" void report_error(char *format, ...)
{
    va_list args;
    va_start(args, format);
    vfprintf(stderr, format, args);
    fprintf(stderr, "\n");
    va_end(args);
}
extern "C" void throw_to_top_level()
{
    RaiseDomainError(NULL);
}

Root* JobsAction(Root* args)
{
    ostream& outs = cout;
    list_jobs(0);
    return &NullSequence;
}

extern "C" sighandler
sigint_sighandler (int sig)
{
#if defined (USG) && !defined (_POSIX_VERSION)
  signal (sig, sigint_sighandler);
#endif
  Signal(GC_NEW SysSignal(sig));
#if !defined (VOID_SIGHANDLER)
  return (0);
#endif /* VOID_SIGHANDLER */
}
