/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*           Copyright (c) 1984-2006 AT&T Knowledge Ventures            *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                      by AT&T Knowledge Ventures                      *
*                                                                      *
*                A copy of the License is available at                 *
*            http://www.opensource.org/licenses/cpl1.0.txt             *
*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                                                                      *
***********************************************************************/
#pragma prototyped
/*
 * Glenn Fowler
 * AT&T Research
 *
 * make - maintain and update programs
 *
 * history
 *
 *   1970's                   pre-make           (research)
 *                                .
 *                                .
 *   1976                       make             S. I. Feldman
 *                              .  .
 *                            .     .
 *                          .        .
 *                        .           .
 *   1978        augmented-make        .         E. G. Bradford
 *                    . . .             .
 *                  .   .   .           .
 *                .     .     .         .
 *              .       .       .       .
 *   1979     build     .         .     .        V. B. Erickson, J. F. Pellegrin
 *              .       .           .   .
 *              .       .             . .
 *   1984       .       .            V8-make     S. I. Feldman
 *              .       .             . .
 *              .       .           .   .
 *              .       .         .     .
 *              .       .       .       .
 *              .       .     .         .
 *              .       .   .           .
 *              .       . .             .
 *   1985       .     nmake             .        G. S. Fowler
 *              .     . .               .
 *              .   .   .               .
 *              . .     .               .
 *   1986    LC-nmake   .               .        J. E. Shopiro, M. S. Freeland
 *              . .     .               . 
 *              .   .   .               mk       A. G. Hume
 *              .     . .             . .
 *   1987       .     nmake         .   .        G. S. Fowler
 *              .       .         .     .
 *            (1.4)     .       .       .
 *                .     .     .         .
 *                  .   .   .           .
 *                    . . .             .
 *   1988             nmake             .        G. S. Fowler
 *                      .               .
 *                    (2.0)             .
 *                      .               .
 *   1990             (2.1)             .
 *                      .               .
 *   1991             (2.2)             .
 *                      .               .
 *   1992             (2.3)             .
 *                      .               .
 *   1993             (3.0)             .
 *                      .               .
 *   1994             (3.1)             .
 *                      .               .
 *   1995             (3.2)             .	AT&T Bell Labs
 *                      .               .
 *   1996             (3.3)             .	AT&T Research
 *                      .               .
 *   1997             (3.4)             .	AT&T Research
 *                      .               .
 *   1998             (3.5)             .	AT&T Research
 *                      .               .
 *   1999             (3.6)             .	AT&T Research
 *                      .               .
 *   2000             (4.0)             .	AT&T Research
 *                      .               .
 *   2001             (4.1)             .	AT&T Research
 *   2002             (4.2)             .	AT&T Research
 *   2003             (4.3)             .	AT&T Research
 *   2004             (4.4)             .	AT&T Research
 *   2005             (5.0)             .	AT&T Research
 *
 * command line arguments are of three types
 *
 *	make [ [ option ] [ script ] [ target ] ... ]
 *
 * options are described in options.h
 *
 * debug trace levels are controlled by error_info.trace
 * debug level n enables tracing for all levels less than or equal to n
 * levels 4 and higher are conditionally compiled with DEBUG!=0
 * if debug level < 20 then all levels are disabled during early bootstrap
 * if debug level = 1 then all levels are disabled until after .INIT is made
 *
 *	level	trace
 *	  0	no trace
 *	  1	basic make trace
 *	  2	major actions
 *	  3	option settings, make object files
 *	  4	state variables
 *	  5	directory and archive scan, some OPT_explain'ations
 *	  6	coshell commands and messages
 *	  7	makefile input lines
 *	  8	lexical analyzer states
 *	  9	metarules applied
 *	 10	variable expansions
 *	 11	bindfile() trace
 *	 12     files added to hash
 *	 13	new atoms
 *	 14	hash table usage
 *		...
 *	 20	bootstrap trace
 *
 * state.questionable registry (looks like it should go but not sure)
 *
 *	0x00000001 *temporary*
 *	0x00000002 *temporary*
 *	0x00000004 *temporary*
 *	0x00000008 1996-10-11 meta rhs prefix dir check even if rhs has prefix
 *	0x00000010 1994-01-01 old out of date if prereq in higher view
 *	0x00000020 1994-01-01 old scan advance logic
 *	0x00000040 2001-10-20 foiled alias still allows bind (!1994-08-11)
 *	0x00000080 1994-08-11 don't catrule() ../.. dir prefixes in bindfile()
 *	0x00000100 1994-10-01 metaclose() recursion even if rule.action!=0
 *	0x00000200 1994-11-11 disable generate() % transformation check
 *	0x00000400 1995-01-19 forceread even if global
 *	0x00000800 1995-07-17 D_global if not P_target in bindfile()
 *	0x00001000 1995-07-17 less agressive aliasing in bindfile()
 *	0x00002000 1995-07-17 less agressive putbound() in bindalias()
 *	0x00004000 1995-11-11 P_target && D_dynamic does not imply generated
 *	0x00008000 1996-02-29 :P=D: returns at most one dir per item
 *	0x00010000 1996-10-11 don't alias check path suffixes in bindfile()
 *	0x00020000 1998-11-11 don't force require_p make() recheck
 *	0x00040000 1999-09-07 generate() intermediates only if (sep & LT)
 *	0x00080000 1999-11-19 disable touch steady state loop
 *	0x00100000 2000-02-14 apply metarule even if dir on unbound lhs
 *	0x00200000 2000-09-08 0x00100000 implied if !P_implicit
 *	0x00400000 2001-02-14 allow P_archive to break job deadlock
 *	0x00800000 2001-10-05 disable early r->status=FAILED when errors!=0
 *	0x01000000 2002-10-02 $(>) doesn't check min(rule.time,state.time)
 *	0x02000000 2002-12-04 don't inhibit .UNBIND of M_bind rules
 *	0x04000000 2004-01-20 don't include triggered time==0 targets in :T=F: 
 *	0x08000000 2004-10-01 metaget does not assume % target for ... : % .NULL
 *
 * state.test registry (conditionally compiled with DEBUG!=0)
 *
 *	0x00000001 *temporary*
 *	0x00000002 *temporary*
 *	0x00000004 *temporary*
 *	0x00000008 bindfile() directory prune trace
 *	0x00000010 expand() trace
 *	0x00000020 force external archive table of contents generation
 *	0x00000040 bindalias() trace
 *	0x00000080 scan state trace
 *	0x00000100 statetime() trace
 *	0x00000200 staterule() view trace
 *	0x00000400 more staterule() view trace
 *	0x00000800 mergestate() trace
 *	0x00001000 job status trace
 *	0x00002000 dump Vmheap stats on exit
 *	0x00004000 dump atom address with name
 *	0x00008000 force state file garbage collection
 *	0x00010000 alarm status trace
 *	0x00020000 close internal.openfd before job exec
 *	0x00040000 set failed state|metarule event to staterule event
 *	0x00080000 replace ' ' with '?' instead of FILE_SPACE
 */

#include "make.h"
#include "options.h"

#include <sig.h>
#include <stak.h>
#include <vecargs.h>
#include <vmalloc.h>

#define settypes(c,t)	for(s=c;*s;settype(*s++,t))

#ifndef PATHCHECK
#define PATHCHECK	IDNAME
#endif

/*
 * global initialization -- external engine names are defined in initrule()
 */

Internal_t		internal;	/* internal rules and vars	*/
State_t			state;		/* program state		*/
Tables_t		table;		/* hash table info		*/
char			null[] = "";	/* null string			*/
char			tmpname[MAXNAME];/* temporary name buffer	*/
short			ctypes[UCHAR_MAX+1];/* istype() character types	*/

/*
 * user error message intercept
 */

static int
intercept(Sfio_t* sp, int level, int flags)
{
	register Rule_t*	r;
	char*			m;
	char*			s;
	char*			t;
	char*			e;
	int			n;
	int			i;
	Sfio_t*			tmp;

	NoP(sp);
	NoP(flags);
	if ((state.mam.level = level) > 0 && !state.hold && (r = internal.error) && (r->property & (P_functional|P_target)) == (P_functional|P_target) && !state.compileonly && !state.interrupt && (m = stakptr(0)) && (n = staktell()) > 0)
	{
		/*
		 * make the error trap
		 */

		state.hold = m;
		while (*m && *m != ':')
			m++;
		while (isspace(*++m));
		n -= m - state.hold;
		tmp = sfstropen();
		sfprintf(tmp, "%d %-.*s", level, n, m);
		s = sfstruse(tmp);

		/*
		 * return [ level | - ] [ message ]
		 * level is new level or - to retain old
		 * omitted message means it has been printed
		 */

		if (t = call(r, s))
		{
			i = strtol(t, &e, 0);
			if (e > t)
			{
				t = e;
				level = i;
			}
			else if (*t == '-')
				t++;
			while (isspace(*t))
				t++;
		}
		if (!t || !*t)
		{
			level |= ERROR_OUTPUT;
			message((-1, "suppressed %s message: `%-.*s'", level == 1 ? "warning" : "error", n, m));
		}
		sfstrclose(tmp);
		state.hold = 0;
	}
	return level;
}

/*
 * make entry point
 */

int
main(int argc, char** argv)
{
	register char*		s;
	register Rule_t*	r;
	register List_t*	p;
	int			i;
	int			args;
	int			trace;
	char*			t;
	char*			buf;
	char*			tok;
	Var_t*			v;
	Stat_t			st;
	Stat_t			ds;
	Sfio_t*			tmp;

	/*
	 * initialize dynamic globals
	 */

	version = strdup(fmtident(version));
	setlocale(LC_ALL, "");
	error_info.id = idname;
	error_info.version = version;
	error_info.exit = finish;
	error_info.auxilliary = intercept;
	if (pathcheck(PATHCHECK, error_info.id, NiL))
		return 1;
	error(-99, "startup");
	settypes("*?[]", C_MATCH);
	settypes("+-|=", C_OPTVAL);
	settypes(" \t\n", C_SEP);
	settype(0, C_SEP);
	settypes(" \t\v\n:+&=;\"\\", C_TERMINAL);
	settype(0, C_TERMINAL);
	settypes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_", C_ID1|C_ID2|C_VARIABLE1|C_VARIABLE2);
	settypes(".", C_VARIABLE1|C_VARIABLE2);
	settypes("0123456789", C_ID2|C_VARIABLE2);

	/*
	 * close garbage fd's -- we'll be tidy from this point on
	 * 3 may be /dev/tty on some systems
	 * 0..9 for user redirection in shell
	 * 10..19 left open by bugs in some shells
	 * error_info.fd interited from parent
	 * any close-on-exec fd's must have been done on our behalf
	 */

	i = 3;
	if (isatty(i))
		i++;
	for (; i < 20; i++)
		if (i != error_info.fd && !fcntl(i, F_GETFD, 0))
			close(i);

	/*
	 * allocate the very temporary buffer streams
	 */

	internal.met = sfstropen();
	internal.nam = sfstropen();
	internal.tmp = sfstropen();
	internal.val = sfstropen();
	internal.wrk = sfstropen();
	tmp = sfstropen();
	sfstrrsrv(tmp, 2 * MAXNAME);

	/*
	 * initialize the code and hash tables
	 */

	initcode();
	inithash();

	/*
	 * set the default state
	 */

	state.alias = 1;
	state.exec = 1;
	state.global = 1;
	state.init = 1;
#if DEBUG
	state.intermediate = 1;
#endif
	state.io[0] = sfstdin;
	state.io[1] = sfstdout;
	state.io[2] = sfstderr;
	state.jobs = 1;
	state.pid = getpid();
	state.readstate = MAXVIEW;
	state.scan = 1;
	state.start = CURTIME;
	state.stateview = -1;
	state.tabstops = 8;
	state.targetview = -1;
#if BINDINDEX
	state.view[0].path = makerule(".");
#else
	state.view[0].path = ".";
#endif
	state.view[0].pathlen = 1;
	state.writeobject = state.writestate = "-";

	/*
	 * pwd initialization
	 *
	 * for project management, if . is group|other writeable
	 * then change umask() for similar write protections
	 */

	buf = sfstrbase(tmp);
	internal.pwd = (s = getcwd(buf, MAXNAME)) ? strdup(s) : strdup(".");
	internal.pwdlen = strlen(internal.pwd);
	if (stat(".", &st))
		error(3, "cannot stat .");
	if (S_ISDIR(st.st_mode) && (st.st_mode & (S_IWGRP|S_IWOTH)))
		umask(umask(0) & ~(st.st_mode & (S_IWGRP|S_IWOTH)));

	/*
	 * set some variable default values
	 */

	hashclear(table.var, HASH_ALLOCATE);
	setvar(external.make, argv[0], V_import);
	t = "lib/make";
	setvar(external.lib, strdup((s = pathpath(buf, t, argv[0], PATH_EXECUTE)) ? s : t), V_import);
	setvar(external.pwd, internal.pwd, V_import);
	setvar(external.version, version, V_import);
	hashset(table.var, HASH_ALLOCATE);

	/*
	 * read the environment
	 */

	readenv();
	if (v = getvar(external.nproc))
		state.jobs = (int)strtol(v->value, NiL, 0);
	if ((v = getvar(external.pwd)) && !streq(v->value, internal.pwd))
	{
		if (!stat(v->value, &st) && !stat(internal.pwd, &ds) && st.st_ino == ds.st_ino && st.st_dev == ds.st_dev)
		{
			free(internal.pwd);
			internal.pwd = strdup(v->value);
			internal.pwdlen = strlen(v->value);
		}
		else
		{
			v->property &= ~V_import;
			v->property |= V_free;
			v->value = strdup(internal.pwd);
		}
	}

	/*
	 * initialize the internal rule pointers
	 */

	initrule();

	/*
	 * read the static initialization script
	 */

	sfputr(tmp, initstatic, -1);
	parse(NiL, sfstruse(tmp), "initstatic", NiL);

	/*
	 * check and read the args file
	 */

	if (s = colonlist(tmp, external.args, 1, ' '))
	{
		i = fs3d(0);
		tok = tokopen(s, 1);
		while (s = tokread(tok))
			if (vecargs(vecfile(s), &argc, &argv) >= 0)
				break;
			else if (errno != ENOENT)
				error(1, "cannot read args file %s", s);
		tokclose(tok);
		fs3d(i);
	}
	state.argf = newof(0, int, argc, 0);

	/*
	 * set the command line options
	 * read the command line assignments
	 * mark the command line scripts and targets
	 */

	state.init = 0;
	state.readonly = 1;
	state.argv = argv;
	state.argc = argc;
	if ((args = scanargs(state.argc, state.argv, state.argf)) < 0)
		return 1;
	state.readonly = 0;
	state.init = 1;
	if (state.base)
		state.readstate = 0;
	if (state.compileonly)
	{
		state.forceread = 1;
		state.virtualdot = 0;
	}

	/*
	 * tone down the bootstrap noise
	 */

	if ((trace = error_info.trace) == -1)
		error_info.trace = 0;

	/*
	 * check explicit environment overrides
	 */

	if (s = colonlist(tmp, external.import, 1, ' '))
	{
		tok = tokopen(s, 1);
		while (s = tokread(tok))
		{
			if (i = *s == '!')
				s++;
			if (v = getvar(s))
			{
				if (i)
					v->property &= ~V_import;
				else
					v->property |= V_readonly;
			}
		}
		tokclose(tok);
	}

	/*
	 * set up the traps
	 */

	inittrap();

	/*
	 * announce the version
	 */

	if (error_info.trace < 0)
	{
		errno = 0;
		error(error_info.trace, "%s [%d %s]", version, state.pid, timestr(state.start));
	}

	/*
	 * initialize the views
	 */

	state.global = 0;
	state.user = 1;
	initview();

	/*
	 * check for mam
	 */

	if (state.mam.out)
	{
		if (!state.mam.statix || *state.mam.label)
			error_info.write = mamerror;
		if (state.mam.regress || state.regress)
		{
			sfprintf(state.mam.out, "%sinfo mam %s %05d\n", state.mam.label, state.mam.type, state.mam.parent);
			if (state.mam.regress)
				sfprintf(state.mam.out, "%sinfo start regression\n", state.mam.label);
		}
		else
		{
			sfprintf(state.mam.out, "%sinfo mam %s %05d 1994-07-17 %s\n", state.mam.label, state.mam.type, state.mam.parent, version);
			if (!state.mam.statix || *state.mam.label)
			{
				sfprintf(state.mam.out, "%sinfo start %lu\n", state.mam.label, CURTIME);
				if (!state.mam.root || streq(state.mam.root, internal.pwd))
					sfprintf(state.mam.out, "%sinfo pwd %s\n", state.mam.label, internal.pwd);
				else
					sfprintf(state.mam.out, "%sinfo pwd %s %s\n", state.mam.label, state.mam.root, mamname(makerule(internal.pwd)));
				buf = sfstrbase(tmp);
				if (state.fsview && !mount(NiL, buf, FS3D_GET|FS3D_ALL|FS3D_SIZE(sfstrsize(tmp)), NiL))
					sfprintf(state.mam.out, "%sinfo view %s\n", state.mam.label, buf);
			}
		}
	}

	/*
	 * read the dynamic initialization script
	 */

	if ((i = error_info.trace) > -20)
		error_info.trace = 0;
	sfputr(tmp, initdynamic, -1);
	parse(NiL, sfstruse(tmp), "initdynamic", NiL);
	error_info.trace = i;
	state.user = 0;
	state.init = 0;

	/*
	 * read the explicit makefiles
	 * readfile() handles the base and global rules
	 *
	 * NOTE: internal.tmplist is used to handle the effects of
	 *	 load() on internal list pointers
	 */

	compref(NiL, 0);
	if (p = internal.makefiles->prereqs)
	{
		p = internal.tmplist->prereqs = listcopy(p);
		for (; p; p = p->next)
			readfile(p->rule->name, COMP_FILE, NiL);
		freelist(internal.tmplist->prereqs);
		internal.tmplist->prereqs = 0;
	}

	/*
	 * if no explicit makefiles then try external.{convert,files}
	 */

	if (!state.makefile)
	{
		int	sep;
		Sfio_t*	exp;
		Sfio_t*	imp;

		exp = 0;
		imp = sfstropen();
		sep = 0;
		s = 0;
		if (*(t = getval(external.convert, VAL_PRIMARY)))
		{
			sfputr(tmp, t, 0);
			sfstrrsrv(tmp, MAXNAME);
			tok = tokopen(sfstrbase(tmp), 0);
			while (s = tokread(tok))
			{
				if (!exp)
					exp = sfstropen();
				if (t = colonlist(exp, s, 0, ' '))
				{
					t = tokopen(t, 0);
					while (s = tokread(t))
					{
						if (readfile(s, COMP_INCLUDE|COMP_DONTCARE, NiL))
							break;
						if (sep)
							sfputc(imp, ',');
						else
							sep = 1;
						sfputr(imp, s, -1);
					}
					tokclose(t);
					if (s)
						break;
				}
				if (!(s = tokread(tok)))
					break;
			}
			tokclose(tok);
			sfstrseek(tmp, 0, SEEK_SET);
		}
		if (!s && (s = colonlist(tmp, external.files, 1, ' ')))
		{
			tok = tokopen(s, 1);
			while (s = tokread(tok))
			{
				if (readfile(s, COMP_INCLUDE|COMP_DONTCARE, NiL))
					break;
				if (sep)
					sfputc(imp, ',');
				else
					sep = 1;
				sfputr(imp, s, -1);
			}
			tokclose(tok);
		}
		if (!s)
		{
			/*
			 * this readfile() pulls in the default base rules
			 * that might resolve any delayed self-documenting
			 * options in optcheck()
			 */

			if (readfile("-", COMP_FILE, NiL))
				optcheck(1);
			if (*(s = sfstruse(imp)))
				error(3, "a makefile must be specified when %s omitted", s);
			else
				error(3, "a makefile must be specified");
		}
		sfstrclose(imp);
		if (exp)
			sfstrclose(exp);
	}

	/*
	 * validate external command line options
	 */

	optcheck(1);

	/*
	 * check for listing of variable and rule definitions
	 */

	if (state.list)
	{
		dump(sfstdout, 0);
		return 0;
	}

	/*
	 * check if makefiles to be compiled
	 */

	if (state.compile && !state.virtualdot && state.writeobject)
	{
		/*
		 * make the compinit trap
		 */

		if (r = getrule(external.compinit))
		{
			state.reading = 1;
			maketop(r, P_dontcare|P_foreground, NiL);
			state.reading = 0;
		}
		if (state.exec && state.objectfile)
		{
			message((-2, "compiling makefile input into %s", state.objectfile));
			compile(state.objectfile, NiL);
		}

		/*
		 * make the compdone trap
		 */

		if (r = getrule(external.compdone))
		{
			state.reading = 1;
			maketop(r, P_dontcare|P_foreground, NiL);
			state.reading = 0;
		}
	}

	/*
	 * makefile read cleanup
	 */

	if (state.compileonly)
		return 0;
	compref(NiL, 0);
	sfstrclose(tmp);
	state.compile = COMPILED;
	if (state.believe)
	{
		if (!state.maxview)
			state.believe = 0;
		else if (state.fsview)
			error(3, "%s: option currently works in 2d only", optflag(OPT_believe)->name);
	}

	/*
	 * read the state file
	 */

	readstate();

	/*
	 * place the command line targets in internal.args
	 */

	if (internal.main->dynamic & D_dynamic)
		dynamic(internal.main);
	internal.args->prereqs = p = 0;
	for (i = args; i < state.argc; i++)
		if (state.argf[i] & ARG_TARGET)
		{
			List_t*		q;

			q = cons(makerule(state.argv[i]), NiL);
			if (p)
				p = p->next = q;
			else
				internal.args->prereqs = p = q;
		}

	/*
	 * the engine bootstrap is complete -- start user activities
	 */

	state.user = 1;

	/*
	 * make the makeinit trap
	 */

	if (r = getrule(external.makeinit))
		maketop(r, P_dontcare|P_foreground, NiL);

	/*
	 * read the command line scripts
	 */

	for (i = args; i < state.argc; i++)
		if (state.argf[i] & ARG_SCRIPT)
		{
			state.reading = 1;
			parse(NiL, state.argv[i], "command line script", NiL);
			state.reading = 0;
		}

	/*
	 * freeze the parameter files and candidate state variables
	 */

	state.user = 2;
	candidates();

	/*
	 * make the init trap
	 */

	if (r = getrule(external.init))
		maketop(r, P_dontcare|P_foreground, NiL);

	/*
	 * internal.args default to internal.main
	 */

	if (!internal.args->prereqs && internal.main->prereqs)
		internal.args->prereqs = listcopy(internal.main->prereqs);

	/*
	 * turn up the volume again
	 */

	if (!error_info.trace)
		error_info.trace = trace;

	/*
	 * make the prerequisites of internal.args
	 */

	if (internal.args->prereqs)
		while (internal.args->prereqs)
		{
			/*
			 * we explicitly allow internal.args modifications
			 */

			r = internal.args->prereqs->rule;
			internal.making->prereqs = internal.args->prereqs;
			internal.args->prereqs = internal.args->prereqs->next;
			internal.making->prereqs->next = 0;
			maketop(r, 0, NiL);
		}
	else if (state.makefile)
		error(3, "%s: a main target must be specified", state.makefile);

	/*
	 * finish up
	 */

	finish(0);
	return 0;
}

/*
 * clean up and exit
 */

void
finish(int n)
{
	Rule_t*		r;
	int		i;

	/*
	 * old error intercept
	 */

	if (!state.hold && (r = internal.error) && (r->property & (P_target|P_functional)) == P_target)
	{
		state.hold = null;
		if (n && error_info.errors && !state.compileonly && !state.interrupt)
		{
			if (r->status == NOTYET)
				maketop(r, P_dontcare|P_foreground, NiL);
			state.hold = 0;
			if (r->status == EXISTS)
			{
				r->status = NOTYET;
				return;
			}
		}
	}

	/*
	 * children exit without cleanup
	 */

	if (getpid() != state.pid)
		_exit(n);
	unparse(0);
	switch (state.finish)
	{

	case 0:
		/*
		 * disable listing and wait for any jobs to finish
		 */

		state.finish++;
		alarm(0);
		state.list = 0;
		message((-1, "%s cleanup", state.interrupt ? "interrupt" : n > 0 ? "error" : "normal"));
		complete(NiL, NiL, NiL, 0);
		/*FALLTHROUGH*/

	case 1:
		/*
		 * make the done trap
		 */

		state.finish++;
		if (!state.compileonly && (r = getrule(external.done)))
			maketop(r, P_dontcare, NiL);
		/*FALLTHROUGH*/

	case 2:
		/*
		 * make the makedone trap
		 */

		state.finish++;
		if (!state.compileonly && (r = getrule(external.makedone)))
			maketop(r, P_dontcare, NiL);
		/*FALLTHROUGH*/

	case 3:
		/*
		 * wait for any job(s) to finish
		 */

		state.finish++;
		complete(NiL, NiL, NiL, 0);
		/*FALLTHROUGH*/

	case 4:
		/*
		 * put all jobs in foreground and save state
		 */

		state.finish++;
		state.jobs = 0;
		savestate();
		/*FALLTHROUGH*/

	case 5:
		/*
		 * clean up temporaries
		 */

		state.finish++;
		remtmp(1);
		/*FALLTHROUGH*/

	case 6:
		/*
		 * drop the coshell
		 */
		
		state.finish++;
		drop();
		/*FALLTHROUGH*/

	case 7:
		/*
		 * dump
		 */

		state.finish++;
		if (state.test & 0x00002000)
		{
			Vmstat_t	vs;

			vmstat(Vmheap, &vs);
			error(0, "vm region %lu segments %lu busy %lu:%lu:%lu free %lu:%lu:%lu", vs.extent, vs.n_seg, vs.n_busy, vs.s_busy, vs.m_busy, vs.n_free, vs.s_free, vs.m_free);
		}
		dump(sfstdout, error_info.trace <= -14);
		/*FALLTHROUGH*/

	case 8:
		/*
		 * final output
		 */

		state.finish++;
		if (state.errors && state.keepgoing && !n)
			n = 1;
		if (state.mam.out)
		{
			if (state.mam.regress)
				sfprintf(state.mam.out, "%sinfo finish regression\n", state.mam.label);
			else if (state.mam.dynamic || *state.mam.label)
				sfprintf(state.mam.out, "%sinfo finish %lu %d\n", state.mam.label, CURTIME, n);
		}
		for (i = 0; i < elementsof(state.io); i++)
			if (state.io[i] != sfstdin && state.io[i] != sfstdout && state.io[i] != sfstderr)
				sfclose(state.io[i]);
		if (state.errors && state.keepgoing)
			error(2, "*** %d action%s failed", state.errors, state.errors == 1 ? null : "s");
		message((-1, "%s exit", state.interrupt ? "interrupt" : n ? "error" : "normal"));
		break;

	}
	if (state.interrupt)
	{
		n = 3;
		signal(state.interrupt, SIG_DFL);
		kill(getpid(), state.interrupt);
		pause();
	}
	exit(n);
}
