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