/*-
 * compat.c --
 *	The routines in this file implement the full-compatibility
 *	mode of PMake. Most of the special functionality of PMake
 *	is available in this mode. Things not supported:
 *	    - different shells.
 *	    - friendly variable substitution.
 *
 * 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.
 *
 * Interface:
 *	Compat_Run	    Initialize things for this module and recreate
 *	    	  	    thems as need creatin'
 */
#ifndef lint
static char *rcsid = "$Id: compat.c,v 1.32 1995/03/11 01:52:10 stolcke Exp $ ICSI (Berkeley)";
#endif /* not lint */

#include    <stdio.h>
#include    <string.h>
#include    <sys/types.h>
#include    <sys/stat.h>
#include    <signal.h>
#include    <sys/wait.h>
#include    <sys/errno.h>
#include    <ctype.h>
#include    "make.h"
extern int errno;

/*
 * The following array is used to make a fast determination of which
 * characters are interpreted specially by the shell.  If a command
 * contains any of these characters, it is executed by the shell, not
 * directly by us.
 */

static char 	    meta[256];

static GNode	    *curTarg = NILGNODE;
static GNode	    *ENDNode;
static int  	    CompatRunCommand();

/*-
 *-----------------------------------------------------------------------
 * CompatInterrupt --
 *	Interrupt the creation of the current target and remove it if
 *	it ain't precious.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The target is removed and the process exits. If .INTERRUPT exists,
 *	its commands are run first WITH INTERRUPTS IGNORED..
 *
 *-----------------------------------------------------------------------
 */
static SIGRET
CompatInterrupt (signo)
    int	    signo;
{
    GNode   *gn;
    
    /*
     * XXX: Do this early to narrow window for back-to-back signals.
     */
    SIGRESTORE(signo, CompatInterrupt);

    if ((curTarg != NILGNODE) && !Targ_Precious (curTarg)) {
	char 	  *file = Targ_Name (curTarg);
	struct	  stat buf;

	/*
	 * Attempt to remove the current target only if
	 * - it exists as a file
	 * - it has been modified during this make run
	 * - it is not a directory
	 */
	if (stat (file, &buf) == SUCCESS && buf.st_mtime >= now) {
	    fprintf (stderr, "*** %s ", file);
	    if (buf.st_mode & S_IFDIR) {
		fprintf (stderr, "not removed\n");
	    } else if (unlink (file) == SUCCESS) {
		fprintf (stderr, "removed\n");
	    } else {
		fprintf (stderr, "not removed (%s)\n", strerror(errno));
	    }
	    fflush (stderr);
	}
    }

    /*
     * Run .INTERRUPT only if hit with interrupt signal
     */
    if ((signo == SIGINT) && !touchFlag && !queryFlag) {
	gn = Targ_FindNode(".INTERRUPT", TARG_NOCREATE);
	if (gn != NILGNODE) {
	    Lst_ForEach(gn->commands, CompatRunCommand, (ClientData)gn);
	    Make_FreeCommands(gn);
	}
    }
    exit (EXIT_ERROR);
}

/*-
 *-----------------------------------------------------------------------
 * CompatRunCommand --
 *	Execute the next command for a target. If the command returns an
 *	error, the node's made field is set to ERROR and creation stops.
 *
 * Results:
 *	0 if the command succeeded, 1 if an error occurred.
 *
 * Side Effects:
 *	The node's 'made' field may be set to ERROR.
 *
 *-----------------------------------------------------------------------
 */
static int
CompatRunCommand (cmd, gn)
    char    	  *cmd;	    	/* Command to execute */
    GNode   	  *gn;    	/* Node from which the command came */
{
    char    	  *cmdStart;	/* Start of expanded command */
    register char *cp;
    Boolean 	  silent,   	/* Don't print command */
		  errCheck, 	/* Check errors */
		  recurse;	/* Recursive make */
    WAIT_STATUS	  reason;   	/* Reason for child's death */
    int	    	  status;   	/* Description of child's death */
    int	    	  cpid;	    	/* Child actually found */
    ReturnStatus  stat;	    	/* Status of fork */
    LstNode 	  cmdNode;  	/* Node where current command is located */
    char    	  **av;	    	/* Argument vector for thing to exec */
    int	    	  argc;	    	/* Number of arguments in av or 0 if not
				 * dynamically allocated */
    Boolean 	  local;    	/* TRUE if command should be executed
				 * locally */

    silent = gn->type & OP_SILENT;
    errCheck = !(gn->type & OP_IGNORE);

    cmdNode = Lst_Member (gn->commands, (ClientData)cmd);

    var_MakeExpanded = FALSE;
    cmdStart = Var_Subst (cmd, gn, FALSE);
    recurse = var_MakeExpanded || (gn->type & OP_MAKE);

    /*
     * Str_BreakString will return an argv with a NULL in av[1], thus causing
     * execvp to choke and die horribly. Besides, how can we execute a null
     * command? In any case, we warn the user that the command expanded to
     * nothing (is this the right thing to do?).
     */
     
    if (*cmdStart == '\0') {
#ifdef notdef
	if (!noWarnings) {
	    Error("%s expands to empty command", cmd);
	}
#endif /* notdef */
	return(0);
    } else {
	free(cmd);
	cmd = cmdStart;
    }
    Lst_Replace (cmdNode, (ClientData)cmdStart);

    if ((gn->type & OP_SAVE_CMDS) && (gn != ENDNode)) {
	(void)Lst_AtEnd(ENDNode->commands, (ClientData)cmdStart);
	return(0);
    } else if (strcmp(cmdStart, "...") == 0) {
	gn->type |= OP_SAVE_CMDS;
	return(0);
    }

    /*
     * The '@' and '-' directives may be interspersed with whitespace.
     * Also eat up any whitespace between them and the command itself.
     */
    while ((*cmd == ' ') || (*cmd == '\t') ||
	   (*cmd == '@') || (*cmd == '-'))
    {
	if (*cmd == '@') {
	    silent = TRUE;
	} else if (*cmd == '-') {
	    errCheck = FALSE;
	}
	cmd++;
    }
    
    /*
     * Search for meta characters in the command. If there are no meta
     * characters, there's no need to execute a shell to execute the
     * command.
     */
    for (cp = cmd; !meta[*cp]; cp++) {
	continue;
    }

    /*
     * Print the command before echoing if we're not supposed to be quiet for
     * this one. We also print the command if -n given or if a recursive
     * make is performed.
     */
    if (touchFlag && recurse && !silent ||
	!touchFlag && !silent ||
	!touchFlag && noExecute)
    {
	printf ("%s\n", cmd);
	fflush(stdout);
    }

    /*
     * If we're not supposed to execute any commands, this is as far as
     * we go...
     */
    if ((noExecute || touchFlag) && !recurse) {
	return (0);
    }
    
    if (*cp != '\0') {
	/*
	 * If *cp isn't the null character, we hit a "meta" character and
	 * need to pass the command off to the shell. We give the shell the
	 * -e flag as well as -c if it's supposed to exit when it hits an
	 * error.
	 */
	static char	*shargv[4];

	shargv[0] = Job_ShellPath (FALSE);
	shargv[1] = (errCheck ? "-ec" : "-c");
	shargv[2] = cmd;
	shargv[3] = (char *)NULL;
	av = shargv;
	argc = 0;
    } else {
	/*
	 * No meta-characters, so no need to exec a shell. Break the command
	 * into words to form an argument vector we can execute.
	 * Str_BreakString sticks our name in av[0], so we have to
	 * skip over it...
	 */
	av = Str_BreakString(cmd, " \t", "\n", &argc);
	av += 1;
    }
    
    /*
     * If the job has not been marked unexportable, tell the Rmt module we've
     * got something for it...local is set TRUE if the job should be run
     * locally.
     */
    if (!(gn->type & OP_NOEXPORT)) {
	local = !Rmt_Begin(av[0], av, gn);
    } else {
	local = TRUE;
    }

    /*
     * Fork and execute the single command. If the fork fails, we abort.
     */
    cpid = vfork();
    if (cpid < 0) {
	Fatal("Could not fork: %s", strerror(errno));
    }
    if (cpid == 0) {
	Main_Access(ACCESS_CHILD);
	if (local) {
	    execvp(av[0], av);
	    Error("%s: %s", av[0], strerror(errno));
	} else {
	    Rmt_Exec(av[0], av, FALSE);
	}
	_exit(1);
    } else if (argc != 0) {
	/*
	 * If there's a vector that needs freeing (the command was executed
	 * directly), do so now, being sure to back up the argument vector
	 * to where it started...
	 */
	av -= 1;
	Str_FreeVec (argc, av);
    }
    
    /*
     * The child is off and running. Now all we can do is wait...
     */
    while (1) {
	int 	  id;

	if (!local) {
	    id = Rmt_LastID(cpid);
	}

	while ((stat = wait(&reason)) != cpid) {
	    if (stat == -1 && errno != EINTR) {
		break;
	    }
	}
	
	if (!local) {
	    Rmt_Done(id);
	}
	

	if (stat > -1) {
	    if (WIFSTOPPED(reason)) {
		status = WSTOPPED;			/* stopped */
	    } else if (WIFEXITED(reason)) {
		status = WEXITSTATUS(reason);		/* exited */
		if (status != 0) {
		    fprintf (stderr, "*** Error code %d", status);
		}
	    } else {
		status = WTERMSIG(reason);		/* signaled */
		fprintf (stderr, "*** Signal %d", status);
	    } 

	    
	    if (!WIFSTOPPED(reason) &&
		(!WIFEXITED(reason) || (status != 0)))
	    {
		if (errCheck) {
		    gn->made = ERROR;
		    if (keepgoing) {
			/*
			 * Abort the current target, but let others
			 * continue.
			 */
			fprintf (stderr, " (continuing)");
		    } 
		} else {
		    /*
		     * Continue executing commands for this target.
		     * If we return 0, this will happen...
		     */
		    fprintf (stderr, " (ignored)");
		    status = 0;
		}
		putc ('\n', stderr);
		fflush (stderr);
	    }

	    break;
	} else {
	    Fatal ("Error in wait: %s", strerror(errno));
	    /*NOTREACHED*/
	}
    }

    /* 
     * For rechecking purposes the make module needs to know
     * whether the target creation involved remote execution.
     */
    if (!local) {
	gn->type |= OP_REMOTE;
    }

    return (status);
}

/*-
 *-----------------------------------------------------------------------
 * CompatMake --
 *	Make a target.
 *
 * Results:
 *	0
 *
 * Side Effects:
 *	If an error is detected and not being ignored, the process exits.
 *
 *-----------------------------------------------------------------------
 */
static int
CompatMake (gn, pgn)
    GNode   	  *gn;	    /* The node to make */
    GNode   	  *pgn;	    /* Parent to abort if necessary */
{
    if (gn->type & OP_USE) {
	Make_HandleUse(gn, pgn);
    } else if (gn->made == UNMADE) {
	/*
	 * First mark ourselves to be made, then apply whatever transformations
	 * the suffix module thinks are necessary. Once that's done, we can
	 * descend and make all our children. If any of them has an error
	 * but the -k flag was given, our 'make' field will be set FALSE again.
	 * This is our signal to not attempt to do anything but abort our
	 * parent as well.
	 */
	gn->make = TRUE;
	gn->made = BEINGMADE;
	Suff_FindDeps (gn);
	Lst_ForEach (gn->children, CompatMake, (ClientData)gn);
	if (!gn->make) {
	    gn->made = ABORTED;
	    pgn->make = FALSE;
	    return (0);
	}

	if (Lst_Member (gn->iParents, pgn) != NILLNODE) {
	    Var_Set (IMPSRC, Targ_Name(gn), pgn);
	}
	
	/*
	 * All the children were made ok. Now cmtime contains the modification
	 * time of the newest child, we need to find out if we exist and when
	 * we were modified last. The criteria for datedness are defined by the
	 * Make_OODate function.
	 */
	if (DEBUG(MAKE)) {
	    Debug ("Examining %s...", gn->name);
	}
	if (! Make_OODate(gn)) {
	    gn->made = UPTODATE;
	    if (DEBUG(MAKE)) {
		Debug ("up-to-date.\n");
	    }
	    return (0);
	} else if (DEBUG(MAKE)) {
	    Debug ("out-of-date.\n");
	}

	/*
	 * We need to be re-made. We also have to make sure we've got a $?
	 * variable. To be nice, we also define the $> variable using
	 * Make_DoAllVar().
	 */
	Make_DoAllVar(gn);
		    
	/*
	 * Alter our type to tell if errors should be ignored or things
	 * should not be printed so CompatRunCommand knows what to do.
	 */
	if (Targ_Ignore (gn)) {
	    gn->type |= OP_IGNORE;
	}
	if (Targ_Silent (gn)) {
	    gn->type |= OP_SILENT;
	}

	if (Job_CheckCommands (gn, Fatal)) {
	    /*
	     * Our commands are ok, but we still have to worry about the -t
	     * flag...
	     */
	    if (touchFlag || queryFlag) {
		Job_Touch (gn, gn->type & OP_SILENT);
	    }

	    /*
	     * Execute/print commands and recursive touches
	     */
	    if (!queryFlag) {
		curTarg = gn;
		Lst_ForEach (gn->commands, CompatRunCommand, (ClientData)gn);
		curTarg = NILGNODE;
		Make_FreeCommands(gn);
	    }

	    /*
	     * If the user is just seeing if something is out-of-date, and
	     * there are actually commands to remake the thing, exit now
	     * to tell him/her "yes".
	     */
	    if (queryFlag && outOfDate) {
		exit (EXIT_YES);
	    }
	} else {
	    gn->made = ERROR;
	}

	if (gn->made != ERROR) {
	    Boolean force = FALSE;

	    /*
	     * If the node was made successfully, mark it so, update
	     * its modification time and timestamp all its parents. Note
	     * that for .ZEROTIME targets, the timestamping isn't done.
	     * This is to keep its state from affecting that of its parent.
	     */
	    gn->made = MADE;
	    if ((recheck == RECHECK_NEVER) ||
		(recheck == RECHECK_LOCALS && (gn->type & OP_REMOTE)))
	    {
		/*
		 * We can't re-stat the thing, but we can at least take care of
		 * rules where a target depends on a source that actually
		 * creates the target, but only if it has changed, e.g.
		 *
		 * parse.h : parse.o
		 *
		 * parse.o : parse.y
		 *  	yacc -d parse.y
		 *  	cc -c y.tab.c
		 *  	mv y.tab.o parse.o
		 *  	cmp -s y.tab.h parse.h || mv y.tab.h parse.h
		 *
		 * In this case, if the definitions produced by yacc haven't
		 * changed from before, parse.h won't have been updated and
		 * gn->mtime will reflect the current modification time for
		 * parse.h. This is something of a kludge, I admit, but it's a
		 * useful one..
		 *
		 * XXX: People like to use a rule like
		 *
		 * FRC:
		 *
		 * To force things that depend on FRC to be made, so we have to
		 * check for gn->children being empty as well...
		 */
		if (!Lst_IsEmpty(gn->commands) || Lst_IsEmpty(gn->children)) {
		    gn->mtime = now;
		    force = TRUE;
		}
	    } else {
		/*
		 * This is what Make does and it's actually a good thing, as it
		 * allows rules like
		 *
		 *	cmp -s y.tab.h parse.h || cp y.tab.h parse.h
		 *
		 * to function as intended. Unfortunately, thanks to the
		 * stateless nature of NFS (and the speed of this program),
		 * there are times when the modification time of a file created
		 * on a remote machine will not be modified before the stat()
		 * implied by the Dir_MTime occurs, thus leading us to believe
		 * that the file is unchanged, wreaking havoc with files that
		 * depend on this one.
		 *
		 * I have decided it is better to make too much than to make
		 * too little, so this stuff is commented out unless you're
		 * sure it's ok.
		 * -- ardeb 1/12/88
		 */
		if (noExecute || Dir_MTime(gn) == 0 ||
	            (gn->type & OP_ARCHV) && (gn->mtime < now)) {
		    gn->mtime = now;
		    force = TRUE;
		}
		if (DEBUG(MAKE)) {
		    Debug ("update time: %s\n", Targ_FmtTime(gn->mtime));
		}
	    }
	    if (!(gn->type & OP_EXEC)) {
		pgn->childMade = TRUE;
		Make_TimeStamp(pgn, gn);
		if (force) {
		    pgn->type |= OP_FORCE;
		}
	    }
	} else if (keepgoing) {
	    pgn->make = FALSE;
	} else {
	    Error ("Command failed for target `%s'", gn->name);
	    exit (EXIT_ERROR);
	}
    } else if (gn->made == ERROR) {
	/*
	 * Already had an error when making this beastie. Tell the parent
	 * to abort.
	 */
	pgn->make = FALSE;
    } else {
	if (Lst_Member (gn->iParents, pgn) != NILLNODE) {
	    Var_Set (IMPSRC, Targ_Name(gn), pgn);
	}
	switch(gn->made) {
	    case BEINGMADE:
#ifdef ABORT_CYCLES
		Error("Graph cycles through `%s: %s'", pgn->name, gn->name);
		gn->made = ERROR;
		pgn->make = FALSE;
#else /* !ABORT_CYCLES */
		Error("Ignoring cycle through `%s: %s'", pgn->name, gn->name);
#endif /* ABORT_CYCLES */
		break;
	    case MADE:
		if ((gn->type & OP_EXEC) == 0) {
		    pgn->childMade = TRUE;
		    Make_TimeStamp(pgn, gn);
		}
		break;
	    case UPTODATE:
		if ((gn->type & OP_EXEC) == 0) {
		    Make_TimeStamp(pgn, gn);
		}
		break;
	}
    }

    return (0);
}
	
/*-
 *-----------------------------------------------------------------------
 * Compat_Run --
 *	Initialize this mode and start making.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Guess what?
 *
 *-----------------------------------------------------------------------
 */
void
Compat_Run(targs)
    Lst	    	  targs;    /* List of target nodes to re-create */
{
    char    	  *cp;	    /* Pointer to string of shell meta-characters */
    GNode   	  *gn;	    /* Current root target */
    int	    	  errors;   /* Number of targets not remade due to errors */

    /*
     * Initialize the shell path if not already done elsewhere.
     */
    (void)Job_ShellPath (TRUE);

    if (SIGNAL(SIGINT, SIG_IGN) != SIG_IGN) {
	SIGNAL(SIGINT, CompatInterrupt);
    }
    if (SIGNAL(SIGTERM, SIG_IGN) != SIG_IGN) {
	SIGNAL(SIGTERM, CompatInterrupt);
    }
    if (SIGNAL(SIGHUP, SIG_IGN) != SIG_IGN) {
	SIGNAL(SIGHUP, CompatInterrupt);
    }
    if (SIGNAL(SIGQUIT, SIG_IGN) != SIG_IGN) {
	SIGNAL(SIGQUIT, CompatInterrupt);
    }
#ifdef SIGXCPU
    if (SIGNAL(SIGXCPU, SIG_IGN) != SIG_IGN) {
	SIGNAL(SIGXCPU, CompatInterrupt);
    }
#endif

    for (cp = "#=|^(){};&<>*?[]:$`\\\n"; *cp != '\0'; cp++) {
	meta[*cp] = 1;
    }
    /*
     * The null character serves as a sentinel in the string.
     */
    meta[0] = 1;

    ENDNode = Targ_FindNode(".END", TARG_CREATE);
    /*
     * If the user has defined a .BEGIN target, execute the commands attached
     * to it.
     */
    if (!queryFlag) {
	gn = Targ_FindNode(".BEGIN", TARG_NOCREATE);
	if (gn != NILGNODE) {
	    Lst_ForEach(gn->commands, CompatRunCommand, (ClientData)gn);
	    Make_FreeCommands(gn);
	}
    }

    /*
     * For each entry in the list of targets to create, call CompatMake on
     * it to create the thing. CompatMake will leave the 'made' field of gn
     * in one of several states:
     *	    UPTODATE	    gn was already up-to-date
     *	    MADE  	    gn was recreated successfully
     *	    ERROR 	    An error occurred while gn was being created
     *	    ABORTED	    gn was not remade because one of its inferiors
     *	    	  	    could not be made due to errors.
     */
    errors = 0;
    while (!Lst_IsEmpty (targs)) {
	gn = (GNode *) Lst_DeQueue (targs);
	CompatMake (gn, gn);

	if (gn->made == UPTODATE) {
	    if (!queryFlag) {
		printf ("`%s' is up to date.\n", gn->name);
	    }
	} else if (gn->made == ABORTED) {
	    Error ("Target `%s' not remade because of errors.", gn->name);
	    errors += 1;
	}
    }
    /*
     * If we made it till here in query mode, that means nothing to be
     * remade has been found.
     */
    if (queryFlag) {
	exit (EXIT_OK);
    }

    /*
     * If the user has defined a .END target, run its commands.
     */
    if (errors == 0) {
	Lst_ForEach(ENDNode->commands, CompatRunCommand, (ClientData)gn);
    }
    Make_FreeCommands(ENDNode);
}
