/*
 *	Copyright 1989 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 */

/*
 * Trap script and signal management, and the builtin "trap" command.
 */

#include "hostenv.h"
#include <stdio.h>
#include <ctype.h>
#include <sys/stat.h>
#include "zmsignal.h"
#include "flags.h"
#include "zmalloc.h"
/* #include "listutils.h" */
#include "sh.h"
#include "io.h"			/* redefines stdio routines */
#include "shconfig.h"
#include "libsh.h"

extern const char *VersionNumb;

#ifndef SIGCHLD
#define SIGCHLD SIGCLD
#endif  /* SIGCHLD */

/* In some cases the NSIG macro will not be defined, and we get
   into trouble below in otder to define array sizes et.al.
   This is true with e.g. Solaris under struct ANSI rules with
   Sun compiler.  In that system there is  _sys_nsig  which does
   tell how many signals there are, but it being an integer variable,
   we can't use it to define array sizes..
   Lets pick something that will be "sufficiently large" in case
   we don't get that macro in compilation.
*/

#ifndef NSIG
#define NSIG 128
#endif

/*
 * The script to execute for a particular trap is stored as a string in
 * malloc()'ed storage, with a pointer to it in the traps[] array.
 */

static const char *traps[NSIG];

/*
 * The effect of a signal is to increment a count of seen but unprocessed
 * (as in the trap script hasn't been run) signals in the spring[] array,
 * one counter per signal.  They had better start out as 0.
 */

STATIC int spring[NSIG];	/* pending trap counts */

/*
 * As a cheap way of testing if there are pending unprocessed signals, the
 * sprung variable is used as a flag to that effect.  It is a cheap test
 * elsewhere in the shell (in interpreter loop and input function).
 */

int sprung;			/* there are pending traps */

/*
 * In order to interrupt builtin function execution (as opposed to builtin
 * functions), set a flag whenever we see an interrupt that doesn't have
 * a trap handler.  This flag should be tested in the interpreter loop, and
 * anywhere else the shell might be spending a lot of time (e.g. the filename
 * expansion routines).
 */

int interrupted;		/* XX: we saw an interrupt */

/*
 * To maintain sh semantics, we need to know what the original signal handler
 * values are.  These are retrieved once at startup time (from main) and
 * stored in the orig_handler[] array.
 */

RETSIGTYPE (*orig_handler[NSIG]) __((int));

/*
 * Indeed, that is what the trapsnap() function does.
 */

void
trapsnap()
{
	int i;

	for (i = 1; i < NSIG; ++i)
		SIGNAL_HANDLESAVE(i, SIG_DFL, orig_handler[i]);
	/* is there a vulnerability here due to SIG_DFL instead of SIG_IGN ? */
	for (i = 1; i < NSIG; ++i)
		if (orig_handler[i] != SIG_DFL && i != SIGCHLD)
			SIGNAL_HANDLE(i, orig_handler[i]);
}


/*
 * Call this to process SH environment arrived SIGHUP
 */

void
traphup(sig)
int sig;
{
	if (traps[SIGHUP] != NULL)
		eval(traps[SIGHUP], "trap", NULL, NULL);
}


/*
 * This is the generic signal handler that is set whenever a trap is laid.
 */

void
trap_handler(sig)
	int sig;
{
	if (sig > 0 && sig < NSIG) {
		SIGNAL_HANDLE(sig, trap_handler);
		spring[sig] += 1;
		sprung = 1;
	}
	if (sig == SIGINT && traps[sig] == NULL)
		interrupted = 1;
}

/*
 * Evaluate the contents of the buffer and invoke the interpreter.
 * This function really should not be here!
 */

#define CFSUFFIX	".cf"
#define FCSUFFIX	".fc"
#define FCSUBDIR	"fc"

STATIC char * makefc __((const char *, char *));
STATIC char *
makefc(path, buf)
	const char *path;
	char *buf;
{
	register char *cp;
	struct stat stbuf;

	u_int plen = strlen(path);

	if (plen <= sizeof CFSUFFIX)
		return NULL;
	if (strcmp(path+plen-(sizeof FCSUFFIX - 1), FCSUFFIX) == 0) {
		strcpy(buf, path);
		return buf;
	}
	if (strcmp(path+plen-(sizeof CFSUFFIX - 1), CFSUFFIX) != 0)
		return NULL;

	strcpy(buf, path);
	if ((cp = strrchr(buf, '/')) != NULL)
		++cp;
	else
		cp = buf;
	strcpy(cp, FCSUBDIR);
	if (stat(buf, &stbuf) == 0 && (stbuf.st_mode & S_IFDIR)) {
		sprintf(buf + strlen(buf), "/%s", path+(cp-buf));
		strcpy(buf + strlen(buf) - (sizeof CFSUFFIX - 1), FCSUFFIX);
	} else {
		strcpy(buf, path);
		strcpy(buf+plen-(sizeof CFSUFFIX - 1), FCSUFFIX);
	}
	return buf;
}


int
eval(script, scriptname, savefile, srcstbufp)
	const char *script, *scriptname, *savefile;
	const struct stat *srcstbufp;
{
	int status;
	void *table, *eotable;
	FILE *fp;
	char *fcfile;
	char *buf = NULL;
#ifdef	USE_ALLOCA
	if (savefile != NULL)
	  buf = alloca(strlen(savefile)+9);
#else
	if (savefile != NULL)
	  buf = emalloc(strlen(savefile)+9);
#endif

	commandline = s_pushstack(commandline, script);
	table = SslWalker(scriptname, stdout, &eotable);
	status = 0;
	if (table != NULL) {
		if (isset('n')) {
			free((char *)table);
			return 0;
		}
		fcfile = NULL;
		if (isset('O')) {
extern void *optimize __((int, void *, void **));
			table = optimize(0, table, &eotable);
			if (isset('V'))
				table = optimize(1, table, &eotable);
			if (savefile != NULL &&
			    (fcfile = makefc(savefile,buf)) != NULL &&
			    (fp = fopen(fcfile, "w")) != NULL) {
				/* magic1, magic2, st_dev, st_ino,
				   st_size, st_mtime, st_ctime */
				fprintf(fp,"#!zmsh -l%s,%d,%ld,%ld,%ld,%ld,%ld,%ld\n",
					VersionNumb,
					magic_number, (long)bin_magic,
					(long)srcstbufp->st_dev,
					(long)srcstbufp->st_ino,
					(long)srcstbufp->st_size,
					(long)srcstbufp->st_mtime,
					(long)srcstbufp->st_ctime);

				std_fwrite(table, 1,
					   (char*)eotable - (char*)table, fp);
				if (fclose(fp) == EOF) {
					fprintf(stderr,
						"%s: write to %s failed\n",
						progname, fcfile);
					unlink(fcfile);
				}
			}
		}
		interpret(table, eotable, (u_char *)NULL,
			  globalcaller == NULL ? &avcmd : globalcaller,
			  &status, (struct codedesc *)NULL);
	}
#ifndef	USE_ALLOCA
	free(buf);
#endif
	return status;
}

int
loadeval(fcfd, path, srcstbufp)
	int fcfd;
	const char *path;
	struct stat *srcstbufp;
{
	int status;
	char *fcfile;
#ifdef	USE_ALLOCA
	char *buf = alloca(strlen(path)+9);
#else
	char *buf = emalloc(strlen(path)+9);
#endif

	fcfile = makefc(path,buf);
	if (fcfile == NULL) {
#ifndef	USE_ALLOCA
		free(buf);
#endif
		return -1;
	}
	status = leaux(fcfd, fcfile, srcstbufp);
#ifndef	USE_ALLOCA
	free(buf);
#endif
	return status;
}

int
leaux(fcfd, path, srcstbufp)
	int fcfd;
	const char *path;
	struct stat *srcstbufp;
{
	FILE *fp;
	int status, len;
	void *table;
	struct stat objstbuf;
	char buf[200];
	char sbuf[200];

	/* magic1, magic2, st_dev, st_ino, st_size, st_mtime, st_ctime */
	sprintf(sbuf,"#!zmsh -l%s,%d,%ld,%ld,%ld,%ld,%d,%d\n",
		VersionNumb,
		magic_number, (long)bin_magic,
		(long)srcstbufp->st_dev,
		(long)srcstbufp->st_ino,
		(long)srcstbufp->st_size,
		(int)srcstbufp->st_mtime,
		(int)srcstbufp->st_ctime);

	fp = fopen(path, "r");
	if (fp == NULL)
		return -1;
	if (std_fgets(buf, sizeof buf, fp) == NULL) {
		fclose(fp);
		fprintf(stderr, "%s: cannot get first line of %s\n",
			progname, path);
		return -1;
	}

	if (fstat(FILENO(fp), &objstbuf) < 0) {
		fclose(fp);
		fprintf(stderr, "%s: fstat failed on %s\n",
				progname, path);
		return -1;
	}
	if (strcmp(buf,sbuf) != 0) {
		fclose(fp);
		unlink(path);
		return -1;
	}
	if (srcstbufp != NULL && srcstbufp->st_mtime > objstbuf.st_mtime) {
		fclose(fp);
		unlink(path);
		return -1;
	}
	len = (int)(objstbuf.st_size - ftell(fp));
	table = (void *)emalloc(len);
	if (std_fread(table, 1, len, fp) != len) {
		fclose(fp);
		fprintf(stderr, "%s: read of %d failed on %s\n",
			progname, len, path);
		return -1;
	}
	fclose(fp);
	if (fcfd >= 0)
	  close(fcfd);
	status = 0;
	interpret(table, (char*)table + len, NULL,
		  globalcaller == NULL ? &avcmd : globalcaller,
		  &status, (struct codedesc *)NULL);
	return status;
}

/*
 * If unprocessed signals are pending (and the sprung flag set), we call
 * this function to do the processing.  It will deal with pending signals
 * in numerical as opposed to chronological order.
 */

void
trapped()
{
	int i;
	static int intrap = 0;

	if (!sprung)
	  return;
	/*
	 * We must reset the sprung flag before calling aval(), or we will
	 * almost certainly get a recursive invocation of this routine from
	 * the interpreter.
	 */
	sprung = 0;

	/*
	 * What about this scenario: interpreter calls trapped.  trapped calls
	 * eval which calls interpreter.  a signal is delivered and sprung
	 * gets set.  the interpreter calls trapped again.  some traps will
	 * then maybe get run twice.  Ergo we need some semaphore here.
	 */
	if (intrap) {
	  /* more signals have arrived, be sure not to miss them */
	  ++intrap;
	  return;
	}
	for (++intrap; intrap > 0; --intrap) {
	  for (i = 1; i < NSIG; ++i)
	    while (spring[i] > 0) {
	      if (traps[i] != NULL)
		eval(traps[i], "trap", NULL, NULL);
	      --spring[i];
	    }
	}
}


/*
 * This is the exit routine used when one wants a "trap 0" to be honoured.
 */

void
trapexit(n)
	int n;
{
	if (traps[0] != NULL) {
	  const char *cmd = traps[0];
	  traps[0] = NULL;
	  eval(cmd, "exit trap", NULL, NULL);
	}

	/* Lets clean these up, malloc tracers complain less.. */
	/* s_free_tree(envarlist); */
	envarlist = NULL;

#ifdef	MALLOC_TRACE
	mal_dumpleaktrace(stderr);
	/* mal_heapdump(&_iob[2]); */
#endif	/* MALLOC_TRACE */

	exit(n);
	/* NOTREACHED */
}

/*
 * The builtin "trap" function is implemented here.
 */

int
sh_trap(argc, argv)
	int argc;
	const char *argv[];
{
	int i;
	const char *av0, *script;
	
	if (argc == 1) {
		/* just print the known traps */
		for (i = 0; i < NSIG; ++i) {
			if (traps[i] != NULL)
				printf("%d: %s\n", i, traps[i]);
		}
		return 0;
	}
	av0 = argv[0];
	--argc, ++argv;
	if (**argv == '\0') {
		/* ignore the specified signals */
		script = *argv;
		--argc, ++argv;
	} else if (isascii((unsigned)(**argv)) && isdigit((unsigned)(**argv))){
		/* reset the signal handlers to original value */
		script = NULL;
	} else {
		/* stash the script away for later execution by a trap */
		script = *argv;
		--argc, ++argv;
		/* don't bother guarding argc > 0, sh doesn't */
	}
	while (argc-- > 0) {
		if (!isascii((255 & **argv)) || !isdigit((255 & **argv))) {
			fprintf(stderr, "%s: bad number: '%s'\n", av0, *argv);
			++argv;
			continue;
		}
		i = atoi(*argv++);
		if (i < 0 || i >= NSIG) {
			fprintf(stderr, "%s: %s: %s\n",
				av0, BAD_TRAP, *(argv-1));
			continue;
		}
		if (traps[i] != NULL)
			free((void*)traps[i]);
		if (script != NULL && *script != '\0') {
			char *tp = (char *)emalloc(strlen(script)+1);
			strcpy(tp, script);
			traps[i] = tp;
			/* enable that signal */
			if (i > 0 && orig_handler[i] != SIG_IGN)
				SIGNAL_HANDLE(i, trap_handler);
		} else if (script != NULL) {
			traps[i] = NULL;
			/* disable that signal */
			if (i > 0)
				SIGNAL_IGNORE(i);
		} else {
			traps[i] = NULL;
			if (i > 0)
				SIGNAL_HANDLE(i, orig_handler[i]);
		}
	}
	return 0;
}
