root/usr/src/cmd/abi/spectrans/parser/frontend.c
/*
 * 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.
 */

#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);
}