/*
 * Copyright (c) 1999, 2000  Motoyuki Kasahara.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions, and the following disclaimer,
 *    without modification, immediately at the beginning of the file.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <regex.h>
#include "pccard_alias.h"

/*
 * Maximum length of a line in an alias file.
 */
#define PCCARD_MAX_ALIAS_LINE_LENGTH	511

/*
 * The number of arguments in a correct alias definition.
 */
#define PCCARD_ALIAS_ARGUMENTS		4

/*
 * Unexported function.
 */
static int pccard_alias_regexp(const char *, const char *);

/*
 * Message handlers.
 */
extern void (*pccard_output_error_message)(const char *, ...);
extern void (*pccard_output_warning_message)(const char *, ...);
extern void (*pccard_output_debug_message)(const char *, ...);

/*
 * Debug flag.
 */
extern int pccard_debug_flag;


/*
 * Find an alias file for an alias matched to `manufacturer' and
 * `version'.
 *
 * If a matched alias entry is found, the alias name is copied to
 * `alias' and 1 is returned.  The alias name is at most
 * `PCCARD_MAX_ALIAS_LENGTH' charactrers.  If no matched entry is
 * found, 0 is returned.
 */
int
pccard_search_alias_file(const char *file_name, char *alias,
    const char *manufacturer, const char *version)
{
    FILE *file;
    char line[PCCARD_MAX_ALIAS_LINE_LENGTH + 1];
    char *inp, *outp;
    int too_long_line;
    int backslash_escaped;
    int single_quoted;
    int double_quoted;
    int separator_preceded;
    char *arguments[PCCARD_ALIAS_ARGUMENTS];
    int argument_count;
    int line_number;
    int is_found = 0;

    *alias = '\0';

    /*
     * Open an alias file.
     */
    file = fopen(file_name, "r");
    if (file == NULL) {
	pccard_output_error_message("failed to open the alias file, %s: %s",
	    strerror(errno), file_name);
	return 0;
    }

    /*
     * Read the alias file.
     */
    too_long_line = 0;
    line_number = 1;

    while (fgets(line, PCCARD_MAX_ALIAS_LINE_LENGTH + 1, file) != NULL) {
	/*
	 * If a line is too long, skip to the beginning of the next line.
	 */
	if (strchr(line, '\n') == NULL
	    && strlen(line) == PCCARD_MAX_ALIAS_LINE_LENGTH) {
	    too_long_line = 1;
	    pccard_output_warning_message("the line too long, at line %d: %s",
		line_number, file_name);
	    continue;
	} else if (too_long_line) {
	    too_long_line = 0;
	    continue;
	}

	/*
	 * Ignore whitespaces in the beginning of the line.
	 */
	inp = line;
	outp = line;
	while (*inp == ' ' || *inp == '\t')
	    inp++;

	/*
	 * Split the line in arguments.
	 */
	argument_count = 0;
	backslash_escaped = 0;
	single_quoted = 0;
	double_quoted = 0;
	separator_preceded = 1;

	for (;;) {
	    /*
	     * Stop parsing if a newline or a non-quoted `#' occurs.
	     */
	    if (*inp == '\n' || *inp == '\0') 
		break;
	    if (*inp == '#' && !backslash_escaped && !single_quoted
		&& !double_quoted)
		break;

	    /*
	     * Here is the beginning of an argument.
	     */
	    if (separator_preceded) {
		if (argument_count < PCCARD_ALIAS_ARGUMENTS)
		    arguments[argument_count] = outp;
		argument_count++;
		separator_preceded = 0;
	    }

	    switch (*inp) {
	    case '\\':
		/*
		 * Backslash is escape character.
		 */
		if (!backslash_escaped && !single_quoted && !double_quoted)
		    backslash_escaped = 1;
		else if (!backslash_escaped && double_quoted
		    && (*(inp + 1) == '$' || *(inp + 1) == '`'
			|| *(inp + 1) == '\"' || *(inp + 1) == '\\')) {
		    backslash_escaped = 1;
		} else {
		    backslash_escaped = 0;
		    *outp++ = *inp;
		}
		break;

	    case ' ':
	    case '\t':
		/*
		 * Whitespace separates arguments.
		 */
		if (backslash_escaped || single_quoted || double_quoted)
		    *outp++ = *inp;
		else {
		    inp++;
		    *outp++ = '\0';
		    while (*inp == ' ' || *inp == '\t')
			inp++;
		    inp--;
		    separator_preceded = 1;
		}
		backslash_escaped = 0;
		break;

	    case '\'':
		/*
		 * Single quotation mark is used for quotation.
		 */
		if (!backslash_escaped && !double_quoted)
		    single_quoted = !single_quoted;
		else 
		    *outp++ = *inp;
		backslash_escaped = 0;
		break;

	    case '\"':
		/*
		 * Double quotation mark is used for quotation.
		 */
		if (!backslash_escaped && !single_quoted)
		    double_quoted = !double_quoted;
		else
		    *outp++ = *inp;
		backslash_escaped = 0;
		break;

	    default:
		*outp++ = *inp;
		backslash_escaped = 0;
		break;
	    }

	    inp++;
	}
	*outp = '\0';
	line_number++;

	/*
	 * Check for parsing status.
	 */
	if (argument_count == 0)
	    continue;
	if (argument_count > PCCARD_ALIAS_ARGUMENTS) {
	    pccard_output_warning_message("too many arguments, at line %d: %s",
		line_number, file_name);
	}
	if (argument_count < PCCARD_ALIAS_ARGUMENTS
	    || backslash_escaped || single_quoted || double_quoted) {
	    pccard_output_warning_message("unexpected end of line, "
		"at line %d: %s", line_number, file_name);
	    continue;
	}

	if (strcmp(arguments[0], "alias") != 0) {
	    pccard_output_warning_message("unknown directive `%s', "
		"at line %d: %s", line_number, file_name, arguments[0]);
	    continue;
	}

	if  (pccard_alias_regexp(arguments[2], manufacturer)
	    && pccard_alias_regexp(arguments[3], version)) {
	    strncpy(alias, arguments[1], PCCARD_MAX_ALIAS_LENGTH);
	    *(alias + PCCARD_MAX_ALIAS_LENGTH) = '\0';
	    is_found = 1;
	    break;
	}
    }

    /*
     * Check for file read error.
     */
    if (ferror(file)) {
	pccard_output_error_message("failed to read the alias file, %s: %s",
	    strerror(errno), file_name);
	fclose(file);
	return 0;
    }

    /*
     * Close the file.
     */
    fclose(file);

    /*
     * Output a debug message if the debug flag is enabled.
     */
    if (pccard_debug_flag) {
	if (is_found) {
	    pccard_output_debug_message("alias found: "
		"alias=%s, manufacturer=%s, version=%s, file=%s",
		alias, manufacturer, version, file_name);
	} else {
	    pccard_output_debug_message("alias not found: "
		"manufacturer=%s, version=%s, file=%s",
		manufacturer, version, file_name);
	}
    }

    return is_found;
}


/*
 * Determine whether `string' is matched to the regular expression
 * `expression'.  It returns 1 if it does, 0 if doesn't.
 */
static int
pccard_alias_regexp(const char *expression, const char *string)
{
    regex_t ex;
    int is_matched = 0;

    if (regcomp(&ex, expression, REG_EXTENDED | REG_NOSUB) == 0) {
	is_matched = (regexec(&ex, string, 0, NULL, 0) == 0) ? 1 : 0;
	regfree(&ex);
    }

    return is_matched;
}


/*
 * For test:
 */
#ifdef TEST

int
main(int argc, char *argv[])
{
    char buffer[256];

    if (argc != 4) {
	fprintf(stderr, "bad usage\n");
	exit(1);
    }

    buffer[0] = '\0';
    if (pccard_alias(argv[1], buffer, argv[2], argv[3]) == -1)
	exit(1);

    printf("%s\n", buffer);
}

#endif


syntax highlighted by Code2HTML, v. 0.9.1