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


/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*	from OpenSolaris "main.c	1.9	05/06/08 SMI"	*/

/*
 * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany
 *
 * Sccsid @(#)main.c	1.4 (gritter) 7/1/05
 */


/*
 * fmtmsg.c
 *
 * Contains:
 *	fmtmsg		Command that writes a message in the standard
 *			message format.  May in future make these
 *			messages available for logging.
 */


/*
 * Header files used:
 *	<stdio.h>	C Standard I/O function definitions
 *	<string.h>	C string-handling definitions
 *	<errno.h>	UNIX error-code "errno" definitions
 *	<fmtmsg.h>	Standard Message definitions
 */

#include	<stdio.h>
#include	<string.h>
#include	<errno.h>
#include	<unistd.h>
#include	<stdlib.h>
#include	<libgen.h>
#include	"fmtmsg.h"



/*
 * Local definitions
 */

/*
 * Local constants
 */


/*
 * Boolean constants
 *	TRUE	Boolean value for "true" (any bits on)
 *	FALSE	Boolean value for "false" (all bits off)
 */

#undef	FALSE
#define	FALSE		(0)

#undef	TRUE
#define	TRUE		(1)


#define	CLASS		(MM_PRINT|MM_SOFT|MM_NRECOV|MM_UTIL)
#define BIGUSAGE	"%s [-a action] [-c class] [-l label] [-s severity] [-t tag]\n       [-u subclass[,subclass[,...]]] [text]\n"


/*
 * Local data-type definitions
 */

/*
 * Structure used for tables containing keywords and integer values
 */

struct sev_info {
	char   *keyword;
	int	value;
};


/*
 * Structure used for tables containing keywords, long values
 */

struct class_info {
	char   *keyword;
	long	value;
	long	conflict;
};


/*
 * Severity string structure
 *
 *	struct sevstr
 *		sevvalue	Value of the severity-level being defined
 *		sevkywd		Keyword identifying the severity
 *		sevprptr	Pointer to the string associated with the value
 *		sevnext		Pointer to the next value in the list.
 */

struct sevstr {
	int		sevvalue;
	char           *sevkywd;
	char	       *sevprstr;
	struct sevstr  *sevnext;
};


/*
 * Local static data
 */


/*
 * Table contains the keywords for the classes of a message
 */

static	struct class_info	classes[] = {

	{"hard", 	MM_HARD,	MM_SOFT|MM_FIRM},	/* hardware */
	{"soft", 	MM_SOFT,	MM_HARD|MM_FIRM},	/* software */
	{"firm", 	MM_FIRM,	MM_SOFT|MM_FIRM},	/* firmware */

	{NULL,		0L,		0L}			/* end of list */

};


/*
 * Table contains the keywords for the subclasses for a message
 */

static	struct class_info	subclasses[] = 	{

	{"appl",     	MM_APPL,	MM_UTIL|MM_OPSYS},	/* Application */
	{"util",     	MM_UTIL,	MM_APPL|MM_OPSYS},	/* Utility */
	{"opsys",    	MM_OPSYS,	MM_APPL|MM_UTIL},	/* Operating System */

	{"recov",    	MM_RECOVER,	MM_NRECOV},		/* Recoverable */
	{"nrecov",   	MM_NRECOV,	MM_RECOVER},		/* Non-recoverable */

	{"print",    	MM_PRINT,	0L}, 			/* Write message to stderr */
	{"console",  	MM_CONSOLE,	0L},			/* Write message on /dev/console */
	{NULL,		0L,		0L}			/* End of list */

};


/*
 * Table contains the keywords for the standard severities of a message.
 * User may supply more through the SEV_LEVEL environment variable.
 */

static  struct sev_info		severities[] =  {
	{"halt",	MM_HALT},	/* halt */
	{"error",	MM_ERROR},	/* error */
	{"warn",	MM_WARNING},	/* warn */
	{"info",	MM_INFO},	/* info */
	{NULL,		0}		/* end of list */
};


/*
 * Buffers used by the command
 */

static	char	labelbuf[128];		/* Buf for message label */
static	char	msgbuf[256];		/* Buf for messages */

/*
 * static char *exttok(str, delims)
 *	char   *str
 *	char   *delims
 *
 *	This function examines the string pointed to by "str", looking
 *	for the first occurrence of any of the characters in the string
 *	whose address is "delims".  It returns the address of that
 *	character or NULL if there was nothing to search.
 *
 * Arguments:
 *	str	Address of the string to search
 *	delims	Address of the string containing delimiters
 *
 * Returns:  char *
 *	Returns the address of the first occurrence of any of the characters
 *	in "delim" in the string "str" (incl '\0').  If there was nothing
 *	to search, the function returns NULL.
 *
 * Notes:
 *    - This function is needed because strtok() can't be used inside a
 *	function.  Besides, strtok() is destructive in the string, which
 *	is undesirable in many circumstances.
 *    - This function understands escaped delimiters as non-delimiters.
 *	Delimiters are escaped by preceding them with '\' characters.
 *	The '\' character also must be escaped.
 */

static char *
exttok(
	char   *tok,		/* Ptr to the token we're parsing */
	char   *delims		/* Ptr to string with delimiters */
)
{

	/* Automatic Data */
	char   *tokend;		/* Ptr to the end of the token */
	char   *p, *q;	 	/* Temp pointers */


	/* Algorithm:
	 *    1.  Get the starting address (new string or where we
	 *	  left off).  If nothing to search, return NULL
	 *    2.  Find the end of the string
	 *    3.  Look for the first unescaped delimiter closest to the 
	 *	  beginning of the string
	 *    4.  Remember where we left off
	 *    5.  Return a pointer to the delimiter we found
	 */

	/* Begin at the beginning, if any */
	if (tok == NULL) {
	    return (NULL);
	}

	/* Find end of the token string */
	tokend = tok + strlen(tok);

	/* Look for the 1st occurrence of any delimiter */
	for (p = delims ; *p != '\0' ; p++) {
	    for (q = strchr(tok, *p) ; q && (q != tok) && (*(q-1) == '\\') ; q = strchr(q+1, *p)) ;
	    if (q && (q < tokend)) tokend = q;
	}

	/* Done */
	return(tokend);
}

/*
 * char *noesc(str)
 *	
 *	This function squeezes out all of the escaped character sequences
 *	from the string <str>.  It returns a pointer to that string.
 *
 *  Arguments:
 *	str	char *
 *		The string that is to have its escaped characters removed.
 *
 *  Returns:  char *
 *	This function returns its argument <str> always.
 *
 *  Notes:
 *	This function potentially modifies the string it is given.
 */

char *
noesc(
	char   *str		/* String to remove escaped characters from */
)
{
	char   *p;		/* Temp string pointer */
	char   *q;		/* Temp string pointer */

	/* Look for an escaped character */
	p = str;
	while (*p && (*p != '\\')) p++;


	/* 
	 * If there was at least one, squeeze them out 
	 * Otherwise, don't touch the argument string 
	 */

	if (*p) {
	    q = p++;
	    while (*q++ = *p++) if (*p == '\\') p++;
	}

	/* Finished.  Return our argument */
	return(str);
}

/*
 * struct sevstr *getauxsevs(ptr)
 *
 *	Parses a string that is in the format of the severity definitions.
 *	Returns a pointer to a (malloc'd) structure that contains the
 *	definition, or NULL if none was parsed.
 *
 * Arguments:
 *	ptr	char *
 *		References the string from which data is to be extracted.
 *		If NULL, continue where we left off.  Otherwise,
 *		start with the string referenced by ptr.
 *
 * Returns: struct sevstr *
 *	A pointer to a malloc'd structure containing the severity definition
 *	parsed from string, or NULL if none.
 *
 * Notes:
 *    - This function is destructive to the string referenced by its argument.
 */


/* Static data */
static	char	       *leftoff = NULL;

static	struct sevstr *
getauxsevs(char *ptr)
{

	/* Automatic data */
	char	       *current;	/* Ptr to current sev def'n */
	char	       *tokend;		/* Ptr to end of current sev def'n */
	char	       *kywd;		/* Ptr to extracted kywd */
	char	       *valstr;		/* Ptr to extracted sev value */
	char	       *prstr;		/* Ptr to extracted print str */
	char	       *p;		/* Temp pointer */
	int		val;		/* Converted severity value */
	int		done;		/* Flag, sev def'n found and ok? */
	struct sevstr  *rtnval;		/* Value to return */


	/* Start anew or start where we left off? */
	current = (ptr == NULL) ? leftoff : ptr;


	/* If nothing to parse, return NULL */
	if (current == NULL) {
	    return (NULL);
	}


	/*
	 * Look through the string "current" for a token of the form
	 * <kywd>,<sev>,<printstring> delimited by ':' or '\0'
	 */

	/* Loop initializations */
	done = FALSE;
	rtnval = NULL;
	while (!done) {

	    /* Eat leading junk */
	    while (*(tokend = exttok(current, ":,")) == ':') {
		current = tokend + 1;
	    }

	    /* If we've found a <kywd>,... */
	    if (*tokend == ',') {
		kywd = current;
		*tokend = '\0';

		/* Look for <kywd>,<sev>,... */
		current = tokend + 1;
		if (*(tokend = exttok(current, ":,")) == ',') {
		    valstr = current;
		    *tokend = '\0';
		    current = tokend+1;
		    prstr = current;

		    /* Make sure <sev> > 4 */
		    val = (int) strtol(noesc(valstr), &p, 0);
		    if ((val > 4) && (p == tokend)) {

			/*
			 * Found <kywd>,<sev>,<printstring>.
			 * remember where we left off
			 */

		        if (*(tokend = exttok(current, ":")) == ':') {
			    *tokend = '\0';
			    leftoff = tokend + 1;
			} else leftoff = NULL;

			/* Alloc structure to contain severity definition */
			if (rtnval = malloc(sizeof(struct sevstr))) {

			    /* Fill in structure */
			    rtnval->sevkywd = noesc(kywd);
			    rtnval->sevvalue = val;
			    rtnval->sevprstr = noesc(prstr);
			    rtnval->sevnext = NULL;
			}

			done = TRUE;

		    } else {

			/* Invalid severity value, eat thru end of token */
			current = tokend;
			if (*(tokend = exttok(prstr, ":")) == ':')
			    current++;
		    }

		} else {

		    /* Invalid severity definition, eat thru end of token */
		    current = tokend;
		    if (*tokend == ':')
			current++;
		}

	    } else {

		/* End of string found */
		done = TRUE;
		leftoff = NULL;
	    }

	} /* while (!done) */

	/* Finished */
	return(rtnval);
}

/*
 * fmtmsg [-a action] [-c classification] [-l label] [-s severity] [-t tag]
 *        [-u subclass[,subclass[,...]]] [text]
 *
 * Function:
 *	Writes a message in the standard format.  Typically used by shell
 *	scripts to write error messages to the user.
 *
 * Arguments:
 *	text		String that is the text of the message
 *
 * Options:
 *   -a action		String that describes user action to take to
 *			correct the situation
 *   -c classification	Keyword that identifies the type of the message
 *   -l label		String that identifies the source of the message
 *   -s severity	Keyword that identifies the severity of the message
 *   -t tag		String that identifies the message (use unclear)
 *   -u sub_classes	Comma-list of keywords that refines the type of
 *			the message
 *
 * Environment Variables Used:
 *	MSGVERB		Defines the pieces of a message the user expects
 *			to see.  It is a list of keywords separated by
 *			colons (':').
 *	SEV_LEVEL	Defines a list of auxiliary severity keywords, values,
 *			and print-strings.  It is a list of fields separated
 *			by colons (':').  Each field consists of three
 *			elements, keyword, value (in octal, hex, or decimal),
 *			and print-string, separated by commas (',').
 *
 * Needs:
 *
 * Open Issues:
 */

int
main(
	int	argc,			/* Argument count */
	char   *argv[]			/* Pointers to arguments */
)
{

	/* Local automatic data */

	long			class;		/* Classification (built) */

	int			severity;	/* User specified severity */
	int			msgrtn;		/* Value returned by fmtmsg() */
	int			optchar;	/* Opt char on cmdline */
	int			exitval;	/* Value to return */

	int			found;		/* FLAG, kywd found yet? */
	int			errflg;		/* FLAG, error seen in cmd */
	int			a_seen;		/* FLAG, -a option seen */
	int			c_seen;		/* FLAG, -c option seen */
	int			l_seen;		/* FLAG, -l option seen */
	int			s_seen;		/* FLAG, -s option seen */
	int			t_seen;		/* FLAG, -t option seen */
	int			u_seen;		/* FLAG, -u option seen */
	int			text_seen;	/* FLAG, text seen */

	char		       *text = NULL;	/* Ptr to user's text */
	char		       *label = NULL;	/* Ptr to user's label */
	char		       *tag = NULL;	/* Ptr to user's tag */
	char		       *action = NULL;	/* Ptr to user's action str */
	char		       *sstr = NULL;	/* Ptr to -s (severity) arg */
	char		       *ustr = NULL;	/* Ptr to -u (subclass) arg */
	char		       *cstr = NULL;	/* Ptr to -c (class) arg */
	char		       *sevstrval;	/* Ptr to SEV_LEVEL argument */
	char		       *sevval;		/* Ptr to temp SEV_LEVEL arg */
	char		       *tokenptr;	/* Ptr to current token */
	char		       *cmdname;	/* Ptr to base command name */
	char		       *p;		/* Multipurpose ptr */

	struct class_info      *class_info;	/* Ptr to class/subclass info structure */
	struct sev_info	       *sev_info;	/* Ptr to severity info struct */
	struct sevstr	       *penvsev;	/* Ptr to SEV_LEVEL values */



	/*
	 * fmtmsg
	 */


	/* Initializations */


	/* Extract the base command name from the command */
	if ((p = strrchr(argv[0], '/')) == NULL)
	    cmdname = argv[0];
	else
	    cmdname = p+1;

	/* Build the label for messages from "fmtmsg" */
	snprintf(labelbuf, sizeof (labelbuf), "UX:%s", cmdname);


	/*
	 * Extract arguments from the command line
	 */

	/* Initializations */

	opterr = 0;			/* Disable messages from getopt() */
	errflg = FALSE;			/* No errors seen yet */

	a_seen = FALSE;			/* No action (-a) text seen yet */
	c_seen = FALSE;			/* No classification (-c) seen yet */
	l_seen = FALSE;			/* No label (-l) seen yet */
	s_seen = FALSE;			/* No severity (-s) seen yet */
	t_seen = FALSE;			/* No tag (-t) seen yet */
	u_seen = FALSE;			/* No subclass (-u) seen yet */
	text_seen = FALSE;		/* No text seen yet */


	/*
	 * If only the command name was used, write out a usage string to
	 * the standard output file.
	 */

	if (argc == 1) {
	    fprintf(stderr, BIGUSAGE, cmdname);
	    exit(0);
	}


	/* Parce command line */
	while (((optchar = getopt(argc, argv, "a:c:l:s:t:u:")) != EOF) &&
	       !errflg) {

	    switch(optchar) {

	    case 'a':		/* -a actiontext */
		if (!a_seen) {
		    action = optarg;
		    a_seen = TRUE;
		} else errflg = TRUE;
		break;

	    case 'c':		/* -c classification */
		if (!c_seen) {
		    cstr = optarg;
		    c_seen = TRUE;
		} else errflg = TRUE;
		break;

	    case 'l':		/* -l label */
		if (!l_seen) {
		    label = optarg;
		    l_seen = TRUE;
		} else errflg = TRUE;
		break;

	    case 's':		/* -s severity */
		if (!s_seen) {
		    sstr = optarg;
		    s_seen = TRUE;
		} else errflg = TRUE;
		break;

	    case 't':		/* -t tag */
		if (!t_seen) {
		    tag = optarg;
		    t_seen = TRUE;
		} else errflg = TRUE;
		break;

	    case 'u':		/* -u subclasslist */
		if (!u_seen) {
		    ustr = optarg;
		    u_seen = TRUE;
		} else errflg = TRUE;
		break;

	    case '?':		/* -? or unknown option */
	    default:
		errflg = TRUE;
		break;

	    } /* esac */
	}


	/* Get the text */
	if (!errflg) {
	    if (argc == (optind+1)) {
		text = argv[optind];
		text_seen = TRUE;
	    }
	    else if (argc != optind) {
		errflg = TRUE;
	    }
	}


	/* Report syntax errors */
	if (errflg) {
	    fprintf(stderr, BIGUSAGE, cmdname);
	    exit(1);
	}


	/*
	 * Classification.
	 */

	class = 0L;
	if (c_seen) {

	    /* Search for keyword in list */
	    for (class_info = &classes[0] ;
		 (class_info->keyword != NULL) &&
		 (strcmp(cstr, class_info->keyword)) ;
		 class_info++) ;

	    /* If invalid (keyword unknown), write a message and exit */
	    if (class_info->keyword == NULL) {
		snprintf(msgbuf, sizeof (msgbuf),
			"Invalid class: %s", cstr);
		fmtmsg(CLASS, labelbuf, MM_ERROR, msgbuf,
		              MM_NULLACT, MM_NULLTAG);
		exit(1);
	    }

	    /* Save classification */
	    class = class_info->value;

	}


	/*
	 * Subclassification.
	 */

	if (u_seen) {

	    errflg = FALSE;
	    p = strcpy(malloc(strlen(ustr)+1), ustr);
	    if ((tokenptr = strtok(p, ",")) == NULL) errflg = TRUE;
	    else do {

		/* Got a keyword.  Look for it in keyword list */
		for (class_info = subclasses ;
		     (class_info->keyword != NULL) &&
		     (strcmp(tokenptr, class_info->keyword) != 0) ;
		     class_info++) ;

		/* If found in list and no conflict, remember in class */
		if ((class_info->keyword != NULL) && ((class & class_info->conflict) == 0L))
		    class |= class_info->value;
		else 
		    errflg = TRUE;

	    } while (!errflg && ((tokenptr = strtok(NULL, ",")) != NULL)) ;

	    if (errflg) {
		snprintf(msgbuf, sizeof (msgbuf),
			"Invalid subclass: %s", ustr);
		fmtmsg(CLASS, labelbuf, MM_ERROR, msgbuf,
		              MM_NULLACT, MM_NULLTAG);
		exit(1);
	    }

	}

	if (!c_seen & !u_seen) class = MM_NULLMC;



	/*
	 * Severity.
	 */

	if (s_seen) {

	    /* If the severity is specified as a number, use that value */
	    severity = strtol(sstr, &p, 10);
	    if (*p || (strlen(sstr) == 0)) {

		/* Look for the standard severities */
		for (sev_info = severities ;
		     (sev_info->keyword != NULL) &&
		     (strcmp(sstr, sev_info->keyword)) ;
		     sev_info++) ;

		/*
		 * If the "severity" argument is one of the standard keywords,
		 * remember it for fmtmsg().  Otherwise, look at the SEV_LEVEL
		 * environment variable for severity extensions.
		 */

		/* If the keyword is one of the standard ones, save severity */
		if (sev_info->keyword != NULL) severity = sev_info->value;

		else {

		    /*
		     * Severity keyword may be one of the extended set, if any.
		     */

		    /* Get the value of the SEV_LEVEL environment variable */
		    found = FALSE;
		    if ((sevstrval = getenv(SEV_LEVEL)) != NULL) {
			sevval = malloc(strlen(sevstrval)+1);
			penvsev = getauxsevs(strcpy(sevval, sevstrval));
			if (penvsev != NULL) do {
			    if (strcmp(penvsev->sevkywd, sstr) == 0) {
				severity = penvsev->sevvalue;
				found = TRUE;
			    }
			    else {
				free(penvsev);
				penvsev = getauxsevs(NULL);
			    }
			} while (!found && (penvsev != NULL));

			if (found) free(penvsev);
			free(sevval);
		    }

		    if (!found) {
			snprintf(msgbuf, sizeof (msgbuf),
				"Invalid severity: %s", sstr);
			fmtmsg(CLASS, labelbuf, MM_ERROR, msgbuf,
				      MM_NULLACT, MM_NULLTAG);
			exit(1);
		    }

		}  /* <severity> is not one of the standard severities */

	    }  /* <severity> is not numeric */

	}  /* if (s_seen) */

	else severity = MM_NULLSEV;


	/*
	 * Other options
	 */

	if (!a_seen) action = MM_NULLACT;
	if (!l_seen) label = MM_NULLLBL;
	if (!t_seen) tag = MM_NULLTAG;
	if (!text_seen) text = MM_NULLTXT;


	/*
	 * Write the message
	 */

	msgrtn = fmtmsg(class, label, severity, text, action ,tag);


	/*
	 * Return appropriate value to the shell (or wherever)
	 */

	exitval = 0;
	if (msgrtn == MM_NOTOK) exitval = 32;
	else {
	    if (msgrtn & MM_NOMSG) exitval += 2;
	    if (msgrtn & MM_NOCON) exitval += 4;
	}

	return(exitval);
}
