/* $NetBSD: compat.c,v 1.268 2025/07/06 07:11:31 rillig Exp $ */12/*3* Copyright (c) 1988, 1989, 1990 The Regents of the University of California.4* All rights reserved.5*6* This code is derived from software contributed to Berkeley by7* Adam de Boor.8*9* Redistribution and use in source and binary forms, with or without10* modification, are permitted provided that the following conditions11* are met:12* 1. Redistributions of source code must retain the above copyright13* notice, this list of conditions and the following disclaimer.14* 2. Redistributions in binary form must reproduce the above copyright15* notice, this list of conditions and the following disclaimer in the16* documentation and/or other materials provided with the distribution.17* 3. Neither the name of the University nor the names of its contributors18* may be used to endorse or promote products derived from this software19* without specific prior written permission.20*21* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND22* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE23* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE24* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE25* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL26* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS27* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)28* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT29* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY30* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF31* SUCH DAMAGE.32*/3334/*35* Copyright (c) 1988, 1989 by Adam de Boor36* Copyright (c) 1989 by Berkeley Softworks37* All rights reserved.38*39* This code is derived from software contributed to Berkeley by40* Adam de Boor.41*42* Redistribution and use in source and binary forms, with or without43* modification, are permitted provided that the following conditions44* are met:45* 1. Redistributions of source code must retain the above copyright46* notice, this list of conditions and the following disclaimer.47* 2. Redistributions in binary form must reproduce the above copyright48* notice, this list of conditions and the following disclaimer in the49* documentation and/or other materials provided with the distribution.50* 3. All advertising materials mentioning features or use of this software51* must display the following acknowledgement:52* This product includes software developed by the University of53* California, Berkeley and its contributors.54* 4. Neither the name of the University nor the names of its contributors55* may be used to endorse or promote products derived from this software56* without specific prior written permission.57*58* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND59* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE60* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE61* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE62* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL63* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS64* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)65* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT66* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY67* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF68* SUCH DAMAGE.69*/7071/*72* This file implements the full-compatibility mode of make, which makes the73* targets without parallelism and without a custom shell.74*75* Interface:76* Compat_MakeAll Initialize this module and make the given targets.77*/7879#ifdef HAVE_CONFIG_H80# include "config.h"81#endif82#include <sys/types.h>83#include <sys/stat.h>84#include "wait.h"8586#include <errno.h>87#include <signal.h>8889#include "make.h"90#include "dir.h"91#include "job.h"92#ifdef USE_META93# include "meta.h"94#endif95#include "metachar.h"96#include "pathnames.h"9798/* "@(#)compat.c 8.2 (Berkeley) 3/19/94" */99MAKE_RCSID("$NetBSD: compat.c,v 1.268 2025/07/06 07:11:31 rillig Exp $");100101static GNode *curTarg;102static pid_t compatChild;103static int compatSigno;104105/*106* Delete the file of a failed, interrupted, or otherwise duffed target,107* unless inhibited by .PRECIOUS.108*/109static void110CompatDeleteTarget(GNode *gn)111{112if (!GNode_IsPrecious(gn) &&113(gn->type & OP_PHONY) == 0) {114const char *file = GNode_VarTarget(gn);115if (!opts.noExecute && unlink_file(file) == 0)116Error("*** %s removed", file);117}118}119120/*121* Interrupt the creation of the current target and remove it if it ain't122* precious. Then exit.123*124* If .INTERRUPT exists, its commands are run first WITH INTERRUPTS IGNORED.125*126* XXX: is .PRECIOUS supposed to inhibit .INTERRUPT? I doubt it, but I've127* left the logic alone for now. - dholland 20160826128*/129static void130CompatInterrupt(int signo)131{132if (curTarg != NULL) {133CompatDeleteTarget(curTarg);134if (signo == SIGINT && !GNode_IsPrecious(curTarg)) {135GNode *gn = Targ_FindNode(".INTERRUPT");136if (gn != NULL)137Compat_Make(gn, gn);138}139}140141if (signo == SIGQUIT)142_exit(signo);143144/*145* If there is a child running, pass the signal on.146* We will exist after it has exited.147*/148compatSigno = signo;149if (compatChild > 0) {150KILLPG(compatChild, signo);151} else {152bmake_signal(signo, SIG_DFL);153kill(myPid, signo);154}155}156157static void158DebugFailedTarget(const char *cmd, const GNode *gn)159{160const char *p = cmd;161debug_printf("\n*** Failed target: %s\n*** Failed command: ",162gn->name);163164/*165* Replace runs of whitespace with a single space, to reduce the166* amount of whitespace for multi-line command lines.167*/168while (*p != '\0') {169if (ch_isspace(*p)) {170debug_printf(" ");171cpp_skip_whitespace(&p);172} else {173debug_printf("%c", *p);174p++;175}176}177debug_printf("\n");178}179180static bool181UseShell(const char *cmd MAKE_ATTR_UNUSED)182{183#if defined(FORCE_USE_SHELL) || !defined(MAKE_NATIVE)184/*185* In a non-native build, the host environment might be weird enough186* that it's necessary to go through a shell to get the correct187* behaviour. Or perhaps the shell has been replaced with something188* that does extra logging, and that should not be bypassed.189*/190return true;191#else192/*193* Search for meta characters in the command. If there are no meta194* characters, there's no need to execute a shell to execute the195* command.196*197* Additionally variable assignments and empty commands198* go to the shell. Therefore treat '=' and ':' like shell199* meta characters as documented in make(1).200*/201202return needshell(cmd);203#endif204}205206static int207Compat_Spawn(const char **av)208{209int pid = FORK_FUNCTION();210if (pid < 0)211Fatal("Could not fork");212213if (pid == 0) {214#ifdef USE_META215if (useMeta)216meta_compat_child();217#endif218(void)execvp(av[0], (char *const *)UNCONST(av));219execDie("exec", av[0]);220}221return pid;222}223224/*225* Execute the next command for a target. If the command returns an error,226* the node's made field is set to ERROR and creation stops.227*228* Input:229* cmdp Command to execute230* gn Node from which the command came231* ln List node that contains the command232*233* Results:234* true if the command succeeded.235*/236bool237Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln)238{239char *cmdStart; /* Start of expanded command */240char *volatile bp;241bool silent; /* Don't print command */242bool doIt; /* Execute even if -n */243volatile bool errCheck; /* Check errors */244WAIT_T reason; /* Reason for child's death */245WAIT_T status; /* Description of child's death */246pid_t retstat; /* Result of wait */247const char **av; /* Arguments for the child process */248char **volatile mav; /* Copy of the argument vector for freeing */249bool useShell; /* True if command should be executed using a250* shell */251const char *cmd = cmdp;252char cmd_file[MAXPATHLEN];253size_t cmd_len;254int parseErrorsBefore;255256silent = (gn->type & OP_SILENT) != OP_NONE;257errCheck = !(gn->type & OP_IGNORE);258doIt = false;259260parseErrorsBefore = parseErrors;261cmdStart = Var_SubstInTarget(cmd, gn);262if (parseErrors != parseErrorsBefore) {263free(cmdStart);264return false;265}266267if (cmdStart[0] == '\0') {268free(cmdStart);269return true;270}271cmd = cmdStart;272LstNode_Set(ln, cmdStart);273274if (gn->type & OP_SAVE_CMDS) {275GNode *endNode = Targ_GetEndNode();276if (gn != endNode) {277/*278* Append the expanded command, to prevent the279* local variables from being interpreted in the280* scope of the .END node.281*282* A probably unintended side effect of this is that283* the expanded command will be expanded again in the284* .END node. Therefore, a literal '$' in these285* commands must be written as '$$$$' instead of the286* usual '$$'.287*/288Lst_Append(&endNode->commands, cmdStart);289goto register_command;290}291}292if (strcmp(cmdStart, "...") == 0) {293gn->type |= OP_SAVE_CMDS;294register_command:295Parse_RegisterCommand(cmdStart);296return true;297}298299for (;;) {300if (*cmd == '@')301silent = !DEBUG(LOUD);302else if (*cmd == '-')303errCheck = false;304else if (*cmd == '+')305doIt = true;306else if (!ch_isspace(*cmd))307/* Ignore whitespace for compatibility with gnu make */308break;309cmd++;310}311312if (cmd[0] == '\0')313goto register_command;314315useShell = UseShell(cmd);316317if (!silent || !GNode_ShouldExecute(gn)) {318printf("%s\n", cmd);319fflush(stdout);320}321322if (!doIt && !GNode_ShouldExecute(gn))323goto register_command;324325DEBUG1(JOB, "Execute: '%s'\n", cmd);326327cmd_len = strlen(cmd);328if (cmd_len > MAKE_CMDLEN_LIMIT)329useShell = true;330else331cmd_file[0] = '\0';332333if (useShell) {334static const char *shargv[5];335336Cmd_Argv(cmd, cmd_len, shargv, cmd_file, sizeof(cmd_file),337errCheck && shellErrFlag != NULL, DEBUG(SHELL));338av = shargv;339bp = NULL;340mav = NULL;341} else {342Words words = Str_Words(cmd, false);343mav = words.words;344bp = words.freeIt;345av = (void *)mav;346}347348#ifdef USE_META349if (useMeta)350meta_compat_start();351#endif352353Var_ReexportVars(gn);354Var_ExportStackTrace(gn->name, cmd);355356compatChild = Compat_Spawn(av);357free(mav);358free(bp);359360/* XXX: Memory management looks suspicious here. */361/* XXX: Setting a list item to NULL is unexpected. */362LstNode_SetNull(ln);363364#ifdef USE_META365if (useMeta)366meta_compat_parent(compatChild);367#endif368369/* The child is off and running. Now all we can do is wait... */370while ((retstat = wait(&reason)) != compatChild) {371if (retstat > 0)372JobReapChild(retstat, reason, false); /* not ours? */373if (retstat == -1 && errno != EINTR)374break;375}376377if (retstat < 0)378Fatal("error in wait: %d: %s", retstat, strerror(errno));379380if (WIFSTOPPED(reason)) {381status = WSTOPSIG(reason);382} else if (WIFEXITED(reason)) {383status = WEXITSTATUS(reason);384#if defined(USE_META) && defined(USE_FILEMON_ONCE)385if (useMeta)386meta_cmd_finish(NULL);387#endif388if (status != 0) {389if (DEBUG(ERROR))390DebugFailedTarget(cmd, gn);391printf("*** Error code %d", status);392}393} else {394status = WTERMSIG(reason);395printf("*** Signal %d", status);396}397398399if (!WIFEXITED(reason) || status != 0) {400if (errCheck) {401#ifdef USE_META402if (useMeta)403meta_job_error(NULL, gn, false, status);404#endif405gn->made = ERROR;406if (WIFEXITED(reason))407gn->exit_status = status;408if (opts.keepgoing) {409/*410* Abort the current target,411* but let others continue.412*/413printf(" (continuing)\n");414} else {415printf("\n");416}417if (deleteOnError)418CompatDeleteTarget(gn);419} else {420/*421* Continue executing commands for this target.422* If we return 0, this will happen...423*/424printf(" (ignored)\n");425status = 0;426}427fflush(stdout);428}429430free(cmdStart);431if (cmd_file[0] != '\0')432unlink(cmd_file);433compatChild = 0;434if (compatSigno != 0) {435bmake_signal(compatSigno, SIG_DFL);436kill(myPid, compatSigno);437}438439return status == 0;440}441442static void443RunCommands(GNode *gn)444{445StringListNode *ln;446447for (ln = gn->commands.first; ln != NULL; ln = ln->next) {448const char *cmd = ln->datum;449if (!Compat_RunCommand(cmd, gn, ln))450break;451}452}453454static void455MakeInRandomOrder(GNode **gnodes, GNode **end, GNode *pgn)456{457GNode **it;458size_t r;459460for (r = (size_t)(end - gnodes); r >= 2; r--) {461/* Biased, but irrelevant in practice. */462size_t i = (size_t)random() % r;463GNode *t = gnodes[r - 1];464gnodes[r - 1] = gnodes[i];465gnodes[i] = t;466}467468for (it = gnodes; it != end; it++)469Compat_Make(*it, pgn);470}471472static void473MakeWaitGroupsInRandomOrder(GNodeList *gnodes, GNode *pgn)474{475Vector vec;476GNodeListNode *ln;477GNode **nodes;478size_t i, n, start;479480Vector_Init(&vec, sizeof(GNode *));481for (ln = gnodes->first; ln != NULL; ln = ln->next)482*(GNode **)Vector_Push(&vec) = ln->datum;483nodes = vec.items;484n = vec.len;485486start = 0;487for (i = 0; i < n; i++) {488if (nodes[i]->type & OP_WAIT) {489MakeInRandomOrder(nodes + start, nodes + i, pgn);490Compat_Make(nodes[i], pgn);491start = i + 1;492}493}494MakeInRandomOrder(nodes + start, nodes + i, pgn);495496Vector_Done(&vec);497}498499static void500MakeNodes(GNodeList *gnodes, GNode *pgn)501{502GNodeListNode *ln;503504if (Lst_IsEmpty(gnodes))505return;506if (opts.randomizeTargets) {507MakeWaitGroupsInRandomOrder(gnodes, pgn);508return;509}510511for (ln = gnodes->first; ln != NULL; ln = ln->next) {512GNode *cgn = ln->datum;513Compat_Make(cgn, pgn);514}515}516517static bool518MakeUnmade(GNode *gn, GNode *pgn)519{520521assert(gn->made == UNMADE);522523/*524* First mark ourselves to be made, then apply whatever transformations525* the suffix module thinks are necessary. Once that's done, we can526* descend and make all our children. If any of them has an error527* but the -k flag was given, our 'make' field will be set to false528* again. This is our signal to not attempt to do anything but abort529* our parent as well.530*/531gn->flags.remake = true;532gn->made = BEINGMADE;533534if (!(gn->type & OP_MADE))535Suff_FindDeps(gn);536537MakeNodes(&gn->children, gn);538539if (!gn->flags.remake) {540gn->made = ABORTED;541pgn->flags.remake = false;542return false;543}544545if (Lst_FindDatum(&gn->implicitParents, pgn) != NULL)546Var_Set(pgn, IMPSRC, GNode_VarTarget(gn));547548/*549* All the children were made ok. Now youngestChild->mtime contains the550* modification time of the newest child, we need to find out if we551* exist and when we were modified last. The criteria for datedness552* are defined by GNode_IsOODate.553*/554DEBUG1(MAKE, "Examining %s...", gn->name);555if (!GNode_IsOODate(gn)) {556gn->made = UPTODATE;557DEBUG0(MAKE, "up-to-date.\n");558return false;559}560561/*562* If the user is just seeing if something is out-of-date, exit now563* to tell him/her "yes".564*/565DEBUG0(MAKE, "out-of-date.\n");566if (opts.query && gn != Targ_GetEndNode())567exit(1);568569/*570* We need to be re-made.571* Ensure that $? (.OODATE) and $> (.ALLSRC) are both set.572*/573GNode_SetLocalVars(gn);574575/*576* Alter our type to tell if errors should be ignored or things577* should not be printed so Compat_RunCommand knows what to do.578*/579if (opts.ignoreErrors)580gn->type |= OP_IGNORE;581if (opts.silent)582gn->type |= OP_SILENT;583584if (Job_CheckCommands(gn, Fatal)) {585if (!opts.touch || (gn->type & OP_MAKE)) {586curTarg = gn;587#ifdef USE_META588if (useMeta && GNode_ShouldExecute(gn))589meta_job_start(NULL, gn);590#endif591RunCommands(gn);592curTarg = NULL;593} else {594Job_Touch(gn, (gn->type & OP_SILENT) != OP_NONE);595}596} else {597gn->made = ERROR;598}599#ifdef USE_META600if (useMeta && GNode_ShouldExecute(gn)) {601if (meta_job_finish(NULL) != 0)602gn->made = ERROR;603}604#endif605606if (gn->made != ERROR) {607/*608* If the node was made successfully, mark it so, update609* its modification time and timestamp all its parents.610* This is to keep its state from affecting that of its parent.611*/612gn->made = MADE;613if (Make_Recheck(gn) == 0)614pgn->flags.force = true;615if (!(gn->type & OP_EXEC)) {616pgn->flags.childMade = true;617GNode_UpdateYoungestChild(pgn, gn);618}619} else if (opts.keepgoing) {620pgn->flags.remake = false;621} else {622PrintOnError(gn, "\nStop.\n");623exit(1);624}625return true;626}627628static void629MakeOther(GNode *gn, GNode *pgn)630{631632if (Lst_FindDatum(&gn->implicitParents, pgn) != NULL) {633const char *target = GNode_VarTarget(gn);634Var_Set(pgn, IMPSRC, target != NULL ? target : "");635}636637switch (gn->made) {638case BEINGMADE:639Error("Graph cycles through %s", gn->name);640gn->made = ERROR;641pgn->flags.remake = false;642break;643case MADE:644if (!(gn->type & OP_EXEC)) {645pgn->flags.childMade = true;646GNode_UpdateYoungestChild(pgn, gn);647}648break;649case UPTODATE:650if (!(gn->type & OP_EXEC))651GNode_UpdateYoungestChild(pgn, gn);652break;653default:654break;655}656}657658/*659* Make a target.660*661* If an error is detected and not being ignored, the process exits.662*663* Input:664* gn The node to make665* pgn Parent to abort if necessary666*667* Output:668* gn->made669* UPTODATE gn was already up-to-date.670* MADE gn was recreated successfully.671* ERROR An error occurred while gn was being created,672* either due to missing commands or in -k mode.673* ABORTED gn was not remade because one of its674* dependencies could not be made due to errors.675*/676void677Compat_Make(GNode *gn, GNode *pgn)678{679if (shellName == NULL) /* we came here from jobs */680Shell_Init();681682if (gn->made == UNMADE && (gn == pgn || !(pgn->type & OP_MADE))) {683if (!MakeUnmade(gn, pgn))684goto cohorts;685686/* XXX: Replace with GNode_IsError(gn) */687} else if (gn->made == ERROR) {688/*689* Already had an error when making this.690* Tell the parent to abort.691*/692pgn->flags.remake = false;693} else {694MakeOther(gn, pgn);695}696697cohorts:698MakeNodes(&gn->cohorts, pgn);699}700701static void702MakeBeginNode(void)703{704GNode *gn = Targ_FindNode(".BEGIN");705if (gn == NULL)706return;707708Compat_Make(gn, gn);709if (GNode_IsError(gn)) {710PrintOnError(gn, "\nStop.\n");711exit(1);712}713}714715static void716InitSignals(void)717{718if (bmake_signal(SIGINT, SIG_IGN) != SIG_IGN)719bmake_signal(SIGINT, CompatInterrupt);720if (bmake_signal(SIGTERM, SIG_IGN) != SIG_IGN)721bmake_signal(SIGTERM, CompatInterrupt);722if (bmake_signal(SIGHUP, SIG_IGN) != SIG_IGN)723bmake_signal(SIGHUP, CompatInterrupt);724if (bmake_signal(SIGQUIT, SIG_IGN) != SIG_IGN)725bmake_signal(SIGQUIT, CompatInterrupt);726}727728void729Compat_MakeAll(GNodeList *targets)730{731GNode *errorNode = NULL;732733if (shellName == NULL)734Shell_Init();735736InitSignals();737738/*739* Create the .END node now, to keep the (debug) output of the740* counter.mk test the same as before 2020-09-23. This741* implementation detail probably doesn't matter though.742*/743(void)Targ_GetEndNode();744745if (!opts.query)746MakeBeginNode();747748/*749* Expand .USE nodes right now, because they can modify the structure750* of the tree.751*/752Make_ExpandUse(targets);753754while (!Lst_IsEmpty(targets)) {755GNode *gn = Lst_Dequeue(targets);756Compat_Make(gn, gn);757758if (gn->made == UPTODATE) {759printf("`%s' is up to date.\n", gn->name);760} else if (gn->made == ABORTED) {761printf("`%s' not remade because of errors.\n",762gn->name);763}764if (GNode_IsError(gn) && errorNode == NULL)765errorNode = gn;766}767768if (errorNode == NULL) {769GNode *endNode = Targ_GetEndNode();770Compat_Make(endNode, endNode);771if (GNode_IsError(endNode))772errorNode = endNode;773}774775if (errorNode != NULL) {776if (DEBUG(GRAPH2))777Targ_PrintGraph(2);778else if (DEBUG(GRAPH3))779Targ_PrintGraph(3);780PrintOnError(errorNode, "\nStop.\n");781exit(1);782}783}784785786