/*
 * 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 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

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

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dlfcn.h>
#include <dirent.h>
#include <libgen.h>
#include <sys/param.h>
#include <errno.h>

#include "parser.h"
#include "errlog.h"

static char const *ARCH_I386 = "i386";
static char const *ARCH_SPARC = "sparc";
static char const *ARCH_SPARCV9 = "sparcv9";
static char const *ARCH_IA64 = "ia64";
static char const *ARCH_AMD64 = "amd64";
static char const *ARCH_ALL = "all";

static int dofiles(const Translator_info *);
static int read_spec(const Translator_info *, char *);

static int Curlineno;

xlator_keyword_t *keywordlist;

/*
 * frontend entry point
 * returns the number of errors encountered
 */
int
frontend(const Translator_info *T_info)
{
	int retval, i = 0, errors = 0;

	keywordlist = xlator_init(T_info);
	if (keywordlist == NULL) {
		errlog(ERROR, "Error: Unable to get keywordlist\n");
		return (1);
	}

	if (T_info->ti_verbosity >= STATUS) {
		errlog(STATUS, "interesting keywords:\n");
		while (keywordlist[i].key != NULL) {
			errlog(STATUS,  "\t%s\n", keywordlist[i].key);
			++i;
		};
	}

	retval = xlator_startlib(T_info->ti_liblist);
	switch (retval) {
	case XLATOR_SKIP:
		if (T_info->ti_verbosity >= STATUS)
			errlog(STATUS,  "Skipping %s\n", T_info->ti_liblist);
		retval = 0;
		break;

	case XLATOR_NONFATAL:
		++errors;
		retval = 0;
		break;

	case XLATOR_SUCCESS:
		retval = dofiles(T_info);
		errors += retval;
		if ((retval = xlator_endlib()) != XLATOR_SUCCESS)
			++errors;
		retval = 0;
		break;

	default:
		errlog(ERROR | FATAL,
		    "Error: Invalid return code from xlator_startlib()\n");
		exit(1);
	}

	if ((retval = xlator_end()) != XLATOR_SUCCESS)
		++errors;

	return (errors);
}

/*
 * dofiles(const Translator_info *T_info);
 *    iterate through files specified in the command line and process
 *    them one by one
 * requires spec files to have a ".spec" suffix
 * returns the number of errors;
 */
static int
dofiles(const Translator_info *T_info)
{
	int nfiles, flen, findex, retval = 0, errors = 0;

	nfiles = T_info->ti_nfiles;

	for (findex = 0; findex < nfiles; ++findex) {
		flen = strlen(filelist[findex]);
		if ((flen <= 5) ||
			strcmp(&filelist[findex][flen-5], ".spec") != 0) {
			errlog(ERROR,
			    "Error: File specified does not have the "
			    ".spec extension: %s\n", filelist[findex]);
			++errors;
			continue;
		};
		retval = read_spec(T_info, filelist[findex]);
		errors += retval;
	}
	return (errors);
}

/*
 * read_spec -
 *   Given a filename, this function will reads the spec file to
 *   recognize keywords which it passes along with the corresponding
 *   value to the back-end translator to process. The following
 *   back-end interfaces are called:
 *	xlator_startfile
 *	xlator_start_if
 *	xlator_take_kvpair
 *	xlator_end_if
 *	xlator_endfile
 */
static int
read_spec(const Translator_info *T_info, char *spec_filename)
{
	FILE *spec_fp;
	Meta_info meta_info;
	char key[BUFSIZ], *value = NULL, *p = NULL;
	char *buf2 = NULL;
	int retval = 0, errors = 0, ki = 0;	/* keyword indicator */
	int start_if_fail = 0, skip_if = 0;
	int extends_err = 0;

	meta_info.mi_ext_cnt = 0; /* All info is non-extends */
	meta_info.mi_flags = 0;

	retval = xlator_startfile(spec_filename);

	switch (retval) {
	case XLATOR_SKIP:
		if (T_info->ti_verbosity >= WARNING)
			errlog(WARNING, "Warning: Skipping %s\n",
			    spec_filename);
		return (errors);

	case XLATOR_NONFATAL:
		errlog(ERROR, "Error in xlator_startfile\n");
		++errors;
		return (errors);

	case XLATOR_SUCCESS:
		break;

	default:
		errlog(ERROR,
		    "Error: Invalid return code from xlator_startfile()\n");
		++errors;
		return (errors);
	};

	/* file processing */
	spec_fp = fopen(spec_filename, "r");
	if (spec_fp == NULL) {
		errlog(ERROR,  "Error: Unable to open spec file %s: %s\n",
		    spec_filename, strerror(errno));
		++errors;
		return (errors);
	}

	(void) strncpy(meta_info.mi_filename, spec_filename, BUFSIZ);
	meta_info.mi_line_number = 0;
	Curlineno = meta_info.mi_line_number;
	while (meta_info.mi_nlines = readline(&buf2, spec_fp)) {
		meta_info.mi_line_number += meta_info.mi_nlines;
		Curlineno = meta_info.mi_line_number;
		if (!non_empty(buf2)) {
			free(buf2);
			buf2 = NULL;
			continue;
		}
		p = realloc(value, sizeof (char)*(strlen(buf2)+1));
		if (p == NULL) {
			errlog(ERROR | FATAL,
			    "Error: Unable to allocate memory for "
			    "value: %d\n", errno);
		}
		value = p;
		split(buf2, key, value);
		ki = interesting_keyword(keywordlist, key);
		switch (ki) {
		case XLATOR_KW_FUNC:	 /* Function keyword */
		case XLATOR_KW_DATA:	 /* Data keyword */
			meta_info.mi_extended = 0;
			retval = xlator_start_if(meta_info, ki, value);
			switch (retval) {
			case XLATOR_FATAL: /* FATAL ERROR */
				if (T_info->ti_verbosity >= STATUS) {
					errlog(STATUS,
					    "Error in xlator_start_if: ");
				}
				++errors;
				return (errors);
			case XLATOR_NONFATAL: /* NON-FATAL ERROR */
				if (T_info->ti_verbosity >= STATUS)
					errlog(STATUS,
					    "Error in xlator_start_if\n");
				++errors;
				start_if_fail = 1;
				break;
			case XLATOR_SUCCESS: /* OK */
				start_if_fail = 0;
				extends_err = check4extends(spec_filename,
				    value, T_info->ti_archtoken, spec_fp);
				switch (extends_err) {
				case -1:	/* Error */
					errlog(ERROR, "\"%s\", line %d: "
					    "Error occurred while "
					    "checking for extends clause\n",
					    spec_filename, Curlineno);
					++errors;
					/*FALLTHRU*/
				case 0:		/* No Extends */
					break;
				case 1:		/* Extends */
					meta_info.mi_extended = 1;
					extends_err = do_extends(meta_info,
					    T_info, value);
					if (extends_err) {
						errors += extends_err;
					}
					break;
				default:	/* Programmer Error */
					errlog(ERROR | FATAL,
					    "Error: invalid return from "
					    "check4extends %d\n", extends_err);
				}
				break;
			case XLATOR_SKIP: /* SKIP */
				if (T_info->ti_verbosity >= WARNING)
					errlog(WARNING, "Warning: Skipping "
					    "interface %s\n", value);
				skip_if = 1;
				start_if_fail = 0;
				break;
			default:
				/* Invalid Return */
				errlog(ERROR | FATAL,
				    "Error:  Invalid return code "
				    "from xlator_start_if (): %d\n", retval);
			}
			break;
		case XLATOR_KW_END: /* END keyword */
			if (start_if_fail == 0 && skip_if == 0) {
				retval = xlator_end_if(meta_info, value);
				if (retval)
					++errors;
			}
			skip_if = 0;
			break;
		case XLATOR_KW_NOTFOUND:
			if (T_info->ti_verbosity >= TRACING)
				errlog(TRACING, "uninteresting keyword: %s\n",
				    key);
			break;
		default:
			if (skip_if == 0 && start_if_fail == 0) {
				retval = xlator_take_kvpair(meta_info,
				    ki, value);
				if (retval) {
					if (T_info->ti_verbosity >= STATUS)
						errlog(STATUS, "Error in "
						    "xlator_take_kvpair\n");
					++errors;
				}
			}
		}
		free(buf2);
		buf2 = NULL;
	}

	if ((retval = xlator_endfile()) != XLATOR_SUCCESS) {
		if (T_info->ti_verbosity >= STATUS)
			errlog(STATUS, "Error in xlator_endfile\n");
		++errors;
	}
	free(p);
	(void) fclose(spec_fp);
	return (errors);
}

/*
 * interesting_keyword(char **keywordlist, const char *key) {
 *   returns the token associated with key if key is found in keywordlist
 *   returns XLATOR_KW_NOTFOUND if key is NOT found in keywordlist
 *   "Function" and "End" are always interesting, return XLATOR_KW_FUNC
 *   and XLATOR_KW_DATA respectively;
 *   "End" is always interesting, return XLATOR_KW_END;
 *
 */
int
interesting_keyword(xlator_keyword_t *keywordlist, const char *key)
{
	int i = 0;

	if (strcasecmp(key, "data") == 0) {
		return (XLATOR_KW_DATA);
	}
	if (strcasecmp(key, "function") == 0) {
		return (XLATOR_KW_FUNC);
	}

	if (strcasecmp(key, "end") == 0)
		return (XLATOR_KW_END);

	while (keywordlist[i].key != NULL) {
		if (strcasecmp(keywordlist[i].key, key) == 0)
			return (keywordlist[i].token);
		++i;
	}
	return (XLATOR_KW_NOTFOUND);
}

/*
 * line_to_buf(char *dest, const char *src) {
 *    appends src to dest, dynamically increasing the size of dest.
 *    replaces the trailing '\' continuation character with a space.
 *
 * if src is continuation of dest, dest != NULL, and
 * the last character in dest before the newline must be a `\'
 * if src is not continuation of dest, then dest must be NULL
 */
char *
line_to_buf(char *dest, const char *src)
{
	int slen = strlen(src);
	int dlen;

	if (dest == NULL) {
		/* We're being called for the first time */
		dest = malloc(sizeof (char) * (slen + 1));
		if (dest == NULL) {
			errlog(ERROR | FATAL,
			    "Error: Unable to allocate memory for dest\n");
		}
		(void) strcpy(dest, src);
		return (dest);
	}

	dlen = strlen(dest);

	dest = realloc(dest, (size_t)(sizeof (char) * (dlen+slen+1)));
	if (dest == NULL) {
		errlog(ERROR | FATAL,
		    "Error: Unable to allocate memory for dest\n");
	}

	if (dlen > 1) {
		/*
		 * remove continuation character
		 * we replace the '\' from the previous line with a space
		 */
		if (dest[dlen-2] == '\\') {
			dest[dlen-2] = ' ';
		}
	}

	/* join the two strings */
	(void) strcat(dest, src);

	return (dest);
}

/*
 * non_empty(const char *str)
 * assumes str is non null
 * checks if str is a non empty string
 * returns 1 if string contains non whitespace
 * returns 0 if string contains only whitespace
 */
int
non_empty(const char *str)
{
	while (*str != '\0') {
		if (!isspace(*str))
			return (1);
		++str;
	};
	return (0);
}

/*
 * split(const char *line, char *key, char *value);
 * splits the line into keyword (key) and value pair
 */
void
split(const char *line, char *key, char *value)
{
	char *p;

	p = (char *)line;

	/* skip leading whitespace */
	while (isspace(*p)&& *p != '\0')
		++p;

	/* copy keyword from line into key */
	while (!isspace(*p) && *p != '\0')
		*key++ = *p++;

	*key = '\0';

	/* skip whitespace */
	while (isspace(*p) && *p != '\0')
		p++;

	(void) strcpy(value, p);

}

/*
 * check4extends(char *filename, char *value, int arch, FILE *fp)
 * if no arch keyword is found or there is a MATCHING arch keyword
 *     returns 1 if value is of the form "data|function name extends"
 *          -1 for error
 *          0  no other keyword after the function name
 * else
 *     return 0
 *
 * filename is used only for error reporting
 */
int
check4extends(const char *filename, const char *value, int arch, FILE *fp)
{
	char fun[BUFSIZ];
	char extends[BUFSIZ];
	int n;

	if (arch_match(fp, arch)) {
		split(value, fun, extends);
		n = strlen(extends);
		if (extends[n-1] == '\n')
			extends[n-1] = '\0';
		if (strncasecmp("extends", extends, 7) == 0) {
			return (1);
		} else {
			if (*extends != '\0') {
				errlog(ERROR, "\"%s\", line %d: Error: "
				    "Trailing garbage after function name\n",
				    filename, Curlineno);
				return (-1);
			}
		}
	}
	return (0);
}

/*
 * remcomment (char *buf)
 * replace comments with single whitespace
 */
/* XXX: There is currently no way to escape a comment character */
void
remcomment(char const *buf)
{
	char *p;
	p = strchr(buf, '#');
	if (p) {
		*p = ' ';
		*(p+1) = '\0';
	}
}

/*
 * arch_strtoi()
 *
 * input: string
 * return: XLATOR_I386 if string == ARCH_I386
 *         XLATOR_SPARC if string == ARCH_SPARC
 *         XLATOR_SPARCV9 if string == ARCH_SPARCV9
 *         XLATOR_IA64 if string == ARCH_IA64
 *         XLATOR_AMD64 if string == ARCH_AMD64
 *         XLATOR_ALLARCH if string == ARCH_ALL
 *         0 if outside the known set {i386, sparc, sparcv9, ia64, amd64}.
 */
int
arch_strtoi(const char *arch_str)
{
	if (arch_str != NULL) {
		if (strcmp(arch_str, ARCH_I386) == 0)
			return (XLATOR_I386);
		else if (strcmp(arch_str, ARCH_SPARC) == 0)
			return (XLATOR_SPARC);
		else if (strcmp(arch_str, ARCH_SPARCV9) == 0)
			return (XLATOR_SPARCV9);
		else if (strcmp(arch_str, ARCH_IA64) == 0)
			return (XLATOR_IA64);
		else if (strcmp(arch_str, ARCH_AMD64) == 0)
			return (XLATOR_AMD64);
		else if (strcmp(arch_str, ARCH_ALL) == 0)
			return (XLATOR_ALLARCH);
	} else {
		errlog(ERROR, "\"%s\", line %d: Error: "
		    "arch keyword with no value");
	}
	return (0);
}

int
readline(char **buffer, FILE *fp)
{
	int nlines = 0;
	int len;
	char buf[BUFSIZ];

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

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