/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 1997-1999 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <libgen.h>
#include <errno.h>
#include "parser.h"
#include "errlog.h"

static int find_fun(char *key, char *value, char *parentfun);

/*
 * handles the extends clause of the 'function' keyword
 * Returns the number of errors encountered
 * This function is recursive.
 */
int
do_extends(const Meta_info parentM, const Translator_info *T_info, char *value)
{
	static int extends_count = 0;
	char funname[BUFSIZ], filename[MAXPATHLEN], parentfun[BUFSIZ],
	    buf[BUFSIZ], key[20];
	char *ifilename, *f, *p;
	char *localvalue = NULL, *buf2 = NULL;
	FILE *efp;
	Meta_info M;
	int found = 0, errors = 0, ki = 0;
	int retval;
	int scan;

	++extends_count;

	if (extends_count > MAX_EXTENDS) {
		errlog(ERROR, "\"%s\", line %d: Error: Too many levels of "
		    "extends\n", parentM.mi_filename, parentM.mi_line_number);
		++errors;
		goto ret;
	}

	scan = sscanf(value, "%s %s %s %s", funname, buf, filename, parentfun);
	switch (scan) {
	case 0: /* funname not set */
	case 1: /* buf not set, though ignored */
	case 2: /* filename not set */
		errlog(ERROR, "\"%s\", line %d: Error: Couldn't parse "
		    "'data' or 'function' line\n",
		    parentM.mi_filename, parentM.mi_line_number);
		++errors;
		goto ret;
		break;
	case 3:
		(void) strncpy(parentfun, funname, BUFSIZ);
		parentfun[BUFSIZ-1] = '\0';
		break;
	default:
		break;
	}

	/* All info is from parent file - extends */
	M.mi_ext_cnt = extends_count;

	if (T_info->ti_verbosity >= TRACING) {
		errlog(TRACING, "Extending file %s\nExtending function %s\n"
		    "SPEC's from %s\n", filename, parentfun,
		    T_info->ti_dash_I);
	}

	f = pathfind(T_info->ti_dash_I, filename, "f");
	if (f == NULL) {
		errlog(ERROR, "\"%s\", line %d: Error: Unable to find spec "
		    "file \"%s\"\n", parentM.mi_filename,
		    parentM.mi_line_number, filename);
		++errors;
		goto ret;
	}
	ifilename = strdup(f);
	if (ifilename == NULL) {
		errlog(ERROR | FATAL, "Error: strdup() of filename failed\n");
	}
	efp = fopen(ifilename, "r");
	if (efp == NULL) {
		errlog(ERROR, "\"%s\", line %d: Error: Unable to open "
		    "file \"%s\"\n", parentM.mi_filename,
		    parentM.mi_line_number, ifilename);
		free(ifilename);
		++errors;
		goto ret;
	}

	(void) strncpy(M.mi_filename, ifilename, MAXPATHLEN);
	M.mi_line_number = 0;

	/* search for begin function */
	while (M.mi_nlines = readline(&buf2, efp)) {
		M.mi_line_number += M.mi_nlines;

		if (!non_empty(buf2)) {	 /* is line non empty */
			free(buf2);
			buf2 = NULL;
			continue;
		}
		p = realloc(localvalue, sizeof (char)*(strlen(buf2)+1));
		if (p == NULL) {
			errlog(ERROR | FATAL, "Error (do_extends): "
			    "Unable to allocate memory\n");
		}
		localvalue = p;
		split(buf2, key, localvalue);
		if ((found = find_fun(key, localvalue, parentfun))) {
			/* check if architecture matches */
			if (found = arch_match(efp, T_info->ti_archtoken))
				break;
		}
		free(buf2);
		buf2 = NULL;
	}

	if (found) {
		int extends_err = 0;
		static int extends_warn = 0;
		extends_err = check4extends(ifilename, localvalue,
		T_info->ti_archtoken, efp);
		switch (extends_err) {
		case -1:	/* Error */
			errlog(ERROR, "\"%s\", line %d: Error occurred while "
			    "checking for extends clause\n",
			    M.mi_filename, M.mi_line_number);
			++errors;
			/*FALLTHRU*/
		case 0:		/* No Extends */
			break;
		case 1:		/* Extends */
			/*
			 * Warning on more then one level of extends
			 * but only warn once.
			 */
			if (extends_count == 1) {
				extends_warn = 1;
			}
			if ((extends_err = do_extends(M, T_info, localvalue))
			    != 0) {
				if (extends_count == 1) {
					errlog(ERROR, "\"%s\", line %d: "
					    "Error occurred while "
					    "processing 'extends'\n",
					    parentM.mi_filename,
					    parentM.mi_line_number);
				}
				errors += extends_err;
			}
			if (extends_warn == 1 && extends_count == 1) {
				errlog(ERROR, "\"%s\", line %d: "
				    "Warning: \"%s\" does not extend "
				    "a base specification",
				    parentM.mi_filename,
				    parentM.mi_line_number,
				    funname);
			}
			break;
		default:	/* Programmer Error */
			errlog(ERROR | FATAL,
			    "Error: invalid return from "
			    "check4extends: %d\n", extends_err);
		}

		free(buf2);
		buf2 = NULL;

		while (M.mi_nlines = readline(&buf2, efp)) {
			M.mi_line_number += M.mi_nlines;

			if (!non_empty(buf2)) { /* is line non empty */
				free(buf2);
				buf2 = NULL;
				continue;
			}
			p = realloc(localvalue, sizeof (char)*(strlen(buf2)+1));
			if (p == NULL) {
				p = realloc(NULL,
				    sizeof (char)*(strlen(buf2)+1));
				if (p == NULL) {
					errlog(ERROR | FATAL,
					    "Error: unable to "
					    "allocate memory\n");
				}
			}
			localvalue = p;
			split(buf2, key, localvalue);
			ki = interesting_keyword(keywordlist, key);
			switch (ki) {
			case XLATOR_KW_END:
				goto end;
				break;
			case XLATOR_KW_FUNC:
			case XLATOR_KW_DATA:
				errlog(ERROR, "\"%s\", line %d: "
				    "Error: Interface is missing \"end\"\n"
				    "\"%s\", line %d: Error while processing "
				    "%s\n", M.mi_filename, M.mi_line_number,
				    parentM.mi_filename,
				    parentM.mi_line_number, ifilename);
				++errors;
				goto end;
				break;
			case XLATOR_KW_NOTFOUND:
				if (T_info->ti_verbosity >= TRACING)
					errlog(STATUS,
					    "uninteresting keyword: %s\n", key);
				break;
			default:
				retval = xlator_take_kvpair(M, ki, localvalue);
				if (retval) {
					if (T_info->ti_verbosity >= STATUS)
						errlog(STATUS,
						    "Error in "
						    "xlator_take_kvpair\n");
					++errors;
				}
			}
			free(buf2);
			buf2 = NULL;
		}
	} else {
		errlog(ERROR, "\"%s\", line %d: Error: Unable to find "
		    "function %s in %s\n", parentM.mi_filename,
		    parentM.mi_line_number, parentfun, ifilename);
		++errors;
	}
end:
	(void) fclose(efp);
	free(localvalue);
	free(ifilename);
	free(buf2);
ret:
	extends_count--;
	return (errors);
}

/*
 * find_fun()
 *    given a key value pair, and the name of the function you are
 *    searching for in the SPEC source file, this function returns 1
 *    if the beginning of the function in the SPEC source file is found.
 *    returns 0 otherwise.
 */
static int
find_fun(char *key, char *value, char *parentfun)
{
	char pfun[BUFSIZ];

	if (strcasecmp(key, "function") != 0 &&
		strcasecmp(key, "data") != 0) {
		return (0);
	}

	(void) sscanf(value, "%1023s", pfun);

	if (strcmp(pfun, parentfun) == 0) {
		return (1);
	}

	return (0);
}

/*
 * arch_match(FILE *fp, int arch)
 * This function takes a FILE pointer, and an architecture token
 * The FILE pointer is assumed to point at the beginning of a Function
 * or Data specification (specifically at the first line of spec AFTER
 * the Function or Data line)
 * It reads all the way to the "End" line.
 * If it finds an "arch" keyword along the way, it is checked to see if
 * it matches the architecture currently being generated and returns
 * 1 if a match is found.  If a match is not found, it returns a
 * 0. If no "arch" keyword is found, it returns 1.
 *
 * XXX - the algorithm in arch_match is very inefficient. it read through
 * the file to find "arch" and rewinds before returning.
 * Later all the data that was skipped while searching for "arch" may
 * be needed and it is re-read from the disk. It would be nice to just
 * read the data once.
 */
int
arch_match(FILE *fp, int arch)
{
	off_t offset;
	char key[20], buf[BUFSIZ], *buf2 = NULL, *localvalue = NULL, *p;
	int len;
	int has_arch = 0;
	int archset = 0;

	offset = ftello(fp);
	if (offset == -1) {
		errlog(ERROR|FATAL, "Unable to determine file position\n");
	}

	while (fgets(buf, BUFSIZ, fp)) {
		/* replace comments with single whitespace */
		remcomment(buf);

		/* get complete line */
		buf2 = line_to_buf(buf2, buf); /* append buf to buf2 */
		len = strlen(buf);
		if (len > 1) {
			while (buf[len-2] == '\\') {
				if (!fgets(buf, BUFSIZ, fp)) {
					buf2 = line_to_buf(buf2, buf);
					break;
				}
				len = strlen(buf);
				buf2 = line_to_buf(buf2, buf);
			}
		} /* end of 'get complete line' */

		if (!non_empty(buf2)) { /* is line non empty */
			free(buf2);
			buf2 = NULL;
			continue;
		}
		p = realloc(localvalue, sizeof (char)*(strlen(buf2)+1));
		if (p == NULL) {
			p = realloc(NULL,
				sizeof (char)*(strlen(buf2)+1));
			if (p == NULL) {
				errlog(ERROR | FATAL,
					"Error: unable to "
					"allocate memory\n");
			}
		}
		localvalue = p;
		split(buf2, key, localvalue);
		if (strcasecmp(key, "arch") == 0) {
			char *alist = localvalue, *a;
			has_arch = 1;
			while ((a = strtok(alist, " ,\n")) != NULL) {
				archset = arch_strtoi(a);
				if (arch & archset) {
					free(buf2);
					free(p);
					if (fseeko(fp, offset, SEEK_SET) < 0) {
						errlog(ERROR|FATAL,
						    "%s", strerror(errno));
					}
					return (1);
				}
				alist = NULL;
			}
		} else if (strcasecmp(key, "end") == 0) {
			break;
		}
		free(buf2);
		buf2 = NULL;
	}

end:
	free(buf2);
	free(p);

	if (fseeko(fp, offset, SEEK_SET) < 0) {
		errlog(ERROR|FATAL, "%s", strerror(errno));
	}
	if (has_arch == 0)
		return (1);

	return (0);
}