/*
** fn.c
**
** Functions for adding and deleting functions from the symbol table.
** Support for signal handlers is also found here.
*/

#include "rc.h"

#include <errno.h>
#include <signal.h>
#include <string.h>

#include "sigmsgs.h"

static void fn_handler(int), dud_handler(int);

static bool runexit = FALSE;
static Node *handlers[NUMOFSIGNALS], null;
static void (*def_sigint)(int) = SIG_DFL,
	(*def_sigquit)(int) = SIG_DFL,
	(*def_sigterm)(int) = SIG_DFL,
	(*def_sigtstp)(int) = SIG_DFL;

/*
   Set signals to default values for rc. This means that interactive
   shells ignore SIGTERM, etc.
*/

extern void
inithandler (void)
{
	int i;
	null.type = nBody;
	null.u[0].p = null.u[1].p = NULL;
	for (i = 1; i < NUMOFSIGNALS; i++)
#ifdef NOSIGCHLD
		if (i != SIGCHLD)
#endif
		if (sighandlers[i] == SIG_IGN)
			fnassign(signals[i].name, NULL); /* ignore incoming ignored signals */
	if (interactive || sighandlers[SIGINT] != SIG_IGN) {
		def_sigint = sigint;
		fnrm("sigint"); /* installs SIGINT catcher if not inherited ignored */
	}
	if (!dashdee) {
		if (interactive || sighandlers[SIGQUIT] != SIG_IGN) {
			def_sigquit = dud_handler;
			fnrm("sigquit"); /* "ignores" SIGQUIT unless inherited ignored */
		}
		if (interactive) {
			def_sigterm = dud_handler;
			fnrm("sigterm"); /* ditto for SIGTERM */
		}
	}

	rc_signal (SIGCHLD, SIG_DFL);	/* initial setup of permanent trap for job control */

	if (interactive) {
		rc_signal (SIGTTOU, SIG_DFL);
		rc_signal (SIGTTIN, SIG_DFL);
		def_sigtstp = SIG_IGN;
		fnrm ("sigtstp");
	} else {
		rc_signal (SIGTSTP, def_sigtstp);
	}
}

/* only run this in a child process! resets signals to their default values */

extern void
setsigdefaults (void)
{
	int i;
	/*
	   General housekeeping: setsigdefaults happens after fork(),
	   so it's a convenient place to clean up open file descriptors.
	   (history file, scripts, etc.)
	*/
	closefds();
	/*
	   Restore signals to SIG_DFL, the default action for SIGINT,
		SIGQUIT and SIGTERM must be set to the appropriate action,
		care must be taken not to set to SIG_DFL any signals which
		are being ignored, with the exception of SIGTSTP
	*/
	for (i = 1; i < NUMOFSIGNALS; i++) {
		if (sighandlers[i] != SIG_IGN) {
			handlers[i] = NULL;
			switch (i) {
			case SIGINT:
				def_sigint = SIG_DFL;
				break;
			case SIGQUIT:
				def_sigquit = SIG_DFL;
				break;
			case SIGTERM:
				def_sigterm = SIG_DFL;
				break;
			}
			if (sighandlers[i] != SIG_DFL) {
				rc_signal(i, SIG_DFL);
				delete_fn(signals[i].name);
			}
		}
	}

	def_sigtstp = SIG_DFL;
	fnrm ("sigtstp");

	delete_fn("sigexit");
	runexit = FALSE; /* No sigexit on subshells */
}

/* rc's exit. if runexit is set, run the sigexit function. */

extern void
rc_exit(int stat)
{
	if (runexit) {
		char *sig[2];
		sig[0] = "sigexit";
		sig[1] = NULL;
		runexit = FALSE;
		funcall(sig);
		stat = getstatus();
	}
	if (interactive && !forked) exit_jobs ();
	exit(stat);
}

/* The signal handler for all functions. calls walk() */

static void fn_handler(int s) {
	char *sig[2];
	int olderrno;
	if (s < 1 || s >= NUMOFSIGNALS)
		 panic("unknown signal");
	olderrno = errno;
	sig[0] = signals[s].name;
	sig[1] = NULL;
	funcall(sig);
	errno = olderrno;
}

/* A dud signal handler for SIGQUIT and SIGTERM */

static void dud_handler(int s) {
}

/*
   Assign a function in Node form. Check to see if the function is also
   a signal, and set the signal vectors appropriately.
*/

extern void
fnassign (char *name, Node *def)
{
	Node *newdef = treecpy(def == NULL ? &null : def, ealloc); /* important to do the treecopy first */
	Function *new = get_fn_place(name);
	int i;
	new->def = newdef;
	new->extdef = NULL;
	if (strncmp(name, "sig", conststrlen("sig")) == 0) { /* slight optimization */
#ifdef NOSIGCHLD /* System V machines treat SIGC(H)LD very specially */
		if (streq(name, "sigcld"))
			rc_error("can't trap SIGC(H)LD");
#endif
		if (streq(name, "sigexit"))
			runexit = TRUE;
		for (i = 1; i < NUMOFSIGNALS; i++) /* zero is a bogus signal */
			if (streq(signals[i].name, name)) {
				handlers[i] = newdef;
				if (def == NULL) {
					rc_signal(i, SIG_IGN);
				} else {
					rc_signal(i, fn_handler);
				}
				break;
			}
	}
}

/* Assign a function from the environment. Store just the external representation */

extern void
fnassign_string (char *extdef)
{
	char *name = get_name(extdef+3); /* +3 to skip over "fn_" */
	Function *new;
	if (name == NULL)
		return;
	new = get_fn_place(name);
	new->def = NULL;
	new->extdef = ecpy(extdef);
}

/* Return a function in Node form, evaluating an entry from the environment if necessary */

extern Node *
fnlookup (char *name)
{
	Function *look = lookup_fn(name);
	Node *ret;
	if (look == NULL)
		return NULL; /* not found */
	if (look->def != NULL)
		return look->def;
	if (look->extdef == NULL) /* function was set to null, e.g., fn foo {} */
		return &null;
	ret = parse_fn(name, look->extdef);
	if (ret == NULL) {
		efree(look->extdef);
		look->extdef = NULL;
		return &null;
	} else {
		return look->def = treecpy(ret, ealloc); /* Need to take it out of nalloc space */
	}
}

/* Return a function in string form (used by makeenv) */

extern char *
fnlookup_string (char *name)
{
	Function *look = lookup_fn(name);

	if (look == NULL)
		return NULL;
	if (look->extdef != NULL)
		return look->extdef;
	return look->extdef = mprint("fn_%F={%T}", name, look->def);
}

/*
   Remove a function from the symbol table. If it also defines a signal
   handler, restore the signal handler to its default value.
*/

extern void
fnrm (char *name)
{
	int i;
	for (i = 1; i < NUMOFSIGNALS; i++)
		if (streq(signals[i].name, name)) {
			handlers[i] = NULL;
			switch (i) {
			case SIGINT:
				rc_signal(i, def_sigint);
				break;
			case SIGQUIT:
				rc_signal(i, def_sigquit);
				break;
			case SIGTERM:
				rc_signal(i, def_sigterm);
				break;
			case SIGTSTP:
				rc_signal (i, def_sigtstp);
				break;
			default:
				rc_signal(i, SIG_DFL);
			}
		}
	if (streq(name, "sigexit")) runexit = FALSE;
	delete_fn(name);
}

extern void
whatare_all_signals ()
{
	int i;
	for (i = 1; i < NUMOFSIGNALS; i++)
		if (*signals[i].name != '\0')
			if (sighandlers[i] == SIG_IGN)
				fprint(1, "fn %s {}\n", signals[i].name);
			else if (sighandlers[i] == fn_handler)
				fprint(1, "fn %S {%T}\n", signals[i].name, handlers[i]);
			else
				fprint(1, "fn %s\n", signals[i].name);
}

extern void
prettyprint_fn (int fd, char *name, Node *n)
{
	fprint(fd, "fn %S {%T}\n", name, n);
}
