/*
 * Copyright (c) 1983 Regents of the University of California.
 * 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.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
 */

#ifndef lint
/*static char sccsid[] = "from: @(#)printcap.c	5.7 (Berkeley) 3/4/91";*/
static char rcsid[] = "$Id: bsdcap.c,v 1.10 1997/11/04 05:22:00 itz Exp $";
#endif					/* not lint */

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>			/* malloc */
#include <string.h>			/* strtok */

static FILE *bsdcap_fp = NULL;		/* data base file pointer */
static int bsdcap_dbidx = 0;		/* which file in data base to read
					   next */
/* set by cgetset(), checked by cgetent() and cgetnext() before
   anything else */
static char *bsdcap_set_entry = 0;

#define SAFE_BUFFER_SIZE 2048

/* bsdcap_nchktc: check the last entry, see if it's tc=xxx. If so,
 * recursively find xxx and append that entry (minus the names) to
 * take the place of the tc=xxx entry. This allows entries to say
 * "like an HP2621 but doesn't turn on the labels".  Note that this
 * works because of the left to right scan.
 */
static int
bsdcap_nchktc(bp, dbarray, tbuf, tsize)
char **bp;
char **dbarray;
char *tbuf;
int tsize;
{
    register char *p, *q;
    char *tcname;			/* name of similar terminal */
    char *tcbuf;
    int poff, lnew;
    static char bad_entry[] = "Bad entry\n";

    p = strrchr(tbuf, ':');
    while (0 != p && p - tbuf >= strlen(tbuf) - 2) {
	*p = 0;
	p = strrchr(tbuf, ':');
    }					/* while */
    if (0 == p) {
	write(2, bad_entry, sizeof (bad_entry) - 1);
	free(tbuf);
	return (-2);
    }					/* if */
    p++;
    /* p now points to beginning of last field */
    if (p[0] != 't' || p[1] != 'c' || p[2] != '=') {
	*bp = tbuf;
	return (0);
    }					/* if */
    poff = p - tbuf;
    tcname = p + 3;
    q = strchr(tcname, ':');
    if (0 != q) {
	*q = 0;
    }					/* if */
    if (0 > cgetent(&tcbuf, dbarray, tcname)) {
	free(tbuf);
	return (-2);
    }					/* if */

    q = strchr(tcbuf, ':');
    if (0 == q) {
	write(2, bad_entry, sizeof (bad_entry) - 1);
	free(tcbuf);
	free(tbuf);
	return (-2);
    }					/* if */

    lnew = strlen(q + 1);
    if (poff + lnew > tsize - 1) {
	char *newbuf = realloc(tbuf, poff + lnew + 1);
	if (0 == newbuf) {
	    free(tbuf);
	    free(tcbuf);
	    return (-2);
	}				/* if */
	tsize = poff + lnew + 1;
	tbuf = newbuf;
	p = tbuf + poff;
    }					/* if */
    strcpy(p, q + 1);
    free(tcbuf);
    *bp = tbuf;
    return (0);
}

/* bsdcap_namatch deals with name matching.  The first field of the entry is
 * a sequence of names separated by |'s, so we compare against each
 * such name.  The normal : terminator after the last name (before the
 * first field) stops us.
 */
int
bsdcap_namatch(np, buf)
char *np, *buf;
{
    char holdchar;
    char *p, *q;
    int found = 0;

    /* take care of comments */
    if ('#' == buf[0]) {
	return (0);
    }					/* if */

    for (p = buf, q = strpbrk(buf, ":|"); 0 != q;
	 p = q + 1, q = strpbrk(p, ":|")) {
	holdchar = *q;
	*q = 0;
	if (0 == strcmp(np, p)) {
	    found = 1;
	}				/* if */
	*q = holdchar;
	if (':' == holdchar || found) {
	    break;
	}				/* if */
    }					/* for */

    return found;
}

/*
 * Skip to the next field.  Notice that this is very dumb, not
 * knowing about \: escapes or any such.  If necessary, :'s can be put
 * into the file in octal.
 */
static char *
bsdcap_skip(bp, sep)
register char *bp;
int sep;
{
    char *p;

    p = strchr(bp, sep);
    return (0 != p ? p + 1 : bp + strlen(bp));
}

/*
 * bsdcap_decode does the grung work to decode the
 * string capability escapes.
 */
static int
bsdcap_decode(str, area)
register char *str;
char **area;
{
    register char *cp;
    register int c;
    register char *dp;
    int i;
    char *mybuf;
    int mysize;

    mybuf = malloc(128);
    if (0 == mybuf) {
	return -1;
    }					/* if */
    mysize = 128;

    cp = mybuf;
    while ((c = *str++) && c != ':') {
	switch (c) {

	case '^':
	    c = *str++ & 037;
	    break;

	case '\\':
	    dp = "E\033^^\\\\::n\nr\rt\tb\bf\f";
	    c = *str++;
    nextc:
	    if (*dp++ == c) {
		c = *dp++;
		break;
	    }
	    dp++;
	    if (*dp)
		goto nextc;
	    if (isdigit(c)) {
		c -= '0', i = 2;
		do
		    c <<= 3, c |= *str++ - '0';
		while (--i && isdigit(*str));
	    }
	    break;
	}
	*cp++ = c;
	if (cp >= mybuf + mysize) {
	    char *newbuf = realloc(mybuf, mysize + 128);
	    if (0 == newbuf) {
		free(mybuf);
		return -1;
	    }				/* if */
	    mybuf = newbuf;
	    mysize += 128;
	}				/* if */
    }
    *cp++ = 0;
    *area = mybuf;
    return 0;
}

typedef enum {
    no_fail = 0,
    fopen_fail = 1,
    malloc_fail = 2,
    read_fail = 4,
} cap_error_type;

/*
 * Get an entry for terminal name in buffer bp,
 * from the file.  Parse is very rudimentary;
 * we just notice escaped newlines.
 */
int
cgetent(bp, dbarray, name)
char **bp, *name, **dbarray;
{
    char **dbfile;
    FILE *tf;
    char *mybuf;
    int mysize;
    int found = 0;
    cap_error_type errflag = no_fail;
    char *myline;
    int mylinesize;

    if (0 != bsdcap_set_entry && bsdcap_namatch(name, bsdcap_set_entry)) {
	*bp = bsdcap_set_entry;
	return 0;
    }					/* if */

    myline = malloc(mylinesize = 256);
    if (0 == myline) {
	return -2;
    }					/* if */

    mybuf = malloc(SAFE_BUFFER_SIZE);
    if (0 == mybuf) {
	free(myline);
	return -2;
    }
    mysize = SAFE_BUFFER_SIZE;

    for (dbfile = dbarray;
	 !(errflag & malloc_fail) && !found && 0 != *dbfile;
	 ++dbfile) {
	tf = fopen(*dbfile, "r");
	if (0 == tf) {
	    errflag |= fopen_fail;
	    continue;			/* back to for() */
	}				/* if */

	errflag &= ~read_fail;
	while (!(errflag & read_fail) && !feof(tf)) {
	    int count = 0;
	    int entry_complete = 0;
	    mybuf[0] = 0;

	    while (!entry_complete) {
#if 0
		int read_chars = getdelim(&myline, &mylinesize, '\n', tf);
#else
		int read_chars = fgets(myline, 256, tf) ? strlen(myline) : (-1);
#endif
		if (-1 == read_chars) {
		    if (!feof(tf)) {
			errflag |= read_fail;
		    }			/* if */
		    break;
		}			/* if */
		if (count + read_chars + 1 > mysize) {
		    char *newbuf = realloc(mybuf, count + read_chars + 1);
		    if (0 == newbuf) {
			errflag |= malloc_fail;
			break;
		    }			/* if */
		    mybuf = newbuf;
		    mysize = count + read_chars + 1;
		}			/* if */
		memcpy(mybuf + count, myline, read_chars);
		count += read_chars;

		/* is a continuation line required? */
		if (count >= 2 && mybuf[count - 2] == '\\' && mybuf[count - 1] == '\n') {
		    count -= 2;
		}
		else {
		    entry_complete = 1;
		    mybuf[count] = 0;
		}			/* if */
	    }				/* while */

	    if (errflag & (malloc_fail | read_fail) || feof(tf)) {
		break;			/* out of outer while */
	    }
	    else if (bsdcap_namatch(name, mybuf)) {
		found = 1;
		break;
	    }				/* if */
	}				/* while */
	fclose(tf);
    }					/* for */

    if (errflag & (malloc_fail | read_fail)) {
	free(mybuf);
	free(myline);
	return -2;
    }
    else if (found) {
	free(myline);

	return bsdcap_nchktc(bp, dbarray, mybuf, mysize);
    }
    else if (errflag & fopen_fail) {
	free(mybuf);
	free(myline);
	return -2;
    }
    else {
	free(mybuf);
	free(myline);
	return -1;
    }					/* if */
}

/*
 * Return the (numeric) option id.
 * Numeric options look like
 *	li#80
 * i.e. the option string is separated from the numeric value by
 * a # character.  If the option is not found we return -1.
 * Note that we handle octal numbers beginning with 0.
 */
int
cgetnum(bp, id, res)
char *bp;
char *id;
long *res;
{
    register int base;

    for (;;) {
	bp = bsdcap_skip(bp, ':');
	if (*bp == 0)
	    return (-1);
	if (*bp++ != id[0] || *bp == 0 || *bp++ != id[1])
	    continue;
	if (*bp == '@')
	    return (-1);
	if (*bp != '#')
	    continue;
	*res = strtoul(++bp, 0, 0);
	return (0);
    }
}

/*
 * Handle a flag option.
 * Flag options are given "naked", i.e. followed by a : or the end
 * of the buffer.  Return 1 if we find the option, or 0 if it is
 * not given.
 */
int
cgetcap(bp, id, sep)
char *bp;
char *id;
int sep;
{
    for (;;) {
	bp = bsdcap_skip(bp, sep);
	if (!*bp)
	    return (0);
	if (*bp++ == id[0] && *bp != 0 && *bp++ == id[1]) {
	    if (!*bp || *bp == sep)
		return (1);
	    else if (*bp == '@')
		return (0);
	}
    }
}

/*
 * Get a string valued option.
 * These are given as
 *	cl=^Z
 * Much decoding is done on the strings, and the strings are
 * placed in area, which is a ref parameter which is updated.
 * No checking on area overflow.
 */
int
cgetstr(bp, id, area)
char *bp;
char *id;
char **area;
{
    for (;;) {
	bp = bsdcap_skip(bp, ':');
	if (!*bp)
	    return (-1);
	if (*bp++ != id[0] || *bp == 0 || *bp++ != id[1])
	    continue;
	if (*bp == '@')
	    return (-1);
	if (*bp != '=')
	    continue;
	bp++;
	return (bsdcap_decode(bp, area));
    }
}

/*
 * Similar to bsdcap_getent except it returns the next enrty instead of
 * doing a lookup.
 */
int
cgetnext(bp, dbarray)
char **bp;
char **dbarray;
{
    char *mybuf;
    int mysize;
    int found = 0;
    cap_error_type errflag = no_fail;
    char *myline;
    int mylinesize;

    myline = malloc(mylinesize = 256);
    if (0 == myline) {
	return -2;
    }					/* if */

    mybuf = malloc(SAFE_BUFFER_SIZE);
    if (0 == mybuf) {
	free(myline);
	return -2;
    }
    mysize = SAFE_BUFFER_SIZE;

    while (!(errflag & malloc_fail) && !found &&
	   0 != dbarray[bsdcap_dbidx]) {
	if (0 == bsdcap_fp) {
	    bsdcap_fp = fopen(dbarray[bsdcap_dbidx], "r");
	}				/* if */
	if (0 == bsdcap_fp) {
	    errflag |= fopen_fail;
	    continue;			/* back to for() */
	}				/* if */

	errflag &= ~read_fail;
	while (!(errflag & read_fail) && !feof(bsdcap_fp)) {
	    int count = 0;
	    int entry_complete = 0;
	    mybuf[0] = 0;

	    while (!entry_complete) {
#if 0
		int read_chars = getdelim(&myline, &mylinesize, '\n', bsdcap_fp);
#else
		int read_chars = fgets(myline, 256, bsdcap_fp) ? strlen(myline) : (-1);
#endif
		if (-1 == read_chars) {
		    if (!feof(bsdcap_fp)) {
			errflag |= read_fail;
		    }			/* if */
		    break;
		}			/* if */
		if (count + read_chars + 1 > mysize) {
		    char *newbuf = realloc(mybuf, count + read_chars + 1);
		    if (0 == newbuf) {
			errflag |= malloc_fail;
			break;
		    }			/* if */
		    mybuf = newbuf;
		    mysize = count + read_chars + 1;
		}			/* if */
		memcpy(mybuf + count, myline, read_chars);
		count += read_chars;

		/* is a continuation line required? */
		if (count >= 2 && mybuf[count - 2] == '\\' && mybuf[count - 1] == '\n') {
		    count -= 2;
		}
		else {
		    entry_complete = 1;
		    mybuf[count] = 0;
		}			/* if */
	    }				/* while */

	    if (errflag & (malloc_fail | read_fail) || feof(bsdcap_fp)) {
		break;			/* out of outer while */
	    }
	    else if ('#' != mybuf[0]) {
		found = 1;
		break;
	    }				/* if */
	}				/* while */
	if (feof(bsdcap_fp) || (errflag & read_fail)) {
	    fclose(bsdcap_fp);
	    bsdcap_fp = 0;
	    ++bsdcap_dbidx;
	}				/* if */
    }					/* while */

    if (errflag & (read_fail | fopen_fail)) {
	bsdcap_dbidx = 0;
    }					/* if */
    if (errflag & (malloc_fail | read_fail)) {
	free(mybuf);
	free(myline);
	return -2;
    }
    else if (found) {
	int status;
	free(myline);
	status = bsdcap_nchktc(bp, dbarray, mybuf, mysize);
	return (0 > status ? status : 1);
    }
    else if (errflag & fopen_fail) {
	free(mybuf);
	free(myline);
	return -2;
    }
    else {
	free(mybuf);
	free(myline);
	return -1;
    }					/* if */
}

void
cgetclose()
{
    if (0 != bsdcap_fp) {
	fclose(bsdcap_fp);
	bsdcap_fp = 0;
    }					/* if */
    bsdcap_dbidx = 0;
}

void
cgetset(bp)
char *bp;
{
    bsdcap_set_entry = bp;
}
