/*	Copyright (c) 1990 UNIX System Laboratories, Inc.	*/
/*	Copyright (c) 1984, 1986, 1987, 1988, 1989, 1990 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF     	*/
/*	UNIX System Laboratories, Inc.                     	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/


#ident	"@(#)lp:lib/lp/tidbit.c	1.12.3.1"

#include "errno.h"
#include "string.h"
#include "sys/types.h"
#include "sys/stat.h"

#if	defined(__STDC__)
#include "stdarg.h"
#else
#include "varargs.h"
#endif

#include "lp.h"

extern char		*boolnames[],
			*numnames[],
			*strnames[];

extern char		*getenv();

ushort			tidbit_boolean	= 0;

short			tidbit_number	= 0;

char			*tidbit_string	= 0;

#if	defined(__STDC__)
static int		open_terminfo_file ( char * , char * );
#else
static int		open_terminfo_file();
#endif

/**
 ** _Getsh() - GET TWO-BYTE SHORT FROM (char *) POINTER PORTABLY
 **/

/*
 * "function" to get a short from a pointer.  The short is in a standard
 * format: two bytes, the first is the low order byte, the second is
 * the high order byte (base 256).  The only negative number allowed is
 * -1, which is represented as 255, 255.  This format happens to be the
 * same as the hardware on the pdp-11 and vax, making it fast and
 * convenient and small to do this on a pdp-11.
 */

#if	vax || pdp11
#define	_Getsh(ip)	(* (short *) ip)
#endif	/* vax || pdp11 */

/*
 * The following macro is partly due to Mike Laman, laman@sdcsvax
 *	NCR @ Torrey Pines.		- Tony Hansen
 */
#if	u3b || u3b15 || u3b2 || m68000
#define	_Getsh(ip)	((short) (*((unsigned char *) ip) | (*(ip+1) << 8)))
#endif	/* u3b || u3b15 || u3b2 || m68000 */

#ifndef	_Getsh
/*
 * Here is a more portable version, which does not assume byte ordering
 * in shorts, sign extension, etc. It does assume that the C preprocessor
 * does sign-extension the same as on the machine being compiled for.
 * When ANSI C comes along, this should be changed to check <limits.h>
 * to see if the low character value is negative.
 */

static int
#if	defined(__STDC__)
_Getsh (
	register char *		p
)
#else
_Getsh (p)
	register char		*p;
#endif
{
	register int		rv,
				rv2;

#if	-1 == '\377'			/* sign extension occurs */
	rv = (*p++) & 0377;
	rv2 = (*p) & 0377;
#else	/* -1 == '\377'			/* no sign extension */
	rv = *p++;
	rv2 = *p;
#endif	/* -1 == '\377' */
	if ((rv2 == 0377) && ((rv == 0377) || (rv == 0376)))
		return (-1);
	return (rv + (rv2 * 256));
}
#endif	/* _Getsh */

#define MAX_TIDBS	32

static struct tidb	{

	int			snames,
				nbools,
				nints,
				nstrs;

	char			*term,
				*tiebuf,
				*boolean_offset,
				*number_offset,
				*string_offset,
				*string_table;

}			tidbs[MAX_TIDBS + 1];	/* one for last ditch */

/**
 ** tidbit() - TERMINFO DATABASE LOOKUP
 **/

/*
 * Four forms of calling:
 *
 *	tidbit ("term-type", "boolean-cap-name", &ushort)
 *	tidbit ("term-type", "numeric-cap-name", &short)
 *	tidbit ("term-type", "string-cap-name", &charstar)
 *	tidbit ("term-type", "any-cap-name", (char *)0)
 *
 * The last one is chancy, because of the pointer alignment
 * problem, but hey--what the heck. Anyway, the last one
 * causes the value to be stored in one of
 *
 *	ushort  tidbit_boolean;
 *	short   tidbit_number;
 *	char   *tidbit_string;
 *
 * as appropriate, and returns one of 1, 2, or 3 as the type
 * of the capability is boolean, numeric, or string.
 *
 * For example, to extract the size of the screen for a 5410:
 *
 *	short cols, lines;
 *
 *	tidbit ("5410", "cols", &cols);
 *	tidbit ("5410", "lines", &lines);
 *
 * Note that for the lines and columns, this does NOT check
 * the LINES and COLUMNS environment variables nor the window
 * size, if running on a windowing terminal. That can be done
 * by the caller.
 *
 * If first argument is (char *)0, "tidbit()" uses the same TERM
 * used in the last call, or the TERM environment variable if this
 * is the first call.
 * If second argument is (char *)0, no lookup just verification
 * of terminal type.
 *
 * Return is 0 (or 1, 2, 3 as above) if successful, otherwise -1
 * with "errno" set:
 *
 *	ENOENT		can't open Terminfo file for terminal type
 *	EBADF		Terminfo file is corrupted
 *	ENOMEM		malloc failed
 */

/*VARARGS2*/
int
#if	defined(__STDC__)
tidbit (
	char *			term,
	char *			cap,
	...
)
#else
tidbit (term, cap, va_alist)
	char			*term,
				*cap;
	va_dcl
#endif
{
	va_list			ap;

	int			rc;

	register int		i;

	register char		**pp;

	register struct tidb	*pt;

	static char		*last_term;


	if (!term)
		if (last_term)
			term = last_term;
		else {
			term = getenv("TERM");
			if (!term || !*term)
				term = NAME_UNKNOWN;
		}
	if (term != last_term) {
		if (last_term)
			Free (last_term);
		last_term = Strdup(term);
	}

	for (i = 0; i < MAX_TIDBS; i++)
		if (tidbs[i].term && STREQU(tidbs[i].term, term)) {
			pt = &tidbs[i];
			break;
		}

	/*
	 * Not cached, so read the file and cache it.
	 */
	if (i >= MAX_TIDBS) {

		register int		n,
					tfd;

		register char		*terminfo;

		struct stat		statbuf;


		/*
		 * If no empty spot can be found, "i" will index the
		 * last spot, a spare reserved to avoid problems with
		 * a full cache.
		 */
		for (i = 0; i < MAX_TIDBS; i++)
			if (!tidbs[i].term)
				break;
		pt = &tidbs[i];

		tfd = -1;
		if ((terminfo = getenv("TERMINFO")) && *terminfo)
			tfd = open_terminfo_file(terminfo, term);
#if	defined(TERMINFO)
		if (tfd < 0)
			tfd = open_terminfo_file(TERMINFO, term);
#endif
		if (tfd >= 0)
			(void)Fstat (tfd, &statbuf);

		if (tfd < 0 || !statbuf.st_size) {
			errno = ENOENT;
			return (-1);
		}

		if (pt->tiebuf)
			Free (pt->tiebuf);
		if (!(pt->tiebuf = Malloc(statbuf.st_size))) {
			errno = ENOMEM;
			return (-1);
		}

		n = Read(tfd, pt->tiebuf, statbuf.st_size);
		(void)Close (tfd);
		if (n <= 0 || n >= 4096 || _Getsh(pt->tiebuf) != 0432) {
			Free (pt->tiebuf);
			pt->tiebuf = 0;
			errno = EBADF;
			return (-1);
		}

		if (pt->term)
			Free (pt->term);
		if (!(pt->term = Strdup(term))) {
			Free (pt->tiebuf);
			pt->tiebuf = 0;
			errno = ENOMEM;
			return (-1);
		}

		pt->snames = _Getsh(pt->tiebuf + 2);
		pt->nbools = _Getsh(pt->tiebuf + 4);
		pt->nints = _Getsh(pt->tiebuf + 6);
		pt->nstrs = _Getsh(pt->tiebuf + 8);

		pt->boolean_offset = pt->tiebuf + 6 * 2 + pt->snames;

		pt->number_offset = pt->boolean_offset + pt->nbools;
		if ((unsigned int)pt->number_offset & 1)
			pt->number_offset++;

		pt->string_offset = pt->number_offset + pt->nints * 2;

		pt->string_table = pt->string_offset + pt->nstrs * 2;

	}

	rc = 0;

#if	defined(__STDC__)
	va_start (ap, cap);
#else
	va_start (ap);
#endif

	if (!cap || !*cap)
		;

	else if ((pp = wherelist(cap, boolnames))) {
		register ushort		*ushort_p;

		register char		*ip;

		register int		index	= pp - boolnames;

		if (!(ushort_p = va_arg(ap, ushort *))) {
			ushort_p = &tidbit_boolean;
			rc = 1;
		}

		if (index >= pt->nbools)
			*ushort_p = 0;
		else {
			ip = pt->boolean_offset + index;
			*ushort_p = (*ip & 01);
		}

	} else if ((pp = wherelist(cap, numnames))) {
		register short		*short_p;

		register char		*ip;

		register int		index	= pp - numnames;

		if (!(short_p = va_arg(ap, short *))) {
			short_p = &tidbit_number;
			rc = 2;
		}

		if (index >= pt->nints)
			*short_p = -1;
		else {
			ip = pt->number_offset + index * 2;
			*short_p = _Getsh(ip);
			if (*short_p == -2)
				*short_p = -1;
		}

	} else if ((pp = wherelist(cap, strnames))) {
		register char		**charstar_p;

		register char		*ip;

		register int		index	= pp - strnames;

		register short		sindex;


		if (!(charstar_p = va_arg(ap, char **))) {
			charstar_p = &tidbit_string;
			rc = 3;
		}

		if (index >= pt->nstrs)
			*charstar_p = 0;
		else {
			ip = pt->string_offset + index * 2;
			if ((sindex = _Getsh(ip)) >= 0)
				*charstar_p = pt->string_table + sindex;
			else
				*charstar_p = 0;
		}
	}

	va_end (ap);
	return (rc);
}

/**
 ** untidbit() - FREE SPACE ASSOCIATED WITH A TERMINFO ENTRY
 **/

void
#if	defined(__STDC__)
untidbit (
	char *			term
)
#else
untidbit (term)
	char			*term;
#endif
{
	register int		i;


	for (i = 0; i < MAX_TIDBS; i++)
		if (tidbs[i].term && STREQU(tidbs[i].term, term)) {
			if (tidbs[i].tiebuf) {
				Free (tidbs[i].tiebuf);
				tidbs[i].tiebuf = 0;
			}
			Free (tidbs[i].term);
			tidbs[i].term = 0;
			break;
		}
	return;
}

/**
 ** open_terminfo_file() - OPEN FILE FOR TERM ENTRY
 **/

static int
#if	defined(__STDC__)
open_terminfo_file (
	char *			terminfo,
	char *			term
)
#else
open_terminfo_file (terminfo, term)
	char			*terminfo,
				*term;
#endif
{
	char			*first_letter	= "X",
				*path;

	int			fd;

	first_letter[0] = term[0];
	path = makepath(terminfo, first_letter, term, (char *)0);
	fd = Open(path, 0);
	Free (path);
	return (fd);
}
