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 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27#include <ctype.h>
28#include <stdio.h>
29#include <unistd.h>
30#include <stdlib.h>
31#include <string.h>
32#include <sys/time.h>
33
34#include <fcode/private.h>
35#include <fcode/log.h>
36
37
38static variable_t verbose_emit;
39
40void
41do_verbose_emit(fcode_env_t *env)
42{
43	verbose_emit ^= 1;
44}
45
46/*
47 * Internal "emit".
48 * Note log_emit gathers up characters and issues a syslog or write to
49 * error log file if enabled.
50 */
51void
52do_emit(fcode_env_t *env, uchar_t c)
53{
54	if (verbose_emit)
55		log_message(MSG_ERROR, "emit(%x)\n", c);
56
57	if (c == '\n') {
58		env->output_column = 0;
59		env->output_line++;
60	} else if (c == '\r')
61		env->output_column = 0;
62	else
63		env->output_column++;
64	if (isatty(fileno(stdout))) {
65		if ((c >= 0x20 && c <= 0x7f) || c == '\n' || c == '\r' ||
66		    c == '\b')
67			(void) putchar(c);
68		else if (c < 0x20)
69			printf("@%c", c + '@');
70		else
71			printf("\\%x", c);
72		fflush(stdout);
73	}
74	log_emit(c);
75}
76
77void
78system_message(fcode_env_t *env, char *msg)
79{
80	throw_from_fclib(env, 1, msg);
81}
82
83void
84emit(fcode_env_t *env)
85{
86	fstack_t d;
87
88	CHECK_DEPTH(env, 1, "emit");
89	d = POP(DS);
90	do_emit(env, d);
91}
92
93#include <sys/time.h>
94
95/*
96 * 'key?' - abort if stdin is not a tty.
97 */
98void
99keyquestion(fcode_env_t *env)
100{
101	struct timeval timeval;
102	fd_set readfds;
103
104	if (isatty(fileno(stdin))) {
105		FD_ZERO(&readfds);
106		FD_SET(fileno(stdin), &readfds);
107		timeval.tv_sec = 0;
108		timeval.tv_usec = 1000;
109		(void) select(fileno(stdin) + 1, &readfds, NULL, NULL,
110		    &timeval);
111		if (FD_ISSET(fileno(stdin), &readfds))
112			PUSH(DS, TRUE);
113		else
114			PUSH(DS, FALSE);
115	} else
116		forth_abort(env, "'key?' called in non-interactive mode");
117}
118
119/*
120 * 'key' - abort if stdin is not a tty, will block on read if char not avail.
121 */
122void
123key(fcode_env_t *env)
124{
125	uchar_t c;
126
127	if (isatty(fileno(stdin))) {
128		(void) read(fileno(stdin), &c, 1);
129		PUSH(DS, c);
130	} else
131		forth_abort(env, "'key' called in non-interactive mode");
132}
133
134void
135type(fcode_env_t *env)
136{
137	int len;
138	char *ptr;
139
140	CHECK_DEPTH(env, 2, "type");
141	ptr = pop_a_string(env, &len);
142	while (len--)
143		do_emit(env, *ptr++);
144}
145
146void
147paren_cr(fcode_env_t *env)
148{
149	do_emit(env, '\r');
150}
151
152void
153fc_crlf(fcode_env_t *env)
154{
155	do_emit(env, '\n');
156}
157
158void
159fc_num_out(fcode_env_t *env)
160{
161	PUSH(DS, (fstack_t)(&env->output_column));
162}
163
164void
165fc_num_line(fcode_env_t *env)
166{
167	PUSH(DS, (fstack_t)(&env->output_line));
168}
169
170void
171expect(fcode_env_t *env)
172{
173	char *buf, *rbuf;
174	int len;
175
176	CHECK_DEPTH(env, 2, "expect");
177	buf = pop_a_string(env, &len);
178	read_line(env);
179	rbuf = pop_a_string(env, NULL);
180	if (rbuf) {
181		(void) strcpy(buf, rbuf);
182		env->span = strlen(buf);
183	} else
184		env->span = 0;
185}
186
187void
188span(fcode_env_t *env)
189{
190	PUSH(DS, (fstack_t)&env->span);
191}
192
193void
194do_ms(fcode_env_t *env)
195{
196	fstack_t d;
197	timespec_t rqtp;
198
199	CHECK_DEPTH(env, 1, "ms");
200	d = POP(DS);
201	if (d) {
202		rqtp.tv_sec = 0;
203		rqtp.tv_nsec = d*1000*1000;
204		(void) nanosleep(&rqtp, 0);
205	}
206}
207
208void
209do_get_msecs(fcode_env_t *env)
210{
211	struct timeval tp;
212	long ms;
213	timespec_t rqtp;
214
215	(void) gettimeofday(&tp, NULL);
216	ms = (tp.tv_usec/1000) + (tp.tv_sec * 1000);
217	PUSH(DS, (fstack_t)ms);
218	rqtp.tv_sec = 0;
219	rqtp.tv_nsec = 1000*1000;
220	(void) nanosleep(&rqtp, 0);
221}
222
223#define	CMN_MSG_SIZE	256
224#define	CMN_MAX_DIGITS	3
225
226typedef struct CMN_MSG_T cmn_msg_t;
227
228struct CMN_MSG_T {
229	char		buf[CMN_MSG_SIZE];
230	int		level;
231	int		len;
232	cmn_msg_t	*prev;
233	cmn_msg_t	*next;
234};
235
236typedef struct CMN_FMT_T cmn_fmt_t;
237
238struct CMN_FMT_T {
239	int	fwidth;	/* format field width */
240	int	cwidth; /* column width specified in format */
241	char	format; /* format type */
242};
243
244static cmn_msg_t	*root = NULL;
245static int		cmn_msg_level = 0;
246
247/*
248 *	validfmt()
249 *
250 * Called by fmt_str() function to validate and extract formatting
251 * information from the supplied input buffer.
252 *
253 * Supported formats are:
254 *	%c - character
255 *	%d - signed decimal
256 *	%x - unsigned hex
257 *	%s - string
258 *	%ld - signed 64 bit data
259 *	%lx - unsigned 64 bit data
260 *	%p - unsigned 64 bit data(pointer)
261 *	%% - print as single "%" character
262 *
263 * Return values are:
264 *	0  - valid formatting
265 *	1  - invalid formatting found in the input buffer
266 *	-1 - NULL pointer passed in for caller's receptacle
267 *
268 *
269 * For valid formatting, caller's supplied cmn_fmt_t elements are
270 * filled in:
271 *	fwidth:
272 *		> 0 - returned value is the field width
273 *		< 0 - returned value is negation of field width for
274 *			64 bit data formats
275 *	cwidth:
276 *	  formatted column width(if specified), otherwise 0
277 *
278 *	format:
279 *	  contains the formatting(single) character
280 */
281static int
282validfmt(char *fmt, cmn_fmt_t *cfstr)
283{
284	int	isll = 0;
285	int	*fwidth, *cwidth;
286	char	*format;
287	char	*dig1, *dig2;
288	char	cdigs[CMN_MAX_DIGITS+1];
289
290	if (cfstr == NULL)
291		return (-1);
292
293	fwidth = &cfstr->fwidth;
294	cwidth = &cfstr->cwidth;
295	format = &cfstr->format;
296	*fwidth = *cwidth = 0;
297	*format = '\0';
298	dig1 = dig2 = NULL;
299
300	/* check for left justification character */
301	if (*fmt == '-') {
302		fmt++;
303		(*fwidth)++;
304
305		/* check for column width specification */
306		if (isdigit(*fmt))
307			dig1 = fmt;	/* save ptr to first digit */
308		while (isdigit(*fmt)) {
309			fmt++;
310			(*fwidth)++;
311		}
312		/* if ljust specified w/o size, return format error */
313		if (*fwidth == 1) {
314			return (1);
315		}
316		dig2 = fmt;		/* save ptr to last digit + 1 */
317	} else {
318		/* check for column width specification */
319		if (isdigit(*fmt)) {
320			dig1 = fmt;	/* save ptr to first digit */
321			while (isdigit(*fmt)) {
322				fmt++;
323				(*fwidth)++;
324			}
325			dig2 = fmt;	/* save ptr to last digit + 1 */
326		}
327	}
328
329	/* if a column width was specified, save it in caller's struct */
330	if (dig1) {
331		int nbytes;
332
333		nbytes = dig2 - dig1;
334		/* if too many digits in the width return error */
335		if (nbytes > CMN_MAX_DIGITS)
336			return (1);
337		(void) strncpy(cdigs, dig1, nbytes);
338		cdigs[nbytes] = 0;
339		*cwidth = atoi(cdigs);
340	}
341
342	/* check for long format specifier */
343	if (*fmt == 'l') {
344		fmt++;
345		(*fwidth)++;
346		isll = 1;
347	}
348
349	/* process by specific format type */
350	switch (*fmt) {
351	case 'c':
352	case 's':
353	case '%':
354		if (isll)
355			return (1);
356		/* FALLTHROUGH */
357	case 'd':
358	case 'x':
359		*format = *fmt;
360		(*fwidth)++;
361		break;
362	case 'p':
363		isll = 1;		/* uses 64 bit format */
364		*format = *fmt;
365		(*fwidth)++;
366		break;
367	default:
368		return (1);		/* unknown format type */
369	}
370	if (isll) {
371		*fwidth *= -1;
372	}
373	return (0);
374}
375
376/*
377 *	fmt_args()
378 *
379 * Called by fmt_str() to setup arguments for subsequent snprintf()
380 * calls.  For cases not involving column width limitations, processing
381 * simply POPs the data stack as required to setup caller's arg(or
382 * llarg, as appropriate). When a column width is specified for output,
383 * a temporary buffer is constructed to contain snprintf() generated
384 * output for the argument. Testing is then performed to determine if
385 * the specified column width will require truncation of the output.
386 * If so, truncation of least significant digits is performed as
387 * necessary, and caller's arg(or llarg) is adjusted to obtain the
388 * specified column width.
389 *
390 */
391
392static void
393fmt_args(fcode_env_t *env, int cw, int fw, char format, long *arg,
394    long long *llarg)
395{
396	char	*cbuf;
397	char	snf[3];
398	int	cbsize;
399	int	cnv = 10, ndigits = 0;
400
401	if (fw > 0) {	/* check for normal (not long) formats */
402
403		/* initialize format string for snprintf call */
404		snf[0] = '%';
405		snf[1] = format;
406		snf[2] = 0;
407
408		/* process by format type */
409		switch (format) {
410		case 'x':
411			cnv = 16;
412			/* FALLTHROUGH */
413		case 'd':
414		case 'c':
415		case 'p':
416			*arg = POP(DS);
417			break;
418		case 's':
419			POP(DS);
420			*arg = POP(DS);
421			break;
422		case '%':
423			return;
424		default:
425			log_message(MSG_ERROR,
426			    "fmt_args:invalid format type! (%s)\n",
427			    &format);
428			return;
429		}
430
431		/* check if a column width was specified */
432		if (cw) {
433			/* allocate a scratch buffer */
434			cbsize = 2*(sizeof (long long)) + 1;
435			cbuf = MALLOC(cbsize);
436
437			if (snprintf(cbuf, cbsize, snf, *arg) < 0)
438				log_message(MSG_ERROR,
439				    "fmt_args: snprintf output error\n");
440			while ((cbuf[ndigits] != '\0') &&
441			    (ndigits < cbsize))
442				ndigits++;
443
444			/* if truncation is necessary, do it */
445			if (ndigits > cw) {
446				cbuf[cw] = 0;
447				if (format == 's') {
448					char *str;
449					str = (char *)*arg;
450					str[cw] = 0;
451				} else
452					*arg = strtol(cbuf, (char **)NULL, cnv);
453			}
454			free(cbuf);
455		}
456
457	} else {	/* process long formats */
458
459		*llarg = POP(DS);
460
461		/* check if a column width was specified */
462		if (cw) {
463			/* allocate a scratch buffer */
464			cbsize = 2*(sizeof (long long)) + 1;
465			cbuf = MALLOC(cbsize);
466
467			switch (format) {
468			case 'p':
469				cnv = 16;
470				if (snprintf(cbuf, cbsize, "%p", *llarg) < 0)
471					log_message(MSG_ERROR,
472					    "fmt_args: snprintf error\n");
473				break;
474			case 'x':
475				cnv = 16;
476				if (snprintf(cbuf, cbsize, "%lx", *llarg) < 0)
477					log_message(MSG_ERROR,
478					    "fmt_args: snprintf error\n");
479				break;
480			case 'd':
481				if (snprintf(cbuf, cbsize, "%ld", *llarg) < 0)
482					log_message(MSG_ERROR,
483					    "fmt_args: snprintf error\n");
484				break;
485			default:
486				log_message(MSG_ERROR,
487				    "invalid long format type! (l%s)\n",
488				    &format);
489				free(cbuf);
490				return;
491			}
492			while ((cbuf[ndigits] != '\0') &&
493			    (ndigits < cbsize)) {
494				ndigits++;
495			}
496			/* if truncation is necessary, do it */
497			if (ndigits > cw) {
498				cbuf[cw] = 0;
499				*llarg = strtoll(cbuf, (char **)NULL, cnv);
500			}
501			free(cbuf);
502		}
503	}
504}
505
506/*
507 *	fmt_str()
508 *
509 * Extracts text from caller's input buffer, processes explicit
510 * formatting as necessary, and outputs formatted text to caller's
511 * receptacle.
512 *
513 *	env  - pointer to caller's fcode environment
514 *	fmt  - pointer to caller's input buffr
515 *	fmtbuf - ponter to caller's receptacle buffer
516 *	bsize - size of caller's fmtbuf buffer
517 *
518 * This function performs an initial test to determine if caller's
519 * input buffer contains formatting(specified by presence of "%")
520 * in the buffer.  If so, validfmt() function is called to verify
521 * the formatting, after which the buffer is processed according
522 * to the field width specified by validfmt() output.  Special
523 * processing is required when caller's buffer contains a double
524 * "%" ("%%"), in which case the second "%" is accepted as normal
525 * text.
526 */
527
528static void
529fmt_str(fcode_env_t *env, char *fmt, char *fmtbuf, int bsize)
530{
531	char	tbuf[CMN_MSG_SIZE];
532	char	*fmptr, *pct;
533	int	l, cw, fw, bytes;
534	long	arg;
535	long long llarg;
536
537	*fmtbuf = 0;
538	if ((pct = strchr(fmt, '%')) != 0) {
539		cmn_fmt_t	cfstr;
540		int		vferr;
541
542		l = strlen(pct++);
543		vferr = validfmt(pct, &cfstr);
544		if (!vferr) {
545			fw = cfstr.fwidth;
546			cw = cfstr.cwidth;
547			fmptr = &cfstr.format;
548		} else {
549			if (vferr < 0) {
550			log_message(MSG_ERROR,
551			    "fmt_str: NULL ptr supplied to validfmt()\n");
552			return;
553			}
554
555			bytes = pct - fmt;
556			(void) strncpy(tbuf, fmt, bytes);
557			(void) strncpy(tbuf+bytes, "%", 1);
558			(void) strncpy(tbuf+bytes+1, fmt+bytes, 1);
559			bytes += 2;
560			tbuf[bytes] = 0;
561
562			log_message(MSG_ERROR,
563			    "fmt_str: invalid format type! (%s)\n",
564			    tbuf+bytes-3);
565
566			(void) strncpy(fmtbuf, tbuf, bsize);
567			return;
568		}
569
570		if (fw > 0) {	/* process normal (not long) formats */
571			bytes = pct - fmt + fw;
572			(void) strncpy(tbuf, fmt, bytes);
573			tbuf[bytes] = 0;
574		} else {
575			/* if here, fw must be a long format */
576			if (*fmptr == 'p') {
577				bytes = pct - fmt - fw;
578				(void) strncpy(tbuf, fmt, bytes);
579				tbuf[bytes] = 0;
580			} else {
581				bytes = pct - fmt - fw - 2;
582				(void) strncpy(tbuf, fmt, bytes);
583				tbuf[bytes] = 'l';
584				(void) strncpy(tbuf+bytes+1, fmt+bytes, 2);
585				tbuf[bytes+1+2] = 0;
586			}
587		}
588
589		/* if more input buffer to process, recurse */
590		if ((l - abs(fw)) != 0) {
591			fmt_str(env, pct+abs(fw), (tbuf + strlen(tbuf)),
592			    CMN_MSG_SIZE - strlen(tbuf));
593		}
594
595		/* call to extract args for snprintf() calls below */
596		fmt_args(env, cw, fw, *fmptr, &arg, &llarg);
597
598		if (fw > 0) {	/* process normal (not long) formats */
599			switch (*fmptr) {
600			case 'd':
601			case 'x':
602			case 'c':
603			case 's':
604			case 'p':
605				(void) snprintf(fmtbuf, bsize, tbuf, arg);
606				break;
607			case '%':
608				(void) snprintf(fmtbuf, bsize, tbuf);
609				break;
610			default:
611				log_message(MSG_ERROR,
612				    "fmt_str: invalid format (%s)\n",
613				    fmptr);
614				return;
615			}
616
617		} else	/* process long formats */
618			(void) snprintf(fmtbuf, bsize, tbuf, llarg);
619
620	} else
621		(void) strncpy(fmtbuf, fmt, bsize);
622}
623
624/*
625 *	fc_cmn_append()
626 *
627 * Pops data stack to obtain message text, and calls fmt_str()
628 * function to perform any message formatting necessary.
629 *
630 * This function is called from fc_cmn_end() or directly in
631 * processing a cmn-append token.  Since a pre-existing message
632 * context is assumed, initial checking is performed to verify
633 * its existence.
634 */
635
636void
637fc_cmn_append(fcode_env_t *env)
638{
639	int len;
640	char *str;
641
642	if (root == NULL) {
643		log_message(MSG_ERROR,
644		    "fc_cmn_append: no message context for append\n");
645		return;
646	}
647
648	len = POP(DS);
649	str = (char *)POP(DS);
650
651	if ((root->len + len) < CMN_MSG_SIZE) {
652		fmt_str(env, str, root->buf+root->len, CMN_MSG_SIZE -
653		    root->len);
654		root->len += len;
655	} else
656		log_message(MSG_ERROR,
657		    "fc_cmn_append: append exceeds max msg size\n");
658}
659
660/*
661 *	fc_cmn_end()
662 *
663 * Process ]cmn-end token to log the message initiated by a preceeding
664 * fc_cmn_start() call.
665 *
666 * Since nested cmn-xxx[ calls are supported, a test is made to determine
667 * if this is the final cmn-end of a nested sequence.  If so, or if
668 * there was no nesting, log_message() is called with the appropriate
669 * text buffer.  Otherwise, the root variable is adjusted to point to
670 * the preceeding message in the sequence and links in the list are
671 * updated. No logging is performed until the final ]cmn-end of the
672 * sequence is processed; then, messages are logged in FIFO order.
673 */
674void
675fc_cmn_end(fcode_env_t *env)
676{
677	cmn_msg_t *old;
678
679	if (root == 0) {
680		log_message(MSG_ERROR, "]cmn-end call w/o buffer\n");
681		return;
682	}
683
684	fc_cmn_append(env);
685
686	if (root->prev == 0) {
687		cmn_msg_t *next;
688		do {
689			log_message(root->level, "%s\n", root->buf);
690			next  = root->next;
691			free(root);
692			root = next;
693		} while (root);
694	} else {
695		old = root->prev;
696		old->next = root;
697		root = old;
698	}
699}
700
701/*
702 *	fc_cmn_start()
703 *
704 * Generic function to begin a common message.
705 *
706 * Allocates a new cmn_msg_t to associate with the message, and sets
707 * up initial text as specified by callers' inputs:
708 *
709 *	env  - pointer to caller's fcode environment
710 *	head - pointer to initial text portion of the message
711 *	path - flag to indicate if a device path is to be generated
712 */
713static void
714fc_cmn_start(fcode_env_t *env, char *head, int path)
715{
716	cmn_msg_t *new;
717	char		*dpath;
718
719	new = MALLOC(sizeof (cmn_msg_t));
720	new->prev = root;
721	if (root != 0)
722		root->next = new;
723	(void) strcpy(new->buf, head);
724	new->len = strlen(head);
725	if (path && env->current_device) {
726		dpath = get_path(env, env->current_device);
727		(void) strcpy(new->buf+new->len, dpath);
728		new->len += strlen(dpath);
729		(void) strncpy(new->buf+new->len++, ": ", 2);
730		++new->len;
731		free(dpath);
732	}
733	new->level = cmn_msg_level;
734	new->next = NULL;
735	root = new;
736}
737
738/*
739 *	fc_cmn_type()
740 *
741 * Process cmn-type[ token.
742 *
743 * Invokes fc_cmn_start() to create a message containing blank
744 * header and no device path information.
745 */
746void
747fc_cmn_type(fcode_env_t *env)
748{
749	cmn_msg_level = MSG_INFO;
750	fc_cmn_start(env, "", 0);
751}
752
753/*
754 *	fc_cmn_msg()
755 *
756 * Process cmn-msg[ token.
757 *
758 * Invokes fc_cmn_start() to create a message containing blank
759 * header but specifying device path information.
760 */
761void
762fc_cmn_msg(fcode_env_t *env)
763{
764
765	cmn_msg_level = MSG_INFO;
766	fc_cmn_start(env, "", 1);
767}
768
769/*
770 *	fc_cmn_note()
771 *
772 * Process cmn-note[ token.
773 *
774 * Invokes fc_cmn_start() to create a message with NOTICE stamping in
775 * the header and specification of device path information.
776 */
777void
778fc_cmn_note(fcode_env_t *env)
779{
780	cmn_msg_level = MSG_NOTE;
781	fc_cmn_start(env, "NOTICE: ", 1);
782}
783
784/*
785 *	fc_cmn_warn()
786 *
787 * Process cmn-warn[ token.
788 *
789 * Invokes fc_cmn_start() to create a message with WARNING stamping in
790 * the header and specification of device path information.
791 */
792void
793fc_cmn_warn(fcode_env_t *env)
794{
795	cmn_msg_level = MSG_WARN;
796	fc_cmn_start(env, "WARNING: ", 1);
797}
798
799/*
800 *	fc_cmn_error()
801 *
802 * Process cmn-error[ token.
803 *
804 * Invokes fc_cmn_start() to create a message with ERROR stamping in
805 * the header and specification of device path information.
806 */
807void
808fc_cmn_error(fcode_env_t *env)
809{
810	cmn_msg_level = MSG_ERROR;
811	fc_cmn_start(env, "ERROR: ", 1);
812}
813
814/*
815 *	fc_cmn_fatal()
816 *
817 * Process cmn-fatal[ token.
818 *
819 * Invokes fc_cmn_start() to create a message with FATAL stamping in
820 * the header and specification of device path information.
821 */
822void
823fc_cmn_fatal(fcode_env_t *env)
824{
825	cmn_msg_level = MSG_FATAL;
826	fc_cmn_start(env, "FATAL: ", 1);
827}
828
829#pragma init(_init)
830
831static void
832_init(void)
833{
834	fcode_env_t *env = initial_env;
835	ASSERT(env);
836	NOTICE;
837
838	ANSI(0x088, 0,		"span",			span);
839	ANSI(0x08a, 0,		"expect",		expect);
840
841	ANSI(0x08d, 0,		"key?",			keyquestion);
842	ANSI(0x08e, 0,		"key",			key);
843	ANSI(0x08f, 0,		"emit",			emit);
844	ANSI(0x090, 0,		"type",			type);
845	ANSI(0x091, 0,		"(cr",			paren_cr);
846	ANSI(0x092, 0,		"cr",			fc_crlf);
847	ANSI(0x093, 0,		"#out",			fc_num_out);
848	ANSI(0x094, 0,		"#line",		fc_num_line);
849
850	FCODE(0x125, 0,		"get-msecs",		do_get_msecs);
851	FCODE(0x126, 0,		"ms",			do_ms);
852
853	FORTH(0,		"verbose-emit",		do_verbose_emit);
854	FCODE(0x7e9, 0,		"cmn-fatal[",		fc_cmn_fatal);
855	FCODE(0x7ea, 0,		"cmn-error[",		fc_cmn_error);
856	FCODE(0x7eb, 0,		"cmn-warn[",		fc_cmn_warn);
857	FCODE(0x7ec, 0,		"cmn-note[",		fc_cmn_note);
858	FCODE(0x7ed, 0,		"cmn-type[",		fc_cmn_type);
859	FCODE(0x7ee, 0,		"cmn-append",		fc_cmn_append);
860	FCODE(0x7ef, 0,		"]cmn-end",		fc_cmn_end);
861	FCODE(0x7f0, 0,		"cmn-msg[",		fc_cmn_msg);
862}
863