1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
23 /*	  All Rights Reserved  	*/
24 
25 
26 /*
27  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
28  * Use is subject to license terms.
29  */
30 
31 /*	from OpenSolaris "main.c	1.9	05/06/08 SMI"	*/
32 
33 /*
34  * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany
35  *
36  * Sccsid @(#)main.c	1.4 (gritter) 7/1/05
37  */
38 
39 
40 /*
41  * fmtmsg.c
42  *
43  * Contains:
44  *	fmtmsg		Command that writes a message in the standard
45  *			message format.  May in future make these
46  *			messages available for logging.
47  */
48 
49 
50 /*
51  * Header files used:
52  *	<stdio.h>	C Standard I/O function definitions
53  *	<string.h>	C string-handling definitions
54  *	<errno.h>	UNIX error-code "errno" definitions
55  *	<fmtmsg.h>	Standard Message definitions
56  */
57 
58 #include	<stdio.h>
59 #include	<string.h>
60 #include	<errno.h>
61 #include	<unistd.h>
62 #include	<stdlib.h>
63 #include	<libgen.h>
64 #include	"fmtmsg.h"
65 
66 
67 
68 /*
69  * Local definitions
70  */
71 
72 /*
73  * Local constants
74  */
75 
76 
77 /*
78  * Boolean constants
79  *	TRUE	Boolean value for "true" (any bits on)
80  *	FALSE	Boolean value for "false" (all bits off)
81  */
82 
83 #undef	FALSE
84 #define	FALSE		(0)
85 
86 #undef	TRUE
87 #define	TRUE		(1)
88 
89 
90 #define	CLASS		(MM_PRINT|MM_SOFT|MM_NRECOV|MM_UTIL)
91 #define BIGUSAGE	"%s [-a action] [-c class] [-l label] [-s severity] [-t tag]\n       [-u subclass[,subclass[,...]]] [text]\n"
92 
93 
94 /*
95  * Local data-type definitions
96  */
97 
98 /*
99  * Structure used for tables containing keywords and integer values
100  */
101 
102 struct sev_info {
103 	char   *keyword;
104 	int	value;
105 };
106 
107 
108 /*
109  * Structure used for tables containing keywords, long values
110  */
111 
112 struct class_info {
113 	char   *keyword;
114 	long	value;
115 	long	conflict;
116 };
117 
118 
119 /*
120  * Severity string structure
121  *
122  *	struct sevstr
123  *		sevvalue	Value of the severity-level being defined
124  *		sevkywd		Keyword identifying the severity
125  *		sevprptr	Pointer to the string associated with the value
126  *		sevnext		Pointer to the next value in the list.
127  */
128 
129 struct sevstr {
130 	int		sevvalue;
131 	char           *sevkywd;
132 	char	       *sevprstr;
133 	struct sevstr  *sevnext;
134 };
135 
136 
137 /*
138  * Local static data
139  */
140 
141 
142 /*
143  * Table contains the keywords for the classes of a message
144  */
145 
146 static	struct class_info	classes[] = {
147 
148 	{"hard", 	MM_HARD,	MM_SOFT|MM_FIRM},	/* hardware */
149 	{"soft", 	MM_SOFT,	MM_HARD|MM_FIRM},	/* software */
150 	{"firm", 	MM_FIRM,	MM_SOFT|MM_FIRM},	/* firmware */
151 
152 	{NULL,		0L,		0L}			/* end of list */
153 
154 };
155 
156 
157 /*
158  * Table contains the keywords for the subclasses for a message
159  */
160 
161 static	struct class_info	subclasses[] = 	{
162 
163 	{"appl",     	MM_APPL,	MM_UTIL|MM_OPSYS},	/* Application */
164 	{"util",     	MM_UTIL,	MM_APPL|MM_OPSYS},	/* Utility */
165 	{"opsys",    	MM_OPSYS,	MM_APPL|MM_UTIL},	/* Operating System */
166 
167 	{"recov",    	MM_RECOVER,	MM_NRECOV},		/* Recoverable */
168 	{"nrecov",   	MM_NRECOV,	MM_RECOVER},		/* Non-recoverable */
169 
170 	{"print",    	MM_PRINT,	0L}, 			/* Write message to stderr */
171 	{"console",  	MM_CONSOLE,	0L},			/* Write message on /dev/console */
172 	{NULL,		0L,		0L}			/* End of list */
173 
174 };
175 
176 
177 /*
178  * Table contains the keywords for the standard severities of a message.
179  * User may supply more through the SEV_LEVEL environment variable.
180  */
181 
182 static  struct sev_info		severities[] =  {
183 	{"halt",	MM_HALT},	/* halt */
184 	{"error",	MM_ERROR},	/* error */
185 	{"warn",	MM_WARNING},	/* warn */
186 	{"info",	MM_INFO},	/* info */
187 	{NULL,		0}		/* end of list */
188 };
189 
190 
191 /*
192  * Buffers used by the command
193  */
194 
195 static	char	labelbuf[128];		/* Buf for message label */
196 static	char	msgbuf[256];		/* Buf for messages */
197 
198 /*
199  * static char *exttok(str, delims)
200  *	char   *str
201  *	char   *delims
202  *
203  *	This function examines the string pointed to by "str", looking
204  *	for the first occurrence of any of the characters in the string
205  *	whose address is "delims".  It returns the address of that
206  *	character or NULL if there was nothing to search.
207  *
208  * Arguments:
209  *	str	Address of the string to search
210  *	delims	Address of the string containing delimiters
211  *
212  * Returns:  char *
213  *	Returns the address of the first occurrence of any of the characters
214  *	in "delim" in the string "str" (incl '\0').  If there was nothing
215  *	to search, the function returns NULL.
216  *
217  * Notes:
218  *    - This function is needed because strtok() can't be used inside a
219  *	function.  Besides, strtok() is destructive in the string, which
220  *	is undesirable in many circumstances.
221  *    - This function understands escaped delimiters as non-delimiters.
222  *	Delimiters are escaped by preceding them with '\' characters.
223  *	The '\' character also must be escaped.
224  */
225 
226 static char *
exttok(char * tok,char * delims)227 exttok(
228 	char   *tok,		/* Ptr to the token we're parsing */
229 	char   *delims		/* Ptr to string with delimiters */
230 )
231 {
232 
233 	/* Automatic Data */
234 	char   *tokend;		/* Ptr to the end of the token */
235 	char   *p, *q;	 	/* Temp pointers */
236 
237 
238 	/* Algorithm:
239 	 *    1.  Get the starting address (new string or where we
240 	 *	  left off).  If nothing to search, return NULL
241 	 *    2.  Find the end of the string
242 	 *    3.  Look for the first unescaped delimiter closest to the
243 	 *	  beginning of the string
244 	 *    4.  Remember where we left off
245 	 *    5.  Return a pointer to the delimiter we found
246 	 */
247 
248 	/* Begin at the beginning, if any */
249 	if (tok == NULL) {
250 	    return (NULL);
251 	}
252 
253 	/* Find end of the token string */
254 	tokend = tok + strlen(tok);
255 
256 	/* Look for the 1st occurrence of any delimiter */
257 	for (p = delims ; *p != '\0' ; p++) {
258 	    for (q = strchr(tok, *p) ; q && (q != tok) && (*(q-1) == '\\') ; q = strchr(q+1, *p)) ;
259 	    if (q && (q < tokend)) tokend = q;
260 	}
261 
262 	/* Done */
263 	return(tokend);
264 }
265 
266 /*
267  * char *noesc(str)
268  *
269  *	This function squeezes out all of the escaped character sequences
270  *	from the string <str>.  It returns a pointer to that string.
271  *
272  *  Arguments:
273  *	str	char *
274  *		The string that is to have its escaped characters removed.
275  *
276  *  Returns:  char *
277  *	This function returns its argument <str> always.
278  *
279  *  Notes:
280  *	This function potentially modifies the string it is given.
281  */
282 
283 char *
noesc(char * str)284 noesc(
285 	char   *str		/* String to remove escaped characters from */
286 )
287 {
288 	char   *p;		/* Temp string pointer */
289 	char   *q;		/* Temp string pointer */
290 
291 	/* Look for an escaped character */
292 	p = str;
293 	while (*p && (*p != '\\')) p++;
294 
295 
296 	/*
297 	 * If there was at least one, squeeze them out
298 	 * Otherwise, don't touch the argument string
299 	 */
300 
301 	if (*p) {
302 	    q = p++;
303 	    while (*q++ = *p++) if (*p == '\\') p++;
304 	}
305 
306 	/* Finished.  Return our argument */
307 	return(str);
308 }
309 
310 /*
311  * struct sevstr *getauxsevs(ptr)
312  *
313  *	Parses a string that is in the format of the severity definitions.
314  *	Returns a pointer to a (malloc'd) structure that contains the
315  *	definition, or NULL if none was parsed.
316  *
317  * Arguments:
318  *	ptr	char *
319  *		References the string from which data is to be extracted.
320  *		If NULL, continue where we left off.  Otherwise,
321  *		start with the string referenced by ptr.
322  *
323  * Returns: struct sevstr *
324  *	A pointer to a malloc'd structure containing the severity definition
325  *	parsed from string, or NULL if none.
326  *
327  * Notes:
328  *    - This function is destructive to the string referenced by its argument.
329  */
330 
331 
332 /* Static data */
333 static	char	       *leftoff = NULL;
334 
335 static	struct sevstr *
getauxsevs(char * ptr)336 getauxsevs(char *ptr)
337 {
338 
339 	/* Automatic data */
340 	char	       *current;	/* Ptr to current sev def'n */
341 	char	       *tokend;		/* Ptr to end of current sev def'n */
342 	char	       *kywd;		/* Ptr to extracted kywd */
343 	char	       *valstr;		/* Ptr to extracted sev value */
344 	char	       *prstr;		/* Ptr to extracted print str */
345 	char	       *p;		/* Temp pointer */
346 	int		val;		/* Converted severity value */
347 	int		done;		/* Flag, sev def'n found and ok? */
348 	struct sevstr  *rtnval;		/* Value to return */
349 
350 
351 	/* Start anew or start where we left off? */
352 	current = (ptr == NULL) ? leftoff : ptr;
353 
354 
355 	/* If nothing to parse, return NULL */
356 	if (current == NULL) {
357 	    return (NULL);
358 	}
359 
360 
361 	/*
362 	 * Look through the string "current" for a token of the form
363 	 * <kywd>,<sev>,<printstring> delimited by ':' or '\0'
364 	 */
365 
366 	/* Loop initializations */
367 	done = FALSE;
368 	rtnval = NULL;
369 	while (!done) {
370 
371 	    /* Eat leading junk */
372 	    while (*(tokend = exttok(current, ":,")) == ':') {
373 		current = tokend + 1;
374 	    }
375 
376 	    /* If we've found a <kywd>,... */
377 	    if (*tokend == ',') {
378 		kywd = current;
379 		*tokend = '\0';
380 
381 		/* Look for <kywd>,<sev>,... */
382 		current = tokend + 1;
383 		if (*(tokend = exttok(current, ":,")) == ',') {
384 		    valstr = current;
385 		    *tokend = '\0';
386 		    current = tokend+1;
387 		    prstr = current;
388 
389 		    /* Make sure <sev> > 4 */
390 		    val = (int) strtol(noesc(valstr), &p, 0);
391 		    if ((val > 4) && (p == tokend)) {
392 
393 			/*
394 			 * Found <kywd>,<sev>,<printstring>.
395 			 * remember where we left off
396 			 */
397 
398 		        if (*(tokend = exttok(current, ":")) == ':') {
399 			    *tokend = '\0';
400 			    leftoff = tokend + 1;
401 			} else leftoff = NULL;
402 
403 			/* Alloc structure to contain severity definition */
404 			if (rtnval = malloc(sizeof(struct sevstr))) {
405 
406 			    /* Fill in structure */
407 			    rtnval->sevkywd = noesc(kywd);
408 			    rtnval->sevvalue = val;
409 			    rtnval->sevprstr = noesc(prstr);
410 			    rtnval->sevnext = NULL;
411 			}
412 
413 			done = TRUE;
414 
415 		    } else {
416 
417 			/* Invalid severity value, eat thru end of token */
418 			current = tokend;
419 			if (*(tokend = exttok(prstr, ":")) == ':')
420 			    current++;
421 		    }
422 
423 		} else {
424 
425 		    /* Invalid severity definition, eat thru end of token */
426 		    current = tokend;
427 		    if (*tokend == ':')
428 			current++;
429 		}
430 
431 	    } else {
432 
433 		/* End of string found */
434 		done = TRUE;
435 		leftoff = NULL;
436 	    }
437 
438 	} /* while (!done) */
439 
440 	/* Finished */
441 	return(rtnval);
442 }
443 
444 /*
445  * fmtmsg [-a action] [-c classification] [-l label] [-s severity] [-t tag]
446  *        [-u subclass[,subclass[,...]]] [text]
447  *
448  * Function:
449  *	Writes a message in the standard format.  Typically used by shell
450  *	scripts to write error messages to the user.
451  *
452  * Arguments:
453  *	text		String that is the text of the message
454  *
455  * Options:
456  *   -a action		String that describes user action to take to
457  *			correct the situation
458  *   -c classification	Keyword that identifies the type of the message
459  *   -l label		String that identifies the source of the message
460  *   -s severity	Keyword that identifies the severity of the message
461  *   -t tag		String that identifies the message (use unclear)
462  *   -u sub_classes	Comma-list of keywords that refines the type of
463  *			the message
464  *
465  * Environment Variables Used:
466  *	MSGVERB		Defines the pieces of a message the user expects
467  *			to see.  It is a list of keywords separated by
468  *			colons (':').
469  *	SEV_LEVEL	Defines a list of auxiliary severity keywords, values,
470  *			and print-strings.  It is a list of fields separated
471  *			by colons (':').  Each field consists of three
472  *			elements, keyword, value (in octal, hex, or decimal),
473  *			and print-string, separated by commas (',').
474  *
475  * Needs:
476  *
477  * Open Issues:
478  */
479 
480 int
main(int argc,char * argv[])481 main(
482 	int	argc,			/* Argument count */
483 	char   *argv[]			/* Pointers to arguments */
484 )
485 {
486 
487 	/* Local automatic data */
488 
489 	long			class;		/* Classification (built) */
490 
491 	int			severity;	/* User specified severity */
492 	int			msgrtn;		/* Value returned by fmtmsg() */
493 	int			optchar;	/* Opt char on cmdline */
494 	int			exitval;	/* Value to return */
495 
496 	int			found;		/* FLAG, kywd found yet? */
497 	int			errflg;		/* FLAG, error seen in cmd */
498 	int			a_seen;		/* FLAG, -a option seen */
499 	int			c_seen;		/* FLAG, -c option seen */
500 	int			l_seen;		/* FLAG, -l option seen */
501 	int			s_seen;		/* FLAG, -s option seen */
502 	int			t_seen;		/* FLAG, -t option seen */
503 	int			u_seen;		/* FLAG, -u option seen */
504 	int			text_seen;	/* FLAG, text seen */
505 
506 	char		       *text = NULL;	/* Ptr to user's text */
507 	char		       *label = NULL;	/* Ptr to user's label */
508 	char		       *tag = NULL;	/* Ptr to user's tag */
509 	char		       *action = NULL;	/* Ptr to user's action str */
510 	char		       *sstr = NULL;	/* Ptr to -s (severity) arg */
511 	char		       *ustr = NULL;	/* Ptr to -u (subclass) arg */
512 	char		       *cstr = NULL;	/* Ptr to -c (class) arg */
513 	char		       *sevstrval;	/* Ptr to SEV_LEVEL argument */
514 	char		       *sevval;		/* Ptr to temp SEV_LEVEL arg */
515 	char		       *tokenptr;	/* Ptr to current token */
516 	char		       *cmdname;	/* Ptr to base command name */
517 	char		       *p;		/* Multipurpose ptr */
518 
519 	struct class_info      *class_info;	/* Ptr to class/subclass info structure */
520 	struct sev_info	       *sev_info;	/* Ptr to severity info struct */
521 	struct sevstr	       *penvsev;	/* Ptr to SEV_LEVEL values */
522 
523 
524 
525 	/*
526 	 * fmtmsg
527 	 */
528 
529 
530 	/* Initializations */
531 
532 
533 	/* Extract the base command name from the command */
534 	if ((p = strrchr(argv[0], '/')) == NULL)
535 	    cmdname = argv[0];
536 	else
537 	    cmdname = p+1;
538 
539 	/* Build the label for messages from "fmtmsg" */
540 	snprintf(labelbuf, sizeof (labelbuf), "UX:%s", cmdname);
541 
542 
543 	/*
544 	 * Extract arguments from the command line
545 	 */
546 
547 	/* Initializations */
548 
549 	opterr = 0;			/* Disable messages from getopt() */
550 	errflg = FALSE;			/* No errors seen yet */
551 
552 	a_seen = FALSE;			/* No action (-a) text seen yet */
553 	c_seen = FALSE;			/* No classification (-c) seen yet */
554 	l_seen = FALSE;			/* No label (-l) seen yet */
555 	s_seen = FALSE;			/* No severity (-s) seen yet */
556 	t_seen = FALSE;			/* No tag (-t) seen yet */
557 	u_seen = FALSE;			/* No subclass (-u) seen yet */
558 	text_seen = FALSE;		/* No text seen yet */
559 
560 
561 	/*
562 	 * If only the command name was used, write out a usage string to
563 	 * the standard output file.
564 	 */
565 
566 	if (argc == 1) {
567 	    fprintf(stderr, BIGUSAGE, cmdname);
568 	    exit(0);
569 	}
570 
571 
572 	/* Parce command line */
573 	while (((optchar = getopt(argc, argv, "a:c:l:s:t:u:")) != EOF) &&
574 	       !errflg) {
575 
576 	    switch(optchar) {
577 
578 	    case 'a':		/* -a actiontext */
579 		if (!a_seen) {
580 		    action = optarg;
581 		    a_seen = TRUE;
582 		} else errflg = TRUE;
583 		break;
584 
585 	    case 'c':		/* -c classification */
586 		if (!c_seen) {
587 		    cstr = optarg;
588 		    c_seen = TRUE;
589 		} else errflg = TRUE;
590 		break;
591 
592 	    case 'l':		/* -l label */
593 		if (!l_seen) {
594 		    label = optarg;
595 		    l_seen = TRUE;
596 		} else errflg = TRUE;
597 		break;
598 
599 	    case 's':		/* -s severity */
600 		if (!s_seen) {
601 		    sstr = optarg;
602 		    s_seen = TRUE;
603 		} else errflg = TRUE;
604 		break;
605 
606 	    case 't':		/* -t tag */
607 		if (!t_seen) {
608 		    tag = optarg;
609 		    t_seen = TRUE;
610 		} else errflg = TRUE;
611 		break;
612 
613 	    case 'u':		/* -u subclasslist */
614 		if (!u_seen) {
615 		    ustr = optarg;
616 		    u_seen = TRUE;
617 		} else errflg = TRUE;
618 		break;
619 
620 	    case '?':		/* -? or unknown option */
621 	    default:
622 		errflg = TRUE;
623 		break;
624 
625 	    } /* esac */
626 	}
627 
628 
629 	/* Get the text */
630 	if (!errflg) {
631 	    if (argc == (optind+1)) {
632 		text = argv[optind];
633 		text_seen = TRUE;
634 	    }
635 	    else if (argc != optind) {
636 		errflg = TRUE;
637 	    }
638 	}
639 
640 
641 	/* Report syntax errors */
642 	if (errflg) {
643 	    fprintf(stderr, BIGUSAGE, cmdname);
644 	    exit(1);
645 	}
646 
647 
648 	/*
649 	 * Classification.
650 	 */
651 
652 	class = 0L;
653 	if (c_seen) {
654 
655 	    /* Search for keyword in list */
656 	    for (class_info = &classes[0] ;
657 		 (class_info->keyword != NULL) &&
658 		 (strcmp(cstr, class_info->keyword)) ;
659 		 class_info++) ;
660 
661 	    /* If invalid (keyword unknown), write a message and exit */
662 	    if (class_info->keyword == NULL) {
663 		snprintf(msgbuf, sizeof (msgbuf),
664 			"Invalid class: %s", cstr);
665 		fmtmsg(CLASS, labelbuf, MM_ERROR, msgbuf,
666 		              MM_NULLACT, MM_NULLTAG);
667 		exit(1);
668 	    }
669 
670 	    /* Save classification */
671 	    class = class_info->value;
672 
673 	}
674 
675 
676 	/*
677 	 * Subclassification.
678 	 */
679 
680 	if (u_seen) {
681 
682 	    errflg = FALSE;
683 	    p = strcpy(malloc(strlen(ustr)+1), ustr);
684 	    if ((tokenptr = strtok(p, ",")) == NULL) errflg = TRUE;
685 	    else do {
686 
687 		/* Got a keyword.  Look for it in keyword list */
688 		for (class_info = subclasses ;
689 		     (class_info->keyword != NULL) &&
690 		     (strcmp(tokenptr, class_info->keyword) != 0) ;
691 		     class_info++) ;
692 
693 		/* If found in list and no conflict, remember in class */
694 		if ((class_info->keyword != NULL) && ((class & class_info->conflict) == 0L))
695 		    class |= class_info->value;
696 		else
697 		    errflg = TRUE;
698 
699 	    } while (!errflg && ((tokenptr = strtok(NULL, ",")) != NULL)) ;
700 
701 	    if (errflg) {
702 		snprintf(msgbuf, sizeof (msgbuf),
703 			"Invalid subclass: %s", ustr);
704 		fmtmsg(CLASS, labelbuf, MM_ERROR, msgbuf,
705 		              MM_NULLACT, MM_NULLTAG);
706 		exit(1);
707 	    }
708 
709 	}
710 
711 	if (!c_seen & !u_seen) class = MM_NULLMC;
712 
713 
714 
715 	/*
716 	 * Severity.
717 	 */
718 
719 	if (s_seen) {
720 
721 	    /* If the severity is specified as a number, use that value */
722 	    severity = strtol(sstr, &p, 10);
723 	    if (*p || (strlen(sstr) == 0)) {
724 
725 		/* Look for the standard severities */
726 		for (sev_info = severities ;
727 		     (sev_info->keyword != NULL) &&
728 		     (strcmp(sstr, sev_info->keyword)) ;
729 		     sev_info++) ;
730 
731 		/*
732 		 * If the "severity" argument is one of the standard keywords,
733 		 * remember it for fmtmsg().  Otherwise, look at the SEV_LEVEL
734 		 * environment variable for severity extensions.
735 		 */
736 
737 		/* If the keyword is one of the standard ones, save severity */
738 		if (sev_info->keyword != NULL) severity = sev_info->value;
739 
740 		else {
741 
742 		    /*
743 		     * Severity keyword may be one of the extended set, if any.
744 		     */
745 
746 		    /* Get the value of the SEV_LEVEL environment variable */
747 		    found = FALSE;
748 		    if ((sevstrval = getenv(SEV_LEVEL)) != NULL) {
749 			sevval = malloc(strlen(sevstrval)+1);
750 			penvsev = getauxsevs(strcpy(sevval, sevstrval));
751 			if (penvsev != NULL) do {
752 			    if (strcmp(penvsev->sevkywd, sstr) == 0) {
753 				severity = penvsev->sevvalue;
754 				found = TRUE;
755 			    }
756 			    else {
757 				free(penvsev);
758 				penvsev = getauxsevs(NULL);
759 			    }
760 			} while (!found && (penvsev != NULL));
761 
762 			if (found) free(penvsev);
763 			free(sevval);
764 		    }
765 
766 		    if (!found) {
767 			snprintf(msgbuf, sizeof (msgbuf),
768 				"Invalid severity: %s", sstr);
769 			fmtmsg(CLASS, labelbuf, MM_ERROR, msgbuf,
770 				      MM_NULLACT, MM_NULLTAG);
771 			exit(1);
772 		    }
773 
774 		}  /* <severity> is not one of the standard severities */
775 
776 	    }  /* <severity> is not numeric */
777 
778 	}  /* if (s_seen) */
779 
780 	else severity = MM_NULLSEV;
781 
782 
783 	/*
784 	 * Other options
785 	 */
786 
787 	if (!a_seen) action = MM_NULLACT;
788 	if (!l_seen) label = MM_NULLLBL;
789 	if (!t_seen) tag = MM_NULLTAG;
790 	if (!text_seen) text = MM_NULLTXT;
791 
792 
793 	/*
794 	 * Write the message
795 	 */
796 
797 	msgrtn = fmtmsg(class, label, severity, text, action ,tag);
798 
799 
800 	/*
801 	 * Return appropriate value to the shell (or wherever)
802 	 */
803 
804 	exitval = 0;
805 	if (msgrtn == MM_NOTOK) exitval = 32;
806 	else {
807 	    if (msgrtn & MM_NOMSG) exitval += 2;
808 	    if (msgrtn & MM_NOCON) exitval += 4;
809 	}
810 
811 	return(exitval);
812 }
813