/*
 * $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

#ident	"$Header: rdmodules.c 1.33 89/12/08 $"

#include <stdio.h>
#include <ctype.h>
#include "config.h"
#include <sys/stat.h>
#include "symbols.h"			/* symbol table defns */
#include "allocate.h"			/* perm_alloc(), perm_str() */

extern char *sname();			/* return simple name */
extern void add_objs();			/* add objects for current product */

#define	PMOD_HASHENTS	50		/* #of primary module hash entries */
static opaque_ptr pmods = (opaque_ptr)NULL; /* primary modules set */
static int nconfigured = 0;		/* #of configured modules */
static boolean import_failure;		/* boolean IMPORT failure flag */
static struct pmodule *import_modconfig;/* ptr to first imported module */
static struct pmodule *pmod_config;	/* current mod_config file */


/*
 * For each entry(X) in the uts/{MACH}/modules file
 * Add to 'pmods' primary modules set, and check to see that the
 * indicated directory is present.
 */
void
read_modules(modules_path, modfp, product)
char *modules_path;			/* "modules" pathname */
FILE *modfp;				/* "modules" file */
void *product;				/* current product */
{
	extern	char	*strtok();
	extern	void	add_pmodset();
	extern	boolean	is_dir();
	static	struct	pmodule *new_pmodule();	/* add a primary module */
	char		modfile_line[MAXPATH];
	char		module_path[MAXPATH];	/* full path of module dir. */
	register char	*module_relpath;	/* relative module path */
	register struct pmodule *pmodp;		/* ptr to created module */
	register struct pmodule	*prev_pmodp;	/* previous pmodule created */
	int		lineno;

	lineno = 0;
	prev_pmodp = (struct pmodule *)NULL;
	SET_FILE(modules_path+Anchor_len+1);
	if (pmods == (opaque_ptr)NULL)
		pmods = create_symtab(PMOD_HASHENTS);
	while(fgets(modfile_line,
		    sizeof(modfile_line), modfp) != (char *)NULL) {
		lineno++;
		if (*modfile_line == '#' ||
		    *modfile_line == '\n' || *modfile_line == '\0') {
			continue;
		}
		/*
		 *  Extract module name.
		 */
		module_relpath = strtok(modfile_line," \t\n");
		if (module_relpath == (char *)NULL) {
			fatal("Format error in: [%s]; line %d.\n",
				modules_path, lineno);
			exit(1);
		}
		/*
		 *  Verify directory (module) existence.
		 */
		(void)sprintf(module_path, "%s/%s", Anchor_dir, module_relpath);
		if (!is_dir(module_path)) {
			fprintf(stderr, "Module: [%s] %s: %d\n\t%s: [%s] %s.\n",
				module_relpath, "as specified at line",
				lineno, "in modules file", modules_path,
				"is not a directory");
			Trouble = TRUE;
		} else {
			pmodp = new_pmodule(prev_pmodp, product,
					    perm_str(module_relpath), FALSE);
			if (pmodp != (struct pmodule *)NULL) {
				if (prev_pmodp == (struct pmodule *)NULL)
					add_pmodset(pmodp);
				prev_pmodp = pmodp;
			}
		}
	}
	(void)fclose(modfp);
	if (DEBUG_LEVEL(0))
		(void)printf("End of read modules\n");
	SET_FILE("");
}

/*
 *  Mark the named module as configured.  If the named module
 *  is not in the possible module list, then indicate the error.
 *  This function is only called when parsing a sys_config file.
 */
void
configure_module(module_relpath)
char *module_relpath;				/* module relative path */
{
	register struct pmodule *pmodule_datap;	/* ptr to primary module */
	register char *simple_name;

	simple_name = sname(module_relpath);
	pmodule_datap = (struct pmodule *)search_symtab(pmods, simple_name);
	if (pmodule_datap != (struct pmodule *)NULL) {
		if (strcmp(pmodule_datap->pmod_common.cmod_relpath,
			   module_relpath)) {
			fprintf(stderr, "Module: [%s] (%s)\n\t%s: (%s).\n",
				simple_name,
				pmodule_datap->pmod_common.cmod_relpath,
				"configured by different relative path",
				module_relpath);
			Trouble = TRUE;
		} else if (pmodule_datap->pmod_configured) {
			fprintf(stderr,
				"Module [%s] (%s) is already configured.\n",
				simple_name,
				pmodule_datap->pmod_common.cmod_relpath);
			Trouble = TRUE;
		} else {
			pmodule_datap->pmod_configured = TRUE;
			nconfigured++;
		}
	} else {
		fprintf(stderr, "[%s] (%s) is not a supported module.\n",
			simple_name, module_relpath);
		Trouble = TRUE;
	}
}

/* 
 *  Check for the indicated module configuration.
 *  Return TRUE if non-trivial import, else FALSE.
 */
boolean
import_mod(module_name, required)
	char *module_name;	
	int required;				/* NOT USED */
{
	extern char *current_product_name();	/* name of current product */
	extern void set_pmodset();		/* set product pmod_set */
	extern boolean is_current_product();	/* TRUE iff module is in */
						/* the current product */
	static char *emsg="module: [%s]\n\tis not supported by product: [%s].";
	char error_message[1024];		/* complicated error msgs */
	register struct pmodule *prev_pmodp;	/* previous pmodule */
	register struct pmodule *next_pmodp;	/* next pmodule */
	register struct pmodule *pmodp;		/* ptr to primary module */

	if (DEBUG_LEVEL(0)) {
		printf("Import_mod: [%s] from: [%s]\n",
			module_name, pmod_config->pmod_common.cmod_relpath);
	}
	pmodp = (struct pmodule *)search_symtab(pmods, module_name);
	if (pmodp == (struct pmodule *)NULL) {
		/*
		 *  The module does not exist as part of the current
		 *  product.
		 */
		if (required) {
			yyerror(emsg, module_name, current_product_name());
			import_failure = TRUE;
			return(TRUE);		/* Non-trivial import */
		}
		return(FALSE);			/* File not present */
	}
	/*
	 *  The named module exists.
	 */
	if (!in_current_product(pmodp)) {
		yyerror(emsg, module_name, current_product_name());
		import_failure = TRUE;
		return(TRUE);			/* Non-trivial import */
	}
	/*
	 *  The named module is part of the current product.
	 */
	if (pmodp->pmod_parsed)			/* if already parsed, */
		return(FALSE);			/* then don't import  */
	if (pmodp == pmod_config) {		/* self referential import! */
		(void)sprintf(error_message, "%s: [%s] (%s)\n\t%s: [%s].",
			"self import of module",
			module_name, pmodp->pmod_common.cmod_relpath,
			"encountered for product", current_product_name());
		yyerror (error_message);
		return(FALSE);
	}
	if (pmodp->pmod_parsing) {		/* circular import */
		(void)sprintf(error_message, "%s: [%s] (%s)\n\t%s: [%s].",
			"circular import of module",
			module_name, pmodp->pmod_common.cmod_relpath,
			"encountered for product", current_product_name());
		yyerror (error_message);
		return(FALSE);
	}
	if (DEBUG_LEVEL(0)) {
		printf("Import_mod: [%s] from: [%s] is non-trivial\n",
			module_name, pmod_config->pmod_common.cmod_relpath);
	}
	prev_pmodp = pmodp->pmod_prev;		/* previous pmodule */
	next_pmodp = pmodp->pmod_next;		/* next pmodule */
	/*
	 *  Since this is a non-trivial import, it is guaranteed that
	 *  prev_pmodp is non-NULL.  Reorder the pmodules list.
	 */
	prev_pmodp->pmod_next = next_pmodp;
	if (next_pmodp != (struct pmodule *)NULL)
		next_pmodp->pmod_prev = prev_pmodp;
	/*
	 *  The imported module is now removed from the modules set of
	 *  the current product.  Reinsert it into the set just before
	 *  the module currently being parsed.
	 */
	prev_pmodp = pmod_config->pmod_prev;
	if (prev_pmodp == (struct pmodule *)NULL)
		set_pmodset(pmodp);
	else
		prev_pmodp->pmod_next = pmodp;
	pmodp->pmod_prev = prev_pmodp;
	pmodp->pmod_next = pmod_config;
	pmod_config->pmod_prev = pmodp;
	if (import_modconfig == (struct pmodule *)NULL)
		import_modconfig = pmodp;	/* remember 1st import */
	return(TRUE);				/* Non-trivial import */
}

/*
 *  Do mod_config imports.
 */
void
do_mod_imports()
{
	if (import_failure)	/* One or more IMPORTed modules missing */
		exit(1);
	if (DEBUG_LEVEL(0)) {
		printf("Processing mod_config imports for: [%s]\n",
			pmod_config->pmod_common.cmod_relpath);
	}
}

/*
 * Each file listed in the system module file is processed.
 *  Parse all module configuration files via parse_modconfig().
 *  Generate global names for source and destination directories.
 */
void
parse_modules(pmodp, select)
register struct pmodule *pmodp;		/* mod_config list for product */
register boolean select;		/* boolean parameters are selected */
{
	static FILE *parse_modconfig();
	extern char *strrchr();
	register FILE *confp;

	confp = (FILE *)NULL;
	while (pmodp != (struct pmodule *)NULL) {
		/*
		 * Generate the names of the source and
		 * destination directories.
		 */
		if (confp == (FILE *)NULL) {
			/*
			 *  Not continuing the previous module because
			 *  of a "NEXT=devname" directive.
			 */
			(void)sprintf(Module_dir, "%s/%s", Anchor_dir,
				      pmodp->pmod_common.cmod_relpath);
			(void)sprintf(T_dirl, "$(UTS)/%s",
				      pmodp->pmod_common.cmod_relpath);
			(void)sprintf(T_ddirl, "$(TUTS)/%s",
				      pmodp->pmod_common.cmod_name);
		}
		confp = parse_modconfig(pmodp, confp, select);
		if (import_modconfig == (struct pmodule *)NULL) {
			pmodp = pmodp->pmod_next;
		} else {			/* handle import list */
			pmodp = import_modconfig;
			import_modconfig = (struct pmodule *)NULL;
		}
	}
}

/*
 *  Allocate a new primary-module, checking to make sure that the
 *  module has not already been added to the primary module set.
 *
 *  Returns a pointer to the newly allocated pmodule structure if
 *  successful.  Otherwise (struct pmodule *)NULL is returned.
 */
static struct pmodule *
new_pmodule(prev_pmodp, product, module_relpath, module_is_internal)
struct pmodule *prev_pmodp;		/* previous pmodule */
void *product;				/* product containing module */
register char *module_relpath;		/* module relative path */
boolean module_is_internal;		/* boolean internal module flag */
{
	static struct pmodule *static_pmodp = (struct pmodule *)NULL;
	register struct pmodule *pmodp;
	register struct pmodule *otherpmodp;

	if (DEBUG_LEVEL(12))
		printf("New primary module: [%s].\n", module_relpath);
	pmodp = static_pmodp;
	if (pmodp == (struct pmodule *)NULL) {
		pmodp = (struct pmodule *)perm_alloc(sizeof(struct pmodule));
		static_pmodp = pmodp;
	}
	pmodp->pmod_configured = FALSE;
	pmodp->pmod_parsing = FALSE;
	pmodp->pmod_parsed = FALSE;
	pmodp->pmod_internalmod = module_is_internal;
	pmodp->pmod_next = (struct pmodule *)NULL;
	pmodp->pmod_fsubm = (struct smodule *)NULL;
	pmodp->pmod_lsubm = (struct smodule *)NULL;
	pmodp->pmod_product = product;
	pmodp->pmod_common.cmod_relpath = module_relpath;
	pmodp->pmod_common.cmod_name = sname(module_relpath);
	pmodp->pmod_common.cmod_subo = (struct macro *)NULL;
	otherpmodp = (struct pmodule *)
		      insert_symtab(pmods, pmodp->pmod_common.cmod_name,
				    (opaque_ptr)pmodp);
	if (otherpmodp == (struct pmodule *)NULL) {
		if (prev_pmodp != (struct pmodule *)NULL) {
			/*
			 *  It is important to support full insertion
			 *  as parse_modconfig() depends upon this....
			 */
			otherpmodp = prev_pmodp->pmod_next;
			if (otherpmodp != (struct pmodule *)NULL)
				otherpmodp->pmod_prev = pmodp;
			pmodp->pmod_next = otherpmodp;
			prev_pmodp->pmod_next = pmodp;
		}
		pmodp->pmod_prev = prev_pmodp;
		static_pmodp = (struct pmodule *)NULL;
	} else {
		fprintf(stderr, "Module collision: [%s] and [%s].\n",
			pmodp->pmod_common.cmod_relpath,
			otherpmodp->pmod_common.cmod_relpath);
		fprintf(stderr, "\tBase names must differ.\n");
		pmodp = (struct pmodule *)NULL;
		Trouble = TRUE;
	}
	return (pmodp);
}

/*
 * Parse a module configuration file.
 * (The mod_config file specifies the sources.)
 */
static FILE *
parse_modconfig(pmodp, confp, select)
register struct pmodule *pmodp;		/* primary module being configured */
register FILE *confp;			/* module configuration file */
boolean select;				/* parameters are selected flag */
{
	extern boolean add_dev();	/* add device */
	extern void can_dev();		/* cancel device */
	extern struct device *cur;
	extern struct list_head Spacefile_listhead;
	static void do_mod_conf();
	char pathbuf[MAXPATH];
	char *next_module;		/* pointer to NEXT=modname module */
	boolean configured;

	/*
	 *  Parse the module configuration file.
	 */
	if (Config) {
		set_rules();
		Pmodp = pmodp;
		Lcmodp = &pmodp->pmod_common;
		new_dev(Lcmodp->cmod_relpath);
		Spacefile_listhead.l_head = (struct list *)NULL;
		Spacefile_listhead.l_last = (struct list *)NULL;
	}
	next_module = (char *)NULL;
	configured = pmodp->pmod_configured;
	(void)sprintf(pathbuf, "%s/%s", Module_dir, CONFIG);
	if (confp != (FILE *)NULL) {
		if (DEBUG_LEVEL(8)) {
			printf(
			"continuing to parse %s mod_config file: [%s].\n",
				configured?"configured":"deconfigured",pathbuf);
		}
		lexinit_mod((char *)NULL, confp, FALSE,
			    select, configured, &next_module);
	} else {
		if (DEBUG_LEVEL(8)) {
			if (!pmodp->pmod_parsing) {
				printf("parsing %s mod_config file: [%s].\n",
					configured?"configured":"deconfigured",
					pathbuf);
			} else {
				printf("returning to %s mod_config: [%s].\n",
					configured?"configured":"deconfigured",
					pathbuf);
			}
		}
		pmod_config = pmodp;
		pmodp->pmod_parsing = TRUE;
		if ((confp = fopen(pathbuf, "r")) == (FILE *)NULL) {
			fatal("missing configuration file: %s\n", pathbuf);
			exit(1);
		}
		/*
		 *  We invoke: perm_str() since 'Ffile' must point
		 *  to data space that is permanently allocated for
		 *  the benefit of the numerical configuration
		 *  parameter directives (see parameters.c).
		 */
		lexinit_mod(perm_str(pathbuf+Anchor_len+1), confp,
			    TRUE, select, configured, &next_module);
	}
	/*
	 *  Parse to end-of-file or NEXT
	 */
	if (yyparse())
		exit(1);
	if (import_modconfig != (struct pmodule *)NULL) { /* Handle import */
		(void)fclose(confp);
		if (DEBUG_LEVEL(8))
			printf ("Handling mod_config IMPORT.\n");
		return ((FILE *)NULL);
	}
	/*
	 *  Check if any keywords have been specified
	 */
	if (Config) {
		if (!configured) {
			really_add_spacefiles();
			do_mod_conf(pmodp);
		} else if (device_spec) {
			if (add_dev(select)) {
				really_add_spacefiles();
				do_mod_conf(pmodp);
			}
		} else { 
			can_dev();
			really_add_spacefiles();
			do_mod_conf(pmodp);
		}
	}
	/*
	 *  If no NEXT directive, then close the current configuration
	 *  file and return indicating no "NEXT=devname" continuation.
	 */
	pmodp->pmod_parsed = TRUE;
	if (next_module == (char *)NULL) {
		(void)fclose(confp);
		if (DEBUG_LEVEL(8))
			printf ("End of mod_config: [%s]\n", pathbuf);
		return ((FILE *)NULL);
	}
	/*
	 *  Start a new module if NEXT incountered 
	 */
	pmodp = new_pmodule(pmodp, pmodp->pmod_product, next_module, TRUE);
	if (pmodp == (struct pmodule *)NULL) {
		/*
		 *  In this case, since we've been
		 *  crunching away on the module config
		 *  file, we're really stuck....
		 */
		fatal("Cannot continue from error.\n");
		exit(1);
	}
	pmodp->pmod_configured = configured;
	return (confp);
}

/*
 * Process the information found by parsing the mod_config file.
 */
static void
do_mod_conf(pmodp)
register struct pmodule *pmodp;
{
	char		source_path[MAXPATH];	/* target pathname */
	char		object_target[MAXPATH];
	char		pathbuf[MAXPATH];
	char		lint_file[MAXPATH];
	char		cname[MAXPATH];
	register struct smodule *smodp;

	if (DEBUG_LEVEL(8))
		printf("Do_mod_conf: primary module \"%s\"\n",
			pmodp->pmod_common.cmod_name);
	/*
	 * proccess {mod}.c or {mod}.o files
	 */
	smodp = pmodp->pmod_fsubm;
	while (smodp != (struct smodule *)NULL) {
		Lcmodp = &smodp->smod_common;
		(void)sprintf(object_target, "%s.o",
			      smodp->smod_common.cmod_name);
		(void)sprintf(source_path, "%s/%s.%c", Module_dir,
			      smodp->smod_common.cmod_name,
			      smodp->smod_type);
		switch(smodp->smod_type) {
		case 'c':		/* C-file */
			(void)sprintf(pathbuf, "%s/%s.c", T_dirl,
				      smodp->smod_common.cmod_name);
			add_cfiles(pathbuf, object_target, source_path);
			if (pmodp->pmod_configured)
				add_objs(object_target);
			else
				add_objs_space(object_target);
			break;
		case 'o':		/* object file */
			if (pmodp->pmod_configured)
				add_objs(object_target);
			else
				add_objs_space(object_target);
			break;
		case 's':		/* assembler module */
			(void)sprintf(pathbuf, "%s/%s.s", T_dirl,
				      smodp->smod_common.cmod_name);
			add_sfiles(pathbuf, object_target, source_path);
			if (pmodp->pmod_configured)
				add_objs(object_target);
			else
				add_objs_space(object_target);
			break;
		default:
			fatal("Internal error in do_mod_conf().\n");
			exit(1);
		}
		smodp = smodp->smod_next;
	}
}
