/* @(#)make.c	1.136 07/03/07 Copyright 1985, 87, 88, 91, 1995-2007 J. Schilling */
#ifndef lint
static	char sccsid[] =
	"@(#)make.c	1.136 07/03/07 Copyright 1985, 87, 88, 91, 1995-2007 J. Schilling";
#endif
/*
 *	Make program
 *
 *	Copyright (c) 1985, 87, 88, 91, 1995-2007 by J. Schilling
 */
/*
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * See the file CDDL.Schily.txt in this distribution for details.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file CDDL.Schily.txt from this distribution.
 */

#include <schily/mconfig.h>
#include <stdio.h>
#include <schily/standard.h>
#include <schily/getargs.h>
#include <schily/errno.h>
#include <schily/stdlib.h>
#include <schily/string.h>
#include <schily/unistd.h>
#include <schily/time.h>
#include <schily/wait.h>
#include <signal.h>
#include "make.h"

#include <schily/dirent.h>
#include <schily/maxpath.h>
#include <schily/getcwd.h>
#include <schily/schily.h>
#if defined(__EMX__) || defined(__DJGPP__)
#include <process.h>
#endif
#include <schily/libport.h>
#include <schily/utime.h>

char	make_version[] = "1.2a41";

#ifdef	_FASCII
LOCAL	void	setup_env	__PR((void));
#endif
EXPORT	void	usage		__PR((int exitcode));
LOCAL	void	initmakefiles	__PR((void));
LOCAL	int	addmakefile	__PR((char *name));
LOCAL	void	read_defs	__PR((void));
LOCAL	void	read_makefiles	__PR((void));
EXPORT	void	setup_dotvars	__PR((void));
LOCAL	void	setup_vars	__PR((void));
LOCAL	void	setup_MAKE	__PR((char *name));
EXPORT	char	*searchtype	__PR((int mode));
LOCAL	void	printdirs	__PR((void));
LOCAL	int	addcommandline	__PR((char *  name));
LOCAL	void	read_cmdline	__PR((void));
EXPORT	void	doexport	__PR((char *));
LOCAL	void	read_environ	__PR((void));
EXPORT	int	main		__PR((int ac, char ** av));
LOCAL	void	check_old_makefiles __PR((void));
LOCAL	void	getmakeflags	__PR((void));
LOCAL	void	read_makemacs	__PR((void));
LOCAL	char	*nextmakemac	__PR((char *s));
LOCAL	BOOL	read_mac	__PR((char *mf));
LOCAL	void	setmakeflags	__PR((void));
LOCAL	char	*stripmacros	__PR((char *macbase, char *new));
LOCAL	void	setmakeenv	__PR((char *envbase, char *envp));
EXPORT	int	docmd		__PR((char * cmd, obj_t * obj));
EXPORT	BOOL	move_tgt	__PR((obj_t * from));
LOCAL	int	copy_file	__PR((char * from, char * objname));
EXPORT	BOOL	touch_file	__PR((char * name));
LOCAL	date_t	gcurtime	__PR((void));
LOCAL	date_t	gnewtime	__PR((void));
EXPORT	date_t	gftime		__PR((char * file));
LOCAL	BOOL	isdir		__PR((char * file));
EXPORT	Llong	gfileid		__PR((char * file));
EXPORT	char	*prtime		__PR((date_t  date));
LOCAL	void	handler		__PR((int signo));
LOCAL	void	exhandler	__PR((int excode, void *arg));
EXPORT	char	*curwdir	__PR((void));
LOCAL	char	*getdefaultsfile	__PR((void));
LOCAL	char	*searchfileinpath	__PR((char *name));
LOCAL	char	*searchonefile	__PR((char *name, char *nbuf, char *np, char *ep));
#ifndef	HAVE_PUTENV
EXPORT	int	putenv		__PR((const char *new));
#endif
#if defined(__DJGPP__)
LOCAL	char 	*strbs2s	__PR((char *s));
#endif

BOOL	posixmode	= FALSE;	/* We found a .POSIX target	*/
BOOL	Eflag		= FALSE;	/* -e Environment overrides vars*/
BOOL	Iflag		= FALSE;	/* -i Ignore command errors	*/
BOOL	Kflag		= FALSE;	/* -k Continue on unrelated tgts */
BOOL	Stopflag	= FALSE;	/* -S Stop on make errors 	*/
BOOL	NSflag		= FALSE;	/* -N Ignore no Source on dep.	*/
BOOL	Nflag		= FALSE;	/* -n Only show what to do	*/
BOOL	Qflag		= FALSE;	/* -q If up to date exit (0)	*/
BOOL	Rflag		= FALSE;	/* -r Turn off internal rules	*/
BOOL	Sflag		= FALSE;	/* -s Be silent			*/
BOOL	Tflag		= FALSE;	/* -t Touch objects		*/
int	Mlevel		= 0;		/* MAKE_LEVEL from environment	*/
int	Debug		= 0;		/* -d Print reason for rebuild	*/
int	XDebug		= 0;		/* -xd Print extended debug info*/
BOOL	Prdep		= FALSE;	/* -xM Print include dependency	*/
BOOL	Probj		= FALSE;	/* -probj   Print object tree	*/
BOOL	Print		= FALSE;	/* -p Print macro/target definitions*/
int	Dmake		= 0;		/* -D Display makefile		*/
BOOL	help		= FALSE;	/* -help    Show Usage		*/
BOOL	pversion	= FALSE;	/* -version Show version string	*/
BOOL	NoWarn		= FALSE;	/* -w No warnings		*/
BOOL	DoWarn		= FALSE;	/* -W Print extra warnings	*/
char	Makeflags[]	= "MAKEFLAGS";
char	Make_Flags[]	= "MAKE_FLAGS";
char	Make_Macs[]	= "MAKE_MACS";
char	Make_Level[]	= "MAKE_LEVEL";
char	Envdefs[]	= "Environment defs";
char	Makedefs[]	= "Internal Makefile";
char	Ldefaults[]	= "defaults.smk";
#ifdef	SVR4
char	Defaults[]	= "/opt/schily/lib/defaults.smk";
#else
char	Defaults[]	= "/usr/bert/lib/defaults.smk";
#endif
#define	MAKEFILECOUNT	32		/* Max number of Makefiles	*/
char	SMakefile[]	= "SMakefile";	/* smake's default Makefile	*/
char	Makefile[]	= "Makefile";	/* Primary default Makefile	*/
char	makefile[]	= "makefile";	/* Secondary default Makefile	*/
char   **MakeFileNames;			/* To hold all Makefilenames	*/
int	Mfileindex;			/* Current Makefile index	*/
int	Mfilesize;			/* Size of Makefile array	*/
int	Mfilecount;			/* Number of Makefiles found+2	*/
char	CmdLMac[]	= "Command Line Macro"; /* Makefile Name for ..	*/
char	**CmdLDefs;			/* To hold all Cmdline Macros	*/
int	Cmdlinecount;			/* Number of Cmdline Macros	*/
int	Cmdlinesize;			/* Size of Cmdline Macro array	*/
char	*MFCmdline;			/* Pointer to Cmdl. Macs fr. env*/

int	Mflags;

char	*ObjDir;		/* .OBJDIR: pathname Target destination dir */
int	ObjDirlen;		/* strlen(.OBJDIR)			    */
int	ObjSearch	= SALL;	/* .OBJSEARCH: searchtype for explicit rules*/
list_t	*SearchList;		/* .SEARCHLIST: list of src/obj dir pairs   */
list_t	*Suffixes;		/* .SUFFIXES: list of suffixes (POSIX)	    */
BOOL	SSuffrules;
obj_t	*Init;			/* .INIT: command to execute at startup	    */
obj_t	*Done;			/* .DONE: command do execute on success	    */
obj_t	*Failed;		/* .FAILED: command to execute on failure   */
obj_t	*IncludeFailed;		/* .INCLUDE_FAILED: cmd to exec if missing  */
obj_t	*Deflt;			/* .DEFAULT: command to execute if no rule  */
obj_t	*Precious;		/* .PRECIOUS: list of targets not to remove */
obj_t	*Phony;			/* .PHONY: list of false targets, no check  */
obj_t	*curtarget;		/* current target of actually running cmd   */
date_t	curtime;		/* Current time				    */
date_t	newtime;		/* Special time newer than all		    */

/*
 * POSIX requieres commands to be executed via 'sh -c command' which is
 * wrong as it causes make not to stop on errors if called with complex
 * commands (e.g. commands that contain a ';').
 *
 * We used to call /bin/sh -ce 'cmd' in former times which was correct,
 * but we got (unfortunately undocumented) problems on QNX and we changed
 * it to sh -c 'cmd' on 00/11/19. In hope that newer versions of QNX have
 * no problems with sh -ce, we changed it back on May 9th 2004.
 *
 * XXX Switching between both variants via .POSIX does not look like a good
 * XXX idea. We rather try to send a bug report against the POSIX standard.
 */
#define	POSIX_SHELL_CEFLAG	"-c"	/* Does not work correctly	*/
#define	SHELL_CEFLAG		"-ce"	/* Needed for correct behavior	*/
#define	SHELL_CFLAG		"-c"	/* Used with make -i		*/

LOCAL	char	ceflag[] = SHELL_CEFLAG; /* Used by default for cmds	*/
LOCAL	char	cflag[]  = SHELL_CFLAG;	/* Used with make -i		*/

#define	MINSECS		60
#define	HOURSECS	(MINSECS*60)
#define	DAYSECS		(HOURSECS*24)
#define	MONTHSECS	(DAYSECS*30)
#define	YEARSECS	(DAYSECS*365)

char	Nullstr[]	= "";
char	slash[]		= PATH_DELIM_STR;
int	slashlen	= sizeof (PATH_DELIM_STR) -1;	/* strlen(slash) */

LOCAL	BOOL	xpatrules;	/* Have non local pattern rules	in Makefile */
EXPORT	int	xssrules;	/* Have non local simple suffix rules	    */

#ifdef	_FASCII				/* Mark Williams C		*/
char	*_stksize	= (char *) 8192;

/*
 * Old and probably outdated setup that was needed in 1986 to make
 * 'smake' run on a ATARI ST using the Marc Williams C development tools.
 */
LOCAL void
setup_env()
{
	register char	*ep;
	extern	 char	*getenv();

	if ((ep = getenv("PATH")) == (char *) NULL || *ep == '\0')
		putenv("PATH=.bin,,\\bin,\\lib");

	if ((ep = getenv("SUFF")) == (char *) NULL || *ep == '\0')
		putenv("SUFF=,.prg,.tos,.ttp");

	if ((ep = getenv("LIBPATH")) == (char *) NULL || *ep == '\0')
		putenv("LIBPATH=\\lib,\\bin");

	if ((ep = getenv("TMPDIR")) == (char *) NULL || *ep == '\0')
		putenv("TMPDIR=\\tmp");

	if ((ep = getenv("INCDIR")) == (char *) NULL || *ep == '\0')
		putenv("INCDIR=\\include");
}
#endif

EXPORT void
usage(exitcode)
	int	exitcode;
{
	error("Usage:	smake [options] [target...] [macro=value...]\n");
	error("Options:\n");
	error("	-e	Environment overrides variables in Makefile.\n");
	error("	-i	Ignore errors from commands.\n");
	error("	-k	Ignore target errors, continue on unrelated targets.\n");
	error("	-N	Continue if no source for nonexistent dependencies found.\n");
	error("	-n	Don't make - only say what to do.\n");
	error("	-p	Print all macro and target definitions.\n");
	error("	-q	Question mode. Exit code is 0 if target is up to date.\n");
	error("	-r	Turn off internal rules.\n");
	error("	-s	Be silent.\n");
	error("	-t	Touch Objects instead of executing defined commands.\n");
	error("	-w	Don't print warning Messages.\n");
	error("	-W	Print extra (debug) warning Messages.\n");
	error("	-D	Display Makefiles as read in.\n");
	error("	-DD	Display Makefiles/Rules as read in.\n");
	error("	-d	Print reason why a target has to be rebuilt.\n");
	error("	-dd	Debug dependency check.\n");
	error("	-xM	Print include dependency.\n");
	error("	-xd	Print extended debug info.\n");
	error("	-probj	Print object tree.\n");
	error("	-help	Print this help.\n");
	error("	-version Print version number.\n");
	error("	-posix	Force POSIX behaviour.\n");
	error("	mf=makefilename | -f makefilename\n");
	error("More than -f makefile option may be specified.\n");
	exit(exitcode);
}

LOCAL void
initmakefiles()
{
	/*
	 * Mfilecount	als Index fuer MakeFileNames[] bei addmakefile()
	 * Mfileindex	als globale Index Variable fuer den Parser
	 *
	 * This code needs to be kept in sync with the MF_IDX_* #defines
	 * in make.h
	 */
	Mfilecount = 0;
	addmakefile(Makedefs);		/* Implicit rules		*/
	addmakefile(Envdefs);		/* Environment strings		*/
	addmakefile(Makefile);		/* Default make file		*/
	Mfilecount--;			/* -f name overwrites Makefile	*/
}

/*
 * Add a new makefile to the list of makefiles.
 * Called by getargs() if a -f option was found.
 */
LOCAL int
addmakefile(name)
	char	*name;
{
	if (MakeFileNames == NULL) {
		/*
		 * Use a default size of 4 as we usually have 4 Makefiles.
		 */
		Mfilesize = 4;
		MakeFileNames = malloc(Mfilesize * sizeof (char *));
	} else if (Mfilesize <= (Mfilecount+1)) { /* One spare for CmdLMac */
		Mfilesize += 4;
		MakeFileNames = realloc(MakeFileNames, Mfilesize * sizeof (char *));
	}
	if (MakeFileNames == NULL)
		comerr("No memory for Makefiles.\n");

	MakeFileNames[Mfilecount++] = name;

	return (1);
}

/*
 * Read the default rules - either compiled in or from file.
 */
LOCAL void
read_defs()
{
		int	MFsave = Mfileindex;
		char	*deflts;
	extern	char	implicit_rules[]; /* Default rules compiled into make */

	Dmake--;
	Mfileindex = MF_IDX_IMPICIT;	/* Index 0 Implicit Rules */

	if (gftime(Ldefaults) != 0) {
		MakeFileNames[0] = Ldefaults;
		readfile(Ldefaults, TRUE);
#ifdef	DEFAULTS_PATH_SEARCH_FIRST
	} else if ((deflts = getdefaultsfile()) != NULL) {
		MakeFileNames[0] = deflts;
		readfile(deflts, TRUE);
#endif
#ifdef	DEFAULTS_PATH				/* This is the install path */
	} else if (gftime(DEFAULTS_PATH) != 0) {
		MakeFileNames[0] = DEFAULTS_PATH;
		readfile(DEFAULTS_PATH, TRUE);
#endif
	} else if (gftime(Defaults) != 0) {
		MakeFileNames[0] = Defaults;
		readfile(Defaults, TRUE);
#ifndef	DEFAULTS_PATH_SEARCH_FIRST
	} else if ((deflts = getdefaultsfile()) != NULL) {
		MakeFileNames[0] = deflts;
		readfile(deflts, TRUE);
#endif
	} else {
		readstring(implicit_rules, Makedefs);
	}
	Dmake++;
	Mfileindex = MFsave;
}

/*
 * Read in all external makefiles. Use either the names from command line
 * or look for the default names "Makefile" and "makefile".
 */
LOCAL void
read_makefiles()
{
	int	MFsave = Mfileindex;
	patr_t	*oPatrules = Patrules;
	patr_t	**opattail = pattail;

	Patrules = 0;
	pattail = &Patrules;
	xssrules = 0;

	Mfileindex = MF_IDX_MAKEFILE;	/* Index 2 Default Makefile */
	if (Mfilecount == MF_IDX_MAKEFILE) {
		/*
		 * First look for "SMakefile",
		 * then for "Makefile", then for "makefile"
		 */
		if (gftime(SMakefile) != 0) {		/* "SMakefile" */
			Mfilecount++;
			MakeFileNames[2] = SMakefile;
		} else if (gftime(Makefile) != 0) {	/* "Makefile" */
			Mfilecount++;
			MakeFileNames[2] = Makefile;
		} else if (gftime(makefile) != 0) {	/* "makefile" */
			Mfilecount++;
			MakeFileNames[2] = makefile;
		}
	}
	while (Mfileindex < Mfilecount) {
		readfile(MakeFileNames[Mfileindex], TRUE);
		Mfileindex++;
	}

	/*
	 * Check for external pattern rule definitions.
	 */
	xpatrules = Patrules != NULL;
	/*
	 * The pattern rules which are defined in the external makefiles
	 * must superseede the pattern rules from the internal rules.
	 * Concat the pattern rules found in internal rules to the end of
	 * the patern rules list from external makefiles.
	 */
	*pattail = oPatrules;
	pattail = opattail;

	Mfileindex = MFsave;
}

/*
 * Setup special variables.
 *
 * NOTE: Must be reentrant because it may be called more than once
 *	 if the "include" directive is used.
 *	 As there is (currently) no way to delete an object
 *	 it is OK not to do anything special if objlook() returns NULL.
 */
EXPORT void
setup_dotvars()
{
	obj_t	*obj;
	list_t	*l;
	char	*name;

	obj = objlook(".OBJDIR", FALSE);
	if (obj != NULL && obj->o_type != ':')	/* Must be a special target */
		obj = NULL;

	if (obj != NULL) {
		if (obj->o_list && (ObjDir = obj->o_list->l_obj->o_name)) {
			if (gfileid(ObjDir) == gfileid(".")) {
				/*
				 * Do not allow moving targets to themselves.
				 */
				ObjDir = NULL;
			}
			ObjDirlen = strlen(ObjDir);
		}
	}
#ifdef	no_longer_needed	/* Has been moved to dynmac expansion */
	/*
	 * XXX We cannot do this here as we are called more than once and
	 * XXX we like to allow $O to be overwritten from Makefiles that
	 * XXX do not know about smake's special features.
	 */
	/*
	 * Create special variable $O -> ObjDir
	 */
	define_var("O", ObjDir ? ObjDir : ".");
}
#endif

	obj = objlook(".OBJSEARCH", FALSE);
	if (obj != NULL && obj->o_type != ':')	/* Must be a special target */
		obj = NULL;
	if (obj != NULL) {
		if ((l = obj->o_list) != NULL) {
			name = l->l_obj->o_name;
			if (streql("src", name)) {
				ObjSearch = SSRC;
			} else if (streql("obj", name)) {
				ObjSearch = SOBJ;
			} else if (streql("all", name)) {
				ObjSearch = SALL;
			} else {
				/*
				 * This is the default.
				 */
				ObjSearch = SALL;
			}
		}
	}

	obj = objlook(".SEARCHLIST", FALSE);
	if (obj != NULL && obj->o_type != ':')	/* Must be a special target */
		obj = NULL;
	if (obj != NULL) {
		SearchList = obj->o_list;
	} else if ((obj = objlook("VPATH", FALSE)) != NULL && obj->o_type == '=') {
		SearchList = cvtvpath(obj->o_list);
	}

	if (objlook(".IGNORE", FALSE))
		Iflag = TRUE;
	if (objlook(".SILENT", FALSE))
		Sflag = TRUE;

	Init = objlook(".INIT", FALSE);
	Done = objlook(".DONE", FALSE);
	Failed = objlook(".FAILED", FALSE);
	IncludeFailed = objlook(".INCLUDE_FAILED", FALSE);
	if (IncludeFailed != NULL && IncludeFailed->o_cmd == NULL)
		IncludeFailed = NULL;
	Deflt = objlook(".DEFAULT", FALSE);
	Precious = objlook(".PRECIOUS", FALSE);
	Phony = objlook(".PHONY", FALSE);

	if (objlook(".POSIX", FALSE))
		posixmode = TRUE;
	obj = objlook(".SUFFIXES", FALSE);
	if (obj != NULL && obj->o_type != ':')	/* Must be a special target */
		obj = NULL;
	if (obj != NULL)
		Suffixes = obj->o_list;
	if (Debug > 1 && Suffixes != (list_t *) NULL) {
		register list_t *p;

		error(".SUFFIXES :\t");
		for (p = Suffixes; p; p = p->l_next)
			error("%s ", p->l_obj->o_name);
		error("\n");
	}
	SSuffrules = check_ssufftab();
}

/*
 * Set up some known special macros.
 */
LOCAL void
setup_vars()
{
	int	i;
	char	make_level[64];
	char	*p;

	define_var("$", "$");			/* Really needed ? */
	define_var("MAKE_NAME", "smake");	/* Needed to identify syntax */
	define_var("MAKE_VERSION", make_version); /* Version dependant files? */
	if ((p = getenv(Make_Level)) != NULL) {
		p = astoi(p, &i);
		if (*p == '\0')
			Mlevel = i;
	}
	snprintf(make_level, sizeof (make_level), "%d", Mlevel+1);
	define_var(Make_Level, make_level);
	doexport(Make_Level);
}

/*
 * Set up the special macro $(MAKE).
 * If we were called with an absolute PATH or without any '/', use argv[0],
 * else compute the absolute PATH by prepending working dir to argv[0].
 */
LOCAL void
setup_MAKE(name)
	char	*name;
{
	char	wd[MAXPATHNAME + 1];
	int	len;

	/*
	 * If argv[0] starts with a slash or contains no slash,
	 * or on DOS like OS starts with MS-DOS drive letter,
	 * it is useful as $(MAKE).
	 */
#ifdef HAVE_DOS_DRIVELETTER
	if (name[0] == SLASH || strchr(name, SLASH) == NULL || name[1] == ':') {
#else
	if (name[0] == SLASH || strchr(name, SLASH) == NULL) {
#endif
		define_var("MAKE", name);
	} else {
		/*
		 * Compute abs pathname for $(MAKE)
		 */
		strncpy(wd, curwdir(), sizeof (wd));
		wd[sizeof (wd)-1] = '\0';
		len = strlen(wd);
		if ((strlen(name) + len + 2) < sizeof (wd)) {
			strcat(wd, PATH_DELIM_STR);
			strcat(wd, name);
		}
		define_var("MAKE", wd);
	}
}

/*
 * Transfer object search types into human readable names.
 */
EXPORT char *
searchtype(mode)
	int	mode;
{
	if (mode == SSRC)
		return ("src");
	if (mode == SOBJ)
		return ("obj");
	if (mode == SALL)
		return ("all");
	return ("invalid Object search mode");
}

/*
 * Print some 'smake' special macros:
 * .OBJDIR, .SEARCHLIST and .OBJSEARCH
 */
LOCAL void
printdirs()
{
	if (ObjDir != NULL)
		error(".OBJDIR :\t%s\n", ObjDir);

	error(".OBJSEARCH :\t%s\n", searchtype(ObjSearch));

	if (SearchList != (list_t *) NULL) {
		register list_t *p;
			error(".SEARCHLIST :\t");
		for (p = SearchList; p; p = p->l_next)
			error("%s ", p->l_obj->o_name);
		error("\n");
	}
}

/*
 * Add a command line macro to our list.
 * This is called by getargs().
 */
LOCAL int
addcommandline(name)
	char	*name;
{
	if (Debug > 1)
		error("got_it: %s\n", name);

/* UNIX make: ":;=$\n\t"	*/

	if (!strchr(name, '='))
		return (NOTAFILE); /* Tell getargs that this may be a flag */
	if (CmdLDefs == NULL) {
		Cmdlinesize = 8;
		CmdLDefs = malloc(Cmdlinesize * sizeof (char *));
	} else if (Cmdlinesize <= Cmdlinecount) {
		Cmdlinesize += 8;
		CmdLDefs = realloc(CmdLDefs, Cmdlinesize * sizeof (char *));
	}
	if (CmdLDefs == NULL)
		comerr("No memory for Commandline Macros.\n");

	CmdLDefs[Cmdlinecount++] = name;
	return (1);
}

/*
 * Read in and parse all command line macro definitions from list.
 */
LOCAL void
read_cmdline()
{
		int	MFsave = Mfileindex;
	register int	i;

	if (Cmdlinecount == 0)
		return;

	/*
	 * Register the command line macros past the last makefile
	 */
	Mfileindex = Mfilecount;
	if (Mfileindex == MF_IDX_MAKEFILE)
		Mfileindex = MF_IDX_MAKEFILE + 1;
	MakeFileNames[Mfileindex] = CmdLMac;

	Mflags |= F_READONLY;
	for (i = 0; i < Cmdlinecount; i++) {
		readstring(CmdLDefs[i], CmdLMac);
		putenv(CmdLDefs[i]);
	}
	Mflags &= ~F_READONLY;
	Mfileindex = MFsave;
}

/*
 * Export a macro into the environment.
 * This is mainly done by the "export" directive inside a makefile.
 */
EXPORT void
doexport(oname)
	char	*oname;
{
	obj_t	*obj;
	list_t	*l;
	char	*name;
	int	len;

	obj = objlook(oname, FALSE);
	if (obj != NULL && basetype(obj->o_type) != '=') /* Must be a macro type target */
		obj = NULL;
	if (obj != NULL) {
		if ((l = obj->o_list) != NULL) {
			char	*xname;

			len = strlen(oname)+1;	/* Env name + '=' */
			while (l && l->l_obj->o_name) {
				xname = l->l_obj->o_name;
				if (gbuf != NULL)
					xname = substitute(xname,
								NullObj, 0, 0);
				len += strlen(xname)+1;
				l = l->l_next;
			}
			name = malloc(len);
			if (name == NULL)
				comerr("Cannot alloc memory for env.\n");
			name[0] = '\0';
			l = obj->o_list;
			strcat(name, oname);
			strcat(name, "=");
			while (l && l->l_obj->o_name) {
				xname = l->l_obj->o_name;
				if (gbuf != NULL)
					xname = substitute(xname,
								NullObj, 0, 0);
				strcat(name, xname);
				if (l->l_next == NULL)
					break;
				strcat(name, " ");
				l = l->l_next;
			}
			putenv(name);
		}
	}
}

/*
 * Read in and parse all environment vars to make them make macros.
 */
LOCAL void
read_environ()
{
		int	MFsave = Mfileindex;
	register char	**env;
	extern	char	**environ;
	char *ev;
	char *p;

	Dmake -= 2;
	Mfileindex = MF_IDX_ENVIRON;	/* Index 1 Environment vars */

	if (Eflag)
		Mflags |= F_READONLY;
	mfname = Envdefs;
	for (env = environ; *env; env++) {
		ev = *env;
		p = strchr(ev, EQUAL);
		if (p == NULL)
			continue;
		if (strncmp(ev, "SHELL=", 6) == 0)
			continue;	/* Never import SHELL */
		*p = '\0';
		define_var(ev, &p[1]);
		*p = EQUAL;
	}
	mfname = NULL;
	Mflags &= ~F_READONLY;

	Dmake += 2;
	Mfileindex = MFsave;
}


EXPORT int
main(ac, av)
	int	ac;
	char	*av[];
{
		int	failures = 0;
		int	i;
		int	cac = ac;
		char	* const *cav = av;
	static	char	options[] = "help,version,posix,e,i,k,n,N,p,q,r,s,S,t,w,W,d+,D+,xM,xd+,probj,mf&,f&,&";

	save_args(ac, av);

#ifdef	__DJGPP__
	set_progname("smake");	/* We may have strange av[0] on DJGPP */
#endif

#ifdef	HAVE_GETPID
	getpid();		/* Give some info for truss(1) users */
#endif
#ifdef	HAVE_GETPGRP
	getpgrp();		/* Give some info for truss(1) users */
#endif

#ifdef	_FASCII			/* Mark Williams C	  */
	stderr->_ff &= ~_FSTBUF; /* setbuf was called ??? */

	setup_env();
#endif
	getmakeflags();		/* Default options from MAKEFLAGS=	*/
	initmakefiles();	/* Set up MakeFileNames[] array		*/

	cac--; cav++;
	if (getallargs(&cac, &cav, options, &help, &pversion, &posixmode,
			&Eflag, &Iflag, &Kflag, &Nflag, &NSflag, &Print,
			&Qflag, &Rflag, &Sflag, &Stopflag, &Tflag,
			&NoWarn, &DoWarn,
			&Debug, &Dmake, &Prdep, &XDebug, &Probj,
			addmakefile, NULL,
			addmakefile, NULL,
			addcommandline, NULL) < 0) {
		errmsgno(EX_BAD, "Bad flag: %s.\n", cav[0]);
		usage(EX_BAD);
	}
	if (help)
		usage(0);
	if (pversion) {
		printf("Smake release %s (%s-%s-%s) Copyright (C) 1985, 87, 88, 91, 1995-2007 Jörg Schilling\n",
				make_version,
				HOST_CPU, HOST_VENDOR, HOST_OS);
		exit(0);
	}
	/*
	 * XXX Is this the right place to set the options and cmd line macros
	 * XXX to the exported environment?
	 * XXX Later in read_makemacs() we may find that MAKEFLAGS= may contain
	 * XXX garbage that has been propagated to MFCmdline.
	 * For this reason, we call read_makemacs() before, let it parse only
	 * and kill any unwanted content from MFCmdline.
	 */
	if (NullObj == 0)	/* First make sure we may expand vars	*/
		NullObj = objlook(Nullstr, TRUE);

	read_makemacs();	/* With gbuf == NULL, this is parse only */
	setmakeflags();
	if (Qflag) {
		Sflag = TRUE;
		Nflag = TRUE;
	}
	if (Stopflag) {
		Kflag = FALSE;
	}
	if (Debug > 0)
		error("MAKEFLAGS value: '%s'\n", getenv(Makeflags));

	/*
	 * XXX Reihenfolge bei UNIX make beachten!!!
	 */
	if (signal(SIGINT, SIG_IGN) != SIG_IGN)
		signal(SIGINT, handler);
#ifdef	SIGQUIT
	if (signal(SIGQUIT, SIG_IGN) != SIG_IGN)
		signal(SIGQUIT, handler);
#endif
#ifdef	SIGHUP
	if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
		signal(SIGHUP, handler);
#endif
	if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
		signal(SIGTERM, handler);
	curtime = gcurtime();
	newtime = gnewtime();

	initchars();
	initgbuf(NAMEMAX);	/* Now the Makefile parser becomes usable */

	/*
	 * The functions read_cmdline() and read_makemacs() are setting
	 * the F_READONLY flag in all objects. The function read_environ()
	 * sets F_READONLY if the -e option was specified.
	 * This differs from the description in POSIX for 'make' but it
	 * is the only way to allow 'include' directives to work as expected.
	 * For the same reason, we are reading the macros in the opposite
	 * order as described in POSIX.
	 */
	read_cmdline();		/* First read cmd line macros		*/
	read_makemacs();	/* then the inherited cmd line macros	*/
	if (!Rflag)
		read_defs();	/* read "defaults.smk"			*/
	setup_MAKE(av[0]);	/* Set up $(MAKE)			*/
	setup_vars();		/* Set up some known special macros	*/
	setup_arch();		/* Set up srch specific macros		*/
	read_environ();		/* Sets F_READONLY if -e flag is present*/

	if (Debug > 0 && Mlevel > 0)
		error("Starting '%s'[%d] in directory   '%s'\n",
			av[0], Mlevel, curwdir());

	/*
	 * Clear default target, then look again in makefiles
	 */
	default_tgt = NULL;
	read_makefiles();

	/*
	 * Let all objects created later seem to bee in
	 * last Makefile or in implicit rules if no
	 * Makefile is present.
	 */
	Mfileindex = Mfilecount - 1;
	if (Mfileindex < MF_IDX_MAKEFILE)
		Mfileindex = MF_IDX_IMPICIT;

	setup_dotvars();
	if (!Rflag)
		check_old_makefiles();

	if (Probj)		/* -probj Flag				*/
		printtree();
	if (Print) {		/* -p Flag				*/
		prtree();
		exit(0);	/* XXX Really exit() here for make -p -f /dev/null ??? */
				/* XXX Posix requires make -p -f /dev/null 2>/dev/null */
				/* XXX to print the internal macros	*/
	}
	on_comerr(exhandler, av[0]);
	if (Debug > 0)
		printdirs();	/* .OBJDIR .OBJSEARCH .SEARCHLIST	*/

	makeincs();		/* Re-make included files */
	omake(Init, TRUE);
	cac = ac;
	cav = av;
	cac--; cav++;
	for (i = 0; getfiles(&cac, &cav, options); cac--, cav++, i++)
		if (!domake(cav[0]))
			failures++;

	if (i == 0 && !domake((char *) NULL))
		failures++;
	if (failures && Failed) {
		omake(Failed, TRUE);
	} else {
		omake(Done, TRUE);
	}
#ifdef	DEBUG
	prmem();
#endif
	if (failures > 0)
		comexit(1);
	comexit(0);
	return (0);		/* keep lint happy :-) */
}

/*
 * Check for old makefile systems without patten matching rules.
 *
 * Old makefile systems only define simple suffix rules. Now that newer smake
 * releases support POSIX suffix rules, the makefile system will only work
 * if the makefile system uses pattern matching rules.
 * We may remove this function in 2005.
 */
LOCAL void
check_old_makefiles()
{
	obj_t	*o;

	if (Suffixes == NULL)	/* No/Empty .SUFFIXES, no compat probelems */
		return;
	if (xssrules == 0)	/* Makefile did not define simple suff rule*/
		return;
	if (xpatrules)		/* New makefiles define pattern rules	   */
		return;

	/*
	 * All makefiles from the Schily (SING) makefile system define
	 * _UNIQ=.XxZzy-
	 */
	if ((o = objlook("_UNIQ", FALSE)) == NULL)
		return;
	if (!streql(".XxZzy-", o->o_list->l_obj->o_name))
		return;

	errmsgno(EX_BAD, "WARNING: this project uses an old version of the makefile system.\n");
	comerrno(EX_BAD, "Update the makefiles or call 'smake -r'.\n");
}

/*
 * Read in and parse the makeflags we got from the MAKEFLAGS= environment var.
 * MAKEFLAGS= may be:
 *
 * -	"et..."			Only option letters.
 *				No space and no macro definitions.
 * -	"NAME=value..."		Only a list of macro definitions (like a
 *				make command line).
 * -	"-et..."		Options as they appear on the command line.
 *				Space and multiple '-' are allowed.
 *	"-et -- NAME=value..."	A complete make command line (except
 *				-f filename options).
 */
LOCAL void
getmakeflags()
{
	int	MFsave = Mfileindex;
	char	*mf = getenv(Makeflags);

	if (!mf || *mf == '\0')	/* No MAKEFLAGS= or empty MAKEFLAGS=	*/
		return;

	Mfileindex = MF_IDX_ENVIRON;	/* Index 1 Environment vars */
	if (*mf != '-') {	/* Only macros if does not start with '-'*/
		char *p = nextmakemac(mf);	/* Next unescaped ' '    */
		char *eql = strchr(mf, '=');

		/*
		 * Be gracious to non POSIX make programs like 'GNUmake' which
		 * may have "e -- MAKE=smake" in the MAKEFLAGS environment.
		 * The correct string would rather be "-e -- MAKE=smake".
		 */
		if (eql != NULL && (p == NULL || eql < p)) {
			/*
			 * No options at all, only cmdline macros.
			 */
			MFCmdline = mf;
			goto out;	/* Allow debug prints */
		}
	}

	while (*mf) {
		switch (*mf) {

		case ' ':
			break;		/* Ignore blanks */

		case '-':		/* look for " -- " separator */
			if (mf[1] == '-') {
				if (mf[2] != ' ') {
					char *p = nextmakemac(mf);

					errmsgno(EX_BAD,
					"Found illegal option '%s' in MAKEFLAGS.\n",
						mf);
					if (p != NULL) {
						size_t	d = p - mf;
						if (d > 50)
							d = 50;
						errmsgno(EX_BAD,
						"Skipping illegal option '%.*s'.\n",
							(int)d, mf);
						mf = p;
						break;
					} else {
						errmsgno(EX_BAD,
						"Ignoring illegal option '%s'.\n",
							mf);
					}
					goto out; /* Allow debug prints */
				}
				MFCmdline = &mf[3];
				goto out;	/* Allow debug prints */
			}
			break;		/* Ignore single '-' */

		case 'D':		/* Display makefile */
			Dmake++;
			break;

		case 'd':		/* Debug */
			Debug++;
			break;

		case 'X':		/* XDebug */
			XDebug++;
			break;

		case 'e':		/* Environment overrides vars */
			Eflag = TRUE;
			break;

		case 'i':		/* Ignore errors from cmds */
			Iflag = TRUE;
			break;

		case 'k':		/* Ignore target errors */
			Kflag = TRUE;
			break;

		case 'N':		/* Ignore no Source on dep. */
			NSflag = TRUE;
			break;

		case 'n':		/* Do not exec any commands */
			Nflag = TRUE;
			break;

		case 'p':		/* Print macros/targets */
			Print = TRUE;
			break;

		case 'q':		/* Question */
			Qflag = TRUE;
			break;

		case 'r':		/* Turn off internal Rules */
			Rflag = TRUE;
			break;

		case 's':		/* Silent */
			Sflag = TRUE;
			break;

		case 'S':		/* Stop on error (opposite of -k) */
			Stopflag = TRUE;
			break;

		case 't':		/* Touch */
			Tflag = TRUE;
			break;

		case 'W':		/* Extra Warnings */
			DoWarn = TRUE;
			break;

		case 'w':		/* No Warnings */
			NoWarn = TRUE;
			break;

		case 'Z':		/* Print includes */
			Prdep = TRUE;
			break;
		}
		mf++;
	}
out:
	/*
	 * As this is called before we call getargs(), Debug may only be true
	 * if the 'd' option is present in the MAKEFLAGS environment.
	 */
	if (Debug > 0)
		error("Read MAKEFLAGS:  '%s'\n", getenv(Makeflags));

	Mfileindex = MFsave;
}

/*
 * Parse a list of macro=value command line macros from the
 * MAKEFLAGS= environment and set up the macro in the make tree.
 * If gbuf is NULL, read_mac() is parse only.
 */
LOCAL void
read_makemacs()
{
	register char	*mf = MFCmdline;
	register char	*p;
		int	MFsave = Mfileindex;

	if (mf == NULL)
		return;

	Mfileindex = MF_IDX_ENVIRON;	/* Index 1 Environment vars */
	while (*mf) {
		p = nextmakemac(mf);
		if (p == NULL) {	/* No other macro def follows */
			if (!read_mac(mf))
				*mf = '\0';
			break;
		} else {		/* Need to temporarily null terminate */
			*p = '\0';
			if (!read_mac(mf)) {
				strcpy(mf, &p[1]);
			} else {
				*p = ' ';
				mf = &p[1];
			}
		}
	}
	Mfileindex = MFsave;
}

/*
 * Find next un-escaped blank (' ') which is a separator
 * for a list of macro=value items.
 */
LOCAL char *
nextmakemac(s)
	char	*s;
{
	while (*s) {
		if (*s == '\\') {	/* escaped character	*/
			if (*++s == '\0')
				return (NULL);
		} else if (*s == ' ') {	/* un-escaped space	*/
			return (s);
		}
		s++;
	}
	return (NULL);
}

/*
 * Remove the escapes that have been introduced before the name=value
 * lists are put together into the MAKEFLAGS= environment.
 * Then parse the result as a string.
 * If gbuf is NULL, read_mac() is parse only.
 */
LOCAL BOOL
read_mac(mf)
	char	*mf;
{
	char	macdef[NAMEMAX*2+1];
	char	*p;

	p = macdef;
	while (*mf) {
		if (p >= &macdef[NAMEMAX*2])
			break;
		/*
		 * Only remove those escape sequences, that we created.
		 * This is "\\" and "\ ".
		 */
		if (mf[0] == '\\' && (mf[1] == '\\' || mf[1] == ' '))
			mf++;
		*p++ = *mf++;
	}
	*p = '\0';

	if (macdef[0] == '\0')			/* Ignore empty definition */
		return (TRUE);
	if (strchr(macdef, '=') == NULL) {	/* Check if it is a macro def*/
		errmsgno(EX_BAD,
			"Found illegal macro definition '%s' in MAKEFLAGS.\n",
			macdef);
		return (FALSE);
	}

	if (gbuf == NULL)			/* Parse only and kill	   */
		return (TRUE);			/* unwanted content	   */

	Mflags |= F_READONLY;
	readstring(macdef, Makeflags);
	Mflags &= ~F_READONLY;
	return (TRUE);
}

/*
 * Prepare the MAKEFLAGS= environment for export.
 */
LOCAL void
setmakeflags()
{
		/*
		 * MAKEFLAGS=-	12 bytes incl '\0'
		 * 3 x 8 bytes=	24 bytes
		 * 12 flags	12 bytes
		 * '-- '	 3 bytes
		 * =====================
		 *		51 bytes
		 */
#define	MAKEENV_SIZE_STATIC	64
static	char	makeenv[MAKEENV_SIZE_STATIC];
	char	*p;
	int	i;

	p = strcatl(makeenv, Makeflags, (char *)NULL);
	*p++ = '=';
	*p++ = '-';		/* Posix make includes '-'	*/
				/* "MAKEFLAGS=-" 12 incl. '\0'	*/

	i = Dmake;		/* Display makefile */
	if (i > 8)
		i = 8;
	while (--i >= 0)
		*p++ = 'D';

	i = Debug;		/* Debug */
	if (i > 8)
		i = 8;
	while (--i >= 0)
		*p++ = 'd';

	i = XDebug;		/* XDebug */
	if (i > 8)
		i = 8;
	while (--i >= 0)
		*p++ = 'X';

	if (Eflag)		/* Environment overrides vars */
		*p++ = 'e';
	if (Iflag)		/* Ignore errors from cmds */
		*p++ = 'i';
	if (Kflag)		/* Ignore target errors */
		*p++ = 'k';
	if (NSflag)		/* Ignore no Source on dep. */
		*p++ = 'N';
	if (Nflag)		/* Do not exec any commands */
		*p++ = 'n';
	if (Print)		/* Print macros/targets */
		*p++ = 'p';
	if (Qflag)		/* Question */
		*p++ = 'q';
	if (Rflag)		/* Turn off internal Rules */
		*p++ = 'r';
	if (Sflag)		/* Silent */
		*p++ = 's';
	if (Stopflag)		/* Stop on error (opposite of -k) */
		*p++ = 'S';
	if (Tflag)		/* Touch */
		*p++ = 't';
	if (DoWarn)		/* Extra Warnings */
		*p++ = 'W';
	if (NoWarn)		/* No Warnings */
		*p++ = 'w';
	if (Prdep)		/* Print includes */
		*p++ = 'Z';

	if (p - makeenv == 11)	/* Empty flags, remove '-' */
		--p;
	*p = '\0';
	define_var(Make_Flags, &makeenv[10]);	/* MAKE_FLAGS= ... */
	doexport(Make_Flags);
	setmakeenv(makeenv, p);			/* Add cmdline macs */
}

/*
 * Strip out macro defs inherited from MAKELAGS, that will be overwritten
 * by command line macro defs.
 * Return new write poiner at end of string.
 */
LOCAL char *
stripmacros(macbase, new)
	char	*macbase;
	char	*new;
{
	char	*p = strchr(new, '=');
	char	*p2;

	if (p == NULL)				/* Paranoia */
		return (macbase + strlen(macbase));

	do {
		p2 = nextmakemac(macbase);	/* Find next macro delim */

		if (strncmp(macbase, new, p - new) == 0) {
			/*
			 * Got a match, need to remove this entry.
			 */
			if (p2 == NULL) {	/* This is the only, zap out */
				*macbase = '\0';
			} else {		/* Copy rest over current */
				strcpy(macbase, &p2[1]);
			}
		} else if (p2) {		/* Continue with next extry */
			macbase = &p2[1];
		}
	} while (p2);
	return (macbase + strlen(macbase));
}

/*
 * Add the actual command line macro definitions to the MAKEFLAGS= string
 * and then putenv() the result.
 */
LOCAL void
setmakeenv(envbase, envp)
	char	*envbase;
	char	*envp;
{
	register int	i;
	register int	l;
	register int	len = 0;
	register char	*p;
	register char	*macbase;

	if (Cmdlinecount == 0 && (MFCmdline == 0 || *MFCmdline == '\0')) {
		/*
		 * No command line macros and no inherited command line
		 * macros from MAKEFLAGS, so just call putenv() and return.
		 */
		putenv(envbase);
		return;
	}

	if ((envp - envbase) > 10) {	/* envbase[] is currently not empty */
		strcpy(envp, " -- ");	/* we need a separator to the flags */

		envp += 4;
		*envp = '\0';
	}
	if (MFCmdline)			/* Add one for '\0' or ' ' at end */
		len = strlen(MFCmdline) + 1;

	for (i = 0; i < Cmdlinecount; i++) {
		p = CmdLDefs[i];
		while (*p) {
			if (*p == '\\' || *p == ' ')
				len++;
			len++;
			p++;
		}
		len += 1;		/* Add one for '\0' or ' ' at end */
	}

	l = strlen(envbase) + len + 1;	/* Add one (see stripmacro comment) */
	if (l > MAKEENV_SIZE_STATIC) {
		p = malloc(l);
		strcpy(p, envbase);
		envp = p + (envp - envbase);
		envbase = p;
	}

	macbase = envp;
	if (MFCmdline) {
		for (p = MFCmdline; *p; )
			*envp++ = *p++;
		*envp++ = ' ';
	}
	*envp = '\0';

	for (i = 0; i < Cmdlinecount; i++) {
		p = CmdLDefs[i];
		envp = stripmacros(macbase, p);
		while (*p) {
			if (*p == '\\' || *p == ' ')
				*envp++ = '\\';
			*envp++ = *p++;
		}
		*envp++ = ' ';
		*envp = '\0';	/* Needed for stripmacros */
	}			/* But overshoots by one  */
	*--envp = '\0';
	putenv(envbase);
	define_var(Make_Macs, macbase);		/* MAKE_MACS= ... */
	doexport(Make_Macs);
}

#define	iswhite(c)	((c) == ' ' || (c) == '\t')

#if	defined(__linux__) || defined(__linux) || defined(SHELL_IS_BASH)
/*
 * Needed to handle the Linux bash signal bug.
 */
int	lpid;
#endif

/*
 * Execute or print a command line.
 * Return exit code from command line.
 */
EXPORT int
docmd(cmd, obj)
	register char	*cmd;
	obj_t		*obj;
{
	int	code;
	int	pid = 0;
	int	retries;
	int	Exit;
	int	Silent = Sflag;
	int	NoError = Iflag;
	int	NoExec = Nflag;
	BOOL	foundplus = FALSE;
	obj_t	*shello;
	char	*shell = NULL;

	while (iswhite(*cmd))
		cmd++;
/*
 * '-' Ignore error, '@' Silent, '+' Always execute
 * '?' No command dependency checking, '!' Force command dependency checking
 */
	for (code = 0; code < 5 &&
	    (*cmd == '@' || *cmd == '-' || *cmd == '+' ||
			*cmd == '?' || *cmd == '!');
							code++, cmd++) {
		if (*cmd == '@')
			Silent = TRUE;
		if (*cmd == '-')
			NoError = TRUE;
		if (*cmd == '+') {
			NoExec = FALSE;
			foundplus = TRUE;
		}
		if (*cmd == '?') {
			/* EMPTY */ ;		/* XXX To be defined !!! */
		}
		if (*cmd == '!') {
			/* EMPTY */ ;		/* XXX To be defined !!! */
		}
	}
	if (foundplus)
		Silent = FALSE;

	if (!Silent || NoExec || Debug > 0) {
/*		error("...%s\n", cmd);*/
		printf("%s%s\n", posixmode?"\t":"...", cmd);	/* POSIX !!! */
	}
	if (NoExec && !found_make)
		return (0);

	curtarget = obj;

	shello = objlook("SHELL", FALSE);
	if (shello != NULL && basetype(shello->o_type) == EQUAL &&
	    shello->o_list != NULL)
		shell = shello->o_list->l_obj->o_name;
	if (shell == NULL || *shell == '\0') {
		shello = NULL;
		shell = "/bin/sh";
	}

#if	!defined(USE_SYSTEM) &&			/* XXX else system() ??? */ \
	((defined(HAVE_FORK) && defined HAVE_EXECL) || defined(JOS))

#if defined(__EMX__) || defined(__DJGPP__)
#ifdef	__EMX__
	pid = spawnl(P_NOWAIT, shell, filename(shell), NoError ? cflag:ceflag,
							cmd, (char *)NULL);
	if (pid < 0)
		comerr("Can't spawn %s.\n", shell);
#else
	{
	/*
	 * exec '/bin/sh' does not work under DJGPP.
	 * Strings like 'c:\djgpp\bin\sh.exe' or '/dev/c/djgpp/bin/sh.exe'
	 * must be used instead.
	 *
	 * Notes: c:/djgpp/share/config.site defines 'SHELL' with the required string.
	 *
	 * Using system("sh -ce 'cmd'") and spawn("command.com /c sh -ce 'cmd'")
	 * cause GPF's (not enough memmory?)
	 *
	 * Temporary solution: Use DJGPP_SH envp. var. (must be set manually)
	 */
		static char	*shellname = NULL;

		if (shello != NULL)			/* On DJGPP too? */
			shellname = shell;
		if (shellname == NULL)
			shellname = getenv("DJGPP_SH");	/* Backward compat */
		if (shellname == NULL)
			shellname = searchfileinpath("bin/sh.exe"); /* alloc() */
		if (shellname == NULL)
			shellname = searchfileinpath("sh.exe");	/* alloc() */
		if (shellname == NULL)
			comerr("Can't find sh.exe.\n");

		Exit = spawnl(P_WAIT, shellname, filename(shellname),
						NoError ? cflag:ceflag,
							cmd, (char *)NULL);
		if (Exit) {
			/* TODO: DOS error code to UNIX error code */
			Exit = 0xFF<<8;
		}
	}
#endif
#else
	/*
	 *  Do several tries to fork child to allow working on loaded systems.
	 */
	for (retries = 0; retries < 10; retries++) {
		pid = fork();
		if (pid >= 0)
			break;
		sleep(1L);		/* Wait for resources to become free.*/
	}
	if (pid < 0)
		comerr("Can't fork.\n");

	if (pid == 0) {		/* Child process: do the work. */
		/*
		 * We used to call /bin/sh -ce 'cmd' but we get problems on QNX
		 * and UNIX-98 requests that the command shall be called as in
		 * system() which means /bin/sh -c 'cmd'.
		 */
		execl(shell, filename(shell), NoError ? cflag:ceflag,
							cmd, (char *)NULL);
		comerr("Can't exec %s.\n", shell);
	}
#endif	/* __EMX__ || __DJGPP__ */

#if	defined(__linux__) || defined(__linux) || defined(SHELL_IS_BASH)
	/*
	 * Needed to handle the Linux bash signal bug.
	 */
	lpid = pid;
#endif

	/* Parent process: wait for child. */
#ifndef	__DJGPP__
	if ((code = wait((WAIT_T *)&Exit)) != pid)
		comerrno(Exit, "code %d waiting for %s.\n", geterrno(), shell);
#endif
#else	/* Use system() */
	Exit = system(cmd);
#endif
	if (Exit) {
		errmsgno(Exit>>8, "*** Code %d from command line for target '%s'%s.\n",
			Exit>>8,
			obj->o_name,
			NoError?" (ignored)":"");
		if (Silent && Debug <= 0) {
			errmsgno(EX_BAD, "The following command caused the error:\n");
			error("%s\n", cmd);
		}
	}
	curtarget = (obj_t *)NULL;

	if (NoError)
		return (0);
	return (Exit);
}

#ifdef	tos
#		include "osbind.h"
#endif

/*
 * Move a target file to ObjDir.
 */
EXPORT BOOL
move_tgt(from)
	register obj_t	*from;
{
	date_t	fromtime;
	int	code;
	char	_objname[TYPICAL_NAMEMAX];
	char	*objname = NULL;
	BOOL	ret = TRUE;

	/*
	 * Move only if:
	 *	objdir to corresponding srcdir exists
	 *	target is known in Makefile
	 */
	if ((ObjDir == NULL && from->o_level == OBJLEVEL) ||
						from->o_level < OBJLEVEL)
		return (TRUE);

	fromtime = gftime(from->o_name);
	if (fromtime == 0)		/* Nothing to move found */
		return (TRUE);

	if (strchr(from->o_name, SLASH)) /* Only move from current directory */
		return (TRUE);

	if (Debug > 3) error("move: from->o_level: %d\n", from->o_level);
	if ((objname = build_path(from->o_level, from->o_name, from->o_namelen,
					_objname, sizeof (_objname))) == NULL)
		return (FALSE);
	if (!Sflag || Nflag)
		printf("%smove %s %s\n", posixmode?"\t":"...", from->o_name, objname);
	if (Nflag) {
		ret = TRUE;
		goto out;
	}

	if ((from->o_name == objname) ||
	    (gfileid(from->o_name) == gfileid(objname))) {
		errmsgno(EX_BAD, "Will not move '%s' to itself.\n",
							from->o_name);
		ret = TRUE;
		goto out;
	}
#	ifdef	tos
	unlink(objname);
	if ((code = Frename(0, from->o_name, objname)) < 0) {
		if (code == EXDEV) {
			code = copy_file(from->o_name, objname);
			if (unlink(from->o_name) < 0)
				errmsg("Can't remove old name '%s'.\n",
								from->o_name);
		} else {
			errmsgno(-code, "Can't rename '%s' to '%s'.\n",
						from->o_name, objname);
		}
	}
	if (code < 0) {
		ret = FALSE;
		goto out;
	}
#	else
	if ((code = rename(from->o_name, objname)) < 0) {
		if (geterrno() == EXDEV) {
			if (rmdir(objname) < 0 && geterrno() == ENOTDIR)
				unlink(objname);
			code = copy_file(from->o_name, objname);
			if (unlink(from->o_name) < 0)
				errmsg("Can't remove old name '%s'.\n",
							from->o_name);
		} else {
			errmsg("Can't rename '%s' to '%s'.\n",
						from->o_name, objname);
		}
	}
	if (code < 0) {
		ret = FALSE;
		goto out;
	}
#endif	/* tos */
out:
	if (objname != NULL && objname != from->o_name && objname != _objname)
		free(objname);
	return (ret);
}

/*
 * Copy File if we cannot rename the file.
 */
#ifdef	tos
LOCAL int
copy_file(from, objname)
	char	*from;
	char	*objname;
{
	int	fin;
	int	fout;
	int	cnt = -1;

	if ((fin = open(from, 0)) < 0)
		errmsg("Can't open '%s'.\n", from);
	else {
		if ((fout = creat(objname, 0666)) < 0)
			errmsg("Can't create '%s'.\n", objname);
		else {
			while ((cnt = read(fin, gbuf, gbufsize)) > 0)
				if (write(fout, gbuf, cnt) != cnt) {
					errmsg("Write error on '%s'.\n",
								objname);
					cnt = -1;
					break;
				}
			close(fout);
		}
		close(fin);
	}
	return (cnt);
}
#else
LOCAL int
copy_file(from, objname)
	char	*from;
	char	*objname;
{
	FILE	*fin;
	FILE	*fout;
	int	cnt = -1;

	if ((fin = fileopen(from, "rub")) == 0)
		errmsg("Can't open '%s'.\n", from);
	else {
		if ((fout = fileopen(objname, "wtcub")) == 0)
			errmsg("Can't create '%s'.\n", objname);
		else {
			while ((cnt = fileread(fin, gbuf, gbufsize)) > 0)
				filewrite(fout, gbuf, cnt);
			fclose(fout);
		}
		fclose(fin);
	}
	return (cnt);
}
#endif

/*
 * This function behaves similar to the UNIX 'touch' command.
 */
EXPORT BOOL
touch_file(name)
	char	*name;
{
	FILE	*f;
	char	_objname[TYPICAL_NAMEMAX];
	char	*objname = _objname;
	size_t	objlen = sizeof (_objname);
	char	*np = NULL;
#ifndef	HAVE_UTIME
	char	c;
#endif

again:
	if (snprintf(objname, objlen, "%s%s%s", ObjDir,
				slash, filename(name)) >= objlen) {
		objlen = strlen(filename(name)) + ObjDirlen + slashlen + 1;
		objname = np = __realloc(np, objlen, "touch path");
		goto again;
	}
#ifdef	__is_this_ok__
	if (!gftime(objname))
		snprintf(_objname, sizeof (_objname), name);
#endif
	if (!Sflag)
		printf("%stouch %s\n", posixmode?"\t":"...", objname);
	if (Nflag)
		return (TRUE);
	if ((f = fileopen(objname, "rwcub")) != (FILE *)NULL) {
#ifdef	HAVE_UTIME
		utime(objname, NULL);
#else
		c = getc(f);
		fileseek(f, (off_t)0);
		putc(c, f);
#endif
		fclose(f);
		if (np)
			free(np);
		return (TRUE);
	}
	if (np)
		free(np);
	return (FALSE);
}

#	include <schily/stat.h>
#	define	STATBUF		struct stat

/*
 * Get current time.
 */
LOCAL date_t
gcurtime()
{
	return ((date_t) time((time_t *) 0));
}

/*
 * Get a time that is in the future (as far as possible).
 */
LOCAL date_t
gnewtime()
{
	time_t	t = curtime;
	time_t	a = YEARSECS;
	int	i = 0;

	while ((a * 2) > a) {
		a *= 2;
		if (++i > 100)
			break;
	}
	i = 0;
	while (a > 0) {
		while ((t + a) > t) {
			t += a;
			if (++i > 100)
				break;
		}
		if (i > 1000)
			break;
		a /= 2;
	}
	while (t == NOTIME || t == BADTIME ||
			t == RECURSETIME || t == PHONYTIME) {
		t--;
	}
/*printf("i: %d, %lu, %lx, %s\n", i, t, t, prtime(-3));*/
	return (t);
}

/*
 * Get the time of last modification for a file.
 * Cannot call it gmtime()
 */
EXPORT date_t
gftime(file)
	char	*file;
{
	STATBUF	stbuf;
	char	this_time[32];
	char	cur_time[32];

	stbuf.st_mtime = NOTIME;
	if (stat(file, &stbuf) < 0) {
		/*
		 * GNU libc.6 destroys st_mtime
		 */
		stbuf.st_mtime = NOTIME;
	} else {
		register time_t	t;
		/*
		 * Make sure that the time for an existing file is not
		 * in the list of special time stamps.
		 */
		t = stbuf.st_mtime;
		while (t == NOTIME || t == BADTIME ||
				t == RECURSETIME || t == PHONYTIME) {
			t++;
		}
		stbuf.st_mtime = t;
	}

	if (Debug > 3)
		error("gftime(%s) = %s\n", file, prtime(stbuf.st_mtime));

	if (stbuf.st_mtime > (curtime +5)) {
		date_t	xcurtime;

		xcurtime = gcurtime();
		if (stbuf.st_mtime <= (xcurtime +5)) {
			curtime = xcurtime;
			return (stbuf.st_mtime);
		}
		strncpy(this_time, prtime(stbuf.st_mtime), sizeof (this_time));
		this_time[sizeof (this_time)-1] = '\0';
		strncpy(cur_time, prtime(curtime), sizeof (cur_time));
		cur_time[sizeof (cur_time)-1] = '\0';
		errmsgno(EX_BAD,
			"WARNING: '%s' has modification time in the future (%s > %s).\n",
			file, this_time, cur_time);
	}
	return (stbuf.st_mtime);
}

/*
 * Check if file is a directory.
 */
LOCAL BOOL
isdir(file)
	char	*file;
{
	STATBUF	stbuf;

	if (stat(file, &stbuf) < 0)
		return (TRUE);
	if ((stbuf.st_mode&S_IFMT) == S_IFDIR)
		return (TRUE);
	return (FALSE);
}

/*
 * Get a unique number for a file to prevent moving targets to themselves.
 * XXX inode number is now long !!!
 */
EXPORT Llong
gfileid(file)
	char	*file;
{
	STATBUF stbuf;
	Llong	result;

	/*
	 * Setup a unique default. In case stat() will fail.
	 */
	stbuf.st_ino = (ino_t) (long) file;
	stbuf.st_dev = 0;
	if (stat(file, &stbuf) < 0) {
		/*
		 * GNU libc.6 destroys st_mtime
		 * Paranoia .... we fall back here too.
		 */
		stbuf.st_ino = (ino_t) (long) file;
		stbuf.st_dev = 0;
	}
#if	SIZEOF_LLONG > 4
	result = stbuf.st_dev;
	result <<= 32;
	result |= stbuf.st_ino;
#else
	result = stbuf.st_dev;
	result <<= 16;
	result |= stbuf.st_ino;
#endif
	if (Debug > 3)
		error("gfileid: %s %lld\n", file, result);
	return (result);
}

/*
 * Transfer UNIX time stamps into human readable names.
 * Take care of our "special timestamps".
 */
EXPORT char *
prtime(date)
	date_t	date;
{
	char	*s;

	if (date == (date_t)0)
		return ("File does not exist");
	if (date == BADTIME)
		return ("File could not be made");
	if (date == RECURSETIME)
		return ("Recursive dependencies");
	if (date == PHONYTIME)
		return ("File is phony");
	if (date == newtime)
		return ("Younger than any file");

	s = ctime((const time_t *)&date);
	s[strlen(s)-1] = '\0';
	return (s);
}

/*
 * Our general signal handler. Does some needed clean up and includes
 * workarounds for buggy OS like Linux.
 */
LOCAL void
handler(signo)
	int	signo;
{
	char	*name;

	signal(signo, handler);
	errmsgno(EX_BAD, "Got signal %d\n", signo);
	if (!curtarget)
		goto out;

	errmsgno(EX_BAD, "Current target is: %s precious: %d phony: %d\n",
			curtarget->o_name,
			isprecious(curtarget),
			isprecious(curtarget));

/*	while(wait(0) >= 0) */
/*		;*/
	/*
	 * Keine Bibliotheken
	 * Kein -t, -q etc.
	 */
	if (isprecious(curtarget))
		goto out;
	if (isphony(curtarget))
		goto out;

	name = curtarget->o_name;

	if (isdir(name)) {
		error("*** %s not removed.\n", name);
		goto out;
	}
	if (unlink(name) >= 0) {
		error("*** %s removed.\n", name);
	} else {
		errmsg("*** %s could not be removed.\n", name);
	}
	/*
	 * Test ob ObjDir/name existiert und neuer als vorher ist.
	 */

out:

#if	defined(__linux__) || defined(__linux) || defined(SHELL_IS_BASH)
	/*
	 * Linux signal handling is broken. This is caused by a bug in 'bash'.
	 * Bash does jobcontrol even if called as "sh -ce 'command'".
	 * This is illegal. Only the foregound (make) process and with some
	 * bash versions the descendant 'make' processes are killed.
	 * The following code tries to kill the others too.
	 */
	signal(signo, SIG_IGN);		/* Make us immune to death ;-)	*/

	/*
	 * First shoot everyone into the foot.
	 */
	kill(lpid, signo);		/* Kill bash that is our child	*/
	kill(-lpid, signo);		/* Kill possible bash children	*/
	kill(-getpgrp(), signo);	/* Kill our process group	*/

	/*
	 * Now shoot everyone into the head.
	 */
	kill(lpid, SIGKILL);		/* Kill bash that is our child	*/
	kill(-lpid, SIGKILL);		/* Kill possible bash children	*/
	kill(-getpgrp(), SIGKILL);	/* Kill our process group	*/
#endif
	comexit(signo);
}

LOCAL void
exhandler(excode, arg)
	int	excode;
	void	*arg;
{
	/*
	 * Ausgabe wenn:
	 *
	 *	-	excode != 0 && Mlevel > 0
	 *	-	Debug > 0   && Mlevel > 0
	 */
	if ((Debug <= 1 && excode == 0) || Mlevel <= 0)
		return;

	errmsgno(EX_BAD, "Leaving  '%s'[%d] from directory '%s'\n",
			(char *)arg, Mlevel, curwdir());
	if (default_tgt != NULL)
		errmsgno(EX_BAD,
			"Default commandline target: '%s'\n", default_tgt->o_name);
	errmsgno(EX_BAD, "Doing                       exit(%d)\n", excode);
}

/*
 * Return current working directory in an allocated string.
 */
EXPORT	char *
curwdir()
{
	static	char	*wdir;
		char	wd[MAXPATHNAME + 1];

	if (wdir != NULL)
		return (wdir);

	if (getcwd(wd, MAXPATHNAME) == NULL) {
		wd[0] = '/';
		wd[1] = '\0';
	}
	wdir = malloc(strlen(wd)+1);
	if (wdir == NULL)
		comerr("Cannot malloc working dir.\n");
	strcpy(wdir, wd);
	return (wdir);
}

/*
 * Search for the defaults.smk file in the PATH of the user.
 * Assume that the file is ... bin/../lib/defaults.smk
 */
LOCAL char *
getdefaultsfile()
{
	return (searchfileinpath("lib/defaults.smk"));
}

LOCAL char *
searchfileinpath(name)
	char	*name;
{
	char	*path = getenv("PATH");
	char	pbuf[NAMEMAX];
	char	*nbuf = pbuf;
	char	*np;
	int	nlen = strlen(name);
	char	*pn = get_progname();

	if (strchr(pn, '/') != NULL) {
		strncpy(nbuf, pn, sizeof (pbuf));
		nbuf[sizeof (pbuf) - 1] = '\0';
		np = nbuf + strlen(nbuf);

		while (np > nbuf && np[-1] != '/')
			*--np = '\0';
		pn = &nbuf[sizeof (pbuf) - 1];
		if ((np = searchonefile(name, nbuf, np, pn)) != NULL)
			return (np);
	}

	if (path == NULL)
		return (NULL);


#ifdef __DJGPP__
	strbs2s(path);	/* PATH under DJGPP can contain both slashes */
#endif

	pn = &nbuf[sizeof (pbuf) - 1];
	for (;;) {
		np = nbuf;
		while (*path != PATH_ENV_DELIM && *path != '\0' &&
		    np < &nbuf[sizeof (pbuf) - nlen])
				*np++ = *path++;
		*np = '\0';
		if ((np = searchonefile(name, nbuf, np, pn)) != NULL)
			return (np);

		if (*path == '\0')
			break;
		path++;
	}
	return (NULL);
}

LOCAL char *
searchonefile(name, nbuf, np, ep)
	register char	*name;
	register char	*nbuf;
	register char	*np;
	register char	*ep;
{
	while (np > nbuf && np[-1] == '/')
		*--np = '\0';
	if (np >= &nbuf[4] && streql(&np[-4], "/bin"))
		np = &np[-4];
	*np++ = '/';
	*np   = '\0';
	strncpy(np, name, ep - np);
	*ep = '\0';

	if (gftime(nbuf)) {
		np = malloc(strlen(nbuf)+1);
		if (np == NULL)
			return (NULL);
		strcpy(np, nbuf);
		return (np);
	}
	return (NULL);
}

#ifdef __DJGPP__
LOCAL char *
strbs2s(s)
	char	*s;
{
	char	*tmp = s;

	if (tmp) {
		while (*tmp) {
			if (*tmp == '\\')
				*tmp = '/';
			tmp++;
		}
	}
	return (s);
}
#endif

#ifndef	HAVE_PUTENV

EXPORT	int	putenv		__PR((const char *new));
LOCAL	int	ev_find		__PR((const char *s));

extern	char 	**environ;	/* The environment array		*/
LOCAL	BOOL	ealloc = FALSE;	/* TRUE if environ is already allocated */

/*
 * Our local putenv implementation for systems that don't have it.
 */
EXPORT int
putenv(new)
	const char	*new;
{
	char 		**newenv;
	register int	idx;

	if ((idx = ev_find(new)) >= 0) {
		/*
		 * An old entry with the same name exists, replace it.
		 */
		environ[idx] = (char *)new;
	} else {
		/*
		 * If idx is < 0, we need to expand environ for the new entry.
		 * In this case -idx is the inverted size of the old environ.
		 */
		idx = -idx + 1;		/* Add space for new entry */
		if (ealloc) {
			/*
			 * environ is allocated, expand with realloc
			 */
			newenv = (char **)realloc(environ, idx*sizeof (char *));
			if (newenv == NULL)
				return (-1);
		} else {
			/*
			 * environ is orig space, copy to malloc'ed space
			 */
			ealloc = TRUE;
			newenv = (char **)malloc(idx*sizeof (char *));
			if (newenv == NULL)
				return (-1);
			(void) movebytes((char *)environ, (char *)newenv,
						(int)(idx*sizeof (char *)));
		}
		environ = newenv;
		environ[idx-2] = (char *)new;
		environ[idx-1] = NULL;
	}
	return (0);
}

/*
 * Check if arg of form name=value is part of environ.
 * Return index on success and -environ_size if not.
 */
LOCAL int
ev_find(s)
	register const char	*s;
{
	register int		i = 0;
	register const char	*ep;
	register const char	*s2;

	for (i = 0; environ[i] != NULL; i++) {
		/*
		 * Find string in environment entry.
		 */
		for (ep = environ[i], s2 = s; *ep == *s2++; ep++) {
			if (*ep == '=')
				return (i);
		}
	}
	return (-(++i));
}
#endif	/* HAVE_PUTENV */


syntax highlighted by Code2HTML, v. 0.9.1