/* @(#)update.c	1.139 18/03/14 Copyright 1985, 88, 91, 1995-2018 J. Schilling */
#include <schily/mconfig.h>
#ifndef lint
static	UConst char sccsid[] =
	"@(#)update.c	1.139 18/03/14 Copyright 1985, 88, 91, 1995-2018 J. Schilling";
#endif
/*
 *	Make program
 *	Macro handling / Dependency Update
 *
 *	Copyright (c) 1985, 88, 91, 1995-2018 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.
 * A copy of the CDDL is also available via the Internet at
 * http://www.opensource.org/licenses/cddl1.txt
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file CDDL.Schily.txt from this distribution.
 */

#include <schily/stdio.h>
#include <schily/types.h>
#include <schily/standard.h>
#include <schily/stdlib.h>		/* for free() */
#include <schily/string.h>
#include <schily/unistd.h>
#include <schily/schily.h>
#include <schily/libport.h>
#include "make.h"
#include "job.h"

EXPORT obj_t	*default_tgt;		/* Current 'make' arg or default tgt */
EXPORT BOOL	found_make;		/* Did we expand the $(MAKE) macro?  */

#define	RTYPE_NONE	-1		/* Undefined type (used to init)    */
#define	RTYPE_DEFAULT	0		/* Rule from .DEFAULT: target	    */
#define	RTYPE_SSUFFIX	1		/* Simple suffix rule		    */
#define	RTYPE_SUFFIX	2		/* Single suffix rule		    */
#define	RTYPE_DSUFFIX	3		/* Double suffix rule		    */
#define	RTYPE_PATTERN	4		/* Pattern matching rule	    */

#define	RTYPE_NEEDFREE	0x1000		/* cmd_t * list needs to be free'd  */

#define	rule_type(t)	((t) & ~RTYPE_NEEDFREE)

EXPORT	void	initchars	__PR((void));
EXPORT	char	*filename	__PR((const char * name));
LOCAL	void	copy_dir	__PR((char * name, char *dir, size_t dsize));
LOCAL	char	*get_suffix	__PR((char *name, char *suffix));
LOCAL	void	copy_base	__PR((char *name, char *dir, size_t dsize, char *suffix));
EXPORT	BOOL	isprecious	__PR((obj_t * obj));
EXPORT	BOOL	isphony		__PR((obj_t * obj));
LOCAL	patr_t	*_pattern_rule	__PR((patr_t * prule, char *name));
LOCAL	obj_t	*pattern_rule	__PR((obj_t * obj));
LOCAL	obj_t	*suffix_rule	__PR((obj_t * obj, int *rtypep));
LOCAL	void	suffix_warn	__PR((obj_t * obj));
LOCAL	obj_t	*ssuffix_rule	__PR((obj_t * obj));
LOCAL	obj_t	*default_rule	__PR((obj_t * obj, int *rtypep));
EXPORT	list_t	*list_nth	__PR((list_t * list, int n));
EXPORT	char	*build_path	__PR((int level, char *name, size_t namelen,
						char *path, size_t psize));
LOCAL	void	etoolong	__PR((char *topic, char *name));
LOCAL	void	grant_gbuf	__PR((int size));
LOCAL	void	sub_put		__PR((char *chunk, int size));
LOCAL	void	sub_c_put	__PR((int c));
LOCAL	void	sub_s_put	__PR((char *chunk));
LOCAL	BOOL	sub_arg		__PR((int n, list_t * depends, obj_t * target));
EXPORT	char	*substitute	__PR((char *cmd, obj_t * obj, obj_t * source, char *suffix));
LOCAL	char	*subst		__PR((char *cmd, obj_t * obj, obj_t * source, char *suffix, list_t * depends));
LOCAL	char	*dynmac		__PR((char *cmd, obj_t * obj, obj_t * source, char *suffix, list_t * depends, BOOL  domod));
LOCAL	void	warn_implicit	__PR((obj_t *obj, char *mac, char *exp));
LOCAL	void	warn_displaced	__PR((obj_t *obj, char *mac, char *exp));
LOCAL	void	extr_filenames	__PR((char *names));
LOCAL	void	extr_dirnames	__PR((char *names));
LOCAL	char	*exp_var	__PR((char end, char *cmd, obj_t * obj, obj_t * source, char *suffix, list_t *depends));
LOCAL	char	*rstr		__PR((char * s1, char * s2));
LOCAL	BOOL	patsub		__PR((char *name, char *f1, char *f2, char *t1, char *t2));
LOCAL	void	patmsub		__PR((char *name, char *f1, char *f2, char *t1, char *t2));
LOCAL	void	parsepat	__PR((char *pat, char **fp1, char **fp2, char **tp1, char **tp2));
EXPORT	char	*shout		__PR((char *cmd));
LOCAL	char	*shsub		__PR((list_t * l, obj_t * obj, obj_t * source, char *suffix, list_t * depends));
LOCAL	void	exp_name	__PR((char * name, obj_t * obj, obj_t * source, char *suffix, list_t * depends, char *));
LOCAL	void	dcolon_time	__PR((obj_t *obj));
LOCAL	date_t	searchobj	__PR((obj_t * obj, int maxlevel, int mode));
LOCAL	obj_t	*patr_src	__PR((char *name, patr_t * prule, int *rtypep, char ** suffixp, cmd_t ** pcmd, int dlev));
LOCAL	obj_t	*suff_src	__PR((char *name, obj_t * rule, int *rtypep, char ** suffixp, cmd_t ** pcmd, int dlev));
LOCAL	obj_t	*one_suff_src	__PR((char *name, char *suffix, cmd_t **pcmd, int dlev));
LOCAL	obj_t	*ssuff_src	__PR((char *name, obj_t * rule, int *rtypep, char ** suffixp, cmd_t ** pcmd, int dlev));
LOCAL	obj_t	*findsrc	__PR((obj_t *obj, obj_t * rule, int *rtypep, char ** suffixp, cmd_t ** pcmd, int dlev));
LOCAL	date_t	default_cmd	__PR((obj_t * obj, char *depname, date_t  deptime, int deplevel, BOOL  must_exist, int dlev));
LOCAL	date_t	make		__PR((obj_t * obj, BOOL must_exist, int dlev));
EXPORT	BOOL	domake		__PR((char *name));
EXPORT	BOOL	omake		__PR((obj_t * obj, BOOL  must_exist));
EXPORT	BOOL	xmake		__PR((char *name, BOOL  must_exist));

char	chartype[256];

/*
 * Optimise character classification
 */
EXPORT void
initchars()
{
	char	*p;
	int	c;

	p = "@*<0123456789r^?";

	while ((c = *p++) != '\0') {
		chartype[c] |= DYNCHAR;
	}

	p = "0123456789";

	while ((c = *p++) != '\0') {
		chartype[c] |= NUMBER;
	}
}

/*
 * Return last pathname component.
 */
EXPORT char *
filename(name)
	register const char	*name;
{
	register const char	*fname;

	for (fname = name; *name; )
		if (*name++ == SLASH)
			fname = name;
	return ((char *)fname);
}

/*
 * Copy directory component of pathname.
 */
LOCAL void
copy_dir(name, dir, dsize)
	register char	*name;
	register char	*dir;
	register size_t	dsize;
{
	register char	*p = filename(name);
		char	*ns = name;

	if (XDebug > 0)
		error("copy_dir(name:'%s', dir:'%s', dsize: %zd) fn: '%s' \n",
				name, dir, dsize, p);
	*dir = '\0';
	if (p == name) {
		if (dsize < 2)
			etoolong("copy directory name", ns);
		*dir++ = '.';
		*dir = '\0';
	} else {
		if (++dsize == 0) {	/* unsigned overflow */
			dsize--;
			while (name < p && dsize-- > 0)
				*dir++ = *name++;
			dsize++;
		} else {
			while (name < p && --dsize > 0)
				*dir++ = *name++;
		}
		if (dsize == 0)
			etoolong("copy directory name", ns);

		*--dir = '\0';		/* POSIX wants the '/' to be removed */
					/* This will make dir(/filename)    */
					/* not usable...		    */
		*dir = '\0';
	}
}

/*
 * Return part after '.' of last pathname component.
 */
LOCAL char *
get_suffix(name, suffix)
	char	*name;
	char	*suffix;
{
	register char	*p;
	register char	*suff = (char *)NULL;

	if (suffix != NULL) {
		p = filename(name);
		suff = rstr(p, suffix);
		if (suff == (char *)NULL) /* No suffix: return end of string */
			suff = &p[strlen(p)];
		return (suff);
	}

	for (p = filename(name); *p; p++)
		if (*p == '.')
			suff = p;
	if (suff == (char *)NULL)	/* No suffix: return end of string */
		suff = p;
	return (suff);
}

/*
 * Copy namebase (everything before '.').
 */
LOCAL void
copy_base(name, dir, dsize, suffix)
	register char	*name;
	register char	*dir;
	register size_t	dsize;
		char	*suffix;
{
	register char	*p = get_suffix(name, suffix);
		char	*ns = name;

	if (++dsize == 0) {	/* unsigned overflow */
		dsize--;
		while (name < p && dsize-- > 0)
			*dir++ = *name++;
		dsize++;
	} else {
		while (name < p && --dsize > 0)
			*dir++ = *name++;
	}
	if (dsize == 0)
		etoolong("copy base name", ns);

	*dir = '\0';
}

/*
 * Return TRUE if 'obj' is in the list of targets that should not be removed
 * or of the list of dependencies for .PRECIOUS is empty.
 */
EXPORT BOOL
isprecious(obj)
	obj_t	*obj;
{
	list_t	*l;

	if (Precious == (obj_t *)NULL)
		return (FALSE);

	if (Precious->o_list == NULL)
		return (TRUE);

	for (l = Precious->o_list; l; l = l->l_next)
		if (obj == l->l_obj)
			return (TRUE);
	return (FALSE);
}

/*
 * Return TRUE if 'obj' is in the list of targets that should not be checked
 * aginst existing files. A .PHONY target is assumed to be never up to date,
 * it is not removed in case a signal is received.
 */
EXPORT BOOL
isphony(obj)
	obj_t	*obj;
{
	list_t	*l;

	if (Phony == (obj_t *)NULL)
		return (FALSE);

	for (l = Phony->o_list; l; l = l->l_next)
		if (obj == l->l_obj)
			return (TRUE);
	return (FALSE);
}

/*
 * Find pattern rule for 'name' starting at 'prule' in rules list.
 */
LOCAL patr_t *
_pattern_rule(prule, name)
	register patr_t	*prule;
	register char	*name;
{
	register char	*p;

	if (prule == NULL)
		return ((patr_t *)0);

	if (Debug > 1)
		printf("Searching pattern rule for: %s \n", name);

	for (; prule != NULL; prule = prule->p_next) {
		register char	*np;

		/*
		 * XXX NeXT Step has a buggy strstr(); returns NULL if p == ""
		 */
		p = (char *)prule->p_tgt_prefix;

		if (*p != '\0' && strstr(name, p) != name)
			continue;		/* no matching prefix */

		np = name + prule->p_tgt_pfxlen; /* strip matching prefix */

		if ((p = rstr(np, (char *)prule->p_tgt_suffix)) == NULL)
			continue;

#ifdef	DEBUG
		if (Debug > 1) {
			register cmd_t	*cmd;
			register list_t	*l;

			printf("name: %s (%s %% %s):%s ", name,
				prule->p_tgt_prefix, prule->p_tgt_suffix,
				(prule->p_flags & PF_TERM) ? ":":"");

			for (l = prule->p_list; l; l = l->l_next)
				printf(" %s", l->l_obj->o_name);
			printf("\n");

			for (cmd = prule->p_cmd; cmd; cmd = cmd->c_next) {
				printf("\t%s\n", cmd->c_line);
			}
		}
#endif
		break;
	}
	return (prule);
}

/*
 * Find pattern rule for 'obj' ... not yet ready.
 */
LOCAL obj_t *
pattern_rule(obj)
	obj_t	*obj;
{
	/*
	 * XXX Hack for now (cast to obj_t *), should return prule.
	 */
	return ((obj_t *)_pattern_rule(Patrules, obj->o_name));
}

/*
 * Find a POSIX suffix rule.
 *
 * Check if obj has a file name with a default dependency for the
 * corresponding source and a rule to compile it.
 */
LOCAL obj_t *
suffix_rule(obj, rtypep)
	register obj_t	*obj;
		int	*rtypep;
{
	list_t	*l;
	list_t	*l2;
	obj_t	*o;
	char	*suffix;
	char	rulename[TYPICAL_NAMEMAX];	/* Space for two suffixes */
	char	*rule = rulename;
	char	*rp = NULL;
	int	rlen = sizeof (rulename);
	BOOL	found_suffix = FALSE;

	if (Suffixes == NULL)
		return ((obj_t *)0);

	if (Debug > 1)
		printf("Searching double suffix rule for: %s \n", obj->o_name);

	for (l = Suffixes; l; l = l->l_next) {
		suffix = l->l_obj->o_name;
		if (rstr(obj->o_name, suffix)) {	/* may be a suffix */

			found_suffix = TRUE;

			for (l2 = Suffixes; l2; l2 = l2->l_next) {
			again:
				if (snprintf(rule, rlen, "%s%s",
						l2->l_obj->o_name,
							suffix) >= rlen) {
					/*
					 * Expand rule name space.
					 */
					rlen = strlen(suffix) +
						l2->l_obj->o_namelen + 16;
					rule = rp = ___realloc(rp, rlen,
							"suffix rule name");
					goto again;
				}
				if ((o = objlook(rule, FALSE)) != NULL && o->o_type == COLON) {
					*rtypep = RTYPE_DSUFFIX;
					if (rp)
						free(rp);
					return (o);
				}
			}
		}
	}
	if (rp)
		free(rp);
	if (found_suffix)
		return ((obj_t *) 0);

	if (Debug > 1)
		printf("Searching single suffix rule for: %s \n", obj->o_name);

	for (l2 = Suffixes; l2; l2 = l2->l_next) {
		rule = l2->l_obj->o_name;
		if ((o = objlook(rule, FALSE)) != NULL && o->o_type == COLON) {
			*rtypep = RTYPE_SUFFIX;
			return (o);
		}
	}
	return ((obj_t *) 0);
}

LOCAL void
suffix_warn(obj)
	obj_t	*obj;
{
	list_t	*l;

	if (obj->o_list == NULL)
		return;

	errmsgno(EX_BAD,
		"WARNING: suffix rule '%s' has superfluous dependency: '",
		obj->o_name);

	for (l = obj->o_list; l; l = l->l_next) {
		error("%s%s",
			l->l_obj->o_name,
			l->l_next?" ":"");
	}
	error("'.\n");
}

/*
 * Find a simple suffix rule.
 *
 * Check if obj has a file name with a default dependency for the
 * corresponding source and a rule to compile it.
 */
LOCAL obj_t *
ssuffix_rule(obj)
	register obj_t	*obj;
{
	register obj_t *rule;
		char	*ext;

	if (!SSuffrules)
		return ((obj_t *)0);

	if (Debug > 1)
		printf("Searching simple-suffix rule for: %s \n", obj->o_name);

	ext = get_suffix(obj->o_name, (char *)0); /* Use '.' (dot) suffix only*/

	if (ext[0] == '\0') {
		if (obj->o_list == (list_t *)NULL) {
			ext = "\"\"";		/* obj has no suffix: use "" */
		} else {
			return ((obj_t *)NULL);	/* obj has dependency list   */
		}
	}
	rule = ssufflook(ext, FALSE);
	if (rule == (obj_t *)NULL ||		/* no default rules known   */
	    rule->o_list == (list_t *)NULL ||	/* no source suffix list    */
	    rule->o_cmd == (cmd_t *)NULL)	/* no commands defined	    */
		return ((obj_t *)NULL);
	return (rule);
}

/*
 * Check if a default rules exists for the target.
 */
LOCAL obj_t *
default_rule(obj, rtypep)
	obj_t	*obj;
	int	*rtypep;
{
	obj_t *rule;

#ifdef	NO_SLASH_IMPLICIT
	if (strchr(obj->o_name, SLASH) != NULL) {
		if (Debug > 3)
			error("%s has slash, no implicit dependency searched.\n",
							obj->o_name);
		/*
		 * XXX We need to check if this is a good idea.
		 */
		rule = (obj_t *)NULL;
	}
#endif
	rule = pattern_rule(obj);
	if (rule) {
		*rtypep = RTYPE_PATTERN;
		return (rule);
	}

	rule = suffix_rule(obj, rtypep);
	if (rule) {
		return (rule);
	}

	rule = ssuffix_rule(obj);
	if (rule) {
		*rtypep = RTYPE_SSUFFIX;
		return (rule);
	}

	/*
	 * XXX Must exits wichtig ??
	 */
	*rtypep = RTYPE_DEFAULT;
	rule = Deflt;	/* .DEFAULT:	*/

	if (rule == (obj_t *)NULL || rule->o_cmd == (cmd_t *)NULL)
		return ((obj_t *)NULL);
	return ((obj_t *)rule->o_cmd);	/* XXX Hack for now, should return drule */
}

/*
 * Return the nth element of a list.
 */
EXPORT list_t *
list_nth(list, n)
	register list_t *list;
		int	n;
{
	if (n < 0)
		return ((list_t *)NULL);

	for (; list; list = list->l_next)
		if (--n < 0)
			return (list);
	return ((list_t *)NULL);
}

/*
 * Create a new file name from name and the n'th directory in SearchList.
 * SearchList lists sourcedirs before objdirs, starting with
 * n = 0 for '.' and n = 1 for ObjDir.
 *
 * Returns:
 *		NULL	No search path at "level"
 *		name	level points to empty dirname
 *		path	New path in space provided py "path"
 *		other	Allocated new path
 */
EXPORT char *
build_path(level, name, namelen, path, psize)
	int	level;
	char	*name;
	size_t	namelen;
	char	*path;
	size_t	psize;
{
	list_t *lp;
	char	*dir_name = (char *)NULL;
	register int n = level;

	if (n <= 1) {
		if (level == OBJLEVEL) {
			dir_name = ObjDir;
			namelen += slashlen + ObjDirlen;
		}
	} else if (level != MAXLEVEL) {
		if ((lp = list_nth(SearchList, n - 2)) == (list_t *)NULL)
			return ((char *)NULL);
		dir_name = lp->l_obj->o_name;
		namelen += slashlen + lp->l_obj->o_namelen;
	}
	if (dir_name == (char *)NULL)
		return (name);

	if (namelen >= psize) {
		psize = namelen + 1;
		path = ___realloc(NULL, psize, "build path name");
	}
	n = snprintf(path, psize, "%s%s%s", dir_name, slash, name);
	if (n >= psize)
		etoolong("build path name", name);

	return (path);
}

LOCAL void
etoolong(topic, name)
	char	*topic;
	char	*name;
{
	comerrno(EX_BAD, "String too long, could not %s for '%s'.\n",
			topic, name);
	/* NOTREACHED */
}

/*
 * The growable buffer (gbuf) defines a string with the following layout
 *      "xxxxxxxxxxxxxxxCxxxxxxxxxxxxxxx________"
 *      ^               ^               ^               ^
 *      |               |               |               |
 *      gbuf            textp           sub_ptr         gbufend
 *      textp points to a string that is currently been worked on,
 *	sub_ptr is the write pointer.
 */
static char *sub_ptr = (char *)NULL;

/*
 * Check whether a pointer is inside the growable buffer (gbuf)
 *
 *	ison_gbuf(p)	depends on the global variables "gbuf" and "gbufend"
 *	wason_gbuf(p)	depens on local copies in "sb" and "sbe" that hold
 *			old values.
 */
#define	ison_gbuf(p)	((p) >= gbuf && (p) < gbufend)
#define	wason_gbuf(p)	((p) >= sb && (p) < sbe)

LOCAL void
grant_gbuf(size)
	int	size;
{
	/*
	 * In order to simplify code we add the space needed for the null byte.
	 */
	size++;

	while (sub_ptr + size >= gbufend)
		sub_ptr = growgbuf(sub_ptr);
}

/*
 * Put a string bounded by size into the growable buffer.
 */
LOCAL void
sub_put(chunk, size)
	char	*chunk;
	int	size;
{
	char	*sb = gbuf;
	char	*sbe = gbufend;

	grant_gbuf(size);
	if (sb != gbuf && wason_gbuf(chunk)) {
		chunk = gbuf + (chunk - sb);
	}
	movebytes(chunk, sub_ptr, size);
	sub_ptr += size;
	*sub_ptr = '\0';	/* Null terminate the gbuf string */
}

/*
 * Put a single character into the growable buffer.
 */
LOCAL void
sub_c_put(c)
	int	c;
{
	grant_gbuf(1);
	*sub_ptr++ = c & 0xFF;
	*sub_ptr   = '\0';	/* Null terminate the gbuf string */
}

/*
 * Put a string bounded by strlen() into the growable buffer.
 */
LOCAL void
sub_s_put(chunk)
	char	*chunk;
{
	sub_put(chunk, strlen(chunk));
}

/*
 * Put one arg into the growable buffer.
 *
 * It target is nonzero, check in addition if the target
 * depends on the date of the currently selected obj too.
 *
 * Return FALSE if no more list elements are available.
 */
LOCAL BOOL
sub_arg(n, depends, target)
	int	n;
	list_t	*depends;
	obj_t	*target;
{
	register obj_t	*obj;
		char	arg[TYPICAL_NAMEMAX];
		char	*argp;

	if ((depends = list_nth(depends, n)) == (list_t *)NULL)
		return (FALSE);

	/*
	 * $0 is not available if no implicit source is present!
	 * Just skip it.
	 */
	if ((obj =
	    depends->l_obj) == (obj_t *)NULL)
		return (TRUE);

	/*
	 * It the target does not yet exist, target->o_date is set
	 * to RECURSETIME. We need to make sure that newtime in the
	 * dependencies (here obj) is considered > target->o_date.
	 */
	if (target != NULL &&
	    VALIDTIME(target->o_date) && target->o_date > obj->o_date) {
		return (TRUE);
	}
	if ((argp = build_path(obj->o_level, obj->o_name, obj->o_namelen,
						arg, sizeof (arg))) != NULL) {
		sub_s_put(argp);
		if (argp != obj->o_name && argp != arg) {
			free(argp);
		}
	} else {
		sub_s_put(obj->o_name);
	}
	return (TRUE);
}

/*
 * Do macro substitution. Substitution is done in the growable buffer buf.
 * The buffer is used as stack to allow recursive substitution.
 */
EXPORT char *
substitute(cmd, obj, source, suffix)
	register char	*cmd;
		obj_t	*obj;
		obj_t	*source;
		char	*suffix;
{
		list_t	depends;

	found_make = FALSE;			/* we did not expand $(MAKE) */
	depends.l_obj = source;			/* define implicit source $< */
	depends.l_next = obj->o_list;

	sub_ptr = gbuf;
	return (subst(cmd, obj, source, suffix, &depends));
}

static int	depth = 0;			/* Keep track of recursion   */

/*
 * Substitute macros.
 */
		/* source wird eigentlich nicht gebraucht */
LOCAL char *
subst(cmd, obj, source, suffix, depends)
	register char	*cmd;
		obj_t	*obj;
		obj_t	*source;
		char	*suffix;
		list_t	*depends;
{
		char	*sp = sub_ptr;
		char	*sb = gbuf;
	register char	*p;
		char	name[2];

	if (++depth > 100)
		comerrno(EX_BAD, "Recursion in macro '%s'.\n", cmd);

	name[1] = '\0';
	while ((p = strchr(cmd, '$')) != NULL) {
		sub_put(cmd, p - cmd);
		cmd = ++p;
		switch (*cmd++) {

		default:
			if (chartype[*(Uchar *)p] & DYNCHAR) {
				cmd = dynmac(p, obj, source, suffix, depends, FALSE);
				continue;
			}
			name[0] = cmd[-1];
			exp_name(name, obj, source, suffix, depends, Nullstr);
			break;
		case '\0':
			*sub_ptr = '\0';
			/*
			 * No need to update 'sb' as we exit here...
			 */
			if (sb != gbuf) {
				sp = gbuf + (sp - sb);
				sb = gbuf;
			}
			comerrno(EX_BAD,
				"Fatal error '$' at end of string '%s$'\n",
									sp);
			/* NOTREACHED */
		case '$':
			sub_c_put('$');
			break;
		case '(':
			cmd = exp_var(')', cmd, obj, source, suffix, depends);
			break;
		case '{':
			cmd = exp_var('}', cmd, obj, source, suffix, depends);
			break;
		}
	}
	sub_s_put(cmd);
	*sub_ptr = '\0';
	depth--;
	if (sb != gbuf) {
		sp = gbuf + (sp - sb);
		sb = gbuf;
	}
	return (sp);
}

/*
 * Substitute dynamic macros.
 */
LOCAL char *
dynmac(cmd, obj, source, suffix, depends, domod)
	char	*cmd;
	obj_t	*obj;
	obj_t	*source;
	char	*suffix;
	list_t	*depends;
	BOOL	domod;
{
	int	num;
	char	_base[TYPICAL_NAMEMAX];
	char	*base = _base;
	char	*bp = NULL;
	size_t	blen = sizeof (_base);
	char	*sp = sub_ptr;
	char	*sb = gbuf;
	char	*sp1;
	char	*sb1;
	register char	*p = cmd;
	char	*ocmd;

	switch (*cmd++) {

	default:
		return (cmd);

	case '@':
		if (obj->o_flags & F_DCOLON)		/* Is a ::@ target */
			sub_s_put(obj->o_node->o_name);	/* $@ -> full target name */
		else
			sub_s_put(obj->o_name);	/* $@ -> full target name */
		break;
	case '*':
		if (obj->o_namelen >= blen) {
			blen = obj->o_namelen + 1;
			base = bp = ___realloc(bp, blen, "base name");
		}
#ifdef	used_to_be_in_former_versions
		copy_base(filename(obj->o_name), base, blen, suffix);
#endif
		if (ObjDir || SearchList) {	/* May be removed in 2010 */

			if (obj->o_name != filename(obj->o_name) &&
			    objlook("VPATH", FALSE) == NULL)
				error(
				"WARNING: Old: convert $* from '%s' -> '%s'\n",
					obj->o_name, filename(obj->o_name));
		}
		if (obj->o_flags & F_PATRULE) {
			patr_t *pr = (patr_t *)obj->o_node;

			copy_base(obj->o_name+pr->p_tgt_pfxlen, base, blen,
							pr->p_tgt_suffix);
		} else {
			copy_base(obj->o_name, base, blen, suffix);
			if (suffix == NULL && !nowarn("$*"))
				warn_implicit(obj, "$*", base);
		}
		sub_s_put(base);		/* $* -> target name base */
		break;
	case '<':
		if (depends->l_obj == NULL && !nowarn("$<"))
			warn_implicit(obj, "$<", "");
		sub_arg(0, depends, (obj_t *)0); /* $< -> implicit source  */
		break;
	case '0': case '1': case '2': case '3': case '4':
	case '5': case '6': case '7': case '8': case '9':
		if (depends->l_obj == NULL && obj->o_type == 0 && !nowarn("$#"))
			warn_displaced(obj, "$#", "");
		cmd = astoi(p, &num);		/* $1 -> first dependency */
		sub_arg(num, depends, (obj_t *)0); /* $0 -> implicit source  */
		break;
	case 'r':
		/*
		 * $r0 -> all dependencies + implicit source
		 * $r1 -> all dependencies
		 */
		if (depends->l_obj == NULL && obj->o_type == 0 &&
		    !nowarn("$r#"))
			warn_displaced(obj, "$r#", "");
		sp1 = sub_ptr;
		sb1 = gbuf;
		ocmd = cmd;
		cmd = astoi(cmd, &num);
		if (num < 0 || cmd == ocmd) {
			/*
			 * sub_arg() deals with negative "num" values, but
			 * while (num++) could nearly take forever.
			 */
			errmsgno(EX_BAD,
			"WARNING: illegal substitution: '$r%s'\n", ocmd);
			break;
		}
		while (sub_arg(num++, depends, (obj_t *)0)) {
			if (sb1 != gbuf)
				sp1 = gbuf + (sp1 - sb1);
			if (sp1 != sub_ptr)	/* Add only if nonempty arg */
				sub_c_put(' ');
			sp1 = sub_ptr;
			sb1 = gbuf;
		}
		if (sb != gbuf)
			sp = gbuf + (sp - sb);
		sb = gbuf;
		if (sp != sub_ptr && sub_ptr[-1] == ' ')
			sub_ptr--;		/* Kill last space */
		break;
	case '^':
		sp1 = sub_ptr;
		sb1 = gbuf;
		num = 1;			/* $^ -> all dependencies */
		while (sub_arg(num++, depends, (obj_t *)0)) {
			if (sb1 != gbuf)
				sp1 = gbuf + (sp1 - sb1);
			if (sp1 != sub_ptr)	/* Add only if nonempty arg */
				sub_c_put(' ');
			sp1 = sub_ptr;
			sb1 = gbuf;
		}
		if (sb != gbuf)
			sp = gbuf + (sp - sb);
		sb = gbuf;
		if (sp != sub_ptr && sub_ptr[-1] == ' ')
			sub_ptr--;		/* Kill last space */
		break;
	case '?':
		sp1 = sub_ptr;
		sb1 = gbuf;
		num = 1;			/* $? -> outdated dependencies*/
		while (sub_arg(num++, depends, obj)) {
			if (sb1 != gbuf)
				sp1 = gbuf + (sp1 - sb1);
			if (sp1 != sub_ptr)	/* Add only if nonempty arg */
				sub_c_put(' ');
			sp1 = sub_ptr;
			sb1 = gbuf;
		}
		if (sb != gbuf)
			sp = gbuf + (sp - sb);
		sb = gbuf;
		if (sp != sub_ptr && sub_ptr[-1] == ' ')
			sub_ptr--;		/* Kill last space */
		break;
	}
	*sub_ptr = '\0';
	if (bp)
		free(bp);
	if (!domod)
		return (cmd);
	if (sb != gbuf)
		sp = gbuf + (sp - sb);
	sb = gbuf;
	if (*cmd == 'F') {
		extr_filenames(sp);		/* 'sp' must be in gbuf */
		return (++cmd);
	} else if (*cmd == 'D') {
		extr_dirnames(sp);		/* 'sp' must be in gbuf */
		return (++cmd);
	}
	return (cmd);
}

LOCAL void
warn_implicit(obj, mac, exp)
	obj_t	*obj;
	char	*mac;
	char	*exp;
{
	errmsgno(EX_BAD,
	"WARNING: requesting implicit dynmac '%s' for explicit target '%s'\n",
			mac, obj->o_name);
	errmsgno(EX_BAD,
		"WARNING: expanding implicit dynmac  '%s' to '%s'\n",
			mac, exp);
	errmsgno(EX_BAD,
		"WARNING: Current working directory:  '%s', Makefile '%s'\n",
				curwdir(), MakeFileNames[obj->o_fileindex]);
}

LOCAL void
warn_displaced(obj, mac, exp)
	obj_t	*obj;
	char	*mac;
	char	*exp;
{
	errmsgno(EX_BAD,
	"WARNING: requesting dynmac '%s' in target definition\n",
			mac);
	errmsgno(EX_BAD,
		"WARNING: expanding implicit dynmac  '%s' to '%s'\n",
			mac, exp);
	errmsgno(EX_BAD,
		"WARNING: Current working directory:  '%s', Makefile '%s'\n",
				curwdir(), mfname);
}

/*
 * Extract the filename parts from a string that contains a list of names.
 *
 * The parameter 'names' is expected to be on the 'growable buffer'.
 * If we ever need to use extr_filenames() otherwise, we need to add a boolean
 * parameter that tells extr_filenames() whether 'names' needs to be corrected
 * if 'gbuf' did change or not.
 */
LOCAL void
extr_filenames(names)
	char	*names;
{
	char	*p;
	char	*np;
	char	*s;
	char	*sp;
	char	*sb;
	char	*sbe;

	sp = ++sub_ptr;
	sb = gbuf;
	sbe = gbufend;
	/*
	 * Make sure that gbuf has enough room for the copy.
	 * Getting the size with sub_ptr - names only works if "names"
	 * is on gbuf.
	 */
	if (wason_gbuf(names))
		grant_gbuf(sub_ptr - names);
	if (sb != gbuf) {
		sp = gbuf + (sp - sb);
		if (wason_gbuf(names))
			names = gbuf + (names - sb);
		sb = gbuf;
		sbe = gbufend;
	}
	for (np = p = names, s = sp; np && *np; p = np) {
		np = strchr(np, ' ');
		if (np)
			*np++ = '\0';
		p = filename(p);

		/*
		 * Make sure the next string fits on gbuf.
		 */
		sub_ptr = s;		/* needed by grant_gbuf() */
		grant_gbuf(strlen(p)+2); /* add null-byte and space */
		if (sb != gbuf) {
			sp = gbuf + (sp - sb);
			s = gbuf + (s - sb);
			if (wason_gbuf(names)) {
				names = gbuf + (names - sb);
				p = gbuf + (p - sb);
				if (np)
					np = gbuf + (np - sb);
			}
			sb = gbuf;
			sbe = gbufend;
		}

		while (*p)
			*s++ = *p++;
		if (np)
			*s++ = ' ';
	}
	*s = '\0';
	/*
	 * Now copy down from upper part of gbuf.
	 */
	for (s = names, p = sp; *p; )
		*s++ = *p++;
	*s = '\0';
	if (wason_gbuf(names))
		sub_ptr = s;
}

/*
 * Extract the directory parts from a string that contains a list of names.
 *
 * The parameter 'names' is expected to be on the 'growable buffer'.
 * If we ever need to use extr_dirnames() otherwise, we need to add a boolean
 * parameter that tells extr_dirnames() whether 'names' needs to be corrected
 * if 'gbuf' did change or not.
 */
LOCAL void
extr_dirnames(names)
	char	*names;
{
	char	*p;
	char	*np;
	char	*s;
	char	*sp;
	char	*sb;
	char	*sbe;
	char	_base[TYPICAL_NAMEMAX];
	char	*base = _base;
	char	*bp = NULL;
	size_t	blen = sizeof (_base);
	size_t	len;

	sp = ++sub_ptr;
	sb = gbuf;
	sbe = gbufend;
	/*
	 * Make sure that gbuf has enough room for the copy.
	 * Getting the size with sub_ptr - names only works if "names"
	 * is on gbuf.
	 */
	if (wason_gbuf(names))
		grant_gbuf(sub_ptr - names);
	if (sb != gbuf) {
		sp = gbuf + (sp - sb);
		if (wason_gbuf(names))
			names = gbuf + (names - sb);
		sb = gbuf;
		sbe = gbufend;
	}
	for (np = p = names, s = sp; np && *np; p = np) {
		np = strchr(np, ' ');
		if (np) {
			*np++ = '\0';
			len = np - p;
		} else {
			len = strlen(p) + 1;
		}

		/*
		 * Make sure the next string fits on gbuf.
		 */
		sub_ptr = s;		/* needed by grant_gbuf() */
		grant_gbuf(len+1);	/* add null-byte and space */
		if (sb != gbuf) {
			sp = gbuf + (sp - sb);
			s = gbuf + (s - sb);
			if (wason_gbuf(names)) {
				names = gbuf + (names - sb);
				p = gbuf + (p - sb);
				if (np)
					np = gbuf + (np - sb);
			}
			sb = gbuf;
			sbe = gbufend;
		}
		if (len > blen) {
			blen = len + 32;	/* Add some reserve */
			base = bp = ___realloc(bp, blen, "dir base");
		}
		copy_dir(p, base, blen);
		p = base;
		while (*p)
			*s++ = *p++;
		if (np)
			*s++ = ' ';
	}
	*s = '\0';
	if (bp)
		free(bp);
	/*
	 * Now copy down from upper part of gbuf.
	 */
	for (s = names, p = sp; *p; )
		*s++ = *p++;
	*s = '\0';
	if (wason_gbuf(names))
		sub_ptr = s;
}

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

/*
 * Expand a macro.
 * As the replacement may be a suffix rule or a pattern rule too,
 * we first must get the basic name the macro refers to.
 */
#ifdef	PROTOTYPES
LOCAL char *
exp_var(
	register char	end,
	char		*cmd,
	obj_t		*obj,
	obj_t		*source,
	char		*suffix,
	list_t		*depends)
#else
LOCAL char *
exp_var(end, cmd, obj, source, suffix, depends)
	register char	end;
		char	*cmd;
		obj_t	*obj;
		obj_t	*source;
		char	*suffix;
		list_t	*depends;
#endif
{
		char	_name[TYPICAL_NAMEMAX];
		char	*name = _name;
		char	*nep = &_name[sizeof (_name) - 2];
		char	*np = NULL;
		size_t	nlen = sizeof (_name);
		char	_pat[TYPICAL_NAMEMAX];
		char	*pat = _pat;
		char	*pep = &_pat[sizeof (_pat) - 2];
		char	*pp = NULL;
		size_t	plen = sizeof (_pat);
	register char	beg = cmd[-1];
	register char	*s = cmd;
	register char	*rname = name;
	register char	ch;
	register int	nestlevel = 0;
		BOOL	funccall = FALSE;

/*error("exp_var(end: '%c' cmd: '%.50s') [beg: '%c']\n", end, cmd, beg);*/
	pat[0] = '\0';
	while ((ch = *s) != '\0') {
		if (nestlevel == 0) {
			/*
			 * Only stop looking for the end of the macro name in
			 * case we are at the initial nestlevel. Collect as
			 * much as possible for a recursive macro name
			 * expansion below.
			 */
			if (ch == ':' || white(ch))
				break;
		}
		if (ch == beg)
			nestlevel++;
		if (ch == end)
			nestlevel--;
		if (nestlevel < 0) {
/*printf("name: %s\n", name);*/
			break;
		}
		if (rname >= nep) {
			nlen += TYPICAL_NAMEMAX*2;
			name = ___realloc(np, nlen, "macro name");
			if (np == NULL) {
				/*
				 * Copy old content.
				 * First null-terminate the current buffer.
				 * We did originally use sizeof (_name) for
				 * strlcpy(), but clang likes to be foxier
				 * than it is and writes a warning.
				 */
				*rname = '\0';
				strlcpy(name, _name, nlen);
				rname = name + (rname - _name);
			} else {
				rname = name + (rname - np);
			}
			np = name;
			nep = &name[nlen - 2];
		}
		*rname++ = *s++;
	}
	*rname = '\0';

	if (ch == ' ')
		funccall = TRUE;

	if (*s != end && *s != ':' && *s != ' ') {
		comerrno(EX_BAD, "Missing '%c' in macro call '%s'\n", end, name);
		/* NOTREACHED */
/*		return (cmd);*/
	}

	if (*s == ':' || white(*s)) {
		rname = pat;
		if (funccall) {
			while (*s && white(*s))
				s++;
		} else {
			s++;
		}
		nestlevel = 0;			/* Paranoia */
		while ((ch = *s) != '\0') {
			if (ch == beg)
				nestlevel++;
			if (ch == end)
				nestlevel--;
			if (nestlevel < 0) {
/*printf("name: %s\n", name);*/
				break;
			}
			if (rname >= pep) {
				plen += TYPICAL_NAMEMAX*2;
				pat = ___realloc(pp, plen, "macro pattern");
				if (pp == NULL) {
					/*
					 * Copy old content.
					 * First null-terminate the
					 * current buffer.
					 * We did originally use sizeof (_pat)
					 * for strlcpy(), but clang likes to be
					 * foxier than it is and writes a
					 * warning.
					 */
					*rname = '\0';
					strlcpy(pat, _pat, plen);
					rname = pat + (rname - _pat);
				} else {
					rname = pat + (rname - pp);
				}
				pp = pat;
				pep = &pat[plen - 2];
			}
			*rname++ = *s++;
		}
		*rname = '\0';

		if (nestlevel >= 0)
			comerrno(EX_BAD, "Missing '%c' in macro call '%s%c%s'\n",
					end, name, funccall?' ':':', pat);
	}
	if (*s)
		s++;

	if (name[0] == 'M' && streql(name, "MAKE"))
		found_make = TRUE;

	/*
	 * If the name of the macro contains a '$', recursively expand it.
	 * We need to check if we should rather expand anything between the
	 * brackets (e.g {...}) however, this may fail to expand long lists.
	 * See also comment in exp_name() regarding USE_SUBPAT
	 */
	if (strchr(name, '$')) {
		char	*sp = sub_ptr;
		char	*sb = gbuf;
		char	*s2;

		*sub_ptr++ = '\0';

		s2 = subst(name, obj, source, suffix, depends);
		if (sb != gbuf)
			sp = gbuf + (sp - sb);
		sb = gbuf;
		sub_ptr = sp;
		if (*s2) {
			if (strlcpy(name, s2, nlen) >= nlen) {
				nlen = strlen(s2) +1;
				name = np = ___realloc(np, nlen, "macro name");
				if (strlcpy(name, s2, nlen) >= nlen)
					etoolong("copy macro content", s2);
			}
		}
	}

	if (funccall) {
		/*
		 * GNU type macro functions will go here.
		 */
		goto out;
	}
/*printf("name: '%s' pat: '%s'\n", name, pat);*/
	exp_name(name, obj, source, suffix, depends, pat);
out:
	if (np)
		free(np);
	if (pp)
		free(pp);
	return (s);
}

/*
 * Check if s1 ends in strings s2
 */
LOCAL char *
rstr(s1, s2)
	char	*s1;
	char	*s2;
{
	int	l1;
	int	l2;

	l1 = strlen(s1);
	l2 = strlen(s2);
	if (l2 > l1)
		return ((char *)NULL);

	if (streql(&s1[l1 - l2], s2))
		return (&s1[l1 - l2]);
	return ((char *)NULL);
}

/*
 * Substitute a pattern:
 * 1)	select the part of 'name' that is surrounded by f1 & f2
 * 2a)	output the selected part from 'name' and t1 (suffix)
 * 2b)	output t2 -
 *	if t2 contains '%' substitute the selected part of 'name'
 *
 * The parameter 'name' is expected to be on the 'growable buffer'.
 * If we ever need to use patsub() otherwise, we need to add a boolean
 * parameter that tells patsub() whether 'name' needs to be corrected
 * if 'gbuf' did change or not.
 */
LOCAL BOOL
patsub(name, f1, f2, t1, t2)
	char	*name;
	char	*f1;
	char	*f2;
	char	*t1;
	char	*t2;
{
	int	l;
	char	*p;
	char	*sb = gbuf;
	char	*sbe = gbufend;

/*	printf("name: '%s' f1: '%s' f2: '%s' t1: '%s' t2: '%s'\n", name, f1, f2, t1, t2);*/

	/*
	 * Make sure $(VAR:%=PREFIX/%) does not expand if $VAR is empty.
	 */
	if (*name == '\0' && *f1 == '\0' && *f2 == '\0')
		return (FALSE);
	/*
	 * XXX NeXT Step has a buggy strstr(); returns NULL if f1 == ""
	 * XXX f1 is guaranteed to be != NULL
	 */
	if (*f1 != '\0' && strstr(name, f1) != name)
		return (FALSE);		/* no matching prefix */

	name += strlen(f1);		/* strip matching prefix */

	if ((p = rstr(name, f2)) == NULL)
		return (FALSE);		/* no matching suffix */

	l = p - name;
	if (t1 != NULL) {		/* This is a suffix rule */
		sub_put(name, l);	/* 'name' maybe on gbuf... */
		if (sb != gbuf) {
			if (wason_gbuf(name))
				name = gbuf + (name - sb);
			sb = gbuf;
			sbe = gbufend;
		}
		p = t1;
	} else {			/* This is a pattern rule */
		p = t2;
	}
	while (*p) {
		/*
		 * Expand '%' only in case of a patten macro expansion.
		 */
		if (*p == '%' && t2 != NULL) {
			p++;
			sub_put(name, l); /* 'name' is on gbuf... */
		} else {
			sub_put(p++, 1);
		}
		if (sb != gbuf) {
			if (wason_gbuf(name))
				name = gbuf + (name - sb);
			if (wason_gbuf(p))
				p = gbuf + (p - sb);
			sb = gbuf;
			sbe = gbufend;
		}
	}
	return (TRUE);
}

#ifdef	needed
/*
 * Do pattern substitution in a macro that may only contain one word.
 *
 * The parameter 'name' is expected to be on the 'growable buffer'.
 * If we ever need to use patssub() otherwise, we need to add a boolean
 * parameter that tells patssub() whether 'name' needs to be corrected
 * if 'gbuf' did change or not.
 */
LOCAL void
patssub(name, f1, f2, t1, t2)
	char	*name;
	char	*f1;
	char	*f2;
	char	*t1;
	char	*t2;
{
	char	*osp = name;
	char	*sp = sub_ptr;
	char	*sb = gbuf;

	*sub_ptr++ = '\0';

	if (!patsub(name, f1, f2, t1, t2)) {
		sub_ptr = sp;
		return;
	}
	*sub_ptr = '\0';
	sub_ptr = osp;
	if (sb != gbuf) {
		sp = gbuf + (sp - sb);
		sub_ptr = gbuf + (sub_ptr - sb);
		sb = gbuf;
	}
	sub_s_put(&sp[1]);
}
#endif

/*
 * Do pattern substitution in a macro that may contain multiple words.
 * Substitution is applied separately to each word.
 *
 * The parameter 'name' is expected to be on the 'growable buffer'.
 * If we ever need to use patmsub() otherwise, we need to add a boolean
 * parameter that tells patmsub() whether 'name' needs to be corrected
 * if 'gbuf' did change or not.
 */
LOCAL void
patmsub(name, f1, f2, t1, t2)
	char	*name;
	char	*f1;
	char	*f2;
	char	*t1;
	char	*t2;
{
	char	*osp = name;
	char	*sp = sub_ptr;
	char	*sb = gbuf;
	char	*sbe = gbufend;
	char	*b;
	char	c;

	*sub_ptr++ = '\0';

	do {
		b = name;
		while (*b != '\0' && !white(*b))
			b++;
		if ((c = *b) != '\0')
			*b++ = '\0';
		else
			b = NULL;

/*error("name '%s'\n", name);*/
		if (!patsub(name, f1, f2, t1, t2)) {
			sub_s_put(name);	/* 'name' maybe on gbuf... */
		}
		if (sb != gbuf) {
			sp = gbuf + (sp - sb);
			osp = gbuf + (osp - sb);
			if (wason_gbuf(name)) {
				name = gbuf + (name - sb);
				if (b != NULL)
					b = gbuf + (b - sb);
			}
			sb = gbuf;
			sbe = gbufend;
		}

		if (b) {
			sub_c_put(c);
			if (sb != gbuf) {
				sp = gbuf + (sp - sb);
				osp = gbuf + (osp - sb);
				if (wason_gbuf(b))
					b = gbuf + (b - sb);
				sb = gbuf;
				sbe = gbufend;
			}
			while (*b != '\0' && white(*b)) {
				sub_put(b++, 1);
				if (sb != gbuf) {
					sp = gbuf + (sp - sb);
					osp = gbuf + (osp - sb);
					if (wason_gbuf(b))
						b = gbuf + (b - sb);
					sb = gbuf;
					sbe = gbufend;
				}
			}
		}
		name = b;
	} while (b);
	*sub_ptr = '\0';
	sub_ptr = osp;
	if (sb != gbuf) {
		sp = gbuf + (sp - sb);
		sub_ptr = gbuf + (sub_ptr - sb);
		sb = gbuf;
		sbe = gbufend;
	}
	sub_s_put(&sp[1]);
}

/*
 * Parse a pattern and divide pattern into parts.
 *
 * Check if this is a suffix replacement macro or a pattern repacement macro.
 *
 * If this is a suffix macro (suf1=suf2), tp2 will point to a NULL pointer,
 * if this is a pattern macro (pref1%suf1=pref1%suf2) tp1 will point to NULL.
 */
LOCAL void
parsepat(pat, fp1, fp2, tp1, tp2)
	char	*pat;
	char	**fp1;
	char	**fp2;
	char	**tp1;
	char	**tp2;
{
	char	*f1;
	char	*f2;
	char	*t1;
	char	*t2;

	t1 = strchr(pat, '=');
	if (t1 == NULL)
		comerrno(EX_BAD, "'=' missing in macro substitution.\n");
	*t1++ = '\0';

	f2 = strchr(pat, '%');		/* Find '%' in from patttern */
	if (f2 != NULL) {
		*f2++ = '\0';
		f1 = pat;
	} else {
		f2 = pat;
		f1 = Nullstr;
	}
	if (f1 == pat) {		/* This is a pattern macro */
		t2 = t1;
		t1 = NULL;
	} else {			/* This is a suffix macro */
		t2 = NULL;
	}
	*fp1 = f1;
	*fp2 = f2;
	*tp1 = t1;
	*tp2 = t2;
/*	printf("f1: '%s' f2: '%s' t1: '%s' t2: '%s'\n", f1, f2, t1, t2);*/
}

#if	!defined(popen) && !defined(HAVE_POPEN) && defined(HAVE__POPEN)
#	define	popen	_popen
#	define	HAVE_POPEN
#endif
#if	!defined(pclose) && !defined(HAVE_PCLOSE) && defined(HAVE__PCLOSE)
#	define	pclose	_pclose
#	define	HAVE_PCLOSE
#endif
/*
 * Call shell command and capture the outpout in the growable buffer.
 */
EXPORT char *
shout(cmd)
	char	*cmd;
{
	FILE	*f;
	int	c;
	char	*sptr = sub_ptr;
	char	*sbuf = gbuf;

	/*
	 * getshvar() does not know about sub_ptr and thus cannot initialize
	 * it. As it first overwrites gbuf and then calls us with name == gbuf,
	 * we can do the needed initialization for sub_c_put() here.
	 */
	if (sub_ptr == NULL || cmd == gbuf)
		sptr = sub_ptr = gbuf;

	f = popen(cmd, "r");
	if (f == NULL)
		comerr("Cannot popen '%s'.\n", cmd);

	while ((c = getc(f)) != EOF) {
		if (c == '\t' || c == '\n')
			c = ' ';
		sub_c_put(c);
	}
	if ((c = pclose(f)) != 0)
		comerr("Shell returns %d from command line.\n", c);
	*sub_ptr = '\0';
	if (sbuf != gbuf)
		sptr = gbuf + (sptr - sbuf);
	sbuf = gbuf;
	return (sptr);
}

/*
 * Substitute a shell command output
 */
LOCAL char *
shsub(l, obj, source, suffix, depends)
	register list_t *l;
		obj_t	*obj;
		obj_t	*source;
		char	*suffix;
		list_t  *depends;
{
	char	*sptr = sub_ptr;
	char	*sbuf = gbuf;

	if (sub_ptr == NULL)
		sptr = sub_ptr = gbuf;

	for (;;) {
		subst(l->l_obj->o_name, obj, source, suffix, depends);
		if ((l = l->l_next) != NULL)
			sub_c_put(' ');
		else
			break;
	}
	*sub_ptr = '\0';
	if (sbuf != gbuf)
		sptr = gbuf + (sptr - sbuf);
	sub_ptr = sptr;
	sbuf = gbuf;

	return (shout(sptr));
}

/*
 * Expand a macro for which the name is explicitly known.
 */
LOCAL void
exp_name(name, obj, source, suffix, depends, pat)
		char	*name;
		obj_t	*obj;
		obj_t	*source;
		char	*suffix;
		list_t  *depends;
		char	*pat;
{
	register list_t	*l = NULL;
		obj_t	*o = NULL;
		BOOL	ispat;
		char	epat[TYPICAL_NAMEMAX*2];	/* Was NAMEMAX */
		char	*epa = epat;
		char	*ep = NULL;
		char	*f1, *f2;
		char	*t1, *t2;
		char	*sp;
		char	*sb;

	if ((chartype[*(Uchar *)name] & DYNCHAR) == 0 ||
			(*name == 'r' && (chartype[((Uchar *)name)[1]] & NUMBER) == 0)) {
		/*
		 * Allow dynamic macros to appear in $() or ${} too.
		 */
		o = objlook(name, FALSE);
		if (o == (obj_t *)NULL) {
			/*
			 * Check for $O -> ObjDir (pseudo dyn) macro.
			 * $O is a pseudo dyn macro as we allow to overwrite it
			 * in order get full POSIX make compliance.
			 */
			if (name[0] == 'O' && name[1] == '\0') {
				/*
				 * If $O has not been overwritten, use ObjDir.
				 */
				if (ObjDir == NULL) {
					sp = sub_ptr;
					sb = gbuf;
					sub_c_put('.');
					*sub_ptr = '\0';
					if (pat[0] != '\0') {
						char	*p;
						p = subst(pat, obj, source,
							suffix, depends);
						if ((sub_ptr - p) >=
						    sizeof (epat)) {
							epa = ep = ___realloc(ep,
								sub_ptr - p + 1,
								"pattern content");
						}
						strcpy(epa, p);
						/*
						 * Free space from subpat()
						 */
						sub_ptr = p;
						parsepat(epa, &f1, &f2, &t1, &t2);
						if (sb != gbuf)
							sp = gbuf + (sp - sb);
						sb = gbuf;
						/*
						 * patmsub() expects first
						 * parameter to be in 'gbuf'
						 */
						patmsub(sp, f1, f2, t1, t2);
					}
				} else {
					exp_name(".OBJDIR", obj, source, suffix, depends, pat);
				}
			}
			if (ep)
				free(ep);
			return;
		}
		if ((l = o->o_list) == (list_t *)NULL)
			return;
	}
	if (streql(pat, "sh")) {
		/*
		 * Allow SunPRO make compatible shell expansion as $(NAME:sh)
		 */
		if (o != NULL) {
			shsub(o->o_list, obj, source, suffix, depends);
		} else {
			/*
			 * Process dynamic macros that appear in $() or ${}.
			 */
			sp = sub_ptr;
			sb = gbuf;
			dynmac(name, obj, source, suffix, depends, TRUE);
/*error("expanded1: '%s'\n", sp);*/
			if (sb != gbuf)
				sp = gbuf + (sp - sb);
			sub_ptr = sp;
			sb = gbuf;
			(void) shout(sp);
		}
		return;
	}
	ispat = pat[0] != '\0';
	if (ispat) {
		char	*p = subst(pat, obj, source, suffix, depends);

		if ((sub_ptr - p) >= sizeof (epat)) {
			epa = ep = ___realloc(ep, sub_ptr - p + 1,
						"pattern content");
		}
		strcpy(epa, p);
		sub_ptr = p;	/* Free space from subpat() */
		parsepat(epa, &f1, &f2, &t1, &t2);
	}

	if (o == NULL) {
		/*
		 * Process dynamic macros that appear in $() or ${}.
		 */
		sp = sub_ptr;
		sb = gbuf;
		dynmac(name, obj, source, suffix, depends, TRUE);
/*error("expanded1: '%s'\n", sp);*/
		if (ispat) {
			if (sb != gbuf)
				sp = gbuf + (sp - sb);
			sb = gbuf;
			/*
			 * patmsub() expects first parameter to be in 'gbuf'
			 */
			patmsub(sp, f1, f2, t1, t2);
/*error("expanded2: '%s'\n", sp);*/
		}
		if (ep)
			free(ep);
		return;
	}
	for (;;) {
		sp = sub_ptr;
		sb = gbuf;
		subst(l->l_obj->o_name, obj, source, suffix, depends);
/*error("expanded1: '%s'\n", sp);*/
		if (ispat) {
			if (sb != gbuf)
				sp = gbuf + (sp - sb);
			sb = gbuf;
			/*
			 * patmsub() expects first parameter to be in 'gbuf'
			 */
			patmsub(sp, f1, f2, t1, t2);
/*error("expanded2: '%s'\n", sp);*/
		}
		if ((l = l->l_next) != NULL)
			sub_c_put(' ');
		else
			break;
/*error("expanded3: '%s'\n", sp);*/
	}
	if (ep)
		free(ep);
}

/*
 * Copy over the time and level from the intermediate :: object
 * to the main base object.
 */
LOCAL void
dcolon_time(obj)
	register obj_t	*obj;
{
#ifdef	__really_needed__
	if (VALIDTIME(obj->o_node->o_date))
		return;
#endif

#ifdef	Do_not_use_o_node
	name = strchr(obj->o_name, '@');
	if (name == NULL)
		return;
	o = objlook(++name, FALSE);
#endif

	obj->o_node->o_date = obj->o_date;
	obj->o_node->o_level = obj->o_level;
}

/*
 * Find a file in .SEARCHLIST
 * .SEARCHLIST is a list of of src/obj dir pairs
 * Before .SEARCHLIST is searched, look for "." and .OBJDIR
 */
LOCAL date_t
searchobj(obj, maxlevel, mode)
	register obj_t	*obj;
		int	maxlevel;
		int	mode;
{
		char	name[TYPICAL_NAMEMAX];
		char	*namep = NULL;
		char	*oname = NULL;
	register int	level = MAXLEVEL;
		date_t	filedate;


	name[0] = '\0';			/* for clean debug print */

	if ((maxlevel & 1) == 0)	/* Source has lower level	*/
		maxlevel += 1;		/* include OBJ directory level	*/

	if (obj->o_date == PHONYTIME) {
		filedate = PHONYTIME;

	} else if (isphony(obj)) {
		obj->o_date = filedate = PHONYTIME;

#ifdef	NO_SEARCHOBJ_SLASH_IN_NAME
	/*
	 * Earlier versions of smake did allow slashes anywhere in path names
	 * and being searched for. A common practice in VPATH usage requires us
	 * to allow slashes inside a path name again.
	 */
	} else if (strchr(obj->o_name, SLASH) != (char *)NULL) {
#else
	/*
	 * If an object file name starts with a slash, is is an absolute path
	 * name so do not search.
	 */
	} else if (obj->o_name[0] == SLASH) {
#endif
		/*
		 * Do not search for pathnames.
		 */
		filedate = gftime(obj->o_name);
	} else for (level = 0, filedate = NOTIME; level <= maxlevel; level++) {
		if (level & 1 ? mode & SOBJ : mode & SSRC) {
			oname = obj->o_name;
			if ((obj->o_flags & F_DCOLON) != 0) {
				oname = obj->o_node->o_name;
				if ((namep = build_path(level,
						obj->o_node->o_name,
						obj->o_node->o_namelen,
						name, sizeof (name))) == NULL)
					break;
			} else if ((namep = build_path(level, obj->o_name,
						obj->o_namelen,
						name, sizeof (name))) == NULL)
				break;
			if ((filedate = gftime(namep)) != NOTIME)
				break;
			if (level == WDLEVEL && ObjDir == (char *)NULL) {
				/*
				 * Working dir has just been searched don't
				 * look again if no .OBJDIR has been defined.
				 */
				level++;
			}
		}
	}
	if (filedate == NOTIME || filedate == PHONYTIME)
		level = MAXLEVEL;

	obj->o_date = filedate;
	obj->o_level = level;
	if (obj->o_flags & F_DCOLON)
		dcolon_time(obj);	/* Copy from intermediate to main obj */

	if (Debug > 2) {
		error("search(%s, %d, %s) = %s %s %d\n",
				obj->o_name, maxlevel, searchtype(mode),
				prtime(filedate), namep, level);
	}
	if (namep != NULL && namep != oname && namep != name) {
		free(namep);
	}
	if (filedate == PHONYTIME) {
		return (NOTIME);
	}
	return (filedate);
}

/*
 * Find a source file for a given object file, use pattern matching rules.
 */
LOCAL obj_t *
patr_src(name, prule, rtypep, suffixp, pcmd, dlev)
	char	*name;
	patr_t	*prule;
	int	*rtypep;
	char	**suffixp;
	cmd_t	**pcmd;
	int	dlev;
{
		char	*xname;
	register obj_t	*source;
		obj_t	newsource;
		char	_sourcename[TYPICAL_NAMEMAX];
		char	*sourcename = _sourcename;
		char	*sp = NULL;
		size_t	slen = sizeof (_sourcename);
		char	_pat[TYPICAL_NAMEMAX];
		char	*pat = _pat;
		char	*pp = NULL;
		size_t	plen = sizeof (_pat);
		char	*p;
		size_t	len;
		obj_t	*obj;

	if (prule->p_list == NULL) {
		source = NullObj;
		goto found;
	}
	if ((prule->p_list->l_obj->o_flags & F_PERCENT) == 0)
		errmsgno(EX_BAD,
		"WARNING: Non-percent pattern dependencies not yet supported.\n");
	if (prule->p_list->l_next)
		errmsgno(EX_BAD,
		"WARNING: More than one pattern dependency not yet supported.\n");

again:
	*suffixp = (char *)prule->p_tgt_suffix;

	p = (char *)prule->p_tgt_prefix;
	xname = name + prule->p_tgt_pfxlen;	/* strip matching prefix */
/*error("name: '%s'\n", xname);*/
	p = rstr(xname, (char *)prule->p_tgt_suffix);
/*error("p: '%s'\n", p);*/
	len = p - xname + 1;
	if (len > plen) {
		plen = len;
		pat = pp = ___realloc(pp, plen, "pattern content");
	}
	strlcpy(pat, xname, len);
/*error("pat: '%s' len %d strlen %d\n", pat, len, strlen(pat));*/
	/*
	 * "len" contains the Null Byte
	 */
	if ((len = prule->p_src_pfxlen + len + prule->p_src_suflen) > slen) {
		slen = len;
		sourcename = sp = ___realloc(sp, slen, "pattern source name");
	}
	if ((len = snprintf(sourcename, slen, "%s%s%s",
			prule->p_src_prefix, pat,
			prule->p_src_suffix)) >= slen) {
		etoolong("copy pattern source name", sourcename);
		/* NOTREACHED */
	}
/*error("sourcename: '%s'\n", sourcename);*/

	newsource.o_name = sourcename;
	newsource.o_namelen = len;
	newsource.o_date = NOTIME;
	newsource.o_type = 0;
	newsource.o_flags = 0;
	newsource.o_level = MAXLEVEL;
	newsource.o_fileindex = 0;
	newsource.o_list = (list_t *)NULL;
	newsource.o_cmd = (cmd_t *)NULL;
	newsource.o_node = (obj_t *)NULL;

	if (prule->p_flags & PF_TERM)
		newsource.o_flags |= F_TERM;

	if ((source = objlook(sourcename, FALSE)) != (obj_t *)NULL) {
		make(source, TRUE, dlev);	/* source found, update it  */
		/* XXX make() return value ??? */

	} else if (make(&newsource, FALSE,
			dlev) != NOTIME) {	/* found or made source */
		source = objlook(sourcename, TRUE);
		source->o_date = newsource.o_date;
		source->o_level = newsource.o_level;
		if (source->o_flags & F_DCOLON)
			dcolon_time(source);

	} else {
		/*
		 * This is a replicated search for additional matches for the
		 * same obj.
		 */
		prule = _pattern_rule(prule->p_next, name);
		if (prule != NULL)
			goto again;

		*pcmd = (cmd_t *)NULL;
		source = (obj_t *)0;
		goto out;
	}

found:
	/*
	 * If we do not create missing nodes here, Pattern Matching Rules like:
	 * %.1:
	 *	command
	 * will cause smake to dump core.
	 */
	obj = objlook(name, TRUE);
	obj->o_node = (obj_t *)prule;
	obj->o_flags |= F_PATRULE;
	*pcmd = prule->p_cmd;
out:
	if (sp)
		free(sp);
	if (pp)
		free(pp);
	return (source);
}

/*
 * Find a source file for a given object file, use POSIX suffix rules.
 * Loop over all possible source names for all possible target suffixes.
 */
LOCAL obj_t *
suff_src(name, rule, rtypep, suffixp, pcmd, dlev)
	char	*name;
	obj_t	*rule;
	int	*rtypep;
	char	**suffixp;
	cmd_t	**pcmd;
	int	dlev;
{
	register list_t	*suf;		/* List of possible suffixes */
	register obj_t	*source;
	register char	*suffix = NULL;
		BOOL	found_suffix = FALSE;

	for (suf = Suffixes; suf; suf = suf->l_next) {
		suffix = suf->l_obj->o_name;
		if (rstr(name, suffix) == NULL)	/* may not be a suffix */
			continue;

		found_suffix = TRUE;
		*suffixp = suffix;
		if ((source = one_suff_src(name, suffix, pcmd, dlev)) != NULL)
			return (source);
	}
	if (found_suffix) {
		*pcmd = (cmd_t *)NULL;
		return ((obj_t *)0);
	}
	/*
	 * XXX We should never come here.
	 */
	errmsgno(EX_BAD, "XXX Expected double suffix rule but found none.\n");
	return (one_suff_src(name, Nullstr, pcmd, dlev));
}

/*
 * Find a source file for a given object file, use POSIX suffix rules.
 * Loop over all possible source names for one given target suffix.
 */
LOCAL obj_t *
one_suff_src(name, suffix, pcmd, dlev)
	char	*name;
	char	*suffix;
	cmd_t	**pcmd;
	int	dlev;
{
	register list_t	*suf;		/* List of possible suffixes */
	register obj_t	*o;
	register obj_t	*source;
		obj_t	newsource;
		char	_sourcename[TYPICAL_NAMEMAX];
		char	*sourcename = _sourcename;
		char	*sp = NULL;
		size_t	slen = sizeof (_sourcename);
		char	*endbase;
		char	_rulename[TYPICAL_NAMEMAX];
		char	*rulename = _rulename;
		char	*rp = NULL;
		size_t	rlen = sizeof (_rulename);
		size_t	endlen;
		size_t	sourcelen;
		size_t	len;

	if ((len = strlen(name)) >= slen) {
		slen = len + 16;		/* Add space for '\0' and suf */
		sourcename = sp = ___realloc(sp, slen,
					"suffix source name");
	}
	copy_base(name, sourcename, slen, suffix);
	endbase = sourcename + strlen(sourcename);
	endlen = slen - (endbase - sourcename);

	newsource.o_name = sourcename;
	newsource.o_namelen = sourcelen = strlen(sourcename);
	newsource.o_date = NOTIME;
	newsource.o_type = 0;
	newsource.o_flags = 0;
	newsource.o_level = MAXLEVEL;
	newsource.o_fileindex = 0;
	newsource.o_list = (list_t *)NULL;
	newsource.o_cmd = (cmd_t *)NULL;
	newsource.o_node = (obj_t *)NULL;

	for (suf = Suffixes; suf; suf = suf->l_next) {
	again:
		if (snprintf(rulename, rlen,
					"%s%s",
					suf->l_obj->o_name,
					suffix) >= rlen) {
			/*
			 * Expand rule name space.
			 */
			rlen = strlen(suffix) + suf->l_obj->o_namelen + 16;
			rulename = rp = ___realloc(rp, rlen, "suffix rule name");
			goto again;

		}

		if ((o = objlook(rulename, FALSE)) == NULL || o->o_type != COLON)
			continue;

		if (o->o_list)
			suffix_warn(o);

		if (Debug > 1)
			error("Trying %s suffix rule '%s' for: %s (suffix: '%s')\n",
				*suffix?"double":"single", rulename, name, suffix);

		if (suf->l_obj->o_namelen >= endlen) {
			slen = sourcelen + suf->l_obj->o_namelen + 1;
			sourcename = ___realloc(sp, slen,
						"suffix source name");
			if (sp == NULL) {
				/*
				 * Copy old content.
				 * First null-terminate the current buffer.
				 */
				_sourcename[sizeof (_sourcename) - 1] = '\0';
				strlcpy(sourcename, _sourcename, sourcelen+1);
			}
			newsource.o_name = sp = sourcename;
			endbase = sourcename + sourcelen;
			endlen = slen - (endbase - sourcename);
		}
		if (strlcpy(endbase, suf->l_obj->o_name, endlen) >= endlen)
			etoolong("build suffix source name", sourcename);
		newsource.o_namelen = sourcelen + suf->l_obj->o_namelen;
		if ((source = objlook(sourcename, FALSE)) != (obj_t *)NULL) {
			make(source, TRUE, dlev); /* source known, update it */
			/* XXX make() return value ??? */

			*pcmd = o->o_cmd;
			if (sp)
				free(sp);
			if (rp)
				free(rp);
			return (source);
		}
		if (make(&newsource, FALSE,
				dlev) != NOTIME) { /* found/made source */
			source = objlook(sourcename, TRUE);
			source->o_date = newsource.o_date;
			source->o_level = newsource.o_level;
			if (source->o_flags & F_DCOLON)
				dcolon_time(source);

			*pcmd = o->o_cmd;
			if (sp)
				free(sp);
			if (rp)
				free(rp);
			return (source);
		}
	}
	if (sp)
		free(sp);
	if (rp)
		free(rp);
	*pcmd = (cmd_t *)NULL;
	return ((obj_t *)0);
}

/*
 * Find a source file for a given object file, use simple suffix rules.
 */
LOCAL obj_t *
ssuff_src(name, rule, rtypep, suffixp, pcmd, dlev)
	char	*name;
	obj_t	*rule;
	int	*rtypep;
	char	**suffixp;
	cmd_t	**pcmd;
	int	dlev;
{
	register list_t	*suf = rule->o_list;	/* List of possible suffixes */
	register cmd_t	*cmd = rule->o_cmd;	/* List of implicit commands */
		cmd_t	*ncmd;			/* for allocated new command */
	register obj_t	*source;
		obj_t	newsource;
		char	_sourcename[TYPICAL_NAMEMAX];
		char	*sourcename = _sourcename;
		char	*sp = NULL;
		size_t	slen = sizeof (_sourcename);
		char	*endbase;
		size_t	endlen;
		size_t	sourcelen;
		size_t	len;

	*suffixp = get_suffix(name, (char *)0);	/* Use '.' (dot) suffix only */

	if ((len = strlen(name)) >= slen) {
		slen = len + 16;		/* Add space for '\0' and suf */
		sourcename = sp = ___realloc(sp, slen,
					"simple suffix source name");
	}
	copy_base(name, sourcename, slen, (char *)NULL);
	endbase = sourcename + strlen(sourcename);
	endlen = slen - (endbase - sourcename);

	newsource.o_name = sourcename;
	newsource.o_namelen = sourcelen = strlen(sourcename);
	newsource.o_date = NOTIME;
	newsource.o_type = 0;
	newsource.o_flags = 0;
	newsource.o_level = MAXLEVEL;
	newsource.o_fileindex = 0;
	newsource.o_list = (list_t *)NULL;
	newsource.o_cmd = (cmd_t *)NULL;
	newsource.o_node = (obj_t *)NULL;

	do {
		if (suf->l_obj->o_namelen >= endlen) {
			slen = sourcelen + suf->l_obj->o_namelen + 1;
			sourcename = ___realloc(sp, slen,
						"simple suffix source name");
			if (sp == NULL) {
				/*
				 * Copy old content.
				 * First null-terminate the current buffer.
				 */
				_sourcename[sizeof (_sourcename) - 1] = '\0';
				strlcpy(sourcename, _sourcename, sourcelen+1);
			}
			newsource.o_name = sp = sourcename;
			endbase = sourcename + sourcelen;
			endlen = slen - (endbase - sourcename);
		}
		if (strlcpy(endbase, suf->l_obj->o_name, endlen) >= endlen)
			etoolong("build simple suffix source name", sourcename);
		newsource.o_namelen = sourcelen + suf->l_obj->o_namelen;
		if ((source = objlook(sourcename, FALSE)) != (obj_t *)NULL) {
			make(source, TRUE, dlev); /* source known, update it */
			/* XXX make() return value ??? */
			break;
		}
		if (make(&newsource, FALSE,
				dlev) != NOTIME) { /* found/made source */
			source = objlook(sourcename, TRUE);
			source->o_date = newsource.o_date;
			source->o_level = newsource.o_level;
			if (source->o_flags & F_DCOLON)
				dcolon_time(source);

			break;
		}
	} while ((suf = suf->l_next) != (list_t *)NULL &&
				(cmd = cmd->c_next) != (cmd_t *)NULL);
	if (sp)
		free(sp);
	if (source) {
		ncmd = (cmd_t *)fastalloc(sizeof (cmd_t));
		ncmd->c_next = (cmd_t *)NULL;
		ncmd->c_line = cmd->c_line;
		*pcmd = ncmd;
		*rtypep |= RTYPE_NEEDFREE;
	} else {
		*pcmd = (cmd_t *)NULL;
	}
	return (source);
}

LOCAL obj_t	*BadObj;
/*
 * Find a source file for a given object file.
 */
LOCAL obj_t *
findsrc(obj, rule, rtypep, suffixp, pcmd, dlev)
	obj_t	*obj;
	obj_t	*rule;
	int	*rtypep;
	char	**suffixp;
	cmd_t	**pcmd;
	int	dlev;
{
	int	rtype = *rtypep;

	if (BadObj == NULL) {
		BadObj = objlook("BAD OBJECT", TRUE);
		BadObj->o_date = BADTIME;
		BadObj->o_type = ':';
	}
	*suffixp = (char *)NULL;
	*pcmd = (cmd_t *)NULL;

	if (obj->o_flags & F_TERM)
		return ((obj_t *)0);

	switch (rtype) {

	case RTYPE_PATTERN:
		return (patr_src(obj->o_name, (patr_t *)rule,
					rtypep, suffixp, pcmd, dlev));

	case RTYPE_DSUFFIX:
		return (suff_src(obj->o_name, rule,
					rtypep, suffixp, pcmd, dlev));

	case RTYPE_SUFFIX:
		*suffixp = Nullstr;
		return (one_suff_src(obj->o_name, Nullstr, pcmd, dlev));

	case RTYPE_SSUFFIX:
		return (ssuff_src(obj->o_name, rule,
					rtypep, suffixp, pcmd, dlev));

	case RTYPE_DEFAULT:
		*pcmd = (cmd_t *)rule;
		return (obj);		/* This causes $< to be == $@ */

	default:
		break;
	}
	errmsgno(EX_BAD, "Impossible default rule type %X for '%s'.\n",
			rtype,
			obj->o_name);
	return (BadObj);
}

/*
 * Process a target with no explicit command list.
 */
LOCAL date_t
default_cmd(obj, depname, deptime, deplevel, must_exist, dlev)
	register obj_t	*obj;
		char	*depname;
		date_t	deptime;
		int	deplevel;
		BOOL	must_exist;
		int	dlev;
{
	obj_t	*rule = (obj_t *)NULL;
	obj_t	*source;
	cmd_t	*cmd;
	cmd_t	*cp;
	char	*suffix;
	int	rtype = RTYPE_NONE;


	if (dlev <= 0) {
		errmsgno(EX_BAD,
			"Recursion in default rule for '%s'.\n",
			obj->o_name);
		return (RECURSETIME);
	}

	if (((obj->o_flags & F_TERM) == 0) &&
	    ((rule = default_rule(obj, &rtype)) == (obj_t *)NULL)) {
		/*
		 * Found no default dependency rule for this target.
		 * This is most likely a placeholder target where no
		 * related file exists. As we did not found rules,
		 * we do not run commands and thus do not propagate deptime.
		 */
		if (obj->o_list != (list_t *)NULL) {
			obj->o_level = deplevel;
			return (NOTIME);
#ifdef	notdef
			/*
			 * This caused "all is up to date" messages.
			 */
			return (deptime);
#endif
		}
		/*
		 * Found no rule to make target.
		 */
		if (obj == default_tgt) {
			/*
			 * For intermediate placeholder targets like FORCE:
			 * that have no prerequisites, do not create an error.
			 */
			if (obj->o_list == (list_t *)NULL)
				return (NOTIME);
			errmsgno(EX_BAD, "'%s' does not say how to make '%s'.\n",
				MakeFileNames[obj->o_fileindex], obj->o_name);
			return (BADTIME);
		}
		/*
		 * Check if it is a source.
		 */
		if (searchobj(obj, MAXLEVEL, SSRC) != NOTIME)
			return (obj->o_date);
		if (!must_exist)
			return (NOTIME);
		/*
		 * For intermediate placeholder targets like FORCE:
		 * that have no prerequisites, do not create an error.
		 */
		if (obj->o_list == (list_t *)NULL)
			return (NOTIME);

		errmsgno(EX_BAD, "'%s' does not exist.\n", obj->o_name);
		return (BADTIME);
	}
	/*
	 * findsrc() returns NULL if (obj->o_flags & F_TERM) != 0.
	 * This prevents problems with rule == NULL from default_rule().
	 */
	if ((source =
		findsrc(obj, rule, &rtype, &suffix, &cmd,
						dlev)) == (obj_t *)NULL) {

		/* Probably a obj file name. */
		if (searchobj(obj, MAXLEVEL, ObjSearch) != NOTIME)
			return (obj->o_date);

		if (Debug > 2)
			error("Cannot find source for: %s->time: %s %s(type: %X)\n",
				obj->o_name, prtime(obj->o_date),
				must_exist?"but must ":"", obj->o_type);
		if (!must_exist)
			return (NOTIME);
		/*
		 * A target must not exist, a dependency must.
		 */
		if (basetype(obj->o_type) == COLON)
			return (NOTIME);
		if (!No_Warn)
			errmsgno(EX_BAD,
				"Can't find any source for '%s'.\n",
							obj->o_name);
		if (NSflag)
			return (NOTIME);	/* -N specified */
		return (BADTIME);
	}

	if (source->o_date == BADTIME)
		goto badtime;

	if (source->o_date > deptime) {	/* target depends on this source too */
		depname = source->o_name;
		deptime = source->o_date;
	}
	if (source->o_level < deplevel)
		deplevel = source->o_level;
	if (deptime == BADTIME)		/* Errors occured cannot make source.*/
		goto badtime;

	if (deptime >= newtime ||		/* made some targets    */
	    !searchobj(obj, deplevel, SALL) ||	/* found no obj file    */
	    deptime > obj->o_date) {		/* target is out of date */

		if (Debug > 0)
			error("Default: %s is out of date to: %s%s\n",
						obj->o_name, depname,
						isphony(obj)?" and .PHONY:":"");
		if (Tflag) {
			if (!isphony(obj) && !touch_file(obj->o_name))
				goto badtime;
		}
		/*
		 * Sun make only calls '+' command with -t when in POSIX mode.
		 */
		for (cp = cmd; cp; cp = cp->c_next) {
			if (docmd(substitute(cp->c_line, obj, source, suffix), obj)) {
				goto badtime;
			}
		}
		obj->o_date = newtime;
		obj->o_level = WDLEVEL;
		if (obj->o_flags & F_DCOLON)
			dcolon_time(obj);

	}
	if ((rtype & RTYPE_NEEDFREE) != 0 && cmd != NULL)
		fastfree((char *)cmd, sizeof (*cmd));

	/*
	 * A file is only subject to automatic movement to the object directory
	 * if there is no slash in the path name. move_tgt() will not move such
	 * a file and we thus do not check such files here as well.
	 */
	if (!Tflag && ObjDir != (char *)NULL &&
	    obj->o_level == WDLEVEL && strchr(obj->o_name, SLASH) == NULL) {
		int	m;			/* for move status */
		int	a = 0;			/* == 1 if file at src level */

		if (Debug > 3) {
			list_t	*l = list_nth(SearchList, source->o_level-2);

			error("%s: obj->o_level: %d %s: source->o_level: %d '%s'\n",
					obj->o_name, obj->o_level,
					source->o_name, source->o_level,
					l ? l->l_obj->o_name:Nullstr);
		}
		obj->o_level = source->o_level;
		if ((source->o_level & 1) == 0)	/* Source has lower level  */
			a = 1;			/* add one if source level */
		obj->o_level += a;		/* use corresponding ObjDir*/

		if ((m = move_tgt(obj)) == 0) {	/* move target into ObjDir */
			obj->o_level = WDLEVEL;
			return (BADTIME);	/* move failed */
		} else if (m == TRUE) {		/* OK, but did not move */
			obj->o_level -= a;	/* Restore original level */
		} else if (m == (TRUE+1) &&
			obj->o_date == newtime) {
			/*
			 * The target may have been created as $(ARCHDIR)/$@
			 * and we thus could not find the target at WDLEVEL.
			 * Search for it in the list of search directories.
			 * Use the correct o_level if we could find it via
			 * searchobj().
			 */
			searchobj(obj, deplevel, SALL);
			obj->o_date = newtime;
		}
/*		obj->o_level = OBJLEVEL;*/
	}
	return (obj->o_date);
badtime:
	if ((rtype & RTYPE_NEEDFREE) != 0 && cmd != NULL)
		fastfree((char *)cmd, sizeof (*cmd));

	return (BADTIME);
}

/*
 * Do all commands that are required to make the current target up to date.
 *
 * If any step fails, return BADTIME. This will stop dependent commands but
 * all other commands, not related to the failed target may be made.
 *
 * If must_exist is FALSE, return value 0 if no source file exists.
 * In this case, findsrc will try other suffixes in the implicit rules.
 */
LOCAL date_t
make(obj, must_exist, dlev)
	register obj_t	*obj;
		BOOL	must_exist;
		int	dlev;
{
		char	*depname = 0;
		date_t	deptime = NOTIME;
		date_t	mtime;
		short	deplevel = MAXLEVEL;
	register list_t	*l;
	register obj_t	*dep;
	register cmd_t	*cmd;

	if (Debug > 1)
		error("%sing %s\n", must_exist?"Mak":"Check", obj->o_name);

	if (obj->o_date != 0) {
		if (Debug > 2)
			error("make: %s->time: %s\n",
				obj->o_name, prtime(obj->o_date));
		/*
		 * We have been here before!
		 */
		if (obj->o_date == RECURSETIME)
			comerrno(EX_BAD, "Recursion in dependencies for '%s'.\n",
				obj->o_name);
		return (obj->o_date);
	}
	obj->o_date = RECURSETIME;	/* mark obj "we have been here" */
	if (obj->o_flags & F_DCOLON)
		dcolon_time(obj);

	/*
	 * Loop through the list of dependencies for obj, but do not try
	 * to "make" the names that appear as content of a NAME=val type macro.
	 */
	if (obj->o_type != EQUAL)
	for (l = obj->o_list; l; l = l->l_next) {
		dep = l->l_obj;
		mtime = make(dep, TRUE, dlev);
		if (!Kflag && mtime == BADTIME) {
			deptime = BADTIME;
			break;
		}


		if (dep->o_date > deptime) {
			/*
			 * Remember the date of the newest
			 * object in the list.
			 */
			depname = dep->o_name;
			deptime = dep->o_date;
		}
		if (dep->o_level < deplevel) {
			/*
			 * Remember the highest index in the search path
			 * for all objects in the list.
			 */
			deplevel = dep->o_level;
		}
	}
	if (deptime == BADTIME) {	/* Errors occured cannot make target.*/
		obj->o_date = BADTIME;
		if (obj->o_flags & F_DCOLON)
			dcolon_time(obj);

		return (obj->o_date);
	}

	if ((obj->o_type != DCOLON) &&		/* If final :: target */
	    (obj->o_flags & F_DCOLON) == 0 &&	/* If no intermediate target */
	    obj->o_cmd == (cmd_t *)NULL) {	/* Find default commands */
		obj->o_date = default_cmd(obj, depname, deptime,
							deplevel, must_exist,
							dlev < 0?16:dlev-1);
		if (obj->o_flags & F_DCOLON)
			dcolon_time(obj);
		/*
		 * Fake success for targets listed in the makefile that have
		 * no explicit commands no explicit dependencies (prerequisites)
		 * and where we could not find implicit dependencies.
		 * These intermediate placeholder targets look similar to FORCE:
		 */
		if (obj->o_list == NULL && obj->o_date == NOTIME && must_exist)
			obj->o_date = newtime;

		if (obj->o_flags & F_DCOLON)
			dcolon_time(obj);

		return (obj->o_date);
	}

	/*
	 * There is an explicit command list for the current target.
	 */
	if (Debug > 2)
		error("deptime: %s\n", prtime(deptime));
	if (deptime >= newtime ||			/* made some targets */
	    !searchobj(obj, deplevel, ObjSearch) ||	/* found no obj file */
	    deptime > obj->o_date) {			/* tgt is out of date*/

		if (Debug > 0)
			error("Make:    %s is out of date to: %s%s\n",
						obj->o_name, depname,
						isphony(obj)?" and .PHONY:":"");
		if (Tflag) {
			if (!isphony(obj) && !touch_file(obj->o_name)) {

				obj->o_date = BADTIME;
				if (obj->o_flags & F_DCOLON)
					dcolon_time(obj);

				return (obj->o_date);
			}
		}
		/*
		 * Sun make only calls '+' command with -t when in POSIX mode.
		 */
		for (cmd = obj->o_cmd; cmd; cmd = cmd->c_next) {
			if (docmd(substitute(cmd->c_line, obj, (obj_t *)NULL, (char *)NULL),
									obj)) {

				obj->o_date = BADTIME;
				if (obj->o_flags & F_DCOLON)
					dcolon_time(obj);
				return (obj->o_date);
			}
		}
		if (obj->o_level == MAXLEVEL)
			searchobj(obj, deplevel, SALL);
		obj->o_date = newtime;
		if (obj->o_flags & F_DCOLON)
			dcolon_time(obj);
	}
	return (obj->o_date);
}

/*
 * This is the interface function that is to be called from main()
 * If called with a NULL pointer, it checks for the default target first,
 * otherwise the named target is made.
 */
EXPORT BOOL
domake(name)
	char	*name;
{
	date_t	mtime;

	if (name) {
		default_tgt = objlook(name, TRUE);
		if (Debug > 0)
			error("Target:  %s\n", name);
	} else if (default_tgt) {
		name = default_tgt->o_name;
		if (Debug > 0)
			error("DTarget:  %s\n", name);
	}
	if (default_tgt) {
		mtime = make(default_tgt, TRUE, -1);
		if (mtime == BADTIME) {
			errmsgno(EX_BAD, "Couldn't make '%s'.\n", name);
			if (Debug > 0)
				error("Current working directory:  %s\n", curwdir());
			return (FALSE);
		}
		if (mtime != NOTIME && mtime != newtime) {
			/*
			 * Nothing to do.
			 */
			if (!Qflag)
				errmsgno(EX_BAD, "'%s' is up to date.\n", name);
			return (TRUE);
		}
		if (Qflag)
			return (FALSE);
		return (TRUE);
	}
	errmsgno(EX_BAD, "No default Command defined.\n");
	usage(1);
	return (FALSE);	/* keep lint happy */
}

/*
 * Try to make target 'obj'.
 * Return TRUE, if target could be made.
 *
 * omake and xmake are used to make intermediate targets with no direct
 * dependency (e.g. .INIT and included files).
 */
EXPORT BOOL
omake(obj, must_exist)
	obj_t	*obj;
	BOOL	must_exist;
{
	date_t	mtime;

	if (obj == (obj_t *)NULL)
		return (TRUE);

	if (Debug > 2)
		error("xmake: %s->time: %s\n",
			obj->o_name, prtime(obj->o_date));
	mtime = make(obj, must_exist, -1);

	if (Debug > 2)
		error("xmake: %s\n", prtime(mtime));
	if (mtime == BADTIME) {
/*		errmsgno(EX_BAD, "Couldn't make '%s'.\n", obj->o_name);*/
		return (FALSE);
	}
	return (TRUE);
}

/*
 * First do an objlook(name), then make it using omake.
 */
EXPORT BOOL
xmake(name, must_exist)
	char	*name;
	BOOL	must_exist;
{
	obj_t	*o;

	o = objlook(name, TRUE);
	return (omake(o, must_exist));
}
