/***********************************************************************1* *2* This software is part of the ast package *3* Copyright (c) 1984-2012 AT&T Intellectual Property *4* and is licensed under the *5* Eclipse Public License, Version 1.0 *6* by AT&T Intellectual Property *7* *8* A copy of the License is available at *9* http://www.eclipse.org/org/documents/epl-v10.html *10* (with md5 checksum b35adb5213ca9657e911e9befb180842) *11* *12* Information and Software Systems Research *13* AT&T Research *14* Florham Park NJ *15* *16* Glenn Fowler <[email protected]> *17* *18***********************************************************************/19#pragma prototyped20/*21* Glenn Fowler22* AT&T Research23*24* make - maintain and update programs25*26* history27*28* 1970's pre-make (research)29* .30* .31* 1976 make S. I. Feldman32* . .33* . .34* . .35* . .36* 1978 augmented-make . E. G. Bradford37* . . . .38* . . . .39* . . . .40* . . . .41* 1979 build . . . V. B. Erickson, J. F. Pellegrin42* . . . .43* . . . .44* 1984 . . V8-make S. I. Feldman45* . . . .46* . . . .47* . . . .48* . . . .49* . . . .50* . . . .51* . . . .52* 1985 . nmake . G. S. Fowler53* . . . .54* . . . .55* . . . .56* 1986 LC-nmake . . J. E. Shopiro, M. S. Freeland57* . . . .58* . . . mk A. G. Hume59* . . . . .60* 1987 . nmake . . G. S. Fowler61* . . . .62* (1.4) . . .63* . . . .64* . . . .65* . . . .66* 1988 nmake . G. S. Fowler67* . .68* (2.0) .69* . .70* 1990 (2.1) .71* . .72* 1991 (2.2) .73* . .74* 1992 (2.3) .75* . .76* 1993 (3.0) .77* . .78* 1994 (3.1) .79* . .80* 1995 (3.2) . AT&T Bell Labs81* . .82* 1996 (3.3) . AT&T Research (Lucent split)83* . .84* 1997 (3.4) . AT&T Research85* . .86* 1998 (3.5) . AT&T Research87* . .88* 1999 (3.6) . AT&T Research89* . .90* 2000 (4.0) . AT&T Research91* . .92* 2001 (4.1) . AT&T Research93* 2002 (4.2) . AT&T Research94* 2003 (4.3) . AT&T Research95* 2004 (4.4) . AT&T Research96* 2005 (5.0) . AT&T Research97* 2006 (5.1) . AT&T Research98* 2007 (5.2) . AT&T Research99* 2008 (5.3) . AT&T Research100* 2009 (5.4) . AT&T Research101* 2010 (5.5) . AT&T Research102* 2011 (5.6) . AT&T Research103* 2012 (5.7) . AT&T Research104*105* command line arguments are of three types106*107* make [ [ option ] [ script ] [ target ] ... ]108*109* options are described in options.h110*111* debug trace levels are controlled by error_info.trace112* debug level n enables tracing for all levels less than or equal to n113* levels 4 and higher are conditionally compiled with DEBUG!=0114* if debug level < 20 then all levels are disabled during early bootstrap115* if debug level = 1 then all levels are disabled until after .INIT is made116*117* level trace118* 0 no trace119* 1 basic make trace120* 2 major actions121* 3 option settings, make object files122* 4 state variables123* 5 directory and archive scan, some OPT_explain'ations124* 6 coshell commands and messages125* 7 makefile input lines126* 8 lexical analyzer states127* 9 metarules applied128* 10 variable expansions129* 11 bindfile() trace130* 12 files added to hash131* 13 new atoms132* 14 hash table usage133* ...134* 20 bootstrap trace135*136* state.questionable registry (looks like it should go but not sure)137*138* 0x00000001 *temporary*139* 0x00000002 *temporary*140* 0x00000004 *temporary* disable bind_p "- ..." and "... ..." virtual logic141* 0x00000008 1996-10-11 meta rhs prefix dir check even if rhs has prefix142* 0x00000010 1994-01-01 old out of date if prereq in higher view143* 0x00000020 1994-01-01 old scan advance logic144* 0x00000040 2001-10-20 foiled alias still allows bind (!1994-08-11)145* 0x00000080 1994-08-11 don't catrule() ../.. dir prefixes in bindfile()146* 0x00000100 1994-10-01 metaclose() recursion even if rule.action!=0147* 0x00000200 1994-11-11 disable generate() % transformation check148* 0x00000400 1995-01-19 forceread even if global149* 0x00000800 1995-07-17 D_global if not P_target in bindfile()150* 0x00001000 1995-07-17 less agressive aliasing in bindfile()151* 0x00002000 1995-07-17 less agressive putbound() in bindalias()152* 0x00004000 1995-11-11 P_target && D_dynamic does not imply generated153* 0x00008000 1996-02-29 :P=D: returns at most one dir per item154* 0x00010000 1996-10-11 don't alias check path suffixes in bindfile()155* 0x00020000 1998-11-11 don't force require_p make() recheck156* 0x00040000 1999-09-07 generate() intermediates only if (sep & LT)157* 0x00080000 1999-11-19 disable touch steady state loop158* 0x00100000 2000-02-14 apply metarule even if dir on unbound lhs159* 0x00200000 2000-09-08 0x00100000 implied if !P_implicit160* 0x00400000 2001-02-14 allow P_archive to break job deadlock161* 0x00800000 2001-10-05 disable early r->status=FAILED when errors!=0162* 0x01000000 2002-10-02 $(>) doesn't check min(rule.time,state.time)163* 0x02000000 2002-12-04 don't inhibit .UNBIND of M_bind rules164* 0x04000000 2004-01-20 don't include triggered time==0 targets in :T=F:165* 0x08000000 2004-10-01 metaget does not assume % target for ... : % .NULL166* 0x10000000 2007-01-08 :P=D: alias check167* 0x20000000 2007-06-15 disable :W: . alias check168* 0x40000000 2007-06-20 don't include intermediate makefile dirs in :W:169* 0x80000000 2007-08-28 don't force reassoc bindalias()170*171* state.test registry (conditionally compiled with DEBUG!=0)172*173* 0x00000001 *temporary*174* 0x00000002 *temporary*175* 0x00000004 *temporary*176* 0x00000008 bindfile() directory prune trace177* 0x00000010 expand() trace178* 0x00000020 force external archive table of contents generation179* 0x00000040 bindalias() trace180* 0x00000080 scan state trace181* 0x00000100 statetime() trace182* 0x00000200 staterule() view trace183* 0x00000400 more staterule() view trace184* 0x00000800 mergestate() trace185* 0x00001000 job status trace186* 0x00002000 dump Vmheap stats on exit187* 0x00004000 dump atom address with name188* 0x00008000 force state file garbage collection189* 0x00010000 alarm status trace190* 0x00020000 close internal.openfd before job exec191* 0x00040000 set failed state|metarule event to staterule event192* 0x00080000 replace ' ' with '?' instead of FILE_SPACE193* 0x00100000 scan action trace194*/195196#include "make.h"197#include "options.h"198199#include <sig.h>200#include <stak.h>201#include <vecargs.h>202#include <vmalloc.h>203204#define settypes(c,t) for(s=c;*s;settype(*s++,t))205206#ifndef PATHCHECK207#define PATHCHECK IDNAME208#endif209210/*211* global initialization -- external engine names are defined in initrule()212*/213214Internal_t internal; /* internal rules and vars */215State_t state; /* program state */216Tables_t table; /* hash table info */217char null[] = ""; /* null string */218char tmpname[MAXNAME];/* temporary name buffer */219short ctypes[UCHAR_MAX+1];/* istype() character types */220221/*222* user error message intercept223*/224225static int226intercept(Sfio_t* sp, int level, int flags)227{228register Rule_t* r;229char* m;230char* s;231char* t;232char* e;233int n;234int i;235Sfio_t* tmp;236237NoP(sp);238NoP(flags);239if ((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)240{241/*242* make the error trap243*/244245state.hold = m;246while (*m && *m != ':')247m++;248while (isspace(*++m));249n -= m - state.hold;250tmp = sfstropen();251sfprintf(tmp, "%d %-.*s", level, n, m);252s = sfstruse(tmp);253254/*255* return [ level | - ] [ message ]256* level is new level or - to retain old257* omitted message means it has been printed258*/259260if (t = call(r, s))261{262i = strtol(t, &e, 0);263if (e > t)264{265t = e;266level = i;267}268else if (*t == '-')269t++;270while (isspace(*t))271t++;272}273if (!t || !*t)274{275level |= ERROR_OUTPUT;276message((-1, "suppressed %s message: `%-.*s'", level == 1 ? "warning" : "error", n, m));277}278sfstrclose(tmp);279state.hold = 0;280}281return level;282}283284/*285* make entry point286*/287288int289main(int argc, char** argv)290{291register char* s;292register Rule_t* r;293register List_t* p;294int i;295int args;296int trace;297char* t;298char* buf;299char* tok;300Var_t* v;301Stat_t st;302Stat_t ds;303Sfio_t* tmp;304305/*306* initialize dynamic globals307*/308309version = strdup(fmtident(version));310setlocale(LC_ALL, "");311error_info.id = idname;312error_info.version = version;313error_info.exit = finish;314error_info.auxilliary = intercept;315if (pathcheck(PATHCHECK, error_info.id, NiL))316return 1;317error(-99, "startup");318settypes("*?[]", C_MATCH);319settypes("+-|=", C_OPTVAL);320settypes(" \t\n", C_SEP);321settype(0, C_SEP);322settypes(" \t\v\n:+&=;\"\\", C_TERMINAL);323settype(0, C_TERMINAL);324settypes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_", C_ID1|C_ID2|C_VARIABLE1|C_VARIABLE2);325settypes(".", C_VARIABLE1|C_VARIABLE2);326settypes("0123456789", C_ID2|C_VARIABLE2);327328/*329* close garbage fd's -- we'll be tidy from this point on330* 3 may be /dev/tty on some systems331* 0..9 for user redirection in shell332* 10..19 left open by bugs in some shells333* error_info.fd interited from parent334* any close-on-exec fd's must have been done on our behalf335*/336337i = 3;338if (isatty(i))339i++;340for (; i < 20; i++)341if (i != error_info.fd && !fcntl(i, F_GETFD, 0))342close(i);343344/*345* allocate the very temporary buffer streams346*/347348internal.met = sfstropen();349internal.nam = sfstropen();350internal.tmp = sfstropen();351internal.val = sfstropen();352internal.wrk = sfstropen();353tmp = sfstropen();354sfstrrsrv(tmp, 2 * MAXNAME);355356/*357* initialize the code and hash tables358*/359360initcode();361inithash();362363/*364* set the default state365*/366367state.alias = 1;368state.exec = 1;369state.global = 1;370state.init = 1;371#if DEBUG372state.intermediate = 1;373#endif374state.io[0] = sfstdin;375state.io[1] = sfstdout;376state.io[2] = sfstderr;377state.jobs = 1;378state.pid = getpid();379state.readstate = MAXVIEW;380state.scan = 1;381state.start = CURTIME;382state.stateview = -1;383state.tabstops = 8;384state.targetview = -1;385#if BINDINDEX386state.view[0].path = makerule(".");387#else388state.view[0].path = ".";389#endif390state.view[0].pathlen = 1;391state.writeobject = state.writestate = "-";392393/*394* pwd initialization395*396* for project management, if . is group|other writeable397* then change umask() for similar write protections398*/399400buf = sfstrbase(tmp);401internal.pwd = (s = getcwd(buf, MAXNAME)) ? strdup(s) : strdup(".");402internal.pwdlen = strlen(internal.pwd);403if (stat(".", &st))404error(3, "cannot stat .");405if (S_ISDIR(st.st_mode) && (st.st_mode & (S_IWGRP|S_IWOTH)))406umask(umask(0) & ~(st.st_mode & (S_IWGRP|S_IWOTH)));407408/*409* set some variable default values410*/411412hashclear(table.var, HASH_ALLOCATE);413setvar(external.make, argv[0], V_import);414t = "lib/make";415setvar(external.lib, strdup((s = pathpath(t, argv[0], PATH_EXECUTE, buf, SF_BUFSIZE)) ? s : t), V_import);416setvar(external.pwd, internal.pwd, V_import);417setvar(external.version, version, V_import);418hashset(table.var, HASH_ALLOCATE);419420/*421* read the environment422*/423424readenv();425if (v = getvar(external.nproc))426state.jobs = (int)strtol(v->value, NiL, 0);427if ((v = getvar(external.pwd)) && !streq(v->value, internal.pwd))428{429if (!stat(v->value, &st) && !stat(internal.pwd, &ds) && st.st_ino == ds.st_ino && st.st_dev == ds.st_dev)430{431free(internal.pwd);432internal.pwd = strdup(v->value);433internal.pwdlen = strlen(v->value);434}435else436{437v->property &= ~V_import;438v->property |= V_free;439v->value = strdup(internal.pwd);440}441}442443/*444* initialize the internal rule pointers445*/446447initrule();448449/*450* read the static initialization script451*/452453sfputr(tmp, initstatic, -1);454parse(NiL, sfstruse(tmp), "initstatic", NiL);455456/*457* check and read the args file458*/459460if (s = colonlist(tmp, external.args, 1, ' '))461{462i = fs3d(0);463tok = tokopen(s, 1);464while (s = tokread(tok))465if (vecargs(vecfile(s), &argc, &argv) >= 0)466break;467else if (errno != ENOENT)468error(1, "cannot read args file %s", s);469tokclose(tok);470fs3d(i);471}472state.argf = newof(0, int, argc, 0);473474/*475* set the command line options476* read the command line assignments477* mark the command line scripts and targets478*/479480state.init = 0;481state.readonly = 1;482state.argv = argv;483state.argc = argc;484if ((args = scanargs(state.argc, state.argv, state.argf)) < 0)485return 1;486state.readonly = 0;487state.init = 1;488if (state.base)489state.readstate = 0;490if (state.compileonly)491{492state.forceread = 1;493state.virtualdot = 0;494}495496/*497* tone down the bootstrap noise498*/499500if ((trace = error_info.trace) == -1)501error_info.trace = 0;502503/*504* check explicit environment overrides505*/506507if (s = colonlist(tmp, external.import, 1, ' '))508{509tok = tokopen(s, 1);510while (s = tokread(tok))511{512if (i = *s == '!')513s++;514if (v = getvar(s))515{516if (i)517v->property &= ~V_import;518else519v->property |= V_readonly;520}521}522tokclose(tok);523}524525/*526* set up the traps527*/528529inittrap();530531/*532* announce the version533*/534535if (error_info.trace < 0)536{537errno = 0;538error(error_info.trace, "%s [%d %s]", version, state.pid, timestr(state.start));539}540541/*542* initialize the views543*/544545state.global = 0;546state.user = 1;547initview();548549/*550* check for mam551*/552553if (state.mam.out)554{555if (!state.mam.statix || *state.mam.label)556error_info.write = mamerror;557if (state.mam.regress || state.regress)558{559sfprintf(state.mam.out, "%sinfo mam %s %05d\n", state.mam.label, state.mam.type, state.mam.parent);560if (state.mam.regress)561sfprintf(state.mam.out, "%sinfo start regression\n", state.mam.label);562}563else564{565sfprintf(state.mam.out, "%sinfo mam %s %05d 1994-07-17 %s\n", state.mam.label, state.mam.type, state.mam.parent, version);566if (!state.mam.statix || *state.mam.label)567{568sfprintf(state.mam.out, "%sinfo start %lu\n", state.mam.label, CURTIME);569if (!state.mam.root || streq(state.mam.root, internal.pwd))570sfprintf(state.mam.out, "%sinfo pwd %s\n", state.mam.label, internal.pwd);571else572sfprintf(state.mam.out, "%sinfo pwd %s %s\n", state.mam.label, state.mam.root, mamname(makerule(internal.pwd)));573buf = sfstrbase(tmp);574if (state.fsview && !mount(NiL, buf, FS3D_GET|FS3D_ALL|FS3D_SIZE(sfstrsize(tmp)), NiL))575sfprintf(state.mam.out, "%sinfo view %s\n", state.mam.label, buf);576}577}578}579580/*581* read the dynamic initialization script582*/583584if ((i = error_info.trace) > -20)585error_info.trace = 0;586sfputr(tmp, initdynamic, -1);587parse(NiL, sfstruse(tmp), "initdynamic", NiL);588error_info.trace = i;589state.user = 0;590state.init = 0;591592/*593* read the explicit makefiles594* readfile() handles the base and global rules595*596* NOTE: internal.tmplist is used to handle the effects of597* load() on internal list pointers598*/599600compref(NiL, 0);601if (p = internal.makefiles->prereqs)602{603p = internal.tmplist->prereqs = listcopy(p);604for (; p; p = p->next)605readfile(p->rule->name, COMP_FILE, NiL);606freelist(internal.tmplist->prereqs);607internal.tmplist->prereqs = 0;608}609610/*611* if no explicit makefiles then try external.{convert,files}612*/613614if (!state.makefile)615{616int sep;617Sfio_t* exp;618Sfio_t* imp;619620exp = 0;621imp = sfstropen();622sep = 0;623s = 0;624if (*(t = getval(external.convert, VAL_PRIMARY)))625{626sfputr(tmp, t, 0);627sfstrrsrv(tmp, MAXNAME);628tok = tokopen(sfstrbase(tmp), 0);629while (s = tokread(tok))630{631if (!exp)632exp = sfstropen();633if (t = colonlist(exp, s, 0, ' '))634{635t = tokopen(t, 0);636while (s = tokread(t))637{638if (readfile(s, COMP_INCLUDE|COMP_DONTCARE, NiL))639break;640if (sep)641sfputc(imp, ',');642else643sep = 1;644sfputr(imp, s, -1);645}646tokclose(t);647if (s)648break;649}650if (!(s = tokread(tok)))651break;652}653tokclose(tok);654sfstrseek(tmp, 0, SEEK_SET);655}656if (!s && (s = colonlist(tmp, external.files, 1, ' ')))657{658tok = tokopen(s, 1);659while (s = tokread(tok))660{661if (readfile(s, COMP_INCLUDE|COMP_DONTCARE, NiL))662break;663if (sep)664sfputc(imp, ',');665else666sep = 1;667sfputr(imp, s, -1);668}669tokclose(tok);670}671if (!s)672{673/*674* this readfile() pulls in the default base rules675* that might resolve any delayed self-documenting676* options in optcheck()677*/678679if (readfile("-", COMP_FILE, NiL))680optcheck(1);681if (*(s = sfstruse(imp)))682error(state.errorid ? 1 : 3, "a makefile must be specified when %s omitted", s);683else684error(state.errorid ? 1 : 3, "a makefile must be specified");685}686sfstrclose(imp);687if (exp)688sfstrclose(exp);689}690691/*692* validate external command line options693*/694695optcheck(1);696697/*698* check for listing of variable and rule definitions699*/700701if (state.list)702{703dump(sfstdout, 0);704return 0;705}706707/*708* check if makefiles to be compiled709*/710711if (state.compile && !state.virtualdot && state.writeobject)712{713/*714* make the compinit trap715*/716717if (r = getrule(external.compinit))718{719state.reading = 1;720maketop(r, P_dontcare|P_foreground, NiL);721state.reading = 0;722}723if (state.exec && state.objectfile)724{725message((-2, "compiling makefile input into %s", state.objectfile));726compile(state.objectfile, NiL);727}728729/*730* make the compdone trap731*/732733if (r = getrule(external.compdone))734{735state.reading = 1;736maketop(r, P_dontcare|P_foreground, NiL);737state.reading = 0;738}739}740741/*742* makefile read cleanup743*/744745if (state.compileonly)746return 0;747compref(NiL, 0);748sfstrclose(tmp);749state.compile = COMPILED;750if (state.believe)751{752if (!state.maxview)753state.believe = 0;754else if (state.fsview)755error(3, "%s: option currently works in 2d only", optflag(OPT_believe)->name);756}757758/*759* read the state file760*/761762readstate();763764/*765* place the command line targets in internal.args766*/767768if (internal.main->dynamic & D_dynamic)769dynamic(internal.main);770internal.args->prereqs = p = 0;771for (i = args; i < state.argc; i++)772if (state.argf[i] & ARG_TARGET)773{774List_t* q;775776q = cons(makerule(state.argv[i]), NiL);777if (p)778p = p->next = q;779else780internal.args->prereqs = p = q;781}782783/*784* the engine bootstrap is complete -- start user activities785*/786787state.user = 1;788789/*790* make the makeinit trap791*/792793if (r = getrule(external.makeinit))794maketop(r, P_dontcare|P_foreground, NiL);795796/*797* read the command line scripts798*/799800for (i = args; i < state.argc; i++)801if (state.argf[i] & ARG_SCRIPT)802{803state.reading = 1;804parse(NiL, state.argv[i], "command line script", NiL);805state.reading = 0;806}807808/*809* freeze the parameter files and candidate state variables810*/811812state.user = 2;813candidates();814815/*816* make the init trap817*/818819if (r = getrule(external.init))820maketop(r, P_dontcare|P_foreground, NiL);821822/*823* internal.args default to internal.main824*/825826if (!internal.args->prereqs && internal.main->prereqs)827internal.args->prereqs = listcopy(internal.main->prereqs);828829/*830* turn up the volume again831*/832833if (!error_info.trace)834error_info.trace = trace;835836/*837* make the prerequisites of internal.args838*/839840if (internal.args->prereqs)841while (internal.args->prereqs)842{843/*844* we explicitly allow internal.args modifications845*/846847r = internal.args->prereqs->rule;848internal.making->prereqs = internal.args->prereqs;849internal.args->prereqs = internal.args->prereqs->next;850internal.making->prereqs->next = 0;851maketop(r, 0, NiL);852}853else if (state.makefile)854error(3, "%s: a main target must be specified", state.makefile);855856/*857* finish up858*/859860finish(0);861return 0;862}863864/*865* clean up and exit866*/867868void869finish(int n)870{871Rule_t* r;872int i;873874/*875* old error intercept876*/877878if (!state.hold && (r = internal.error) && (r->property & (P_target|P_functional)) == P_target)879{880state.hold = null;881if (n && error_info.errors && !state.compileonly && !state.interrupt)882{883if (r->status == NOTYET)884maketop(r, P_dontcare|P_foreground, NiL);885state.hold = 0;886if (r->status == EXISTS)887{888r->status = NOTYET;889return;890}891}892}893894/*895* children exit without cleanup896*/897898if (getpid() != state.pid)899_exit(n);900unparse(0);901switch (state.finish)902{903904case 0:905/*906* disable listing and wait for any jobs to finish907*/908909state.finish++;910alarm(0);911state.list = 0;912message((-1, "%s cleanup", state.interrupt ? "interrupt" : n > 0 ? "error" : "normal"));913complete(NiL, NiL, NiL, 0);914/*FALLTHROUGH*/915916case 1:917/*918* make the done trap919*/920921state.finish++;922if (!state.compileonly && (r = getrule(external.done)))923maketop(r, P_dontcare, NiL);924/*FALLTHROUGH*/925926case 2:927/*928* make the makedone trap929*/930931state.finish++;932if (!state.compileonly && (r = getrule(external.makedone)))933maketop(r, P_dontcare, NiL);934/*FALLTHROUGH*/935936case 3:937/*938* wait for any job(s) to finish939*/940941state.finish++;942complete(NiL, NiL, NiL, 0);943/*FALLTHROUGH*/944945case 4:946/*947* put all jobs in foreground and save state948*/949950state.finish++;951state.jobs = 0;952savestate();953/*FALLTHROUGH*/954955case 5:956/*957* clean up temporaries958*/959960state.finish++;961remtmp(1);962/*FALLTHROUGH*/963964case 6:965/*966* drop the coshell967*/968969state.finish++;970drop();971/*FALLTHROUGH*/972973case 7:974/*975* dump976*/977978state.finish++;979if (state.test & 0x00002000)980{981Vmstat_t vs;982983vmstat(Vmheap, &vs);984error(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);985}986dump(sfstdout, error_info.trace <= -14);987/*FALLTHROUGH*/988989case 8:990/*991* final output992*/993994state.finish++;995if (state.errors && state.keepgoing && !n)996n = 1;997if (state.mam.out)998{999if (state.mam.regress)1000sfprintf(state.mam.out, "%sinfo finish regression\n", state.mam.label);1001else if (state.mam.dynamic || *state.mam.label)1002sfprintf(state.mam.out, "%sinfo finish %lu %d\n", state.mam.label, CURTIME, n);1003}1004for (i = 0; i < elementsof(state.io); i++)1005if (state.io[i] != sfstdin && state.io[i] != sfstdout && state.io[i] != sfstderr)1006sfclose(state.io[i]);1007if (state.errors && state.keepgoing)1008error(2, "*** %d action%s failed", state.errors, state.errors == 1 ? null : "s");1009message((-1, "%s exit", state.interrupt ? "interrupt" : n ? "error" : "normal"));1010break;10111012}1013if (state.interrupt)1014{1015n = 3;1016signal(state.interrupt, SIG_DFL);1017kill(getpid(), state.interrupt);1018pause();1019}1020exit(n);1021}102210231024