/*-
 * main.c --
 *	The main file for this entire program. Exit routines etc
 *	reside here.
 *
 * Copyright (c) 1988, 1989 by the Regents of the University of California
 * Copyright (c) 1988, 1989 by Adam de Boor
 * Copyright (c) 1989 by Berkeley Softworks
 *
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any non-commercial purpose
 * and without fee is hereby granted, provided that the above copyright
 * notice appears in all copies.  The University of California,
 * Berkeley Softworks and Adam de Boor make no representations about
 * the suitability of this software for any purpose.  It is provided
 * "as is" without express or implied warranty.
 *
 * Utility functions defined in this file:
 *	Main_ParseArgLine   	Takes a line of arguments, breaks them and
 *	    	  	    	treats them as if they were given when first
 *	    	  	    	invoked. Used by the parse module to implement
 *	    	  	    	the .MFLAGS target.
 *
 *	Main_Unlock		Remove the lock file, if any.  Should be called
 *				prior to termination other than by exit(),
 *				e.g., when killing ourselves with a signal.
 *
 *	Main_Access		Set access level of make process.
 *
 *	Error	  	    	Print a tagged error message. The global
 *	    	  	    	MAKE variable must have been defined. This
 *	    	  	    	takes a format string and two optional
 *	    	  	    	arguments for it.
 *
 *	Fatal	  	    	Print an error message and exit. Also takes
 *	    	  	    	a format string and two arguments.
 *
 *	Punt	  	    	Aborts all jobs and exits with a message. Also
 *	    	  	    	takes a format string and two arguments.
 *
 *	Finish	  	    	Finish things up by printing the number of
 *	    	  	    	errors which occured, as passed to it, and
 *	    	  	    	exiting.
 */
#ifndef lint
static char     *rcsid = "$Id: main.c,v 1.53 1999/06/28 22:20:44 stolcke Exp $ ICSI (Berkeley)";
#endif /* not lint */

#include    <stdio.h>
#include    <string.h>
#include    <sys/types.h>
#include    <signal.h>
#include    <sys/stat.h>
#include    <fcntl.h>
#include    <sys/errno.h>
#include    <ctype.h>
#include    <sys/param.h>

extern int errno;
extern char *getenv();

#include    "make.h"
#include    "../patchlevel.h"

#ifndef EXITRET
#ifdef __STDC__
#define EXITRET void
#else
#define EXITRET int
#endif
#endif

EXITRET exit();		/* use our own exit() for cleanup handling */

#ifndef DEFMAXLOCAL
#define DEFMAXLOCAL DEFMAXJOBS
#endif  /* DEFMAXLOCAL */

#define MAKEFLAGS 	".MAKEFLAGS"
#define PMAKE_ENV	"PMAKE"
#define MAKE_ENV  	"MAKEFLAGS"

char	 	  	*progName;  	/* Our invocation name */
static Boolean	  	lockSet;    	/* TRUE if we set the lock file */
Lst			create;	    	/* Targets to be made */
time_t			now;	    	/* Time at start of make */
GNode			*DEFAULT;   	/* .DEFAULT node */
Boolean	    	    	allPrecious;	/* .PRECIOUS given on line by itself */

int			printGraph;	/* -p flag */
static Boolean          noBuiltins;	/* -r flag */
static Boolean	  	noLocking;      /* -l flag */
static Lst  	    	makefiles;  	/* List of makefiles to read (in
					 * order) */
int		    	maxJobs;	/* -J argument */
static int  	  	maxLocal;  	/* -L argument */
Boolean	    	  	debug;	    	/* -d flag */
Boolean	  	  	amMake; 	/* -M flag */
Boolean	    	  	noWarnings; 	/* -W flag */
Boolean	    	    	noExecute;  	/* -n flag */
Boolean	    	    	keepgoing;  	/* -k flag */
Boolean			queryFlag;  	/* -q flag */
Boolean			touchFlag;  	/* -t flag */
Boolean			usePipes;   	/* !-P flag */
Boolean			backwards;  	/* -B flag */
Boolean			ignoreErrors;	/* -i flag */
Boolean			beSilent;   	/* -s flag */
Boolean	    	    	sysVmake;   	/* -v flag */
Boolean			oldVars;    	/* -V flag */
Boolean	    	    	checkEnvFirst;	/* -e flag */
static Boolean	  	XFlag=FALSE;   	/* -X flag given */
static Boolean	  	xFlag=FALSE;	/* -x flag given */
Recheck			recheck=RECHECK_DEFAULT;
					/* -R flag */
Boolean	    	  	noExport;   	/* Set TRUE if shouldn't export */
static Boolean	  	jobsRunning;	/* TRUE if the jobs might be running */
Boolean  	  	useLocal;  	/* TRUE if local machine is preferred */
Boolean         	outOfDate=FALSE;/* FALSE if all targets up to date */

static Boolean	    	ReadMakefile();

static char		*curdir = NULL;	/* if chdir'd for an architecture */

static char		syspath[] = DEFSYSPATH; /* System include path */

/*
 * Initial value for optind when parsing args. Different getopts start it
 * differently...
 */
static int  	initOptInd;

#ifdef CAN_EXPORT
#define OPTSTR "BCD:I:J:L:MPR:SVWXZ:d:ef:iklnp:qrstvxh"
#else
#define OPTSTR "BCD:I:J:L:MPR:SVWZ:d:ef:iklnp:qrstvh"
#endif

static char 	    *help[] = {
"-B	    	Be as backwards-compatible with make as possible without\n\
		being make.",
"-C	    	Cancel any current indications of compatibility.",
"-D<var>	Define the variable <var> with value 1.",
"-I<dir>	Specify another directory in which to search for included\n\
		makefiles.",
"-J<num>	Specify maximum overall concurrency.",
"-L<num>	Specify maximum local concurrency.",
"-M		Be Make as closely as possible.",
"-P		Don't use pipes to catch the output of jobs, use files.",
"-R<when>	Recheck created target date (never, locals only, always).",
"-S	    	Turn off the -k flag (see below).",
"-V		Use old-style variable substitution.",
"-W		Don't print warning messages.",
#ifdef CAN_EXPORT
"-X		Turn off exporting of commands.",
#endif
"-Z<char>  	Set the special character introducing makefile directives.",
"-d<flags>  	Turn on debugging output.",
"-e		Give environment variables precedence over those in the\n\
		makefile(s).",
"-f<file>	Specify a(nother) makefile to read",
"-i		Ignore errors from executed commands.",
"-k		On error, continue working on targets that do not depend on\n\
		the one for which an error was detected.",
#ifdef DONT_LOCK
"-l	    	Turn on locking of the current directory.",
#else
"-l	    	Turn off locking of the current directory.",
#endif
"-n	    	Don't execute commands, just print them.",
"-p<num>    	Tell when to print the input graph: 1 (before processing),\n\
		2 (after processing), or 3 (both).",
"-q	    	See if anything needs to be done. Exits 1 if so.",
"-r	    	Do not read the system makefile for pre-defined rules.",
"-s	    	Don't print commands as they are executed.",
"-t	    	Update targets by \"touching\" them (see touch(1)).",
"-v	    	Be compatible with System V make. Implies -B, -V and no\n\
		directory locking.",
#ifdef CAN_EXPORT
"-x	    	Allow exportation of commands.",
#endif
};

static char 	    config[] = 
#ifndef CAN_EXPORT
"System configuration information:\n\
\tDirectory for system makefiles: %s\n\
\tSystem makefile: %s\n\
\tDefault shell: %s\n";
#else /* CAN_EXPORT */
"System configuration information:\n\
\tDirectory for system makefiles: %s\n\
\tSystem makefile: %s\n\
\tDefault shell: %s\n\
\tExportation facility: %s\n";
#endif  /* !CAN_EXPORT */

/*-
 *----------------------------------------------------------------------
 * MainParseArgs --
 *	Parse a given argument vector. Called from main() and from
 *	Main_ParseArgLine() when the .MAKEFLAGS target is used.
 *
 *	XXX: Deal with command line overriding .MAKEFLAGS in makefile
 *
 * Results:
 *	None
 *
 * Side Effects:
 *	Various global and local flags will be set depending on the flags
 *	given
 *----------------------------------------------------------------------
 */
static void
MainParseArgs (argc, argv)
    int		  argc;	      /* Number of arguments in argv */
    char	  **argv;     /* The arguments themselves */
{
    extern int	optind;
    extern char	*optarg;
    int    	c;
    char	*argv0;

parseMore:
    optind = initOptInd;

    /*
     * Make sure getopt uses our name for error messages
     */
    argv0 = *argv;
    *argv = progName;

    /*
     * This is how we avoid duplicate flags in MAKEFLAGS
     * and .MAKEFLAGS variables.  (We only do this for
     * traditional Make flags.)  Why do we check the 
     * flag string rather than simply the associated
     * variables?  Because the variable values may be
     * modified in other places, e.g., as a result of
     * parsing certain dummy targets.
     */
#define CHECK_FLAG(flag) \
	strchr(Var_Value(MAKE_ENV, VAR_GLOBAL), flag)

    /*
     * Make sure MAKEFLAG variable exists
     */
    Var_Append(MAKE_ENV, "", VAR_GLOBAL, FALSE);

    while((c = getopt(argc, argv, OPTSTR)) != -1) {
	switch(c) {
	    case 'B':
		backwards = oldVars = TRUE;
		Var_Append(MAKEFLAGS, "-B", VAR_GLOBAL, TRUE);
		break;
	    case 'C':
		oldVars = backwards = sysVmake = amMake = FALSE;
		Var_Append(MAKEFLAGS, "-C", VAR_GLOBAL, TRUE);
		break;
	    case 'D':
		Var_Set(optarg, "1", VAR_GLOBAL);
		Var_Append(MAKEFLAGS, "-D", VAR_GLOBAL, TRUE);
		Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL, TRUE);
		break;
	    case 'I':
		Parse_AddIncludeDir(optarg);
		Var_Append(MAKEFLAGS, "-I", VAR_GLOBAL, TRUE);
		Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL, TRUE);
		break;
	    case 'J':
		maxJobs = atoi(optarg);
		Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL, TRUE);
		Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL, TRUE);
		break;
	    case 'L':
		maxLocal = atoi(optarg);
		if (maxLocal < 0) {
		    maxLocal = -maxLocal;
		    useLocal = TRUE;
		}
		else {
		    useLocal = FALSE;
		}
		Var_Append(MAKEFLAGS, "-L", VAR_GLOBAL, TRUE);
		Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL, TRUE);
		break;
	    case 'M':
		amMake = TRUE;
		Var_Append(MAKEFLAGS, "-M", VAR_GLOBAL, TRUE);
		break;
	    case 'P':
		usePipes = FALSE;
		Var_Append(MAKEFLAGS, "-P", VAR_GLOBAL, TRUE);
		break;
	    case 'R':
	    {
		switch (*optarg) {
		    case 'A':
		    case 'a':
			recheck = RECHECK_ALWAYS;
			break;
		    case 'L':
		    case 'l':
			recheck = RECHECK_LOCALS;
			break;
		    case 'N':
		    case 'n':
			recheck = RECHECK_NEVER;
			break;
		}
		Var_Append(MAKEFLAGS, "-R", VAR_GLOBAL, TRUE);
		Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL, TRUE);
		break;
	    }
	    case 'S':
		if (keepgoing || !CHECK_FLAG('S')) {
		    keepgoing = FALSE;
		    Var_Append(MAKEFLAGS, "-S", VAR_GLOBAL, TRUE);
		    Var_Append(MAKE_ENV, "S", VAR_GLOBAL, FALSE);
		}
		break;
	    case 'V':
		oldVars = TRUE;
		Var_Append(MAKEFLAGS, "-V", VAR_GLOBAL, TRUE);
		break;
	    case 'W':
		noWarnings = TRUE;
		Var_Append(MAKEFLAGS, "-W", VAR_GLOBAL, TRUE);
		break;
#ifdef CAN_EXPORT
	    case 'X':
		XFlag = TRUE;
		Var_Append(MAKEFLAGS, "-X", VAR_GLOBAL, TRUE);
		break;
#endif /* CAN_EXPORT */
	    case 'Z':
		specialChar = *optarg;
		Var_Append(MAKEFLAGS, "-Z", VAR_GLOBAL, TRUE);
		Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL, TRUE);
		break;
	    case 'd':
	    {
		char	*modules = optarg;

		while (*modules) {
		    switch (*modules) {
			case 's':
			    debug |= DEBUG_SUFF;
			    break;
			case 'm':
			    debug |= DEBUG_MAKE;
			    break;
			case 'j':
			    debug |= DEBUG_JOB;
			    break;
			case 't':
			    debug |= DEBUG_TARG;
			    break;
			case 'd':
			    debug |= DEBUG_DIR;
			    break;
			case 'v':
			    debug |= DEBUG_VAR;
			    break;
			case 'c':
			    debug |= DEBUG_COND;
			    break;
			case 'p':
			    debug |= DEBUG_PARSE;
			    break;
			case 'r':
			    debug |= DEBUG_RMT;
			    break;
			case 'a':
			    debug |= DEBUG_ARCH;
			    break;
			case 'q':
			    debug |= DEBUG_QUEUE;
			    break;
			case '*':
			    debug = ~0;
			    break;
		    }
		    modules++;
		}
		Var_Append(MAKEFLAGS, "-d", VAR_GLOBAL, TRUE);
		Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL, TRUE);
		break;
	    }
	    case 'e':
		checkEnvFirst = TRUE;
		if (!CHECK_FLAG('e')) {
		    Var_Append(MAKEFLAGS, "-e", VAR_GLOBAL, TRUE);
		    Var_Append(MAKE_ENV, "e", VAR_GLOBAL, FALSE);
		}
		break;
	    case 'f':
		(void)Lst_AtEnd(makefiles, (ClientData)optarg);
		break;
	    case 'i':
		ignoreErrors = TRUE;
		if (!CHECK_FLAG('i')) {
		    Var_Append(MAKEFLAGS, "-i", VAR_GLOBAL, TRUE);
		    Var_Append(MAKE_ENV, "i", VAR_GLOBAL, FALSE);
		}
		break;
	    case 'k':
		if (!keepgoing || !CHECK_FLAG('k')) {
		    keepgoing = TRUE;
		    Var_Append(MAKEFLAGS, "-k", VAR_GLOBAL, TRUE);
		    Var_Append(MAKE_ENV, "k", VAR_GLOBAL, FALSE);
		}
		break;
	    case 'l':
#ifdef DONT_LOCK
		noLocking = FALSE;
#else
		noLocking = TRUE;
#endif
		Var_Append(MAKEFLAGS, "-l", VAR_GLOBAL, TRUE);
		break;
	    case 'n':
		noExecute = TRUE;
		if (!CHECK_FLAG('n')) {
		    Var_Append(MAKEFLAGS, "-n", VAR_GLOBAL, TRUE);
		    Var_Append(MAKE_ENV, "n", VAR_GLOBAL, FALSE);
		}
		break;
	    case 'p':
		printGraph = atoi(optarg);
		break;
	    case 'q':
		queryFlag = TRUE;
		if (!CHECK_FLAG('q')) {
		    Var_Append(MAKEFLAGS, "-q", VAR_GLOBAL, TRUE);
		    Var_Append(MAKE_ENV, "q", VAR_GLOBAL, FALSE);
		}
		break;
	    case 'r':
		noBuiltins = TRUE;
		if (!CHECK_FLAG('r')) {
		    Var_Append(MAKEFLAGS, "-r", VAR_GLOBAL, TRUE);
		    Var_Append(MAKE_ENV, "r", VAR_GLOBAL, FALSE);
		}
		break;
	    case 's':
		beSilent = TRUE;
		if (!CHECK_FLAG('s')) {
		    Var_Append(MAKEFLAGS, "-s", VAR_GLOBAL, TRUE);
		    Var_Append(MAKE_ENV, "s", VAR_GLOBAL, FALSE);
		}
		break;
	    case 't':
		touchFlag = TRUE;
		if (!CHECK_FLAG('t')) {
		    Var_Append(MAKEFLAGS, "-t", VAR_GLOBAL, TRUE);
		    Var_Append(MAKE_ENV, "t", VAR_GLOBAL, FALSE);
		}
		break;
	    case 'v':
		sysVmake = oldVars = backwards = noLocking = TRUE;
		Var_Append(MAKEFLAGS, "-v", VAR_GLOBAL, TRUE);
		break;
#ifdef CAN_EXPORT
	    case 'x':
		xFlag = TRUE;
		Var_Append(MAKEFLAGS, "-x", VAR_GLOBAL, TRUE);
		break;
#endif /* CAN_EXPORT */
	    case 'h':
	    case '?':
	    {
		int 	i;

		for (i = 0; i < sizeof(help)/sizeof(help[0]); i++) {
		    printf("%s\n", help[i]);
		}

		printf(config,
		       DEFSYSPATH,
		       DEFSYSMK,
		       SHELLDOC,
#ifdef CAN_EXPORT
		       EXPORTDOC,
#endif
		       0);
		
		printf("PMake version %d.%d.%d\n",
		       MAJORVERSION, MINORVERSION, PATCHLEVEL);
		exit(c == '?' ? EXIT_USAGE : EXIT_OK);
	    }
	}
    }
    *argv = argv0;

    /*
     * Take care of encompassing compatibility levels...
     */
    if (amMake) {
	backwards = TRUE;
    }
    if (backwards) {
	oldVars = TRUE;
    }

    /*
     * See if the rest of the arguments are variable assignments and perform
     * them if so. Else take them to be targets and stuff them on the end
     * of the "create" list.
     */
    for (argv += optind, argc -= optind; argc > 0; argv++, argc--) {
	if ((*argv)[0] == '-') {
	    /*
	     * More options -- restart option parsing
	     * XXX: This is ugly, but it works.
	     */
	    argv--; argc++;
	    goto parseMore;
	} else if (Parse_IsVar (*argv)) {
	    Parse_DoVar(*argv, VAR_CMD);
	} else {
	    if ((*argv)[0] == '\0') {
		Punt("Null target.");
	    }
	    (void)Lst_AtEnd (create, (ClientData)*argv);
	}
    }
}

/*-
 *----------------------------------------------------------------------
 * Main_ParseArgLine --
 *  	Used by the parse module when a .MFLAGS or .MAKEFLAGS target
 *	is encountered and by main() when reading the .MAKEFLAGS envariable.
 *	Takes a line of arguments and breaks it into its
 * 	component words and passes those words and the number of them to the
 *	MainParseArgs function.
 *	The line should have all its leading whitespace removed.
 *
 * Results:
 *	None
 *
 * Side Effects:
 *	Only those that come from the various arguments.
 *-----------------------------------------------------------------------
 */
void
Main_ParseArgLine (line)
    char    	  *line;      /* Line to fracture */
{
    char    	  **argv;     /* Manufactured argument vector */
    int     	  argc;	      /* Number of arguments in argv */

    if (line == NULL) return;
    while (*line == ' ') line++;

    argv = Str_BreakString (line, " \t", "\n", &argc);

    MainParseArgs(argc, argv);

    Str_FreeVec(argc, argv);
}

/*-
 *-----------------------------------------------------------------------
 * Main_Unlock --
 *	Unlock the current directory. Called as an ExitHandler.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The locking file LOCKFILE is removed.
 *
 *-----------------------------------------------------------------------
 */
void
Main_Unlock ()
{
    if (lockSet)
	(void)unlink (LOCKFILE);
}

/*-
 *-----------------------------------------------------------------------
 * Main_Access --
 *	Set access level of the make process.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Effective uid/gid are set to the requested level.
 *
 *-----------------------------------------------------------------------
 */
void
Main_Access (access)
    Access access;		/* Requested access level */
{
    static int makeUid, makeGid;
    static int userUid, userGid;
    static Access currentAccess = ACCESS_NONE;
    int rUid, rGid;
    int eUid, eGid;

    if (currentAccess == ACCESS_NONE) {
	makeUid = geteuid (); makeGid = getegid ();
	userUid = getuid (); userGid = getgid ();

	currentAccess = ACCESS_MAKE;
    }

    if (currentAccess == access)
	return;
    
    if (access == ACCESS_MAKE) {
	rUid = userUid;
	rGid = userGid;
	eUid = makeUid;
	eGid = makeGid;
    }
    else if (access == ACCESS_USER) {
	rUid = makeUid;
	rGid = makeGid;
	eUid = userUid;
	eGid = userGid;
    }
    else if (access == ACCESS_CHILD) {
	rUid = userUid;
	rGid = userGid;
	eUid = userUid;
	eGid = userGid;
    }
    else
	return;

#ifndef SETREUID
    /*
     * We rely on the saved uid/gid to allow us to come back to 
     * priviledged status.
     * XXX: To ensure that setuid/setgid also sets the saved uid/gid we
     * switch to root if possible.  This is required to prevent failure
     * of /bin/csh scripts in SunOS5.x.
     */
    seteuid(0);
    if ((rGid == rGid ? setgid(rGid) : setegid(eGid)) < 0 ||
	(rUid == eUid ? setuid(rUid) : seteuid(eUid)) < 0)
#else /* SETREUID */
    /*
     * With setreuid/gid we can switch back and forth by always swapping
     * real and effective ids
     */
    if (SETREGID (rGid, eGid) < 0 ||
	SETREUID (rUid, eUid) < 0)
#endif /* !SETREUID */
    {
	Fatal ("Setting uid/gid failed: %s", strerror(errno));
    }

    /*
     * Don't modify currentAccess in vfork thread -- parent would be
     * affected, too, and badly confused!
     */
    if (access != ACCESS_CHILD) {
	currentAccess = access;
    }
}

/*-
 *----------------------------------------------------------------------
 * main --
 *	The main function, for obvious reasons. Initializes variables
 *	and a few modules, then parses the arguments give it in the
 *	environment and on the command line. Reads the system makefile
 *	followed by either Makefile, makefile or the file given by the
 *	-f argument. Sets the .MAKEFLAGS PMake variable based on all the
 *	flags it has received by then uses either the Make or the Compat
 *	module to create the initial list of targets.
 *
 * Results:
 *	If -q was given, exits -1 if anything was out-of-date. Else it exits
 *	0.
 *
 * Side Effects:
 *	The program exits when done. Targets are created. etc. etc. etc.
 *
 *----------------------------------------------------------------------
 */
main (argc, argv)
    int           argc;
    char          **argv;
{
    Lst             targs;     	/* list of target nodes to create. Passed to
				 * Make_Init */
    char    	    *cp;
    extern int	    optind;
    char	    *path;
    struct stat	    sb;

    /*
     * Find self name
     */
    cp = strrchr (argv[0], '/');
    if (cp != (char *)NULL) {
	cp += 1;
    } else {
	cp = argv[0];
    }
    progName = cp;

#ifdef USE_RESERVED_PORTS
    /*
     * XXX: Hack to fix OS brain damage:
     * When a pmake script with #!/usr/local/bin/pmake -f is execed,
     * the process is not given euid root even though /usr/local/bin/pmake
     * is suid root.  We check for this case and if necessary re-exec
     * ourselves.  We only do this, however, if argv[0] is an absolute
     * path whose filename component is "pmake".  This prevents lossage on
     * systems that omit the full path from argv[0] or use the script's
     * name instead (i.e., this hack won't work on those systems).
     * An environment variable is set to prevent exec loops, e.g., due
     * to non-suid installation of the pmake binary.
     */
#define EXEC_LOOP	"PMAKE_SELF_EXECED"

    if ((geteuid() != 0) &&
	(argv[0][0] == '/') &&
	(strcmp(progName, "pmake") == 0))
    {
	char *cp = getenv(EXEC_LOOP);

	if (cp && *cp) {
	    Error ("cannot become root -- reserved ports won't work");
	} else {
	    setenv(EXEC_LOOP, "1", 1);
	    (void)execv(argv[0], argv);
	    Error(argv[0]);
	}
    }
    setenv(EXEC_LOOP, "", 1);
#endif /* USE_RESERVED_PORTS */

    create = Lst_Init (FALSE);
    makefiles = Lst_Init(FALSE);

    beSilent = FALSE;	      	/* Print commands as executed */
    ignoreErrors = FALSE;     	/* Pay attention to non-zero returns */
    noExecute = FALSE;	      	/* Execute all commands */
    keepgoing = FALSE;	      	/* Stop on error */
    allPrecious = FALSE;      	/* Remove targets when interrupted */
    queryFlag = FALSE;	      	/* This is not just a check-run */
    noBuiltins = FALSE;	      	/* Read the built-in rules */
    touchFlag = FALSE;	      	/* Actually update targets */
    usePipes = TRUE;	      	/* Catch child output in pipes */
#ifndef DONT_LOCK
    noLocking = FALSE;	      	/* Lock the current directory against other
				 * pmakes */
#else
    noLocking = TRUE;
#endif /* DONT_LOCK */
    debug = 0;	      	    	/* No debug verbosity, please. */
    noWarnings = FALSE;	    	/* Print warning messages */
    sysVmake = FALSE;	    	/* Don't be System V compatible */

    jobsRunning = FALSE;

    maxJobs = DEFMAXJOBS;     	/* Set the default maximum concurrency */
    maxLocal = DEFMAXLOCAL;   	/* Set the default local max concurrency */
    useLocal = FALSE;		/* Try to export by default */
    
    /*
     * Deal with disagreement between different getopt's as to what
     * the initial value of optind should be by simply saving the
     * damn thing.
     */
    initOptInd = optind;
    
    /*
     * See what the user calls us. If s/he calls us (yuck) "make", then
     * act like it. Otherwise act like our normal, cheerful self.
     */
    if (strcmp (cp, "make") == 0) {
	amMake = TRUE;	      	/* Be like make */
	backwards = TRUE;     	/* Do things the old-fashioned way */
	oldVars = TRUE;	      	/* Same with variables */
    } else if (strcmp(cp, "smake") == 0 || strcmp(cp, "vmake") == 0) {
	sysVmake = oldVars = backwards = noLocking = TRUE;
    } else {
	amMake = FALSE;
	backwards = FALSE;    	/* Do things MY way, not MAKE's */
#ifdef DEF_OLD_VARS
	oldVars = TRUE;
#else
	oldVars = FALSE;      	/* don't substitute for undefined variables */
#endif
    }

    /*
     * Performs all initializations as user.
     */
    Main_Access (ACCESS_USER);

    /*
     * Initialize various variables.
     *	.PMAKE gets how we were executed.
     *	MAKE also gets this name, for compatibility
     *	.MAKEFLAGS gets set to the empty string just in case.
     *	MAKEFLAGS is initialized, for compatibility.
     *  MFLAGS also gets initialized empty, for compatibility.
     *  SHELL gets set to the default shell path.
     */
    Var_Init ();
    Var_Set (".PMAKE", argv[0], VAR_GLOBAL);
    Var_Set ("MAKE", argv[0], VAR_GLOBAL);
    Var_Set (MAKEFLAGS, "", VAR_GLOBAL);
    Var_Set (MAKE_ENV, "", VAR_GLOBAL);
    Var_Set ("MFLAGS", "", VAR_GLOBAL);
    Var_Set ("SHELL", Job_ShellPath (FALSE), VAR_GLOBAL);

    /*
     * If the MAKEOBJDIR (or by default, the DEFOBJDIR) directory
     * exists, change into it and build there.  Once things are
     * initted, have to add the original directory to the search path,
     * and modify the paths for the Makefiles apropriately.  The
     * current directory is also placed as a variable for make scripts.
     */
    if (!(path = getenv("MAKEOBJDIR"))) {
#ifdef DEFOBJDIR
	path = DEFOBJDIR;
#else
	;
#endif
    }
    if (path && lstat(path, &sb) == 0) {
	if (strcmp(path, ".") == 0) {
	    curdir = ".";
	} else if (!strchr(path, '/') &&
	    strcmp(path, ".") != 0 && strcmp(path, "..") != 0 &&
	    (sb.st_mode & S_IFMT) == S_IFDIR)
	{
	    curdir = "..";
	} else {
	    curdir = emalloc(MAXPATHLEN);
	    if (!GETWD(curdir)) {
		Fatal ("Couldn't determine current directory");
	    }
	}
	if (chdir(path) < 0) {
	    Fatal ("Cannot chdir to %s: %s", path, strerror(errno));
	}
    }

    /*
     * Initialize the parsing and directory modules to prepare
     * for the reading of inclusion paths and variable settings on the
     * command line 
     */
					
    Dir_Init ();		/* Initialize directory structures so -I flags
				 * can be processed correctly */
    cp = getenv("MAKESYSPATH");
    Parse_Init(cp ? cp : syspath);    /* Need to initialize the paths of
				       * #include directories */

    if (curdir) {
	Dir_AddDir(dirSearchPath, curdir);
	Var_Set(".CURDIR", curdir, VAR_GLOBAL);
    } else {
	Var_Set(".CURDIR", ".", VAR_GLOBAL);
    }

    /*
     * First snag any flags out of the MAKEFLAGS and PMAKE environment
     * variables.  The former is common to most make programs, but can
     * be overridden by the second.  Note that MAKEFLAGS contains the
     * option letters without spaces or hyphens.
     */
    if ((cp = getenv(MAKE_ENV)) && *cp) {
	char *newArgv[3];
	
	newArgv[0] = argv[0];
	newArgv[1] = emalloc(strlen(cp) + 2);
	newArgv[2] = (char *)0;
	sprintf(newArgv[1], "-%s", cp);

	/*
	 * We use MainParseArgs rather than Main_ParseArgLine to
	 * prevent MAKEFLAGS from being parsed into something other
	 * than option letters
	 */
	MainParseArgs(2, newArgv);
	free(newArgv[1]);
    }
    Main_ParseArgLine(getenv(PMAKE_ENV));
    
    MainParseArgs (argc, argv);

    /*
     * If the user didn't tell us not to lock the directory, attempt to create
     * the lock file. Complain if we can't, otherwise set up an exit handler
     * to remove the lock file...
     */
    if (!noExecute && !noLocking) {
	int	  	lockID;     /* Stream ID of opened lock file */
	SIGSET_T  	mask, oldMask;    /* Previous signal mask */

	SIGEMPTYSET(mask);
	SIGADDSET(mask, SIGINT);
	SIGBLOCK(mask, oldMask);
	
	lockID = open (LOCKFILE, O_CREAT | O_EXCL, 0666);
	if (lockID < 0 && errno == EEXIST) {
	    /*
	     * Find out who owns the file. If the user who called us
	     * owns it, then we ignore the lock file. Note that we also
	     * do not install an exit handler to remove the file -- if the
	     * lockfile is there from a previous make, it'll still be there
	     * when we leave.
	     */
	    struct stat   fsa;    /* Attributes of the lock file */

	    (void) stat (LOCKFILE,  &fsa);
	    if (fsa.st_uid == geteuid()) {
		Error ("Lockfile owned by you -- ignoring it");
		lockSet = FALSE;
	    } else {
		char  	lsCmd[40];
		(void)sprintf (lsCmd, "ls -l %s", LOCKFILE);
		(void)system(lsCmd);
		Fatal ("This directory is already locked (%s exists)",
		       LOCKFILE);
	    }
	} else if (lockID < 0) {
	    /*
	     * If invoked by root just continue and assume the invoker knows
	     * what he is doing.  This typically happens when the current
	     * directory is on an NFS filesystem.  In those cases we mostly
	     * are just doing make install or similar, which is fine.  Building
	     * things will fail anyway because other files can't be created
	     * either.
	     */
	    if (geteuid() == 0 && errno == EACCES) {
		Error ("Lockfile creation by root denied -- proceeding");
	    } else {
		Fatal ("Could not create lock file %s: %s",
		       LOCKFILE, strerror(errno));
	    }
	} else {
	    lockSet = TRUE;
	    SIGNAL(SIGINT, (SIGRET (*)())exit);
	    (void)close (lockID);
	}
	
	SIGSETMASK(oldMask, mask);
    }

    /*
     * Initialize archive, target and suffix modules in preparation for
     * parsing the makefile(s) 
     */
    Arch_Init ();
    Targ_Init ();
    Suff_Init ();

    DEFAULT = NILGNODE;

    now = time(0);

    /*
     * Set up the .TARGETS variable to contain the list of targets to be
     * created. If none specified, make the variable empty -- the parser
     * will fill the thing in with the default or .MAIN target.
     */
    if (!Lst_IsEmpty(create)) {
	LstNode	ln;

	for (ln = Lst_First(create); ln != NILLNODE; ln = Lst_Succ(ln)) {
	    char    *name = (char *)Lst_Datum(ln);

	    Var_Append(".TARGETS", name, VAR_GLOBAL, TRUE);
	}
    } else {
	Var_Set(".TARGETS", "", VAR_GLOBAL);
    }

    /*
     * Read in the built-in rules first, followed by the specified makefile,
     * if it was (makefile != (char *) NULL), or the default Makefile and
     * makefile, in that order, if it wasn't. 
     */
    if (!noBuiltins && !ReadMakefile (DEFSYSMK, TRUE)) {
	Fatal ("Could not open system rules (%s)", DEFSYSMK);
    }

    if (!Lst_IsEmpty(makefiles)) {
	LstNode	ln = Lst_Find(makefiles, (ClientData)FALSE, ReadMakefile);

	if (ln != NILLNODE) {
	    Fatal ("Cannot open %s", (char *)Lst_Datum(ln));
	}
    } else {
	if (!ReadMakefile ((amMake || sysVmake) ?
				"makefile" : "Makefile", FALSE))
	{
	    (void) ReadMakefile ((amMake||sysVmake) ?
					"Makefile" : "makefile", FALSE);
	}
    }

#ifdef PURIFY
    Debug("Memory after makefile parsing\n");
    purify_new_leaks();
#endif

    /*
     * Figure "noExport" out based on the current mode. Since exporting each
     * command in make mode is rather inefficient, we only export if the -x
     * flag was given. In regular mode though, we only refuse to export if
     * -X was given. In case the operative flag was given in the environment,
     * however, the opposite one may be given on the command line and cancel
     * the action.
     * Also, don't export if invoked by root and root exportation is diallowed.
     */
#ifndef INSECURE
    if (geteuid() == 0) {
	noExport = TRUE;
    } else
#endif /* !INSECURE */
    if (amMake) {
	noExport = !xFlag || XFlag;
    } else {
	noExport = XFlag && !xFlag;
    }
    
    Var_Append ("MFLAGS", Var_Value(MAKEFLAGS, VAR_GLOBAL), VAR_GLOBAL, TRUE);

    /*
     * Install all the flags back into their envariables.
     */
    setenv(MAKE_ENV, Var_Value(MAKE_ENV, VAR_GLOBAL), 1);
    setenv(PMAKE_ENV, Var_Value(MAKEFLAGS, VAR_GLOBAL), 1);

    /*
     * For compatibility, look at the directories in the VPATH variable
     * and add them to the search path, if the variable is defined. The
     * variable's value is in the same format as the PATH envariable, i.e.
     * <directory>:<directory>:<directory>...
     */
    if (Var_Exists ("VPATH", VAR_CMD)) {
	char	  *vpath;
	char	  *path;
	char  	  *cp;
	char  	  savec;
	static char VPATH[] = "${VPATH}";   /* GCC stores string constants in
					     * read-only memory, but Var_Subst
					     * will want to write this thing,
					     * so store it in an array */
	
	/*
	 * VPATH searches implicitly always look relative to the current
	 * directory first.  To make this true even for relative pathnames
	 * we add dot to the front of the search path.
	 */
	Dir_AddDir (dirSearchPath, ".");

	vpath = Var_Subst (VPATH, VAR_CMD, FALSE);
	
	path = vpath;
	do {
	    /*
	     * Skip to end of directory
	     */
	    for (cp = path; *cp != ':' && *cp != '\0'; cp++) {
		continue;
	    }
	    /*
	     * Save terminator character to figure out when to stop
	     */
	    savec = *cp;
	    *cp = '\0';
	    /*
	     * Add directory to search path
	     */
	    Dir_AddDir (dirSearchPath, path);
	    *cp = savec;
	    path = cp + 1;
	} while (savec == ':');
	free((Address)vpath);
    }
	    
    /*
     * Now that all search paths have been read for suffixes et al, it's
     * time to add the default search path to their lists...
     */
    Suff_DoPaths();

    /*
     * Print the initial graph, if the user requested it
     */
    if (printGraph & 1) {
	Targ_PrintGraph (1);
    }

    Rmt_Init();

    /*
     * Have now read the entire graph and need to make a list of targets to
     * create. If none was given on the command line, we consult the parsing
     * module to find the main target(s) to create.
     */
    if (Lst_IsEmpty (create)) {
	targs = Parse_MainName ();
    } else {
	targs = Targ_FindList (create, TARG_CREATE);
    }

    if (!amMake) {
	/*
	 * Initialize job module before traversing the graph, now that any
	 * .BEGIN and .END targets have been read. This is done only if the
	 * -q flag wasn't given (to prevent the .BEGIN from being executed
	 * should it exist).
	 */
	if (maxLocal == -1) {
	    maxLocal = maxJobs;
	}
	Job_Init (maxJobs, maxLocal);
	jobsRunning = TRUE;
	
	/*
	 * Traverse the graph, checking on all the targets 
	 */
	(void)Make_Run (targs);
    } else {
	/*
	 * Compat_Init will take care of creating all the targets as well
	 * as initializing the module.
	 */
	Compat_Run(targs);
    }
    
    if (queryFlag) {
	exit (outOfDate ? EXIT_YES : EXIT_OK);
    }

    /*
     * Print the graph now it's been processed if the user requested it
     */
    if (printGraph & 2) {
	Targ_PrintGraph (2);
    }

    /*
     * All errors lead to early exits, so we must be o.k.
     */
    exit (EXIT_OK);
}

/*-
 *-----------------------------------------------------------------------
 * ReadMakefile  --
 *	Open and parse the given makefile.
 *
 * Results:
 *	TRUE if ok. FALSE if couldn't open file.
 *
 * Side Effects:
 *	lots
 *-----------------------------------------------------------------------
 */
static Boolean
ReadMakefile (fname, sysInclude)
    char          *fname;     /* makefile to read */
    Boolean	  sysInclude; /* search on system include path only */
{
    if (strcmp (fname, "-") == 0) {
	Var_Set("MAKEFILE", "", VAR_GLOBAL);
	Parse_File ("(stdin)", stdin);
	return (TRUE);
    } else {
	FILE *	  stream;
	extern Lst parseIncPath, sysIncPath;
	char	  path[MAXPATHLEN];
	
	if (*fname == '/' || !sysInclude) {
	    stream = fopen (fname, "r");
	
	    if (stream == (FILE *) NULL) {
		/*
		 * If we've chdir'd, rebuild the path name.
		 */
		if (curdir && *fname != '/') {
		    (void)sprintf (path, "%s/%s", curdir, fname);
		    if (stream = fopen (path, "r")) {
			fname = path;
		    }
		}
	    }
	} else {
	    stream = (FILE *) NULL;
	}

	if (stream == (FILE *) NULL) {
	    /*
	     * Look in -I directories...
	     */
	    char    *name;
	    
	    if (!sysInclude) {
		name = Dir_FindFile(fname, parseIncPath);
	    } else {
		name = NULL;
	    }

	    if (name == NULL) {
		/*
		 * Last-ditch: look in system include directories.
		 */
		name = Dir_FindFile(fname, sysIncPath);
		if (name == NULL) {
		    return (FALSE);
		}
	    }
	    stream = fopen(name, "r");
	    if (stream == (FILE *)NULL) {
		/* Better safe than sorry... */
		return(FALSE);
	    }
	    fname = name;
	}
	/*
	 * Set the MAKEFILE variable desired by System V fans -- the placement
	 * of the setting here means it gets set to the last makefile
	 * specified, as it is set by SysV make...
	 */
	Var_Set("MAKEFILE", fname, VAR_GLOBAL);
	Parse_File (fname, stream);
	fclose (stream);
	return (TRUE);
    }
}

/*-
 *-----------------------------------------------------------------------
 * Error --
 *	Print an error message given its format and 0, 1, 2 or 3 arguments.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The message is printed.
 *
 *-----------------------------------------------------------------------
 */
/*VARARGS1*/
void
Error (fmt, arg1, arg2, arg3)
    char    	  *fmt;	    	    /* Format string */
    char    	  *arg1,	    /* First optional argument */
		  *arg2,	    /* Second optional argument */
		  *arg3;	    /* Third optional argument */
{
    static char   estr[BSIZE0];	    /* output string */

    sprintf (estr, "%s: ", progName);
    sprintf (&estr[strlen (estr)], fmt, arg1, arg2, arg3);
    (void) strcat (estr, "\n");

    fputs (estr, stderr);
    fflush (stderr);
}

/*-
 *-----------------------------------------------------------------------
 * Fatal --
 *	Produce a Fatal error message. If jobs are running, waits for them
 *	to finish.
 *
 * Results:
 *	None
 *
 * Side Effects:
 *	The program exits
 *-----------------------------------------------------------------------
 */
/* VARARGS1 */
void
Fatal (fmt, arg1, arg2)
    char          *fmt;	      	  /* format string */
    char          *arg1;	  /* first optional argument */
    char          *arg2;	  /* second optional argument */
{
    if (jobsRunning) {
	Job_Wait();
    }
    
    Error (fmt, arg1, arg2);

    if (printGraph & 2) {
	Targ_PrintGraph(2);
    }
    exit (EXIT_ERROR);	/* Not EXIT_YES so -q can distinguish error */
}

/*
 *-----------------------------------------------------------------------
 * Punt --
 *	Major exception once jobs are being created. Kills all jobs, prints
 *	a message and exits.
 *
 * Results:
 *	None 
 *
 * Side Effects:
 *	All children are killed indiscriminately and the program Lib_Exits
 *-----------------------------------------------------------------------
 */
/* VARARGS1 */
void
Punt (fmt, arg1, arg2)
    char          *fmt;		/* format string */
    char          *arg1;	/* optional argument */
    char	  *arg2;	/* optional second argument */
{
    Error (fmt, arg1, arg2);

    DieHorribly();
}

/*-
 *-----------------------------------------------------------------------
 * DieHorribly --
 *	Exit without giving a message.
 *
 * Results:
 *	None
 *
 * Side Effects:
 *	A big one...
 *-----------------------------------------------------------------------
 */
void
DieHorribly()
{
    if (jobsRunning) {
	Job_AbortAll ();
    }
    if (printGraph & 2) {
	Targ_PrintGraph(2);
    }
    
    exit (EXIT_ERROR);	/* Not EXIT_YES, so -q can distinguish error */
}

/*
 *-----------------------------------------------------------------------
 * Finish --
 *	Called when aborting due to errors in child shell to signal
 *	abnormal exit. 
 *
 * Results:
 *	None 
 *
 * Side Effects:
 *	The program exits
 * -----------------------------------------------------------------------
 */
void
Finish (errors)
    int             errors;	/* number of errors encountered in Make_Make */
{
    Fatal ("%d error%s", errors, errors == 1 ? "" : "s");
}

/*-
 *-----------------------------------------------------------------------
 * Debug --
 *	Print a debugging message given its format and 0, 1, 2 or 3 arguments.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The message is printed.
 *
 *-----------------------------------------------------------------------
 */
/*VARARGS1*/
void
Debug (fmt, arg1, arg2, arg3)
    char    	  *fmt;	    	    /* Format string */
    char	  *arg1,	    /* First optional argument */
		  *arg2,	    /* Second optional argument */
		  *arg3;	    /* Third optional argument */
{
    fprintf (stderr, fmt, arg1, arg2, arg3);
    fflush (stderr);
}

/*
 * Replacement for generic handler.
 */
void
enomem ()
{
   Fatal ("%s", strerror(errno));
}

EXITRET
exit(status)
    int status;
{
    Main_Unlock();

#ifdef PURIFY
    Debug("Memory at termination\n");
    purify_new_leaks();
#endif

#ifdef linux
    fcloseall();
#else
    _cleanup();
#endif
    _exit(status);
}
