/*
 * 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.
 */
/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved	*/

#include	<unistd.h>
#include	<stdlib.h>
#include	<stdio.h>
#include	<ctype.h>
#include	<string.h>
#include	<sys/types.h>
#include	<sys/stat.h>
#include	<termio.h>
#include	<sys/stermio.h>
#include	<sys/termiox.h>
#include	"ttymon.h"
#include	"tmstruct.h"
#include	"tmextern.h"
#include	"stty.h"

static	void	insert_def(struct Gdef *);
static	void	zero(char *, int);

/*
 *	read_ttydefs	- read in the /etc/ttydefs and store in Gdef array
 *			- if id is not NULL, only get entry with that id
 *			- if check is TRUE, print out the entries
 */
void
read_ttydefs(const char *id, int check)
{
	FILE	*fp;
	static	struct Gdef def;
	struct	Gdef *gptr;
	static	char	line[BUFSIZ];
	static	char	dbuf[BUFSIZ];
	char	*ptr;
	int	len;
	int	input, state, size, rawc, field;
	char	oldc;
	static	char	d_id[MAXID+1],
	    d_nextid[MAXID+1],
	    d_autobaud[MAXID+1],
	    d_if[BUFSIZ],
	    d_ff[BUFSIZ];
	static	char *states[] = {
	    "", "tty label", "Initial flags", "Final flags", "Autobaud",
	    "Next label"
	};

	if ((fp = fopen(TTYDEFS, "r")) == NULL) {
		log("can't open \"%s\".\n", TTYDEFS);
		return;
	}

	if (check) {
		for (len = 0; len < (size_t)(BUFSIZ - 1); len++)
			dbuf[len] = '-';
		dbuf[len] = '\0';
	}

	/* Start searching for the line with the proper "id". */
	input = ACTIVE;
	field = FAILURE;
	do {
		line[0] = '\0';
		for (ptr = line, oldc = '\0'; ptr < &line[sizeof (line) - 1] &&
		    (rawc = getc(fp)) != '\n' && rawc != EOF;
		    ptr++, oldc = (char)rawc) {
			if ((rawc == '#') && (oldc != '\\'))
				break;
			*ptr = (char)rawc;
		}
		*ptr = '\0';

		/* skip rest of the line */
		if (rawc != EOF && rawc != '\n') {
			if (check && rawc != '#')
				log("Entry too long.");
			while ((rawc = getc(fp)) != EOF && rawc != '\n')
				;
		}

		if (rawc == EOF) {
			if (ptr == line)
				break;
			else
				input = FINISHED;
		}

		/* if empty line, skip */
		for (ptr = line; *ptr != '\0' && isspace(*ptr); ptr++)
			;
		if (*ptr == '\0')
			continue;

		/* Now we have the complete line */

		/* Initialize "def" and "gptr". */
		gptr = &def;
		zero((char *)gptr, sizeof (struct Gdef));

		ptr = line;
		state = T_TTYLABEL;
		(void) strncpy(d_id, getword(ptr, &size, 0), MAXID);
		gptr->g_id = d_id;
		ptr += size;
		if (*ptr != ':') {
			field = state;
			state = FAILURE;
		} else {
			ptr++;	/* Skip the ':' */
			state++;
		}

		/* If "id" != NULL, and it does not match, go to next entry */
		if ((id != NULL) && (strcmp(id, gptr->g_id) != 0))
			continue;

		if (check) {
			len = strlen(line);
			dbuf[len] = '\0';
			log("\n%s", dbuf);
			log("%s", line);
			log("%s\n", dbuf);
			dbuf[len] = '-';
		}


		for (; state != FAILURE && state != SUCCESS; ) {
			switch (state) {

			case T_IFLAGS:
				(void) strncpy(d_if, getword(ptr, &size, 1),
				    BUFSIZ);
				gptr->g_iflags = d_if;
				ptr += size;
				if ((*ptr != ':') || (check_flags(d_if) != 0)) {
					field = state;
					state = FAILURE;
				} else {
					ptr++;
					state++;
				}
				break;

			case T_FFLAGS:
				(void) strncpy(d_ff, getword(ptr, &size, 1),
				    BUFSIZ);
				gptr->g_fflags = d_ff;
				ptr += size;
				if ((*ptr != ':') || (check_flags(d_ff) != 0)) {
					field = state;
					state = FAILURE;
				} else {
					ptr++;
					state++;
				}
				break;

			case T_AUTOBAUD:
				(void) strncpy(d_autobaud,
				    getword(ptr, &size, 0), MAXID);
				if (size > 1) {
					ptr += size;
					field = state;
					state = FAILURE;
					break;
				}
				if (size == 1) {
					if (*d_autobaud == 'A') {
						gptr->g_autobaud |= A_FLAG;
					} else {
						ptr += size;
						field = state;
						state = FAILURE;
						break;
					}
				}
				ptr += size;
				if (*ptr != ':') {
					field = state;
					state = FAILURE;
				} else {
					ptr++;	/* Skip the ':' */
					state++;
				}
				break;

			case T_NEXTLABEL:
				(void) strncpy(d_nextid,
				    getword(ptr, &size, 0), MAXID);
				gptr->g_nextid = d_nextid;
				ptr += size;
				if (*ptr != '\0') {
					field = state;
					state = FAILURE;
				} else {
					state = SUCCESS;
				}
				break;

			} /* end switch */
		} /* end for loop */

		if (state == SUCCESS) {

			if (check) {
				log("ttylabel:\t%s", gptr->g_id);
				log("initial flags:\t%s", gptr->g_iflags);
				log("final flags:\t%s", gptr->g_fflags);
				if (gptr->g_autobaud & A_FLAG)
					log("autobaud:\tyes");
				else
					log("autobaud:\tno");
				log("nextlabel:\t%s", gptr->g_nextid);
			}
			if (Ndefs < MAXDEFS) {
				insert_def(gptr);
			} else {
				log("can't add more entries to ttydefs table, "
				    " Maximum entries = %d", MAXDEFS);
				(void) fclose(fp);
				return;
			}
			if (id != NULL) {
				return;
			}
		} else {
			*++ptr = '\0';
			log("Parsing failure in the \"%s\" field\n"
			    "%s<--error detected here\n", states[field], line);
		}
	} while (input == ACTIVE);
	(void) fclose(fp);
}

/*
 *	zero	- zero out the buffer
 */
static void
zero(char *adr, int size)
{
	if (adr != NULL) {
		while (size--)
			*adr++ = '\0';
	}
}

/*
 * find_def(ttylabel)
 *	- scan Gdef table for an entry with requested "ttylabel".
 *	- return a Gdef ptr if entry with "ttylabel" is found
 *	- return NULL if no entry with matching "ttylabel"
 */

struct Gdef *
find_def(char *ttylabel)
{
	int	i;
	struct	Gdef	*tp;

	tp = &Gdef[0];
	for (i = 0; i < Ndefs; i++, tp++) {
		if (strcmp(ttylabel, tp->g_id) == 0) {
			return (tp);
		}
	}
	return (NULL);
}

/*
 *	check_flags	- check to see if the flags contains options that are
 *			  recognizable by stty
 *			- return 0 if no error. Otherwise return -1
 */
int
check_flags(char *flags)
{
	struct	 termio termio;
	struct	 termios termios;
	struct	 termiox termiox;
	struct	 winsize winsize;
	int	 term;
	int	 cnt = 1;
	char	 *argvp[MAXARGS];	/* stty args */
	static   char	 *binstty = "/usr/bin/stty";
	static	 char	buf[BUFSIZ];
	char	*s_arg;		/* this will point to invalid option */

	/* put flags into buf, because strtok will break up buffer */
	(void) strcpy(buf, flags);
	argvp[0] = binstty;	/* just a place holder */
	mkargv(buf, &argvp[1], &cnt, MAXARGS - 1);
	argvp[cnt] = NULL;

	/*
	 * because we don't know what type of terminal we have now,
	 * just set term = everything, so all possible stty options
	 * are accepted
	 */
	term = ASYNC|TERMIOS|FLOW;
	if ((s_arg = sttyparse(cnt, argvp, term, &termio, &termios,
	    &termiox, &winsize)) != NULL) {
		log("invalid mode: %s", s_arg);
		return (-1);
	}
	return (0);
}

/*
 *	insert_def	- insert one entry into Gdef table
 */
static void
insert_def(struct Gdef *gptr)
{
	struct	Gdef	*tp;

	if (find_def(gptr->g_id) != NULL) {
		log("Warning -- duplicate entry <%s>, ignored", gptr->g_id);
		return;
	}
	tp = &Gdef[Ndefs];
	tp->g_id = strsave(gptr->g_id);
	tp->g_iflags = strsave(gptr->g_iflags);
	tp->g_fflags = strsave(gptr->g_fflags);
	tp->g_autobaud = gptr->g_autobaud;
	tp->g_nextid = strsave(gptr->g_nextid);
	Ndefs++;
}

/*
 *	mkargv	- parse the string into args, starting from args[cnt]
 */

void
mkargv(char *string, char **args, int *cnt, int maxargs)
{
	char *ptrin, *ptrout;
	int i;
	int qsize;

	for (i = 0; i < maxargs; i++)
		args[i] = NULL;

	ptrin = ptrout = string;
	for (i = 0; *ptrin != '\0' && i < maxargs; i++) {
		/* Skip excess white spaces between arguments. */
		while (*ptrin == ' ' || *ptrin == '\t') {
			ptrin++;
			ptrout++;
		}
		/* Save the address of argument if there is something there. */
		if (*ptrin == '\0')
			break;
		else
			args[i] = ptrout;

/* Span the argument itself.  The '\' character causes quoting */
/* of the next character to take place (except for '\0'). */
		while (*ptrin != '\0') {
			if (*ptrin == '\\') {
				*ptrout++ = quoted(ptrin, &qsize);
				ptrin += qsize;

/* Is this the end of the argument?  If so quit loop. */
			} else if (*ptrin == ' ' || *ptrin == '\t') {
				ptrin++;
				break;

/* If this is a normal letter of the argument, save it, advancing */
/* the pointers at the same time. */
			} else *ptrout++ = *ptrin++;
		}
		/* Null terminate the string. */
		*ptrout++ = '\0';
	}
	(*cnt) += i;
}

#ifdef	DEBUG
/*
 *	dump_ttydefs	- dump Gdef table to log file
 */
void
dump_ttydefs(void)
{
	int	i;
	struct	Gdef	*gptr;

	gptr = &Gdef[0];
	log("********** dumping ttydefs table **********");
	log("Ndefs = %d", Ndefs);
	log(" ");
	for (i = 0; i < Ndefs; i++, gptr++) {
		log("----------------------------------------");
		log("ttylabel:\t%s", gptr->g_id);
		log("initial flags:\t%s", gptr->g_iflags);
		log("final flags:\t%s", gptr->g_fflags);
		if (gptr->g_autobaud & A_FLAG)
			log("autobaud:\tyes");
		else
			log("Autobaud:\tno");
		log("nextlabel:\t%s", gptr->g_nextid);
		log(" ");
	}
	log("********** end dumping ttydefs table **********");
}
#endif


/*
 * this is copies from uucp/strsave.c
 * and is modified that if malloc fails, it will exit
 */
char *
strsave(char *str)
{
	char *rval;

	if (str == NULL) {
		if ((rval = malloc(1)) == NULL) {
			log("strsave: malloc failed");
			exit(1);
		}
		*rval = '\0';
	} else {
		if ((rval = malloc(strlen(str) + 1)) == NULL) {
			log("strsave: malloc failed");
			exit(1);
		}
		(void) strcpy(rval, str);
	}
	return (rval);
}