/*
   Merge the makefile database with the partial global makefile to create the
   final global makefile.
 */

/* Copyright, compile requirements.

   (c) Keith Owens <kaos@ocs.com.au> January 2002.
   Released under GPL version 2, (you know the rest).
 */

/* Run requirements.

   Makefile pre-processing environment variables must be set.
 */

/* Processing.

   Read .tmp_select and built the complete list of what has been selected,
   extra flags etc.

   Copy .tmp_global_makefile_in.2 to .tmp_global_makefile, converting from the
   intermediate format to the final global makefile.

   For each base in base_list:
     Write .tmp_<base>_order to document the directory and object order defined
     for base.

     Write .tmp_<base>_objects_new containing a list of the objects linked into
     base, in the order they are linked.

     Write .tmp_<base>_modules_new containing a list of the module objects
     selected for base, in the order they were defined.

     The .tmp_<base> files are created in every directory whose Makefile.in
     refers to $(__pp_<base>_{objects,modules}).
 */

#include "pp_makefile.h"
#include "pp_db.h"

#include <ctype.h>
#include PP_GETOPT_H
#include <limits.h>
#include <memory.h>
#include <signal.h>

const char *program = "pp_makefile4";

int errors;
int norun;
int verbose;
int signalled;

char *reldir;
DB_TOKEN reldir_token;
char **arch_head;
DB_REC_BASE_1 *current_base;

struct objmod_data {
	DB_TOKEN target;
	DB_TOKEN parent;
};

struct objmod {
	struct objmod_data *list;
	int alloc;
	int used;
};
static struct objmod *base_objects, *base_modules;

/*============================================================================*/

static inline int
base_number(DB_REC_BASE_1 * b1)
{
	int n = b1 - rec_base->list;
	assert(n >= 0 && n < rec_base->count);
	return (n);
}

static inline int
current_base_number(void)
{
	return (base_number(current_base));
}

static inline DB_REC_BASE_1 *
default_base(void)
{
	return (rec_base->list + 0);
}

static inline DB_REC_BASE_1 *
host_base(void)
{
	return (rec_base->list + 1);
}

static inline void
set_oais(DB_REC_TARGET * t, char c)
{
	t->flags.object = c == 'o';
	t->flags.archive = c == 'a';
	t->flags.gen_i = c == 'i';
	t->flags.gen_s = c == 's';
}

/*============================================================================*/

typedef struct texttree TEXTTREE;
struct texttree {
	unsigned int edges;
	char *s;
	TEXTTREE **t;
};

/* Extract the next token from text.  A token is delimited by white space
 * outside '()', '"' and "'".  Returns 0 if token extracted, 1 at end of text,
 * -1 if the token has invalid syntax such as unbalanced '()' or mismatched
 * quotes.  The returned token is dynamic, the caller must free it.
 */

static int
next_text(const char **line, char **text)
{
	const char *t;
	char quote = '\0';
	int stack = 0, ret = 0, l;
	*text = NULL;
	t = *line;
	while (isspace(*t)) {
		++t;
	}
	*line = t;
	if (*t == '\0') {
		return (1);
	}
	for (; *t; ++t) {
		if (*t == '\\') {
			if (*++t == '\0') {
				goto out_error;
			}
		} else if (quote) {
			if (*t == quote) {
				quote = '\0';
			}
		} else if (isspace(*t)) {
			if (!stack) {
				goto out;
			}
		} else if (*t == '(') {
			++stack;
		} else if (*t == ')') {
			if (!stack--) {
				++t;
				goto out_error;
			}
		} else if (*t == '\'' || *t == '"') {
			quote = *t;
		}
	}
	if (!(stack || quote)) {
		goto out;
	}
      out_error:
	ret = -1;
      out:
	l = t - *line;
	*text = k_malloc(l + 1);
	memcpy(*text, *line, l);
	*(*text + l) = '\0';
	*line = t;
	return (ret);
}

static TEXTTREE *split_line(const char *line);

static void
add_text(TEXTTREE * t, const char *token)
{
	TEXTTREE *t1;
	t->t = k_realloc(t->t, (t->edges + 1) * sizeof (*(t->t)));
	if (*token == '(') {
		int l = strlen(token + 1);
		char *new_token = k_malloc(l);
		memcpy(new_token, token + 1, l);
		*(new_token + l - 1) = '\0';
		t1 = split_line(new_token);
		free(new_token);
		free(t1->s);
	} else {
		t1 = k_malloc(sizeof (*t1));
		memset(t1, 0, sizeof (*t1));
	}
	if (t1) {
		t1->s = k_strdup(token);
		t->t[(t->edges)++] = t1;
	}
}

/* Split a line into fields on whitespace.  Fields enclosed in '(' and ')' are
 * recursively split and the '()' are discarded.  The result is a text tree,
 * each node contains a string (edges == 0) or a subtree (edges != 0).  The
 * string contains the entire text, even if it has been split into a subtree.
 * The caller must free the tree after use.  A blank line returns NULL.
 */

static TEXTTREE *
split_line(const char *line)
{
	int ret;
	char *text = NULL;
	const char *line2 = line;
	TEXTTREE *t = NULL;
	while ((ret = next_text(&line2, &text)) == 0) {
		if (!t) {
			t = k_malloc(sizeof (*t));
			t->edges = 0;
			t->s = k_strdup(line);
			t->t = NULL;
		}
		add_text(t, text);
		free(text);
		text = NULL;
	}
	if (ret < 0) {
		fprintf(stderr, "%s: Invalid text in text '%s'\n", program,
			text);
		++errors;
		free(text);
	}
	return (t);
}

/* Join fields into a line with whitspace, the opposite of split_line.
 * The caller must free the line after use.
 */

static char *
join_line(const TEXTTREE * t)
{
	int i, l = 0;
	char *line;
	const TEXTTREE *ti;
	for (i = 0; i < t->edges; ++i) {
		if ((ti = t->t[i])) {
			if (ti->edges) {
				char *line2 = join_line(ti);
				l += strlen(line2);
				free(line2);
			} else if (ti->s) {
				l += strlen(ti->s);
			}
		}
		if (l && i < t->edges - 1) {
			++l;
		}
	}
	line = k_malloc(l + 1);
	line[0] = '\0';
	for (i = 0; i < t->edges; ++i) {
		if ((ti = t->t[i])) {
			if (ti->edges) {
				char *line2 = join_line(ti);
				strcat(line, line2);
				free(line2);
			} else if (ti->s) {
				strcat(line, ti->s);
			}
		}
		if (line[0] && i < t->edges - 1) {
			strcat(line, " ");
		}
	}
	assert(strlen(line) == l);
	return (line);
}

static void
free_texttree(TEXTTREE * t)
{
	int i;
	for (i = 0; i < t->edges; ++i) {
		if (t->t[i]) {
			free_texttree(t->t[i]);
			free(t->t[i]);
			t->t[i] = NULL;
		}
	}
	if (t->t) {
		free(t->t);
		t->t = NULL;
	}
	t->edges = 0;
	if (t->s) {
		free(t->s);
	}
	t->s = NULL;
}

/*============================================================================*/

/* General notes on command processing from .tmp_select.
 *
 * The texttree value has at least 1 edge, the first field is always the command
 * name.
 *
 * If parameters are present, edges is > 1, t->t[1] describes the first
 * parameter, t->t[2] the second etc.
 *
 * If a parameter is enclosed in '()' then t->t[i] (ti) has sub-parameters,
 * ti->t[0]->s is the first sub-parameter, ti->t[1]->s is the second
 * sub-parameter etc.
 *
 * If a parameter is not enclosed in '()' then the text is in ti->s.
 */

/* reldir dirname */

static void
cmd_reldir(PPDB * db, const TEXTTREE * t)
{
	current_base = default_base();
	if (reldir) {
		free(reldir);
	}
	if (t->edges != 2) {
		fprintf(stderr, "%s: reldir requires exactly one parameter\n",
			program);
		++errors;
		reldir = k_strdup("/missing/reldir/");
	} else {
		/* directory names must end in a single '/' */
		char *name = t->t[1]->s;
		int l = strlen(name);
		t->t[1]->s = NULL;
		while (l && *(name + l - 1) == '/') {
			*(name + --l) = '\0';
		}
		name = k_realloc(name, l + 2);
		memcpy(name + l, "/", 2);
		reldir = name;
	}
	reldir_token = lookup_db_directory(db, reldir);
	assert(!DB_IS_NULL_TOKEN(reldir_token));
}

static void
cmd_extra_flags_1(PPDB * db, const TEXTTREE * ti, char flagtype)
{
	DB_TOKEN token, *flags_token;
	DB_REC_TARGET *target = NULL;
	DB_REC_DIRECTORY *directory = NULL;
	char *line, *flags;
	int n;
	/* First parameter can be directory or target */
	token = lookup_db_directory(db, ti->t[0]->s);
	if (DB_IS_NULL_TOKEN(token)) {
		token = findnew_db_target(db, ti->t[0]->s);
	}
	free(ti->t[0]->s);
	ti->t[0]->s = NULL;
	n = flagtype == 'a' ? 0 : flagtype == 'c' ? 1 : 2;
	if (ti->edges >= 2 && strcmp(ti->t[1]->s, "STRIP") == 0) {
		free(ti->t[1]->s);
		ti->t[1]->s = NULL;
		n += 3;		/* Use strip_*_flags instead of extra_*_flags */
	}
	line = join_line(ti);
	if (token.type == DB_RECTYPE_DIRECTORY) {
		directory = read_db_directory(db, token);
		flags_token = directory->acld_flags + n;
	} else {
		target = read_db_target(db, token);
		flags_token = target->acld_flags + n;
		target->flags.makefile = 1;
	}
	if (DB_IS_NULL_TOKEN(*flags_token)) {
		token = new_db_text(db, "", flags_token);
	} else {
		token = *flags_token;
	}
	flags = read_db_text(db, token);
	if (*flags) {
		char *line2 = k_malloc(strlen(flags) + strlen(line) + 2);
		strcpy(line2, flags);
		strcat(line2, " ");
		strcat(line2, line);
		update_db_text(db, line2, token);
		free(line2);
	} else {
		update_db_text(db, line, token);
	}
	free(line);
}

/* extra_{a,c,ld}flags (file/dirname flags ...) ... */
static void
cmd_extra_flags(PPDB * db, const TEXTTREE * t, char flagtype)
{
	assert(flagtype == 'a' || flagtype == 'c' || flagtype == 'l');
	if (t->edges > 1) {
		int i;
		for (i = 1; i < t->edges; ++i) {
			cmd_extra_flags_1(db, t->t[i], flagtype);
		}
	}
}

/*============================================================================*/

/* expsyms objects ... */

static void
cmd_expsyms(PPDB * db, const TEXTTREE * t)
{
	if (t->edges > 1) {
		int i;
		DB_TOKEN token;
		for (i = 1; i < t->edges; ++i) {
			token = findnew_db_target(db, t->t[i]->s);
			read_db_target(db, token)->flags.expsyms = 1;
		}
	}
}

/* objlink (target_object objects ...) ... */

static void
cmd_objlink_1(PPDB * db, const TEXTTREE * ti)
{
	DB_TOKEN token1, token2;
	DB_REC_TARGET *t;
	DB_REC_TARGET_1 add;
	int j;
	token1 = findnew_db_target(db, ti->t[0]->s);
	for (j = 1; j < ti->edges; ++j) {
		token2 = findnew_db_target(db, ti->t[j]->s);
		t = read_db_target(db, token1);
		t->flags.makefile = 1;
		if (DB_IS_NULL_TOKEN(t->objlink)) {
			new_db_target_list(db, &(t->objlink));
		}
		memset(&add, 0, sizeof (add));
		add.entry = token2;
		add_db_target_list(db, t->objlink, &add, NULL);
		read_db_target(db, token2)->flags.makefile = 1;
	}
}

static void
cmd_objlink(PPDB * db, const TEXTTREE * t)
{
	if (t->edges > 1) {
		int i;
		for (i = 1; i < t->edges; ++i) {
			cmd_objlink_1(db, t->t[i]);
		}
	}
}

/* select (type_ object/dirname ...) ... */

/* Remove lines marked SE when the select_elsewhere kludge disappears */

static void
cmd_select_1_target(PPDB * db, char type, char *name)
{
	DB_TOKEN token, *token_p;
	DB_TOKEN save_reldir_token;
	/*SE*/ DB_REC_DIRECTORY * d;
	DB_REC_SELECT_LIST *s;
	DB_REC_SELECT_1 *s1, add;
	DB_REC_BASE_1 *save_base = current_base;
	DB_REC_BASE_SELECT_LIST *bs;
	if (type == 'C') {
		current_base = host_base();
	}
	token = findnew_db_target(db, name);
	read_db_target(db, token)->flags.makefile = 1;
	{			/*SE start */
		int l = strlen(name);
		save_reldir_token = reldir_token;
		while (l-- && name[l] == '/') {
		};
		while (name[l] != '/') {
			--l;
		};
		name[l] = '\0';
		reldir_token = lookup_db_directory(db, name);
		assert(!DB_IS_NULL_TOKEN(reldir_token));
		name[l] = '/';
	}			/*SE end */
	d = read_db_directory(db, reldir_token);
	bs = read_db_base_select_list(db, d->base_select);
	token_p = &(bs->list[current_base_number()].entry);
	if (DB_IS_NULL_TOKEN(*token_p)) {
		new_db_select_list(db, token_p);
	}
	memset(&add, 0, sizeof (add));
	add.entry = token;
	s = add_db_select_list(db, *token_p, &add, &s1);
	switch (type) {
	case 'y':		/* built in */
		s1->flags.sely = 1;
		break;
	case 'm':		/* module */
		if (current_base->flags.has_modules) {
			s1->flags.selm = 1;
		}
		break;
	case 'C':		/* hostcc */
		break;
	}
	current_base = save_base;
	reldir_token = save_reldir_token;
/*SE*/}

static void
cmd_select_1_directory(PPDB * db, char type, char **name)
{
	DB_TOKEN token, *token_p;
	DB_REC_DIRECTORY *d;
	DB_REC_BASE_SELECT_LIST *bs;
	int l = strlen(*name);
	while (l && *(*name + l - 1) == '/') {
		*(*name + --l) = '\0';
	}
	*name = k_realloc(*name, l + 2);
	memcpy(*name + l, "/", 2);
	/* link_subdirs() for non-existent directory is valid */
	token = lookup_db_directory(db, *name);
	if (DB_IS_NULL_TOKEN(token)) {
		token = new_db_directory(db, *name);
	}
	if (type == 's') {
		/* link_subdirs */
		DB_REC_SELECT_LIST *s;
		DB_REC_SELECT_1 *s1, add;
		d = read_db_directory(db, reldir_token);
		bs = read_db_base_select_list(db, d->base_select);
		token_p = &(bs->list[current_base_number()].entry);
		if (DB_IS_NULL_TOKEN(*token_p)) {
			new_db_select_list(db, token_p);
		}
		memset(&add, 0, sizeof (add));
		add.entry = token;
		s = add_db_select_list(db, *token_p, &add, &s1);
		s1->flags.sels = 1;
	}
	if (type == 'c') {
		/* create_objdir */
		read_db_directory(db, token)->flags.create_objdir = 1;
	}
}

static void
cmd_select_1(PPDB * db, const TEXTTREE * ti)
{
	int j;
	char type = *(ti->t[0]->s);
	switch (type) {
	case 'n':		/* drop through */
	case '_':
		break;
	case 'y':		/* drop through */
	case 'm':		/* drop through */
	case 'C':
		for (j = 1; j < ti->edges; ++j) {
			cmd_select_1_target(db, type, ti->t[j]->s);
		}
		break;
	case 's':		/* drop through */
	case 'c':
		for (j = 1; j < ti->edges; ++j) {
			cmd_select_1_directory(db, type, &(ti->t[j]->s));
		}
		break;
	case 'b':		/* base_target */
		if (ti->edges != 2) {
			fprintf(stderr,
				"%s: select(b) for base requires exactly two fields, '%s' is invalid\n",
				program, ti->s);
			++errors;
		} else {
			current_base = scan_db_base(db, ti->t[1]->s);
			if (!current_base) {
				add_db_base(db, ti->t[1]->s, 0, 0, 0,
					    reldir_token);
				current_base = scan_db_base(db, ti->t[1]->s);
			} else if (current_base->flags.is_host) {
				fprintf(stderr,
					"%s: base_target(%s) is reserved\n",
					program, ti->t[1]->s);
				++errors;
			}
		}
		break;
	case 'L':		/* hostld */
		if (ti->edges < 2) {
			fprintf(stderr,
				"%s: select(L) for hostld requires at least two fields, '%s' is invalid\n",
				program, ti->s);
			++errors;
		} else {
			/* hostld is equivalent to objlink followed by select(y)
			 * using the host base.  Convert the text to the format
			 * cmd_objlink() expects and reuse that function.
			 */
			char *objlink_text;
			TEXTTREE *objlink_tree;
			DB_REC_BASE_1 *save_base;
			objlink_text = k_strdup(ti->s);	/* (L_ binary object_list) */
			memcpy(objlink_text, "xx (", 4);	/* xx (binary object_list) */
			objlink_tree = split_line(objlink_text);
			cmd_objlink(db, objlink_tree);	/* Handle (binary object_list) */
			free_texttree(objlink_tree);
			free(objlink_tree);
			free(objlink_text);
			save_base = current_base;
			current_base = host_base();
			cmd_select_1_target(db, 'y', ti->t[1]->s);
			current_base = save_base;
		}
		break;
	default:
		while (strchr(ti->s, '\n')) {
			*(strchr(ti->s, '\n')) = ' ';
		}
		fprintf(stderr, "%s: Unknown select type '%c' in '%s'\n",
			program, type, ti->s);
		++errors;
	}
}

static void
cmd_select(PPDB * db, const TEXTTREE * t)
{
	if (t->edges > 1) {
		int i;
		for (i = 1; i < t->edges; ++i) {
			cmd_select_1(db, t->t[i]);
		}
	}
}

/*============================================================================*/

static int
get_arch_head(void)
{
	char *env, *p;
	int i = 0;
	if (!(env = getenv("arch_head"))) {
		fprintf(stderr,
			"%s: Environment variable arch_head is not set, vmlinux will not link properly\n",
			program);
		return (1);
	}
	while ((env += strspn(env, " \t\n")), *env) {
		p = env + strcspn(env, " \t\n");
		if (*p) {
			*p = '\0';
		} else {
			--p;
		}
		arch_head =
		    k_realloc(arch_head, (i + 2) * (sizeof (*arch_head)));
		arch_head[i++] = k_strdup(env);
		arch_head[i] = NULL;
		env = p + 1;
	}
	return (0);
}

/*============================================================================*/

static int
read_tmp_select(PPDB * db)
{
	FILE *database;
	TEXTTREE *t = NULL;
	char *line;
	static const char *database_name = ".tmp_select";
	int ret = 0, eof = 0, line_size;
	if (!(database = fopen(database_name, "r"))) {
		fprintf(stderr, "%s: fopen(%s, r) failed: %m\n", program,
			database_name);
		return (1);
	}
	line = k_malloc((line_size = 4096));
	while (!ret && !(eof = fgets_long_line(&line, &line_size, database))) {
		if ((t = split_line(line))) {
			const char *command = t->t[0]->s;
			if (strcmp(command, "reldir") == 0) {
				cmd_reldir(db, t);
			} else if (strcmp(command, "extra_aflags") == 0) {
				cmd_extra_flags(db, t, 'a');
			} else if (strcmp(command, "extra_cflags") == 0) {
				cmd_extra_flags(db, t, 'c');
			} else if (strcmp(command, "extra_ldflags") == 0) {
				cmd_extra_flags(db, t, 'l');
			} else if (strcmp(command, "select") == 0) {
				cmd_select(db, t);
			} else if (strcmp(command, "objlink") == 0) {
				cmd_objlink(db, t);
			} else if (strcmp(command, "expsyms") == 0) {
				cmd_expsyms(db, t);
			} else {
				fprintf(stderr,
					"%s: Unexpected command in .tmp_select '%s'\n",
					program, command);
				ret = 1;
			}
			free_texttree(t);
			free(t);
		}
	}
	if (eof < 0) {
		fprintf(stderr, "%s: Error reading from %s: %m\n", program,
			database_name);
		ret = 1;
	}
	free(line);
	fclose(database);
	return (ret);
}

/*============================================================================*/

/* Read .tmp_global_makefile_in.2 and extract any information that cannot be
 * extracted from .tmp_select.
 */

static int
read_makefile_commands(PPDB * db, const char *prevline, char *p)
{
	DB_TOKEN token;
	DB_REC_TARGET *t;
	const char *prevline2;
	char *text;
	int ret;
	*p = '\0';
	prevline2 = prevline;
	while ((ret = next_text(&prevline2, &text)) == 0) {
		if (*text == '/') {
			token = findnew_db_target(db, text);
			t = read_db_target(db, token);
			t->flags.commands = 1;
			t->flags.makefile = 1;
		} else {
			int l = strlen(text) + 1;
			char text2[l + 1];
			*text2 = '/';
			memcpy(text2 + 1, text, l);
			token = findnew_db_target(db, text2);
			t = read_db_target(db, token);
			t->flags.commands = 1;
			t->flags.makefile = 1;
		}
		free(text);
	}
	if (ret < 0) {
		fprintf(stderr, "%s: Invalid text in command line '%s'\n",
			program, prevline);
		++errors;
		free(text);
	} else {
		ret = 0;
	}
	return (ret);
}

static int
read_makefile(PPDB * db)
{
	FILE *makefile;
	char *line, *prevline, *thisline, *p;
	static const char *makefile_name = ".tmp_global_makefile_in.2";
	int ret = 0, lineno = 0, l, eof = 0, line_size, thisline_size;
	if (!(makefile = fopen(makefile_name, "r"))) {
		fprintf(stderr, "%s: fopen(%s, r) failed: %m\n", program,
			makefile_name);
		return (1);
	}
	line = k_malloc((line_size = 4096));
	thisline = k_malloc((thisline_size = line_size));
	prevline = k_malloc(line_size);
	*prevline = '\0';
	*thisline = '\0';
	p = thisline;
	while (!ret && !(eof = fgets_long_line(&line, &line_size, makefile))) {
		++lineno;
		l = strlen(line) - 1;
		line[l] = '\0';
		if ((p - thisline + l + 1) > thisline_size) {
			thisline = k_realloc(thisline, thisline_size +=
					     l + 4096);
			p = thisline + strlen(thisline);
		}
		memcpy(p, line, l + 1);
		p += l;
		if (p != thisline && *(p - 1) == '\\') {
			*(--p) = '\0';
		} else {
			if (*thisline == '\t' &&
			    *prevline != '\t' &&
			    *prevline != '\0' &&
			    strncmp(prevline, "if", 2) != 0 &&
			    (p = strchr(prevline, ':'))) {
				/* looks like an explicit make command */
				ret = read_makefile_commands(db, prevline, p);
			}
			free(prevline);
			prevline = thisline;
			thisline = k_malloc(thisline_size);
			p = thisline;
		}
	}
	if (eof < 0) {
		fprintf(stderr, "%s: Error reading from %s: %m\n", program,
			makefile_name);
		ret = 1;
	}
	free(line);
	free(thisline);
	free(prevline);
	fclose(makefile);
	return (ret);
}

/*============================================================================*/

static void
do_setup(PPDB * db, DB_REC_TARGET * t, const char *name, FILE * fout)
{
	if (!t->flags.setup) {
		fprintf(fout, "%s: .tmp_setup\n", name);
	}
}

/* Generate commands for an objlink target, either LD or AR */

static int
generate_commands_objlink(PPDB * db, DB_REC_TARGET * t, const char *tname,
			  DB_REC_DIRECTORY * d, const char *dname,
			  DB_TOKEN t_token, FILE * fout)
{
	int i, j, k, host = t->flags.host_summary;
	const char *type = t->flags.archive ? "AR" : "LD";
	const char *HOST = host ? "HOST" : "";
	DB_TOKEN token;
	DB_REC_TARGET_LIST *objlink;

	objlink = read_db_target_list(db, t->objlink);
	do_setup(db, t, tname, fout);

	for (k = 0; k < 2; ++k) {
		if (k == 0) {
			fprintf(fout, "KCN_%s := $(%s%s_real) ", tname, HOST,
				type);
		} else {
			fprintf(fout, "%s: ", tname);
		}
		for (i = 0; i < objlink->count; ++i) {
			DB_REC_TARGET *tsel;
			tsel = read_db_target(db, objlink->list[i].entry);
			fprintf(fout, "%s ",
				read_db_text(db, tsel->relname) + 1);
		}
		if (k == 0) {
			fprintf(fout, " ");
		} else {
			fprintf(fout,
				"\n\t$(KBUILD_QUIET)scripts/pp_makefile5 $(PP_MAKEFILE5_FLAGS) --type=%s%s --target=$@ --objects='",
				HOST, type);
			for (i = 0; i < objlink->count; ++i) {
				DB_REC_TARGET *tsel;
				tsel =
				    read_db_target(db, objlink->list[i].entry);
				fprintf(fout, "%s ",
					read_db_text(db, tsel->relname) + 1);
			}
			fprintf(fout, "' --flags='");
		}
		fprintf(fout, "$(%s%sFLAGS)", HOST, type);
		for (j = 0; j < 2; ++j) {
			token = j == 0 ? d->extra_ldflags : t->extra_ldflags;
			if (!DB_IS_NULL_TOKEN(token)) {
				fprintf(fout, " %s", read_db_text(db, token));
			}
		}
		if (!host) {
			fprintf(fout, " $(%sFLAGS_%s)", type,
				t->flags.selm_summary ? "MODULE" : "KERNEL");
		}
		fprintf(fout, " $(EXTRA_%sFLAGS_%s) $(EXTRA_%sFLAGS_%s)", type,
			dname, type, tname);
		if (k == 1) {
			fprintf(fout, "'");	/* end --flags='...' */
		}
		fprintf(fout, "\n\n");
	}
	return (0);
}

/* gcc looks in the current directory for #include "file.h".  With shadow trees
 * there are multiple instances of the "current" directory, starting with the
 * object directory (for generated include files) and working down the list of
 * source trees.  The entire list must only be scanned for #include "file.h",
 * not for <file.h> so end the list with -I-.
 */

static void
generate_include_currdir(const char *name, FILE * fout)
{
	int i;
	fprintf(fout, "-I%s", name);
	if (!common_src_obj) {
		for (i = MAX_SRCTREE - 1; i >= 0; --i) {
			if (srctree[i]) {
				fprintf(fout, " -I%s%s", srctree[i], name);
			}
		}
	}
	fprintf(fout, " -I-");
}

/* If flags are to be stripped, generate leading $(patsubst) commands for each
 * flag.  Return the number of ')' required for the tail.
 */

static int
generate_strip_head(const TEXTTREE * strip_flags, FILE * fout)
{
	const TEXTTREE *ti;
	int i;
	for (i = 0; i < strip_flags->edges; ++i) {
		ti = strip_flags->t[i];
		fprintf(fout, "$(patsubst %s,,", ti->s);
	}
	return (strip_flags->edges);
}

/* Close off the $(patsubst) opened by generate_strip_head.  */

static void
generate_strip_tail(int strip, FILE * fout)
{
	while (strip--) {
		fprintf(fout, ")");
	}
}

/* Convert a db token pointing at an optional text string into a text tree.
 * Always return a tree, even if there is no text.
 */

static TEXTTREE *
token_to_tree(PPDB * db, DB_TOKEN token)
{
	TEXTTREE *t = NULL;
	if (!DB_IS_NULL_TOKEN(token)) {
		t = split_line(read_db_text(db, token));
	}
	if (!t) {
		t = k_malloc(sizeof (*t));
		memset(t, 0, sizeof (*t));
	}
	return (t);
}

/* Generate the -DKBUILD_... defines */

static void
generate_kbuild_defines(PPDB * db, DB_REC_TARGET * t, const char *tname,
			FILE * fout)
{
	char *obj_base, *p;
	const char *name;
	int i;
	if (t->flags.multi_linked) {
		name = NULL;
	} else if (!DB_IS_NULL_TOKEN(t->link)) {
		name = read_db_text(db, read_db_target(db, t->link)->relname);
	} else {
		name = tname;
	}
	if (name) {
		obj_base = k_strdup(strrchr(name, '/') + 1);
		i = strlen(obj_base);
		if (i > 2 && obj_base[i - 2] == '.') {
			obj_base[i - 2] = '\0';
		}
		while ((p = index(obj_base, '-')) || (p = index(obj_base, ','))) {
			*p = '_';
		}
		fprintf(fout, " -DKBUILD_OBJECT=%s", obj_base);
		free(obj_base);
	}
	name = tname;
	obj_base = k_strdup(strrchr(name, '/') + 1);
	i = strlen(obj_base);
	if (i > 2 && obj_base[i - 2] == '.') {
		obj_base[i - 2] = '\0';
	}
	while ((p = index(obj_base, '-')) || (p = index(obj_base, ','))) {
		*p = '_';
	}
	fprintf(fout, " -DKBUILD_BASENAME=%s", obj_base);
	free(obj_base);
	if (t->flags.setup) {
		fprintf(fout, " -DKBUILD_SETUP");
	}
	if (t->flags.expsyms) {
		fprintf(fout, " -DEXPORT_SYMTAB");
	}
}

/* Generate commands to compile Assembler source */

static void
generate_commands_assembler(PPDB * db, DB_REC_TARGET * t, const char *tname,
			    DB_REC_SOURCE * s, const char *sname,
			    DB_REC_DIRECTORY * d, const char *dname,
			    FILE * fout)
{
	int j, k, strip, host = t->flags.host_summary;
	TEXTTREE *ts, *ds;
	DB_TOKEN token;

	do_setup(db, t, tname, fout);

	for (k = 0; k < 2; ++k) {
		if (k == 0) {
			/* pp_makefile5 generates a $(CC) command even for AS targets */
			fprintf(fout, "KCN_%s := $(%s_real) ",
				tname,
				t->flags.gen_i ?
				"CPP" : host ? "HOSTCC" : "CC");
		} else {
			fprintf(fout,
				"%s: %s%s\n"
				"\t$(KBUILD_QUIET)scripts/pp_makefile5 $(PP_MAKEFILE5_FLAGS) --type=%s "
				"--target=%s "
				"--src=%s "
				"--srctree=%03d "
				"--flags='",
				tname,
				s->index == objtree_treename_index ?
				"" : srctree[s->index],
				sname,
				t->flags.gen_i ? "CPP" : host ? "HOSTAS" : "AS",
				tname, sname, s->index);
		}
		generate_include_currdir(dname, fout);
		fprintf(fout, " ");
		ds = token_to_tree(db, d->strip_aflags);
		ts = token_to_tree(db, t->strip_aflags);
		strip = generate_strip_head(ds, fout) +
		    generate_strip_head(ts, fout);
		fprintf(fout, "$(%sAFLAGS)", host ? "HOST" : "");
		generate_strip_tail(strip, fout);
		if (!host) {
			fprintf(fout, " $(AFLAGS_NOSTDINC)");
			fprintf(fout, " $(EXTRA_AFLAGS)");
		}
		generate_kbuild_defines(db, t, tname, fout);
		for (j = 0; j < 2; ++j) {
			token = j == 0 ? d->extra_aflags : t->extra_aflags;
			if (!DB_IS_NULL_TOKEN(token)) {
				fprintf(fout, " %s", read_db_text(db, token));
			}
		}
		if (!host) {
			fprintf(fout, " ");
			strip = generate_strip_head(ds, fout) +
			    generate_strip_head(ts, fout);
			fprintf(fout, "$(AFLAGS_%s)",
				t->flags.selm_summary ? "MODULE" : "KERNEL");
			generate_strip_tail(strip, fout);
		}
		fprintf(fout, " $(EXTRA_AFLAGS_%s) $(EXTRA_AFLAGS_%s)",
			dname, tname);
		if (k == 1) {
			fprintf(fout, "'");	/* end --flags='...' */
		}
		fprintf(fout, "\n\n");
		free_texttree(ts);
		free(ts);
		free_texttree(ds);
		free(ds);
	}
}

/* Generate commands to compile C source */

static void
generate_commands_c(PPDB * db, DB_REC_TARGET * t, const char *tname,
		    DB_REC_SOURCE * s, const char *sname,
		    DB_REC_DIRECTORY * d, const char *dname, FILE * fout)
{
	int j, k, strip, host = t->flags.host_summary;
	TEXTTREE *ts, *ds;
	DB_TOKEN token;

	do_setup(db, t, tname, fout);

	for (k = 0; k < 2; ++k) {
		if (k == 0) {
			fprintf(fout, "KCN_%s := $(%s_real) ",
				tname,
				t->flags.gen_i ?
				"CPP" : host ? "HOSTCC" : "CC");
		} else {
			fprintf(fout,
				"%s: %s%s\n"
				"\t$(KBUILD_QUIET)scripts/pp_makefile5 $(PP_MAKEFILE5_FLAGS) --type=%s "
				"--target=%s "
				"--src=%s "
				"--srctree=%03d "
				"--flags='",
				tname,
				s->index == objtree_treename_index ?
				"" : srctree[s->index],
				sname,
				t->flags.gen_i ? "CPP" : host ? "HOSTCC" : "CC",
				tname, sname, s->index);
		}
		if (t->flags.gen_s) {
			fprintf(fout, "-S ");
		}
		generate_include_currdir(dname, fout);
		fprintf(fout, " ");
		ds = token_to_tree(db, d->strip_cflags);
		ts = token_to_tree(db, t->strip_cflags);
		strip = generate_strip_head(ds, fout) +
		    generate_strip_head(ts, fout);
		fprintf(fout, "$(%sCFLAGS)", host ? "HOST" : "");
		generate_strip_tail(strip, fout);
		if (!host) {
			fprintf(fout, " $(CFLAGS_NOSTDINC)");	/* FIXME: !targetcc */
			fprintf(fout, " $(EXTRA_CFLAGS)");
		}
		generate_kbuild_defines(db, t, tname, fout);
		for (j = 0; j < 2; ++j) {
			token = j == 0 ? d->extra_cflags : t->extra_cflags;
			if (!DB_IS_NULL_TOKEN(token)) {
				fprintf(fout, " %s", read_db_text(db, token));
			}
		}
		if (!host) {
			fprintf(fout, " ");
			strip = generate_strip_head(ds, fout) +
			    generate_strip_head(ts, fout);
			fprintf(fout, "$(CFLAGS_%s)",
				t->flags.selm_summary ? "MODULE" : "KERNEL");
			generate_strip_tail(strip, fout);
		}
		fprintf(fout, " $(EXTRA_CFLAGS_%s) $(EXTRA_CFLAGS_%s)",
			dname, tname);
		if (k == 1) {
			fprintf(fout, "'");	/* end --flags='...' */
		}
		fprintf(fout, "\n\n");
		free_texttree(ts);
		free(ts);
		free_texttree(ds);
		free(ds);
	}
}

/* Special dependency processing for sources marked as setup().
 * If any dependency is marked as setup then add an explicit rule
 * between the target and the dependency.  Without this rule, make would
 * not detect changes to setup files until the next pass.
 */

static void
generate_commands_source_setup(PPDB * db, DB_TOKEN token, DB_REC_TARGET * t,
			       const char *tname, FILE * fout)
{
	DB_REC_CCD_LIST *c;
	DB_REC_CCD_1 *c1;
	DB_REC_SOURCE *s;
	int i;
	if (!t->flags.uptodate || DB_IS_NULL_TOKEN(t->dependency)) {
		return;
	}
	c = read_db_ccd_list(db, t->dependency);
	for (i = 0, c1 = c->list; i < c->count; ++i, ++c1) {
		s = read_db_source(db, c1->entry);
		if (s->flags.setup) {
			fprintf(fout,
				"# Implicit dependencies on files marked setup() require an explicit dependency\n");
			fprintf(fout, "%s: %s%s\n\n", tname,
				srctree[s->index],
				read_db_text(db, s->relname));
		}
	}
	return;
}

/* Generate commands for an object that is built from source */

static int
generate_commands_source(PPDB * db, DB_TOKEN token, DB_REC_TARGET * t,
			 const char *tname, DB_REC_SOURCE * s,
			 const char *sname, DB_REC_DIRECTORY * d,
			 const char *dname, FILE * fout)
{
	if (t->flags.assembler) {
		generate_commands_assembler(db, t, tname, s, sname, d, dname,
					    fout);
	} else {
		generate_commands_c(db, t, tname, s, sname, d, dname, fout);
	}
	generate_commands_source_setup(db, token, t, tname, fout);
	return (0);
}

/* Generate commands for object targets in this directory that have been
 * selected but do not already have commands.  Flags are defined in least
 * specific to most specific order, global flags, include from object directory
 * (generated .h files), include from source directory (static .h files), extra
 * flags for directory, module/kernel flags, extra flags for object.
 */

/* Generate the commands for a target without user supplied commands */

static int
generate_commands_target(PPDB * db, const DB_TOKEN token, DB_REC_DIRECTORY * d,
			 const char *dname, FILE * fout)
{
	DB_REC_TARGET *t = read_db_target(db, token);
	const char *tname = read_db_text(db, t->relname) + 1;
	int ret;
	if (t->flags.commands || t->flags.commands_gen || t->flags.ignore) {
		ret = 0;
	} else if (!DB_IS_NULL_TOKEN(t->objlink)) {
		ret =
		    generate_commands_objlink(db, t, tname, d, dname, token,
					      fout);
	} else {
		DB_REC_SOURCE *s = read_db_source(db, t->source);
		const char *sname = read_db_text(db, s->relname) + 1;
		ret =
		    generate_commands_source(db, token, t, tname, s, sname, d,
					     dname, fout);
	}
	t->flags.commands_gen = 1;
	/* Force a rebuild if the command has changed */
	if (t->flags.uptodate && !DB_IS_NULL_TOKEN(t->command)) {
		fprintf(fout, "KCO_%s := %s\n", tname,
			read_db_text(db, t->command));
		/*
		 * The following is designed to add dependencies on
		 * passed in or overriden flags for each and every
		 * file.  This is a good feature, but doesn't work
		 * within the TiVo build process.  (It always comes
		 * back as true, and requires a re-build of every
		 * file for every pass)
		 *
		 * My tendency is to say that if you are changing
		 * compilation flags by hand, then you must know
		 * what you will need to clobber and recompile.
		 * The feature could be fixed, but it would probably
		 * be too invasive.  Currently left out on
		 * purpose.  SRN
		 * 
		 */
#if 0
		fprintf(fout,
			"ifneq ($(filter-out -I%%,$(KCN_%s)),$(filter-out -I%%,$(KCO_%s)))\n",
			tname, tname);
		fprintf(fout, "  %s: FORCE_BUILD_COMMAND\n", tname);
		fprintf(fout, "endif\n\n");
#endif
	}
	return (ret);
}

/* List the targets in this directory, both direct and recursive targets.  This
 * supports 'make directory' and 'make directory-r'.
 */

static void
generate_commands_dir_targets(PPDB * db, DB_TOKEN token, DB_REC_DIRECTORY * d,
			      const char *dname, FILE * fout)
{
	int i, j;
	DB_REC_SELECT_LIST *s;
	DB_REC_SELECT_1 *s1;
	DB_REC_BASE_SELECT_LIST *bs;
	bs = read_db_base_select_list(db, d->base_select);
	fprintf(fout, ".PHONY: %s %s/\n%s %s/:", dname, dname, dname, dname);
	for (i = 0; i < bs->count; ++i) {
		if (DB_IS_NULL_TOKEN(bs->list[i].entry)) {
			continue;
		}
		s = read_db_select_list(db, bs->list[i].entry);
		for (j = 0, s1 = s->list; j < s->count; ++j, ++s1) {
			if (s1->entry.type == DB_RECTYPE_TARGET
			    && (s1->flags.selm || s1->flags.sely)
			    && !s1->flags.objlinked) {
				DB_REC_TARGET *tsel;
				tsel = read_db_target(db, s1->entry);
				fprintf(fout, " %s",
					read_db_text(db, tsel->relname) + 1);
			}
		}
	}
	fprintf(fout, "\n\n");

	fprintf(fout, ".PHONY: %s-r\n%s-r: %s", dname, dname, dname);
	for (i = 0; i < bs->count; ++i) {
		if (DB_IS_NULL_TOKEN(bs->list[i].entry)) {
			continue;
		}
		s = read_db_select_list(db, bs->list[i].entry);
		for (j = 0, s1 = s->list; j < s->count; ++j, ++s1) {
			if (s1->entry.type == DB_RECTYPE_DIRECTORY) {
				DB_REC_DIRECTORY *dsel;
				dsel = read_db_directory(db, s1->entry);
				if (!dsel->flags.makefile) {
					continue;
				}
				fprintf(fout, " %s-r",
					read_db_text(db, dsel->relname) + 1);
			}
		}
	}
	fprintf(fout, "\n\n");
}

static int
generate_commands(PPDB * db, DB_TOKEN token, DB_REC_DIRECTORY * d, FILE * fout)
{
	int i, j;
	char *dname;
	DB_REC_SELECT_LIST *s;
	DB_REC_SELECT_1 *s1;
	DB_REC_BASE_SELECT_LIST *bs;
	bs = read_db_base_select_list(db, d->base_select);
	dname = read_db_text(db, d->relname) + 1;
	for (i = 0; i < bs->count; ++i) {
		if (DB_IS_NULL_TOKEN(bs->list[i].entry)) {
			continue;
		}
		s = read_db_select_list(db, bs->list[i].entry);
		for (j = 0, s1 = s->list; j < s->count; ++j, ++s1) {
			if (s1->entry.type == DB_RECTYPE_TARGET
			    && (s1->flags.selm || s1->flags.sely)) {
				if (generate_commands_target
				    (db, s1->entry, d, dname, fout)) {
					return (1);
				}
			}
		}
	}
	if (*dname) {
		generate_commands_dir_targets(db, token, d, dname, fout);
	}
	return (0);
}

/*============================================================================*/

/* Process the database */

/* Summarize the selection criteria over all bases that have selected this
 * entry.
 */

static void
select_summary(PPDB * db, DB_REC_SELECT_1 * s1, DB_REC_BASE_1 * b1)
{
	if (s1->entry.type == DB_RECTYPE_TARGET) {
		DB_REC_TARGET *t = read_db_target(db, s1->entry);
		t->flags.sely_summary |= s1->flags.sely;
		t->flags.selm_summary |= s1->flags.selm;
		t->flags.host_summary |= b1->flags.is_host;
	}
}

/* objlink cleanups and cross referencing.
 *
 * Set flags according to the type of target.  Most of the flags are set during
 * Makefile.in parsing but statements that use $ variables may not be expanded
 * at the parse phase, so do it again here.
 * 
 * Walk nested objlink entries from the bottom object up to the top level
 * object, so the sub-objects get a KBUILD_OBJECT that correctly reflects the
 * overall package.  At the same time, mark archive members.
 *
 * An objlink list can contain duplicate entries, silently drop all except the
 * first.
 *
 * Warn if an object is selected and is part of an objlink list, that leads to
 * an ambiguous select status.
 */

static void
cleanup_objlink(PPDB * db, DB_TOKEN token, DB_REC_TARGET * target)
{
	int i, j, k, inc;
	DB_REC_TARGET_LIST *t;
	DB_REC_TARGET *tsub, *tparent;

	tparent = target;
	while (!DB_IS_NULL_TOKEN(tparent->link)) {
		target->link = tparent->link;
		if (tparent->flags.archive && !target->flags.archive) {
			target->flags.archive_member = 1;
		}
		tparent = read_db_target(db, tparent->link);
	}

	if (DB_IS_NULL_TOKEN(target->objlink)) {
		return;
	}

	t = read_db_target_list(db, target->objlink);
	/* Simple bubble compare to remove duplicates */
	for (i = 0; i < t->count; i += inc) {
		inc = 1;
		for (j = i + 1; j < t->count; ++j) {
			if (memcmp(t->list + i, t->list + j,
				   sizeof (t->list[0]))) {
				continue;
			}
			k = j + 1;
			memcpy(t->list + j, t->list + k,
			       (t->count - k) * (sizeof (t->list[0])));
			memset(t->list + t->count - 1, 0, sizeof (t->list[0]));
			--(t->count);
			inc = 0;
			break;
		}
		i += inc;
	}

	for (i = 0; i < t->count; ++i) {
		tsub = read_db_target(db, t->list[i].entry);
		if (DB_IS_NULL_TOKEN(tsub->link)) {
			tsub->link = token;
			continue;
		}
		tsub->flags.multi_linked = 1;
		if (tsub->flags.sely_summary
		    && read_db_target(db, tsub->link)->flags.sely_summary
		    && !tsub->flags.warned_multi_linked) {
			DB_REC_TARGET *tsub1 = read_db_target(db, tsub1->link);
			const char *relname = read_db_text(db, target->relname);
			fprintf(stderr,
				"%s:\n" "Warning: %s is a sub-object of %s\n"
				"         but is already a sub-object of %s.\n"
				"         This may result in multiple copies and link errors.\n",
				program, read_db_text(db, tsub->relname) + 1,
				relname + 1, read_db_text(db,
							  tsub1->relname) + 1);
			tsub->flags.warned_multi_linked = 1;
		}
	}
}

/* Select should always be at the objlink level, selecting sub-objects is
 * ambiguous.
 */

static void
warn_subobj_select(PPDB * db, DB_REC_TARGET * t)
{
	if (DB_IS_NULL_TOKEN(t->link)) {
		return;
	}
	if (t->flags.sely_summary || t->flags.selm_summary) {
		const char *relname = read_db_text(db, t->relname);
		fprintf(stderr,
			"%s:\n"
			"Warning: %s is a sub-object and it appears on select().\n"
			"         This is an ambiguous combination and is not recommended.\n",
			program, relname + 1);
	}
}

/* Mark $(arch_head) files, they are a special case for linking into vmlinux */

static void
mark_arch_head(PPDB * db, DB_REC_TARGET * t, const char *relname)
{
	char **a;
	if (!arch_head) {
		return;
	}
	for (a = arch_head; *a; ++a) {
		if (strcmp(relname + 1, *a) == 0) {
			t->flags.arch_head = 1;
		}
	}
}

/* Delete any dangling dependency files for objects and the objects */

static void
delete_old_dependencies(PPDB * db, DB_REC_TARGET * t, const char *relname,
			int l)
{
	DB_TOKEN token;
	char name[l - 1];
	if (!t->mtime || l < 3 || relname[l - 2] != '.'
	    || relname[l - 1] != 'd') {
		return;
	}
	memcpy(name, relname, l - 2);
	name[l - 2] = '\0';
	token = lookup_db_target(db, name);
	if (DB_IS_NULL_TOKEN(token)) {
		return;
	}
	if (read_db_target(db, token)->flags.object) {
		unlink_db_files(db, name);
		unlink_db_files(db, relname);
	}
}

/* Match each target to its corresponding source file */

static void
match_object_source(PPDB * db, DB_TOKEN token, DB_REC_TARGET * t,
		    const char *relname, int l)
{
	static const char source_suffix[] = { 'c', 'S', 's' };
	DB_TOKEN source;
	DB_REC_SOURCE *s;
	int i;
	char srcname[l + 1];
	if (!t->flags.object && !t->flags.gen_i && !t->flags.gen_s) {
		return;
	}
	if (t->flags.commands) {
		return;
	}
	if (!DB_IS_NULL_TOKEN(t->objlink)) {
		return;		/* conglomerate, not an object built from source */
	}
	memcpy(srcname, relname, l + 1);
	for (i = 0; i < sizeof (source_suffix); ++i) {
		srcname[l - 1] = source_suffix[i];
		if (relname[l - 1] == srcname[l - 1]) {
			continue;
		}
		source =
		    lookup_db_byname_highest(db, srcname, DB_RECTYPE_SOURCE);
		if (!DB_IS_NULL_TOKEN(source)) {
			break;
		}
	}
	if (DB_IS_NULL_TOKEN(source)) {
		if (!t->flags.ignore) {
			fprintf(stderr,
				"%s: Cannot find source for target %s\n",
				program, relname + 1);
			++errors;
			t->flags.ignore = 1;
		}
		return;
	}
	t->source = source;
	s = read_db_source(db, source);
	s->flags.setup = t->flags.setup;
	if (i) {
		t->flags.assembler = 1;
	}
}

/* First pass over the database.  Set the flags according to the filename
 * and attributes.  Lots of other code relies on these flags so do this first.
 */

static int
process_pass1(PPDB * db, kvpair kv, void *userdata)
{
	DB_TOKEN token;
	DB_REC_TARGET *t;
	const char *relname;
	int l;
	memcpy(&token, (void *) (kv.key.dptr), sizeof (token));
	if (token.type != DB_RECTYPE_TARGET) {
		return (0);
	}
	t = (DB_REC_TARGET *) (kv.val.dptr);
	relname = read_db_text(db, t->relname);
	l = strlen(relname);
	if (l > 2 && relname[l - 2] == '.') {
		set_oais(t, relname[l - 1]);
	}
	return (0);
}

/* Refresh the timestamps in each source config list */

static void
refresh_config_timestamps(PPDB * db, DB_REC_SOURCE * s, DB_TOKEN token)
{
	DB_REC_SOURCE *config;
	DB_REC_CCD_LIST *c;
	DB_REC_CCD_1 *c1;
	int i;
	c = read_db_ccd_list(db, s->config);
	for (i = 0, c1 = c->list; i < c->count; ++i, ++c1) {
		config = read_db_source(db, c1->entry);
		if (config->mtime != c1->mtime) {
			VERBOSE(3,
				"    %s -> %s changed, was %ld, now %ld\n",
				read_db_text(db, config->relname),
				read_db_text(db, s->relname) + 1,
				c1->mtime, config->mtime);
			c1->mtime = config->mtime;
		}
	}
}

/* Second pass over the database.  Propogate select status and do some
 * processing that only depends on local data.
 */

static int
process_pass2(PPDB * db, kvpair kv, void *userdata)
{
	DB_TOKEN token;
	memcpy(&token, (void *) (kv.key.dptr), sizeof (token));
	if (token.type == DB_RECTYPE_DIRECTORY) {
		DB_REC_DIRECTORY *d = (DB_REC_DIRECTORY *) (kv.val.dptr);
		DB_REC_SELECT_LIST *s;
		DB_REC_SELECT_1 *s1;
		DB_REC_BASE_1 *b1;
		DB_REC_BASE_SELECT_LIST *bs;
		int i, j;
		bs = read_db_base_select_list(db, d->base_select);
		for (i = 0; i < bs->count; ++i) {
			if (DB_IS_NULL_TOKEN(bs->list[i].entry)) {
				continue;
			}
			b1 = rec_base->list + i;
			s = read_db_select_list(db, bs->list[i].entry);
			for (j = 0, s1 = s->list; j < s->count; ++j, ++s1) {
				select_summary(db, s1, b1);
			}
		}
	}
	if (token.type == DB_RECTYPE_TARGET) {
		DB_REC_TARGET *t = (DB_REC_TARGET *) (kv.val.dptr);
		const char *relname = read_db_text(db, t->relname);
		int l = strlen(relname);
		mark_arch_head(db, t, relname);
		delete_old_dependencies(db, t, relname, l);
		match_object_source(db, token, t, relname, l);
	}
	if (token.type == DB_RECTYPE_SOURCE) {
		DB_REC_SOURCE *s = (DB_REC_SOURCE *) (kv.val.dptr);
		if (!DB_IS_NULL_TOKEN(s->config)) {
			refresh_config_timestamps(db, s, token);
		}
	}
	return (0);
}

/* Clean up objlink status.  The warning messages depend on select_summary()
 * having been done for the entire database.
 */

static int
process_pass3(PPDB * db, kvpair kv, void *userdata)
{
	DB_TOKEN token;
	memcpy(&token, (void *) (kv.key.dptr), sizeof (token));
	if (token.type == DB_RECTYPE_TARGET) {
		DB_REC_TARGET *t = (DB_REC_TARGET *) (kv.val.dptr);
		cleanup_objlink(db, token, t);
	}
	return (0);
}

/* Warn about multiple selected sub objects.  This depends on cleanup_objlink()
 * having been done for the entire database.
 */

static int
process_pass4(PPDB * db, kvpair kv, void *userdata)
{
	DB_TOKEN token;
	memcpy(&token, (void *) (kv.key.dptr), sizeof (token));
	if (token.type == DB_RECTYPE_TARGET) {
		DB_REC_TARGET *t = (DB_REC_TARGET *) (kv.val.dptr);
		warn_subobj_select(db, t);
	}
	return (0);
}

/* Propagate select status from each object onto its sub-objects.  Because the
 * modified sub-objects may be earlier in the tree than their parent, we cannot
 * assume that the change will always propagate all the way down the tree.  This
 * routine is invoked repeatedly until the select attributes are stable.  It
 * should not require more than three passes of the tree.
 *
 * When an object is selected as both y and m then only the y status is
 * propagated onto the sub-objects.  This reflects the rule that select(y)
 * overrides select(m).
 */

static int propagate_changes;

static int
process_pass5(PPDB * db, kvpair kv, void *userdata)
{
	int i;
	DB_TOKEN token;
	DB_REC_TARGET *t, *tsub;
	DB_REC_TARGET_LIST *objlink;
	memcpy(&token, (void *) (kv.key.dptr), sizeof (token));
	if (token.type != DB_RECTYPE_TARGET) {
		return (0);
	}
	t = (DB_REC_TARGET *) (kv.val.dptr);
	if (DB_IS_NULL_TOKEN(t->objlink)) {
		return (0);;
	}
	objlink = read_db_target_list(db, t->objlink);
	for (i = 0; i < objlink->count; ++i) {
		int old;
		tsub = read_db_target(db, objlink->list[i].entry);
		old = tsub->flags.sely_summary + tsub->flags.selm_summary;
		tsub->flags.sely_summary |= t->flags.sely_summary;
		tsub->flags.selm_summary |= (t->flags.selm_summary
					     && !t->flags.sely_summary);
		if (old != tsub->flags.sely_summary + tsub->flags.selm_summary) {
			propagate_changes = 1;
		}
	}
	return (0);

}

/*============================================================================*/

/* Add an entry to the directory base select list */

static DB_REC_SELECT_1 *
add_db_select(PPDB * db, DB_TOKEN ttoken, int selm, int sely)
{
	DB_REC_TARGET *t = read_db_target(db, ttoken);
	DB_TOKEN dtoken, *token_p;
	DB_REC_DIRECTORY *d;
	DB_REC_SELECT_1 *s1, add;
	DB_REC_BASE_SELECT_LIST *bs;
	const char *relname = read_db_text(db, t->relname);
	int l = strlen(relname);
	char parent[l + 1];
	assert(relname[0] == '/' && l > 1);
	memcpy(parent, relname, l + 1);
	while (l-- && parent[l] == '/') {
	};
	while (parent[l] != '/') {
		--l;
	};
	parent[l] = '\0';
	dtoken = lookup_db_directory(db, parent);
	d = read_db_directory(db, dtoken);
	bs = read_db_base_select_list(db, d->base_select);
	token_p = &(bs->list[current_base_number()].entry);
	if (DB_IS_NULL_TOKEN(*token_p)) {
		new_db_select_list(db, token_p);
	}
	memset(&add, 0, sizeof (add));
	add.entry = ttoken;
	add_db_select_list(db, *token_p, &add, &s1);
	s1->flags.selm |= selm;
	s1->flags.sely |= sely;
	return (s1);
}

/*============================================================================*/

/* .i and .s files are awkward.  They can be specified in makefiles (where the
 * files are actually used) or the user can specify them on the command line (to
 * view the output for debugging).  When specified in a makefile the base and
 * select status may or may not be known, when specified on the command line the
 * base and select status are definitely not known.  In a makefile the user can
 * use extra_*flags(), on the command line they cannot.
 *
 * The logic is :-
 *
 *   .[is] files referenced in makefiles default to target_base(vmlinux) and
 *   select(y), an explicit select in the makefile will override this default.
 *   Any flags for .[is] files referenced in makefiles must be supplied by the
 *   makefile, they are not copied from any objects.
 *
 *   .[is] files specified on the command line and which were not referenced
 *   in makefiles are debugging targets.  For debugging targets, there must
 *   exist a corresponding object that has been selected.  The base, select
 *   and flags data are copied from the object to the debugging target.
 *
 *   .[is] files specified both on the command line and in makefiles are not
 *   debugging targets.  Instead the user wants to explicitly build the target,
 *   using the rules from the makefile.
 */

static void
is_files_makecmdgoals(PPDB * db, char *makecmdgoals)
{
	char *goal;
	while (*makecmdgoals) {
		goal = makecmdgoals += strspn(makecmdgoals, " \t\n");
		makecmdgoals += strcspn(makecmdgoals, " \t\n");
		if (makecmdgoals - goal > 2 && makecmdgoals[-2] == '.' &&
		    (makecmdgoals[-1] == 'i' || makecmdgoals[-1] == 's')) {
#if 1
			fprintf(stderr,
				"***********************************************************************************\n");
			fprintf(stderr,
				"Specifying .[is] files on the command line is not working correctly at the moment,\n");
			fprintf(stderr,
				"this new kbuild 2.5 feature has been disabled to avoid database problems.\n");
			fprintf(stderr,
				"Until this new feature is working correctly, do it by hand, like this\n");
			fprintf(stderr, "\n");
			fprintf(stderr, "  rm %s%.*s.o\n", objtree,
				makecmdgoals - goal - 2, goal);
			fprintf(stderr, "  make -f %sMakefile-2.5 \\\n",
				srctree[0]);
			fprintf(stderr, "    PP_MAKEFILE5_FLAGS=-v %.*s.o\n",
				makecmdgoals - goal - 2, goal);
			fprintf(stderr, "\n");
			fprintf(stderr,
				"Cut and paste the gcc line, change .o to .i or .s and append -E or -S respectively.\n");
			fprintf(stderr,
				"%.*s on command line has been ignored.\n",
				makecmdgoals - goal, goal);
			fprintf(stderr,
				"***********************************************************************************\n");
			++errors;
#else
			char name[makecmdgoals - goal + 2], c;
			DB_TOKEN token;
			DB_REC_TARGET *t;
			int l;
			c = *makecmdgoals;
			*makecmdgoals = '\0';
			name[0] = '/';
			strcpy(name + 1, goal);
			token = findnew_db_target(db, name);
			t = read_db_target(db, token);
			t->flags.makecmdgoal = 1;
			t->flags.ignore = 0;	/* specified on this run, not dead */
			set_oais(t, makecmdgoals[-1]);
			l = strlen(name);
			match_object_source(db, token, t, name, l);
			*makecmdgoals = c;
#endif
		}
	}
}

/* Processing the .[is] files can add entries which disturbs run_db, so extract
 * the list of files to process first.
 */

struct is_files_data {
	DB_TOKEN is;
	DB_TOKEN object;
	char *object_name;
	char *is_name;
};
static struct is_files_data *is_files;
static int is_files_alloc;
static int is_files_used;

static int
is_files_list(PPDB * db, kvpair kv, void *userdata)
{
	DB_TOKEN token;
	DB_REC_TARGET *t;
	struct is_files_data *is;
	memcpy(&token, (void *) (kv.key.dptr), sizeof (token));
	if (token.type != DB_RECTYPE_TARGET) {
		return (0);
	}
	t = (DB_REC_TARGET *) (kv.val.dptr);
	assert(kv.val.dsize = DB_SIZE_TARGET(t));
	if (!t->flags.gen_i && !t->flags.gen_s) {
		return (0);
	}
	if (DB_IS_NULL_TOKEN(t->source)) {
		return (0);
	}
	if (is_files_used == is_files_alloc) {
		is_files =
		    k_realloc(is_files,
			      (is_files_alloc += 1000) * sizeof (*is_files));
	}
	is = is_files + is_files_used;
	memset(is, 0, sizeof (*is));
	is->is = token;
	is->is_name = k_strdup(read_db_text(db, t->relname));
	is->object_name = k_strdup(is->is_name);
	is->object_name[strlen(is->object_name) - 1] = 'o';
	is->object = lookup_db_target(db, is->object_name);
	++is_files_used;
	return (0);
}

/* Find and copy the select status and flags from an object onto a .[is] file */

static int
is_files_select_run(PPDB * db, DB_REC_BASE_1 * base1, DB_REC_SELECT_1 * s1,
		    void *userdata)
{
	struct is_files_data *is = userdata;
	DB_REC_TARGET *t, *tis;
	if (memcmp(&(s1->entry), &(is->object), sizeof (s1->entry))) {
		return (0);

	}
	t = read_db_target(db, s1->entry);
	tis = read_db_target(db, is->is);
	tis->flags.sely_summary = t->flags.sely_summary;
	tis->flags.selm_summary = t->flags.selm_summary;
	tis->flags.host_summary = t->flags.host_summary;
	memcpy(tis->acld_flags, t->acld_flags, sizeof (tis->acld_flags));
	return (1);
}

static void
is_files_select(PPDB * db, struct is_files_data *is)
{
	DB_REC_TARGET *t = read_db_target(db, is->is);
	DB_REC_BASE_1 *base1;
	int i;
	if (t->flags.sely_summary || t->flags.sely_summary) {
		return;
	}
	if (t->flags.makefile) {
		int l = strlen(is->object_name);
		char line[l + 80];	/* oversized */
		char dirname[l + 1];
		TEXTTREE *t;
		snprintf(line, sizeof (line), "select (y_ %s)", is->is_name);
		t = split_line(line);
		memcpy(dirname, is->object_name, l + 1);
		while (dirname[--l] != '/') {
		}
		dirname[l] = '\0';
		reldir_token = lookup_db_directory(db, dirname);
		assert(!DB_IS_NULL_TOKEN(reldir_token));
		cmd_select(db, t);
		read_db_target(db, is->is)->flags.sely_summary = 1;
		free_texttree(t);
		free(t);
		return;
	}
	/* Debugging .[is], generate commands according to the .o file */
	if (DB_IS_NULL_TOKEN(is->object)) {
		fprintf(stderr, "%s: Cannot find object for target %s\n",
			program, is->is_name);
		++errors;
		t->flags.ignore = 1;
		return;
	}
	for (i = 0, base1 = rec_base->list; i < rec_base->count; ++i, ++base1) {
		run_db_select(db, is_files_select_run, is, base1);
		if (t->flags.sely_summary || t->flags.selm_summary) {
			break;
		}
	}
	if (!t->flags.sely_summary && !t->flags.selm_summary) {
		fprintf(stderr,
			"%s: object %s is not selected, cannot generate %s\n",
			program, is->object_name, is->is_name);
		++errors;
		t->flags.ignore = 1;
		return;
	}
	add_db_select(db, is->is, t->flags.selm_summary, t->flags.sely_summary);
}

static void
process_is_files(PPDB * db)
{
	char *makecmdgoals = getenv("MAKECMDGOALS");
	int i;
	current_base = default_base();
	/* Any debugging .[is] files? */
	if (makecmdgoals) {
		is_files_makecmdgoals(db, makecmdgoals);
	}
	/* Get a list of the .[is] files */
	run_db(db, is_files_list, NULL);
	/* Set select and flags for .[is] files */
	for (i = 0; i < is_files_used; ++i) {
		is_files_select(db, is_files + i);
		free(is_files[i].is_name);
		free(is_files[i].object_name);
	}
	free(is_files);
	is_files_alloc = is_files_used = 0;
	is_files = NULL;
}

/*============================================================================*/

/* Resolve multiple selections */

static int
multiple_select_run(PPDB * db, kvpair kv, void *userdata)
{
	DB_TOKEN token;
	DB_REC_TARGET *t;
	memcpy(&token, (void *) (kv.key.dptr), sizeof (token));
	if (token.type != DB_RECTYPE_TARGET) {
		return (0);
	}
	t = (DB_REC_TARGET *) (kv.val.dptr);
	assert(kv.val.dsize = DB_SIZE_TARGET(t));
	if (t->flags.sely_summary && t->flags.selm_summary) {
		/* select(y) overrides select(m) for compilation flags, with a
		 * warning if the object is linked into other objects.
		 */
		if (!DB_IS_NULL_TOKEN(t->link)) {
			fprintf(stderr,
				"%s:\n"
				"Warning: %s is selected as both y and m.  It will be compiled as built in, even\n"
				"         when linked into modules.\n",
				program, read_db_text(db, t->relname) + 1);
		}
		t->flags.selm_summary = 0;
	}
	return (0);
}

static void
multiple_select(PPDB * db)
{
	run_db(db, multiple_select_run, NULL);
}

/*============================================================================*/

/* Where targets are selected via objlink, add them to the base_select lists. */

static void
select_objlinked_1(PPDB * db, DB_TOKEN token, int selm, int sely)
{
	DB_REC_SELECT_1 *s1 = add_db_select(db, token, selm, sely);
	DB_REC_TARGET *t = read_db_target(db, token);
	DB_REC_TARGET_LIST *objlink;
	int i;
#if 0
	DB_REC_TARGET *t = read_db_target(db, ttoken);
	DB_TOKEN dtoken, *token_p;
	DB_REC_DIRECTORY *d;
	const char *relname = read_db_text(db, t->relname);
	int i, l = strlen(relname);
	char parent[l + 1];
	assert(relname[0] == '/' && l > 1);
	memcpy(parent, relname, l + 1);
	while (l-- && parent[l] == '/') {
	};
	while (parent[l] != '/') {
		--l;
	};
	parent[l] = '\0';
	dtoken = lookup_db_directory(db, parent);
	d = read_db_directory(db, dtoken);
	token_p = d->base_select + current_base_number();
	if (DB_IS_NULL_TOKEN(*token_p)) {
		new_db_select_list(db, token_p);
		/* may have moved, reestablish pointers */
		d = read_db_directory(db, dtoken);
		token_p = d->base_select + current_base_number();
		t = read_db_target(db, ttoken);
	}
	memset(&add, 0, sizeof (add));
	add.entry = ttoken;
	add_db_select_list(db, token_p, &add, &s1);
	s1->flags.selm |= selm;
	s1->flags.sely |= sely;
#endif
	s1->flags.objlinked = 1;
	if (DB_IS_NULL_TOKEN(t->objlink)) {
		return;
	}
	/* select_objlinked_1() can expand the list, take a copy */
	objlink = copy_db_target_list(db, t->objlink);
	for (i = 0; i < objlink->count; ++i) {
		select_objlinked_1(db, objlink->list[i].entry, selm, sely);
	}
	free(objlink);
}

static int
select_objlinked_run_select(PPDB * db, DB_REC_BASE_1 * base1,
			    DB_REC_SELECT_1 * s1, void *userdata)
{
	DB_REC_TARGET *t;
	DB_REC_TARGET_LIST *objlink;
	int i;
	int sely, selm;
	if (s1->entry.type != DB_RECTYPE_TARGET || s1->flags.objlinked) {
		return (0);
	}
	t = read_db_target(db, s1->entry);
	if (DB_IS_NULL_TOKEN(t->objlink)) {
		return (0);
	}
	sely = s1->flags.sely;
	selm = s1->flags.selm;
	objlink = copy_db_target_list(db, t->objlink);
	/* Do not rely on s1 after this point, select entries get expanded */
	s1 = NULL;
	for (i = 0; i < objlink->count; ++i) {
		select_objlinked_1(db, objlink->list[i].entry, selm, sely);
	}
	free(objlink);
	return (0);
}

static void
select_objlinked(PPDB * db)
{
	DB_REC_BASE_1 *b1;
	int i;
	for (i = 0, b1 = rec_base->list; i < rec_base->count; ++i, ++b1) {
		current_base = b1;
		run_db_select(db, select_objlinked_run_select, NULL, b1);
	}
}

/*============================================================================*/

/* Clear the directory 'walked' flags */

static int
clear_walked_run(PPDB * db, kvpair kv, void *userdata)
{
	DB_TOKEN token;
	DB_REC_DIRECTORY *d;
	DB_REC_SELECT_LIST *s;
	DB_REC_SELECT_1 *s1;
	DB_REC_BASE_SELECT_LIST *bs;
	int i, j;
	memcpy(&token, (void *) (kv.key.dptr), sizeof (token));
	if (token.type == DB_RECTYPE_TARGET) {
		DB_REC_TARGET *t = (DB_REC_TARGET *) (kv.val.dptr);
		t->flags.walked = 0;
		return (0);
	}
	if (token.type != DB_RECTYPE_DIRECTORY) {
		return (0);
	}
	d = (DB_REC_DIRECTORY *) (kv.val.dptr);
	d->flags.walked = 0;
	bs = read_db_base_select_list(db, d->base_select);
	for (i = 0; i < bs->count; ++i) {
		if (DB_IS_NULL_TOKEN(bs->list[i].entry)) {
			continue;
		}
		s = read_db_select_list(db, bs->list[i].entry);
		for (j = 0, s1 = s->list; j < s->count; ++j, ++s1) {
			s1->flags.walked = 0;
		}
	}
	return (0);
}

/*============================================================================*/

/* Identify any top level objects that are selected as built in but are not
 * marked as 'walked'.  These will be missing from the base, it usually
 * indicates a missing link_subdirs command.  This routine is invoked for
 * each base, with current_base set accordingly.
 */

static int
check_walked_run(PPDB * db, DB_REC_CHILD_1 * c1, void *userdata)
{
	DB_REC_DIRECTORY *d;
	DB_REC_TARGET *t;
	DB_REC_SELECT_LIST *s;
	DB_REC_SELECT_1 *s1;
	DB_REC_BASE_SELECT_LIST *bs;
	int i, j;
	if (c1->entry.type != DB_RECTYPE_DIRECTORY) {
		return (0);
	}
	i = current_base_number();
	d = read_db_directory(db, c1->entry);
	bs = read_db_base_select_list(db, d->base_select);
	if (DB_IS_NULL_TOKEN(bs->list[i].entry)) {
		return (0);
	}
	s = read_db_select_list(db, bs->list[i].entry);
	for (j = 0, s1 = s->list; j < s->count; ++j, ++s1) {
		if (s1->flags.walked) {
			continue;
		}
		if (!s1->flags.sely && !s1->flags.selm) {
			continue;
		}
		if (s1->flags.objlinked) {
			continue;
		}
		t = read_db_target(db, s1->entry);
		if (t->flags.arch_head) {
			continue;
		}
		if (s1->flags.sely) {
			fprintf(stderr,
				"%s: %s is selected but is not part of %s, missing link_subdirs?\n",
				program, read_db_text(db, t->relname) + 1,
				read_db_text(db, rec_base->list[i].entry));
		} else {
			fprintf(stderr,
				"%s: %s is selected as a module but is not in the module list, missing link_subdirs?\n",
				program, read_db_text(db, t->relname) + 1);
		}
		++errors;
		t->flags.ignore = 1;
	}
	return (0);
}

/*============================================================================*/

/* Check if the selected targets are uptodate */

static int
check_uptodate_run(PPDB * db, kvpair kv, void *userdata)
{
	DB_TOKEN token;
	DB_REC_TARGET *t;
	DB_REC_SOURCE *s;
	DB_REC_CCD_LIST *d, *c;
	DB_REC_CCD_1 *d1, *c1;
	const char *relname;
	int i, j;
	memcpy(&token, (void *) (kv.key.dptr), sizeof (token));
	if (token.type != DB_RECTYPE_TARGET) {
		return (0);
	}
	t = (DB_REC_TARGET *) (kv.val.dptr);
	relname = read_db_text(db, t->relname);
	if (t->flags.issrcfile && t->flags.symlink && t->flags.commands) {
		fprintf(stderr,
			"%s is both shipped and overwritten so symlinks are not safe.  Removing the symlink.\n",
			relname + 1);
		unlink_db_files(db, relname);
	}
	if (!t->flags.sely_summary && !t->flags.selm_summary &&
	    !t->flags.gen_i && !t->flags.gen_s) {
		return (0);
	}
	if (t->mtime && !t->flags.uptodate) {
		VERBOSE(3, "    check_uptodate %s is not uptodate\n",
			relname + 1);
		unlink_db_files(db, relname);
	}
	if (!t->flags.uptodate) {
		return (0);
	}
	if (DB_IS_NULL_TOKEN(t->dependency)) {
		return (0);
	}
	/* The target claims to be up to date.  I could give the full dependency
	 * list to make and let it work out if the file is really up to date but
	 * bitter experience shows that make is far too slow when given the true
	 * picture, it is far faster to do the work in this program.
	 */
	d = read_db_ccd_list(db, t->dependency);
	if (d->parent_mtime != t->mtime) {
		VERBOSE(3,
			"    check_uptodate %s target mtime changed, was %ld, now %ld\n",
			relname + 1, d->parent_mtime, t->mtime);
		t->flags.uptodate &= norun;
	}
	for (i = 0, d1 = d->list; i < d->count && t->flags.uptodate; ++i, ++d1) {
		s = read_db_source(db, d1->entry);
		if (s->index != d1->index || s->mtime != d1->mtime) {
			VERBOSE(3,
				"    check_uptodate %s dependency %s index/mtime changed, was %d/%ld, now %d/%ld\n",
				relname + 1,
				read_db_text(db, s->relname) + 1,
				d1->index, d1->mtime, s->index, s->mtime);
			t->flags.uptodate &= norun;
			break;
		}
		if (s->mtime > t->mtime) {
			VERBOSE(3,
				"    check_uptodate dependency %s is newer (%ld) than %s (%ld)\n",
				read_db_text(db, s->relname) + 1,
				s->mtime, relname + 1, t->mtime);
			t->flags.uptodate &= norun;
			break;
		}
		if (DB_IS_NULL_TOKEN(s->config)) {
			continue;
		}
		c = read_db_ccd_list(db, s->config);
		if (c->parent_index != s->index || c->parent_mtime != s->mtime) {
			VERBOSE(3,
				"    check_uptodate %s config %s index/mtime changed, was %d/%ld, now %d/%ld\n",
				relname + 1,
				read_db_text(db, s->relname) + 1,
				c->parent_index, c->parent_mtime,
				s->index, s->mtime);
			t->flags.uptodate &= norun;
			break;
		}
		for (j = 0, c1 = c->list; j < c->count && t->flags.uptodate;
		     ++j, ++c1) {
			if (t->mtime >= c1->mtime) {
				continue;
			}
			VERBOSE(3,
				"    check_uptodate %s (%ld) -> %s (%ld) -> %s (%ld)\n",
				read_db_text(db,
					     read_db_source(db,
							    c1->entry)->
					     relname), c1->mtime,
				read_db_text(db, s->relname) + 1, s->mtime,
				relname + 1, t->mtime);
			t->flags.uptodate &= norun;
			break;
		}
	}
	if (!t->flags.uptodate) {
		unlink_db_files(db, relname);
	}
	return (0);
}

static void
check_uptodate(PPDB * db)
{
	run_db(db, check_uptodate_run, NULL);
}

/*============================================================================*/

/* Print the link order for all bases */

static void
print_objlink(PPDB * db, FILE * f, DB_REC_TARGET * t, DB_TOKEN token, int depth)
{
	DB_REC_TARGET_LIST *objlink;
	int i;
	objlink = read_db_target_list(db, t->objlink);
	for (i = 0; i < objlink->count; ++i) {
		t = read_db_target(db, objlink->list[i].entry);
		if (t->flags.walked) {
			continue;
		}
		fprintf(f, "%*s%s (%s)\n", 2 * depth, "",
			read_db_text(db, t->relname) + 1,
			t->flags.archive ? "archive" :
			DB_IS_NULL_TOKEN(t->objlink) ? "leaf" : "objlink");
		t->flags.walked = 1;
		if (!DB_IS_NULL_TOKEN(t->objlink)) {
			print_objlink(db, f, t, objlink->list[i].entry,
				      ++depth);
		}
	}
}

/* The next routine has userdata pointing to FILE *f[3].  f[0] is the link order
 * tree, f[1] is the new object list, f[2] is the new module list.
 */

static int
print_base_order_run_select(PPDB * db, DB_REC_BASE_1 * base1,
			    DB_REC_SELECT_1 * s1, void *userdata)
{
	FILE **f = userdata;
	DB_REC_TARGET *t;
	struct objmod *om;
	int n = current_base_number();
	const char *name;
	s1->flags.walked = 1;	/* walked via select/link_subdirs */
	if (s1->entry.type == DB_RECTYPE_DIRECTORY) {
		DB_REC_DIRECTORY *d = read_db_directory(db, s1->entry);
		if (base1->flags.is_host) {
			return (0);
		}
		name = read_db_text(db, d->relname) + 1;
		fprintf(f[0], "%*s%s", 2 * run_db_select_depth, "", name);
		if (d->flags.walked) {
			fprintf(f[0], " (pruned)\n");
			return (2);
		}
		fprintf(f[0], "\n");
		d->flags.walked = 1;
		return (0);
	}
	if (s1->flags.objlinked) {
		/* objlinked entries are pretty printed via their parent, see below */
		return (0);
	}
	t = read_db_target(db, s1->entry);
	if (t->flags.arch_head) {
		return (0);
	}
	if (!t->flags.sely_summary && !t->flags.selm_summary) {
		return (0);
	}
	if (!t->flags.object && DB_IS_NULL_TOKEN(t->objlink)) {
		return (0);
	}
	name = read_db_text(db, t->relname) + 1;
	if (t->flags.sely_summary) {
		fprintf(f[0], "%*s%s (%s)", 2 * run_db_select_depth, "",
			name,
			t->flags.archive ? "archive" :
			DB_IS_NULL_TOKEN(t->objlink) ? "leaf" : "objlink");
		if (t->flags.walked) {
			fprintf(f[0], " (pruned)\n");
			return (2);
		}
		fprintf(f[0], "\n");
		t->flags.walked = 1;
		if (!DB_IS_NULL_TOKEN(t->objlink)) {
			print_objlink(db, f[0], t, s1->entry,
				      run_db_select_depth + 1);
		}
		if (f[1]) {
			fprintf(f[1], "INPUT(\"%s\")\n", name);
		}
	}
	if (t->flags.selm_summary && f[2]) {
		fprintf(f[2], "%s\n", name);
	}
	om = t->flags.sely_summary ? base_objects + n : base_modules + n;
	if (om->used == om->alloc) {
		om->list =
		    k_realloc(om->list,
			      (om->alloc += 100) * sizeof (*(om->list)));
	}
	om->list[om->used].target = s1->entry;
	om->list[om->used].parent = t->parent;
	++(om->used);
	return (0);
}

static void
print_base_order(PPDB * db)
{
	DB_REC_BASE_1 *b1;
	FILE *f[3];		/* order, objects, modules */
	int i, j;
	char name[PATH_MAX + 1], *basename;
	base_modules = k_malloc(rec_base->count * sizeof (*base_modules));
	base_objects = k_malloc(rec_base->count * sizeof (*base_objects));
	for (i = 0, b1 = rec_base->list; i < rec_base->count; ++i, ++b1) {
		current_base = b1;
		memset(base_modules + i, 0, sizeof (base_modules[0]));
		memset(base_objects + i, 0, sizeof (base_objects[0]));
		basename = read_db_text(db, b1->entry);
		run_db(db, clear_walked_run, NULL);
		for (j = 0; j < 3; ++j) {
			/* Only base[0] (vmlinux) uses f[1] (link commands for
			 * objects) and f[2] (modules).  Other bases only use
			 * f[0] (tree order).
			 */
			if (i && j) {
				f[j] = NULL;
				continue;
			}
			snprintf(name, sizeof (name), ".tmp_%s_%s", basename,
				 j == 0 ? "order" : j ==
				 1 ? "objects_new" : "modules_new");
			if (!(f[j] = fopen(name, "w"))) {
				fprintf(stderr,
					"%s: Cannot open %s for writing\n",
					program, name);
				++errors;
				return;
			}
		}
		fprintf(f[0], "# Directory and object order for %s\n",
			basename);
		if (b1->flags.has_arch_head) {
			char **a;
			fprintf(f[0], "$(arch_head)=");
			for (a = arch_head; a && *a; ++a) {
				fprintf(f[0], "%s%s",
					a == arch_head ? "" : " ", *a);
			}
			fprintf(f[0], "\n");
		}
		run_db_select(db, print_base_order_run_select, f, b1);
		for (j = 0; j < 3; ++j) {
			if (f[j]) {
				fclose(f[j]);
			}
		}
		run_db_child(db, check_walked_run, NULL, "/");
	}
}

/*============================================================================*/

/* For every directory that contains targets, write a .tmp_implicit file to
 * give NO_MAKEFILE_GEN a small chance of being correct.
 */

/* Write implicit config dependencies for a source */

static void
write_tmp_implicit_1_config(PPDB * db, DB_TOKEN token, DB_REC_SOURCE * s,
			    FILE * f)
{
	DB_REC_CCD_LIST *c;
	DB_REC_CCD_1 *c1;
	int i;
	c = read_db_ccd_list(db, s->config);
	for (i = 0, c1 = c->list; i < c->count; ++i, ++c1) {
		s = read_db_source(db, c1->entry);
		if (s->mtime) {
			fprintf(f, "\t\t%s%s \\\n", srctree[s->index],
				read_db_text(db, s->relname) + 1);
		}
	}
}

/* Write implicit dependency data for one target.  The target depends on its
 * dependency list plus config options referenced by any of the dependencies.
 */

static void
write_tmp_implicit_1(PPDB * db, DB_TOKEN token, FILE * f)
{
	DB_REC_TARGET *t = read_db_target(db, token);
	DB_REC_SOURCE *s;
	DB_REC_CCD_LIST *c;
	DB_REC_CCD_1 *c1;
	int i;
	if (!t->flags.uptodate || DB_IS_NULL_TOKEN(t->dependency)) {
		return;
	}
	fprintf(f, "%s: \\\n", read_db_text(db, t->relname) + 1);
	c = read_db_ccd_list(db, t->dependency);
	for (i = 0, c1 = c->list; i < c->count; ++i, ++c1) {
		s = read_db_source(db, c1->entry);
		fprintf(f, "\t%s%s \\\n", srctree[s->index],
			read_db_text(db, s->relname) + 1);
		if (!DB_IS_NULL_TOKEN(s->config)) {
			write_tmp_implicit_1_config(db, c1->entry, s, f);
		}
	}
	fprintf(f, "\n");
}

/* Get all the targets for the same directory together */

static int
qsort_tmp_implicit(const void *va, const void *vb)
{
	const struct objmod_data *a = va, *b = vb;
	if (a->parent.recno <= b->parent.recno) {
		return (-1);
	}
	if (a->parent.recno == b->parent.recno) {
		return (0);
	}
	return (1);
}

/* Write .tmp_implicit for all bases in all directories.  These files are only
 * written when pp_makefile4 is invoked with NO_MAKEFILE_GEN exported, i.e. on
 * the NO_MAKEFILE_GEN run after a normal build.  See scripts/Makefile-2.5.
 */

static void
write_tmp_implicit(PPDB * db)
{
	DB_TOKEN prev;
	struct objmod_data *od, *od1;
	FILE *f = NULL;
	int i, n;
	char name[PATH_MAX + 1];
	if (!getenv("NO_MAKEFILE_GEN")) {
		return;
	}
	for (i = 0, n = 0; i < rec_base->count; ++i) {
		n += base_objects[i].used + base_modules[i].used;
	}
	od1 = od = k_malloc(n * sizeof (*od));
	for (i = 0; i < rec_base->count; ++i) {
		memcpy(od1, base_objects[i].list,
		       base_objects[i].used * sizeof (*od1));
		od1 += base_objects[i].used;
		memcpy(od1, base_modules[i].list,
		       base_modules[i].used * sizeof (*od1));
		od1 += base_modules[i].used;
	}
	qsort(od, n, sizeof (*od), qsort_tmp_implicit);
	for (i = 0, od1 = od; i < n; ++i, ++od1) {
		if (!f || prev.recno != od1->parent.recno) {
			DB_REC_DIRECTORY *d =
			    read_db_directory(db, od1->parent);
			if (f) {
				fclose(f);
			}
			snprintf(name, sizeof (name), ".%s/.tmp_implicit",
				 read_db_text(db, d->relname));
			if (!(f = fopen(name, "w"))) {
				fprintf(stderr,
					"%s: Cannot open %s for writing\n",
					program, name);
				++errors;
				break;
			}
			prev = od1->parent;
		}
		write_tmp_implicit_1(db, od1->target, f);

	}
	if (f) {
		fclose(f);
	}
	free(od);
}

/*============================================================================*/

/* Process the database after reading the select information */

static int
process_database(PPDB * db)
{
	if (run_db(db, process_pass1, NULL) || signalled ||
	    run_db(db, process_pass2, NULL) || signalled ||
	    run_db(db, process_pass3, NULL) || signalled ||
	    run_db(db, process_pass4, NULL) || signalled) {
		return (1);
	}
	/* oais processing requires that the select status be stable first */
	do {
		propagate_changes = 0;
		if (run_db(db, process_pass5, NULL) || signalled) {
			return (1);
		}
	} while (propagate_changes == 0);
	check_uptodate(db);
	multiple_select(db);
	select_objlinked(db);
	process_is_files(db);
	sort_db_directories(db);
	print_base_order(db);
	write_tmp_implicit(db);
	return (0);
}

/*============================================================================*/

/* Functions used by copy_makefile */

/* Print directory names if there are any targets to be built in them */

static int
needed_directories(PPDB * db, DB_REC_CHILD_1 * c1, void *userdata)
{
	DB_REC_DIRECTORY *d;
	DB_REC_SELECT_LIST *s;
	DB_REC_SELECT_1 *s1;
	DB_REC_BASE_SELECT_LIST *bs;
	FILE *f = userdata;
	int i, j, md;
	if (c1->entry.type != DB_RECTYPE_DIRECTORY) {
		return (0);
	}
	d = read_db_directory(db, c1->entry);
	md = d->flags.create_objdir;
	bs = read_db_base_select_list(db, d->base_select);
	for (i = 0; !md && i < bs->count; ++i) {
		if (DB_IS_NULL_TOKEN(bs->list[i].entry)) {
			continue;
		}
		s = read_db_select_list(db, bs->list[i].entry);
		for (j = 0, s1 = s->list; j < s->count; ++j, ++s1) {
			if (s1->entry.type == DB_RECTYPE_DIRECTORY) {
				continue;
			}
			if (s1->flags.sely || s1->flags.selm) {
				md = 1;
				break;
			}
		}
	}
	if (md) {
		fprintf(f, " %s", read_db_text(db, d->relname) + 1);
	}
	return (0);
}

/* With common source and object, files that are both shipped and overwritten
 * cause problems.  Remove them from the CLEAN list.
 */

static int
no_clean_srcfile(PPDB * db, kvpair kv, void *userdata)
{
	DB_TOKEN token;
	memcpy(&token, (void *) (kv.key.dptr), sizeof (token));
	if (token.type == DB_RECTYPE_TARGET) {
		FILE *fout = userdata;
		DB_REC_TARGET *t = (DB_REC_TARGET *) (kv.val.dptr);
		if (t->flags.issrcfile) {
			fprintf(fout, "CLEAN\t:=$(CLEAN:%%%s= )\n",
				read_db_text(db, t->relname) + 1);
		}
	}
	return (0);
}

/* Generate KCO_ checks for user commands.  These do not fit in the selm/sely
 * model nor are they per-directory targets, command driven builds are invoked
 * as a side effect of other targets.  So do the check at the end of the global
 * makefile.
 */

static int
kco_user(PPDB * db, kvpair kv, void *userdata)
{
	DB_TOKEN token;
	memcpy(&token, (void *) (kv.key.dptr), sizeof (token));
	if (token.type == DB_RECTYPE_TARGET) {
		FILE *fout = userdata;
		DB_REC_TARGET *t = (DB_REC_TARGET *) (kv.val.dptr);
		if (t->flags.commands) {
			generate_commands_target(db, token, NULL, NULL, fout);
		}
	}
	return (0);
}

/*============================================================================*/

/* Copy .tmp_global_makefile_in.2 to .tmp_global_makefile with all the required
 * commands.
 */

static int
copy_makefile(PPDB * db)
{
	FILE *fin, *fout;
	char *line, *p, *q;
	static const char *in_name = ".tmp_global_makefile_in.2";
	static const char *out_name = ".tmp_global_makefile";
	int ret = 0, start = 1, eof = 0, line_size;

	if (!(fin = fopen(in_name, "r"))) {
		fprintf(stderr, "%s: fopen(%s, r) failed: %m\n", program,
			in_name);
		return (1);
	}
	if (!(fout = fopen(out_name, "w"))) {
		fprintf(stderr, "%s: fopen(%s, w) failed: %m\n", program,
			out_name);
		return (1);
	}

	line = k_malloc((line_size = 4096));
	while (!ret && !(eof = fgets_long_line(&line, &line_size, fin))) {
		if (start) {
			if (*line == '#') {
				continue;
			}
			start = 0;
			fprintf(fout,
				"# Generated by linux/scripts/pp_makefile4 from Makefile.in files, do not edit.\n"
				"# This Makefile will not work without the variables that are exported from the\n"
				"# top level Makefile, do not try to use this Makefile directly.\n"
				"\n"
				"# Discard all the implicit make rules, kernel defines everything it needs.\n"
				"MAKEFLAGS	+= -r\n"
				".SUFFIXES:\n"
				"SUFFIXES	:=\n"
				".DELETE_ON_ERROR:\n"
				".PHONY: all_sum_shipped\n"
				"\n"
				"__pp_makefile_dummy	:= $(shell mkdir -p");
			ret = run_db_child(db, needed_directories, fout, "/");
			fprintf(fout, ")\n");
			if (ret) {
				continue;
			}
		}
		if (strncmp(line, "#pp_makefile_in_start", 21) == 0) {
			const char *line1 = line;
			current_base = default_base();
			if (reldir) {
				free(reldir);
			}
			if (next_text(&line1, &reldir) == 0 &&
			    (free(reldir), next_text(&line1, &reldir) == 0) &&
			    (free(reldir), next_text(&line1, &reldir) == 0)) {
				/* reldir has been extracted */
			} else {
				fprintf(stderr,
					"%s: Cannot extract reldir from '%s'\n",
					program, line);
				reldir = NULL;
				ret = 1;
				continue;
			}
		}
		for (p = line; isspace(*p); ++p) {
		};
		if (strncmp(p, "__pp_", 5) == 0) {
			/* Comment out the __pp_ assignments, they have done their job and make
			 * will run faster without them.
			 */
			fprintf(fout, "# %s", line);
		} else if (strncmp(line, "#__pp_targets", 13) == 0) {
			/* Everything has led up to this, generate the automatic commands */
			DB_TOKEN token;
			DB_REC_DIRECTORY *d;
			fprintf(fout, "%s\n", line);
			token = lookup_db_directory(db, reldir);
			d = read_db_directory(db, token);
			ret = generate_commands(db, token, d, fout);
			fprintf(fout, "\n");
		} else if ((p = strstr(line, "$(__pp_"))
			   && ((q = strstr(p + 7, "_modules)")) ||
			       (q = strstr(p + 7, "_objects)")))) {
			char base_wanted[q - (p + 7) + 1];
			DB_REC_TARGET *t;
			DB_REC_BASE_1 *base;
			struct objmod *om;
			int i, n, count = 0;
			memcpy(base_wanted, p + 7, q - (p + 7));
			base_wanted[q - (p + 7)] = '\0';
			if (!(base = scan_db_base(db, base_wanted))) {
				fprintf(stderr, "%s: unknown base\n%s\n",
					program, line);
				++errors;
				continue;
			}
			*p = '\0';
			fprintf(fout, "%s", line);
			n = base_number(base);
			om = q[1] == 'o' ? base_objects + n : base_modules + n;
			for (i = 0; i < om->used; ++i) {
				++count;
				if (count % 4 == 0 && count) {
					fprintf(fout, "\\\n\t\t");
				}
				t = read_db_target(db, om->list[i].target);
				fprintf(fout, "%s ",
					read_db_text(db, t->relname) + 1);
			}
			fprintf(fout, "\n");
		} else {
			fprintf(fout, "%s", line);
		}
	}
	if (eof < 0) {
		fprintf(stderr, "%s: Error reading from %s: %m\n", program,
			in_name);
		ret = 1;
	}
	if (reldir) {
		free(reldir);
		reldir = NULL;
	}
	free(line);
	fclose(fin);
	if (common_src_obj) {
		ret = ret || run_db(db, no_clean_srcfile, fout);
		fprintf(fout, "\n");
	}
	ret = ret || run_db(db, kco_user, fout);
	fclose(fout);
	return (ret);
}

/*============================================================================*/

/* Clear old data from the database.  Empty extra/strip a/c/ld flags, select
 * lists, objlink lists, source and link pointers, command, most target and
 * directory flags.
 */

static int
clear_old_data_run(PPDB * db, kvpair kv, void *userdata)
{
	DB_TOKEN token;
	int i;
	memcpy(&token, (void *) (kv.key.dptr), sizeof (token));
	if (token.type == DB_RECTYPE_TARGET) {
		DB_REC_TARGET *t = (DB_REC_TARGET *) (kv.val.dptr), temp;
		for (i = 0; i < 5; ++i) {
			if (!DB_IS_NULL_TOKEN(t->acld_flags[i])) {
				empty_db_text(db, t->acld_flags[i]);
			}
		}
		if (!DB_IS_NULL_TOKEN(t->objlink)) {
			empty_db_target_list(db, t->objlink);
		}
		if (!t->flags.uptodate && !DB_IS_NULL_TOKEN(t->command)) {
			empty_db_text(db, t->command);
		}
		memset(&(t->source), 0, sizeof (t->source));
		memset(&(t->link), 0, sizeof (t->link));
		temp.flags = t->flags;
		memset(&(t->flags), 0, sizeof (t->flags));
		t->flags.nodepend = temp.flags.nodepend;
		/* Preserve the flags that are set by programs before pp_makefile4 */
		t->flags.setup = temp.flags.setup;
		t->flags.uptodate = temp.flags.uptodate;
		t->flags.issrcfile = temp.flags.issrcfile;
		t->flags.makefile = temp.flags.makefile;
		t->flags.ignore = temp.flags.ignore || temp.flags.makecmdgoal;
		t->flags.symlink = temp.flags.symlink;
	}
	if (token.type == DB_RECTYPE_DIRECTORY) {
		DB_REC_DIRECTORY *d = (DB_REC_DIRECTORY *) (kv.val.dptr), temp;
		DB_REC_BASE_SELECT_LIST *bs;
		bs = read_db_base_select_list(db, d->base_select);
		for (i = 0; i < 5; ++i) {
			if (!DB_IS_NULL_TOKEN(d->acld_flags[i])) {
				empty_db_text(db, d->acld_flags[i]);
			}
		}
		for (i = 0; i < bs->count; ++i) {
			if (!DB_IS_NULL_TOKEN(bs->list[i].entry)) {
				empty_db_select_list(db, bs->list[i].entry);
			}
		}
		temp.flags = d->flags;
		memset(&(d->flags), 0, sizeof (d->flags));
		d->flags.explicit = temp.flags.explicit;
		d->flags.makefile = temp.flags.makefile;
	}
	return (0);
}

static int
clear_old_data(PPDB * db)
{
	return (run_db(db, clear_old_data_run, NULL));
}

/*============================================================================*/

/* Host commands do not require the user to specify link_subdirs, host commands
 * and objects and independent, they are not linked into a single base object.
 * However it makes it easier to process host if the link_subdirs chain exists,
 * in particular it makes it easier to generate commands for host targets.
 * Synthesize a link_subdirs chain for the host base, from the child lists.
 */

static int
set_host_link_subdirs_run(PPDB * db, DB_REC_CHILD_1 * c1, void *userdata)
{
	DB_TOKEN token = c1->entry, *token_p;
	DB_REC_DIRECTORY *d;
	DB_REC_CHILD_LIST *c;
	DB_REC_SELECT_1 add;
	int i;
	if (token.type != DB_RECTYPE_DIRECTORY) {
		return (0);
	}
	d = read_db_directory(db, token);
	if (DB_IS_NULL_TOKEN(d->children)) {
		return (0);
	}
	/* Read child list, expand select list */
	c = read_db_child_list(db, d->children);
	memset(&add, 0, sizeof (add));
	add.flags.sels = 1;
	for (i = 0, c1 = c->list; i < c->count; ++i, ++c1) {
		if (c1->entry.type == DB_RECTYPE_DIRECTORY) {
			DB_REC_BASE_SELECT_LIST *bs;
			bs = read_db_base_select_list(db, d->base_select);
			add.entry = c1->entry;
			token_p = &(bs->list[current_base_number()].entry);
			if (DB_IS_NULL_TOKEN(*token_p)) {
				new_db_select_list(db, token_p);
			}
			add_db_select_list(db, *token_p, &add, NULL);
		}
	}
	return (0);
}

static int
set_host_link_subdirs(PPDB * db)
{
	DB_REC_CHILD_1 root;
	current_base = host_base();
	memset(&root, 0, sizeof (root));
	root.entry = lookup_db_directory(db, "/");
	set_host_link_subdirs_run(db, &root, NULL);
	return (run_db_child(db, set_host_link_subdirs_run, NULL, "/"));
	current_base = default_base();
}

/*============================================================================*/

static void
usage(void)
{
	fprintf(stderr, "usage: %s [--norun] [--verbose]\n", program);
	exit(1);
}

static int
parse(int argc, char **argv)
{
	int o;
	static struct option long_opts[] = {
		{"norun", 0, 0, 'n'},
		{"verbose", 0, 0, 'v'},
		{0, 0, 0, 0}
	};

	/* Parse the command line.  */
	while ((o = getopt_long(argc, argv, "nv", &long_opts[0], NULL)) != EOF) {
		switch (o) {
		case 'n':	/* --norun */
			norun = 1;
			break;
		case 'v':	/* --verbose */
			++verbose;
			break;
		default:
			usage();
			break;
		}
	}

	if (optind != argc) {
		usage();
	}

	return (errors);
}

static void
handler(int signum)
{
	if (++signalled > 2) {
		exit(2);
	}
}

int
main(int argc, char **argv)
{
	PPDB *db_main;
	int ret = 0;
	signal(SIGHUP, handler);
	signal(SIGINT, handler);
	signal(SIGQUIT, handler);
	signal(SIGPIPE, handler);
	signal(SIGTERM, handler);

	if (pp_getenv() || parse(argc, argv)) {
		return (1);
	}
	VERBOSE(1, "%s verbose %d\n", program, verbose);
	db_main = open_db_main(1);

	reldir = k_strdup("/");

	if (!db_main || signalled ||
	    clear_old_data(db_main) || signalled ||
	    sort_db_directories(db_main) || signalled ||
	    set_host_link_subdirs(db_main) || signalled ||
	    get_arch_head() || signalled ||
	    read_tmp_select(db_main) || signalled ||
	    read_makefile(db_main) || signalled ||
	    process_database(db_main) || signalled ||
	    copy_makefile(db_main) || signalled) {
		ret = 1;
	}
	if (arch_head) {
		char **a = arch_head;
		for (a = arch_head; *a; ++a) {
			free(*a);
		}
		free(arch_head);
	}
	if (db_main) {
		close_db_main(db_main);
	}
	pp_freeenv();
	return (errors || ret);
}
