xref: /illumos-gate/usr/src/cmd/mdb/common/mdb/mdb_io.c (revision 18c2aff776a775d34a4c9893a4c72e0434d68e36)
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 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * MDB uses its own enhanced standard i/o mechanism for all input and output.
31  * This file provides the underpinnings of this mechanism, including the
32  * printf-style formatting code, the output pager, and APIs for raw input
33  * and output.  This mechanism is used throughout the debugger for everything
34  * from simple sprintf and printf-style formatting, to input to the lexer
35  * and parser, to raw file i/o for reading ELF files.  In general, we divide
36  * our i/o implementation into two parts:
37  *
38  * (1) An i/o buffer (mdb_iob_t) provides buffered read or write capabilities,
39  * as well as access to formatting and the ability to invoke a pager.  The
40  * buffer is constructed explicitly for use in either reading or writing; it
41  * may not be used for both simultaneously.
42  *
43  * (2) Each i/o buffer is associated with an underlying i/o backend (mdb_io_t).
44  * The backend provides, through an ops-vector, equivalents for the standard
45  * read, write, lseek, ioctl, and close operations.  In addition, the backend
46  * can provide an IOP_NAME entry point for returning a name for the backend,
47  * IOP_LINK and IOP_UNLINK entry points that are called when the backend is
48  * connected or disconnected from an mdb_iob_t, and an IOP_SETATTR entry point
49  * for manipulating terminal attributes.
50  *
51  * The i/o objects themselves are reference counted so that more than one i/o
52  * buffer may make use of the same i/o backend.  In addition, each buffer
53  * provides the ability to push or pop backends to interpose on input or output
54  * behavior.  We make use of this, for example, to implement interactive
55  * session logging.  Normally, the stdout iob has a backend that is either
56  * file descriptor 1, or a terminal i/o backend associated with the tty.
57  * However, we can push a log i/o backend on top that multiplexes stdout to
58  * the original back-end and another backend that writes to a log file.  The
59  * use of i/o backends is also used for simplifying tasks such as making
60  * lex and yacc read from strings for mdb_eval(), and making our ELF file
61  * processing code read executable "files" from a crash dump via kvm_uread.
62  *
63  * Additionally, the formatting code provides auto-wrap and indent facilities
64  * that are necessary for compatibility with adb macro formatting.  In auto-
65  * wrap mode, the formatting code examines each new chunk of output to determine
66  * if it will fit on the current line.  If not, instead of having the chunk
67  * divided between the current line of output and the next, the auto-wrap
68  * code will automatically output a newline, auto-indent the next line,
69  * and then continue.  Auto-indent is implemented by simply prepending a number
70  * of blanks equal to iob_margin to the start of each line.  The margin is
71  * inserted when the iob is created, and following each flush of the buffer.
72  */
73 
74 #include <sys/types.h>
75 #include <sys/termios.h>
76 #include <stdarg.h>
77 #include <arpa/inet.h>
78 #include <sys/socket.h>
79 
80 #include <mdb/mdb_types.h>
81 #include <mdb/mdb_argvec.h>
82 #include <mdb/mdb_stdlib.h>
83 #include <mdb/mdb_string.h>
84 #include <mdb/mdb_target.h>
85 #include <mdb/mdb_signal.h>
86 #include <mdb/mdb_debug.h>
87 #include <mdb/mdb_io_impl.h>
88 #include <mdb/mdb_modapi.h>
89 #include <mdb/mdb_demangle.h>
90 #include <mdb/mdb_err.h>
91 #include <mdb/mdb_nv.h>
92 #include <mdb/mdb_frame.h>
93 #include <mdb/mdb_lex.h>
94 #include <mdb/mdb.h>
95 
96 /*
97  * Define list of possible integer sizes for conversion routines:
98  */
99 typedef enum {
100 	SZ_SHORT,		/* format %h? */
101 	SZ_INT,			/* format %? */
102 	SZ_LONG,		/* format %l? */
103 	SZ_LONGLONG		/* format %ll? */
104 } intsize_t;
105 
106 /*
107  * The iob snprintf family of functions makes use of a special "sprintf
108  * buffer" i/o backend in order to provide the appropriate snprintf semantics.
109  * This structure is maintained as the backend-specific private storage,
110  * and its use is described in more detail below (see spbuf_write()).
111  */
112 typedef struct {
113 	char *spb_buf;		/* pointer to underlying buffer */
114 	size_t spb_bufsiz;	/* length of underlying buffer */
115 	size_t spb_total;	/* total of all bytes passed via IOP_WRITE */
116 } spbuf_t;
117 
118 /*
119  * Define VA_ARG macro for grabbing the next datum to format for the printf
120  * family of functions.  We use VA_ARG so that we can support two kinds of
121  * argument lists: the va_list type supplied by <stdarg.h> used for printf and
122  * vprintf, and an array of mdb_arg_t structures, which we expect will be
123  * either type STRING or IMMEDIATE.  The vec_arg function takes care of
124  * handling the mdb_arg_t case.
125  */
126 
127 typedef enum {
128 	VAT_VARARGS,		/* va_list is a va_list */
129 	VAT_ARGVEC		/* va_list is a const mdb_arg_t[] in disguise */
130 } vatype_t;
131 
132 typedef struct {
133 	vatype_t val_type;
134 	union {
135 		va_list	_val_valist;
136 		const mdb_arg_t *_val_argv;
137 	} _val_u;
138 } varglist_t;
139 
140 #define	val_valist	_val_u._val_valist
141 #define	val_argv	_val_u._val_argv
142 
143 #define	VA_ARG(ap, type) ((ap->val_type == VAT_VARARGS) ? \
144 	va_arg(ap->val_valist, type) : (type)vec_arg(&ap->val_argv))
145 #define	VA_PTRARG(ap) ((ap->val_type == VAT_VARARGS) ? \
146 	(void *)va_arg(ap->val_valist, uintptr_t) : \
147 	(void *)(uintptr_t)vec_arg(&ap->val_argv))
148 
149 /*
150  * Define macro for converting char constant to Ctrl-char equivalent:
151  */
152 #ifndef CTRL
153 #define	CTRL(c)	((c) & 0x01f)
154 #endif
155 
156 /*
157  * Define macro for determining if we should automatically wrap to the next
158  * line of output, based on the amount of consumed buffer space and the
159  * specified size of the next thing to be inserted (n).
160  */
161 #define	IOB_WRAPNOW(iob, n)	\
162 	(((iob)->iob_flags & MDB_IOB_AUTOWRAP) && ((iob)->iob_nbytes != 0) && \
163 	((n) + (iob)->iob_nbytes > (iob)->iob_cols))
164 
165 /*
166  * Define prompt string and string to erase prompt string for iob_pager
167  * function, which is invoked if the pager is enabled on an i/o buffer
168  * and we're about to print a line which would be the last on the screen.
169  */
170 
171 static const char io_prompt[] = ">> More [<space>, <cr>, q, n, c, a] ? ";
172 static const char io_perase[] = "                                      ";
173 
174 static const char io_pbcksp[] =
175 /*CSTYLED*/
176 "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b";
177 
178 static const size_t io_promptlen = sizeof (io_prompt) - 1;
179 static const size_t io_peraselen = sizeof (io_perase) - 1;
180 static const size_t io_pbcksplen = sizeof (io_pbcksp) - 1;
181 
182 static ssize_t
183 iob_write(mdb_iob_t *iob, mdb_io_t *io, const void *buf, size_t n)
184 {
185 	ssize_t resid = n;
186 	ssize_t len;
187 
188 	while (resid != 0) {
189 		if ((len = IOP_WRITE(io, buf, resid)) <= 0)
190 			break;
191 
192 		buf = (char *)buf + len;
193 		resid -= len;
194 	}
195 
196 	/*
197 	 * Note that if we had a partial write before an error, we still want
198 	 * to return the fact something was written.  The caller will get an
199 	 * error next time it tries to write anything.
200 	 */
201 	if (resid == n && n != 0) {
202 		iob->iob_flags |= MDB_IOB_ERR;
203 		return (-1);
204 	}
205 
206 	return (n - resid);
207 }
208 
209 static ssize_t
210 iob_read(mdb_iob_t *iob, mdb_io_t *io)
211 {
212 	ssize_t len;
213 
214 	ASSERT(iob->iob_nbytes == 0);
215 	len = IOP_READ(io, iob->iob_buf, iob->iob_bufsiz);
216 	iob->iob_bufp = &iob->iob_buf[0];
217 
218 	switch (len) {
219 	case -1:
220 		iob->iob_flags |= MDB_IOB_ERR;
221 		break;
222 	case 0:
223 		iob->iob_flags |= MDB_IOB_EOF;
224 		break;
225 	default:
226 		iob->iob_nbytes = len;
227 	}
228 
229 	return (len);
230 }
231 
232 /*ARGSUSED*/
233 static void
234 iob_winch(int sig, siginfo_t *sip, ucontext_t *ucp, void *data)
235 {
236 	siglongjmp(*((sigjmp_buf *)data), sig);
237 }
238 
239 static int
240 iob_pager(mdb_iob_t *iob)
241 {
242 	int status = 0;
243 	sigjmp_buf env;
244 	uchar_t c;
245 
246 	mdb_signal_f *termio_winch;
247 	void *termio_data;
248 	size_t old_rows;
249 
250 	if (iob->iob_pgp == NULL || (iob->iob_flags & MDB_IOB_PGCONT))
251 		return (0);
252 
253 	termio_winch = mdb_signal_gethandler(SIGWINCH, &termio_data);
254 	(void) mdb_signal_sethandler(SIGWINCH, iob_winch, &env);
255 
256 	if (sigsetjmp(env, 1) != 0) {
257 		/*
258 		 * Reset the cursor back to column zero before printing a new
259 		 * prompt, since its position is unreliable after a SIGWINCH.
260 		 */
261 		(void) iob_write(iob, iob->iob_pgp, "\r", sizeof (char));
262 		old_rows = iob->iob_rows;
263 
264 		/*
265 		 * If an existing SIGWINCH handler was present, call it.  We
266 		 * expect that this will be termio: the handler will read the
267 		 * new window size, and then resize this iob appropriately.
268 		 */
269 		if (termio_winch != (mdb_signal_f *)NULL)
270 			termio_winch(SIGWINCH, NULL, NULL, termio_data);
271 
272 		/*
273 		 * If the window has increased in size, we treat this like a
274 		 * request to fill out the new remainder of the page.
275 		 */
276 		if (iob->iob_rows > old_rows) {
277 			iob->iob_flags &= ~MDB_IOB_PGSINGLE;
278 			iob->iob_nlines = old_rows;
279 			status = 0;
280 			goto winch;
281 		}
282 	}
283 
284 	(void) iob_write(iob, iob->iob_pgp, io_prompt, io_promptlen);
285 
286 	for (;;) {
287 		if (IOP_READ(iob->iob_pgp, &c, sizeof (c)) != sizeof (c)) {
288 			status = MDB_ERR_PAGER;
289 			break;
290 		}
291 
292 		switch (c) {
293 		case 'N':
294 		case 'n':
295 		case '\n':
296 		case '\r':
297 			iob->iob_flags |= MDB_IOB_PGSINGLE;
298 			goto done;
299 
300 		case CTRL('c'):
301 		case CTRL('\\'):
302 		case 'Q':
303 		case 'q':
304 			mdb_iob_discard(iob);
305 			status = MDB_ERR_PAGER;
306 			goto done;
307 
308 		case 'A':
309 		case 'a':
310 			mdb_iob_discard(iob);
311 			status = MDB_ERR_ABORT;
312 			goto done;
313 
314 		case 'C':
315 		case 'c':
316 			iob->iob_flags |= MDB_IOB_PGCONT;
317 			/*FALLTHRU*/
318 
319 		case ' ':
320 			iob->iob_flags &= ~MDB_IOB_PGSINGLE;
321 			goto done;
322 		}
323 	}
324 
325 done:
326 	(void) iob_write(iob, iob->iob_pgp, io_pbcksp, io_pbcksplen);
327 winch:
328 	(void) iob_write(iob, iob->iob_pgp, io_perase, io_peraselen);
329 	(void) iob_write(iob, iob->iob_pgp, io_pbcksp, io_pbcksplen);
330 	(void) mdb_signal_sethandler(SIGWINCH, termio_winch, termio_data);
331 
332 	if ((iob->iob_flags & MDB_IOB_ERR) && status == 0)
333 		status = MDB_ERR_OUTPUT;
334 
335 	return (status);
336 }
337 
338 static void
339 iob_indent(mdb_iob_t *iob)
340 {
341 	if (iob->iob_nbytes == 0 && iob->iob_margin != 0 &&
342 	    (iob->iob_flags & MDB_IOB_INDENT)) {
343 		size_t i;
344 
345 		ASSERT(iob->iob_margin < iob->iob_cols);
346 		ASSERT(iob->iob_bufp == iob->iob_buf);
347 
348 		for (i = 0; i < iob->iob_margin; i++)
349 			*iob->iob_bufp++ = ' ';
350 
351 		iob->iob_nbytes = iob->iob_margin;
352 	}
353 }
354 
355 static void
356 iob_unindent(mdb_iob_t *iob)
357 {
358 	if (iob->iob_nbytes != 0 && iob->iob_nbytes == iob->iob_margin) {
359 		const char *p = iob->iob_buf;
360 
361 		while (p < &iob->iob_buf[iob->iob_margin]) {
362 			if (*p++ != ' ')
363 				return;
364 		}
365 
366 		iob->iob_bufp = &iob->iob_buf[0];
367 		iob->iob_nbytes = 0;
368 	}
369 }
370 
371 mdb_iob_t *
372 mdb_iob_create(mdb_io_t *io, uint_t flags)
373 {
374 	mdb_iob_t *iob = mdb_alloc(sizeof (mdb_iob_t), UM_SLEEP);
375 
376 	iob->iob_buf = mdb_alloc(BUFSIZ, UM_SLEEP);
377 	iob->iob_bufsiz = BUFSIZ;
378 	iob->iob_bufp = &iob->iob_buf[0];
379 	iob->iob_nbytes = 0;
380 	iob->iob_nlines = 0;
381 	iob->iob_lineno = 1;
382 	iob->iob_rows = MDB_IOB_DEFROWS;
383 	iob->iob_cols = MDB_IOB_DEFCOLS;
384 	iob->iob_tabstop = MDB_IOB_DEFTAB;
385 	iob->iob_margin = MDB_IOB_DEFMARGIN;
386 	iob->iob_flags = flags & ~(MDB_IOB_EOF|MDB_IOB_ERR) | MDB_IOB_AUTOWRAP;
387 	iob->iob_iop = mdb_io_hold(io);
388 	iob->iob_pgp = NULL;
389 	iob->iob_next = NULL;
390 
391 	IOP_LINK(io, iob);
392 	iob_indent(iob);
393 	return (iob);
394 }
395 
396 void
397 mdb_iob_pipe(mdb_iob_t **iobs, mdb_iobsvc_f *rdsvc, mdb_iobsvc_f *wrsvc)
398 {
399 	mdb_io_t *pio = mdb_pipeio_create(rdsvc, wrsvc);
400 	int i;
401 
402 	iobs[0] = mdb_iob_create(pio, MDB_IOB_RDONLY);
403 	iobs[1] = mdb_iob_create(pio, MDB_IOB_WRONLY);
404 
405 	for (i = 0; i < 2; i++) {
406 		iobs[i]->iob_flags &= ~MDB_IOB_AUTOWRAP;
407 		iobs[i]->iob_cols = iobs[i]->iob_bufsiz;
408 	}
409 }
410 
411 void
412 mdb_iob_destroy(mdb_iob_t *iob)
413 {
414 	/*
415 	 * Don't flush a pipe, since it may cause a context swith when the
416 	 * other side has already been destroyed.
417 	 */
418 	if (!mdb_iob_isapipe(iob))
419 		mdb_iob_flush(iob);
420 
421 	if (iob->iob_pgp != NULL)
422 		mdb_io_rele(iob->iob_pgp);
423 
424 	while (iob->iob_iop != NULL) {
425 		IOP_UNLINK(iob->iob_iop, iob);
426 		(void) mdb_iob_pop_io(iob);
427 	}
428 
429 	mdb_free(iob->iob_buf, iob->iob_bufsiz);
430 	mdb_free(iob, sizeof (mdb_iob_t));
431 }
432 
433 void
434 mdb_iob_discard(mdb_iob_t *iob)
435 {
436 	iob->iob_bufp = &iob->iob_buf[0];
437 	iob->iob_nbytes = 0;
438 }
439 
440 void
441 mdb_iob_flush(mdb_iob_t *iob)
442 {
443 	int pgerr = 0;
444 
445 	if (iob->iob_nbytes == 0)
446 		return; /* Nothing to do if buffer is empty */
447 
448 	if (iob->iob_flags & MDB_IOB_WRONLY) {
449 		if (iob->iob_flags & MDB_IOB_PGSINGLE) {
450 			iob->iob_flags &= ~MDB_IOB_PGSINGLE;
451 			iob->iob_nlines = 0;
452 			pgerr = iob_pager(iob);
453 
454 		} else if (iob->iob_nlines >= iob->iob_rows - 1) {
455 			iob->iob_nlines = 0;
456 			if (iob->iob_flags & MDB_IOB_PGENABLE)
457 				pgerr = iob_pager(iob);
458 		}
459 
460 		if (pgerr == 0) {
461 			/*
462 			 * We only jump out of the dcmd on error if the iob is
463 			 * m_out. Presumably, if a dcmd has opened a special
464 			 * file and is writing to it, it will handle errors
465 			 * properly.
466 			 */
467 			if (iob_write(iob, iob->iob_iop, iob->iob_buf,
468 			    iob->iob_nbytes) < 0 && iob == mdb.m_out)
469 				pgerr = MDB_ERR_OUTPUT;
470 			iob->iob_nlines++;
471 		}
472 	}
473 
474 	iob->iob_bufp = &iob->iob_buf[0];
475 	iob->iob_nbytes = 0;
476 	iob_indent(iob);
477 
478 	if (pgerr)
479 		longjmp(mdb.m_frame->f_pcb, pgerr);
480 }
481 
482 void
483 mdb_iob_nlflush(mdb_iob_t *iob)
484 {
485 	iob_unindent(iob);
486 
487 	if (iob->iob_nbytes != 0)
488 		mdb_iob_nl(iob);
489 	else
490 		iob_indent(iob);
491 }
492 
493 void
494 mdb_iob_push_io(mdb_iob_t *iob, mdb_io_t *io)
495 {
496 	ASSERT(io->io_next == NULL);
497 
498 	io->io_next = iob->iob_iop;
499 	iob->iob_iop = mdb_io_hold(io);
500 }
501 
502 mdb_io_t *
503 mdb_iob_pop_io(mdb_iob_t *iob)
504 {
505 	mdb_io_t *io = iob->iob_iop;
506 
507 	if (io != NULL) {
508 		iob->iob_iop = io->io_next;
509 		io->io_next = NULL;
510 		mdb_io_rele(io);
511 	}
512 
513 	return (io);
514 }
515 
516 void
517 mdb_iob_resize(mdb_iob_t *iob, size_t rows, size_t cols)
518 {
519 	if (cols > iob->iob_bufsiz)
520 		iob->iob_cols = iob->iob_bufsiz;
521 	else
522 		iob->iob_cols = cols != 0 ? cols : MDB_IOB_DEFCOLS;
523 
524 	iob->iob_rows = rows != 0 ? rows : MDB_IOB_DEFROWS;
525 }
526 
527 void
528 mdb_iob_setpager(mdb_iob_t *iob, mdb_io_t *pgio)
529 {
530 	struct winsize winsz;
531 
532 	if (iob->iob_pgp != NULL) {
533 		IOP_UNLINK(iob->iob_pgp, iob);
534 		mdb_io_rele(iob->iob_pgp);
535 	}
536 
537 	iob->iob_flags |= MDB_IOB_PGENABLE;
538 	iob->iob_flags &= ~(MDB_IOB_PGSINGLE | MDB_IOB_PGCONT);
539 	iob->iob_pgp = mdb_io_hold(pgio);
540 
541 	IOP_LINK(iob->iob_pgp, iob);
542 
543 	if (IOP_CTL(pgio, TIOCGWINSZ, &winsz) == 0)
544 		mdb_iob_resize(iob, (size_t)winsz.ws_row, (size_t)winsz.ws_col);
545 }
546 
547 void
548 mdb_iob_tabstop(mdb_iob_t *iob, size_t tabstop)
549 {
550 	iob->iob_tabstop = MIN(tabstop, iob->iob_cols - 1);
551 }
552 
553 void
554 mdb_iob_margin(mdb_iob_t *iob, size_t margin)
555 {
556 	iob_unindent(iob);
557 	iob->iob_margin = MIN(margin, iob->iob_cols - 1);
558 	iob_indent(iob);
559 }
560 
561 void
562 mdb_iob_setbuf(mdb_iob_t *iob, void *buf, size_t bufsiz)
563 {
564 	ASSERT(buf != NULL && bufsiz != 0);
565 
566 	mdb_free(iob->iob_buf, iob->iob_bufsiz);
567 	iob->iob_buf = buf;
568 	iob->iob_bufsiz = bufsiz;
569 
570 	if (iob->iob_flags & MDB_IOB_WRONLY)
571 		iob->iob_cols = MIN(iob->iob_cols, iob->iob_bufsiz);
572 }
573 
574 void
575 mdb_iob_clearlines(mdb_iob_t *iob)
576 {
577 	iob->iob_flags &= ~(MDB_IOB_PGSINGLE | MDB_IOB_PGCONT);
578 	iob->iob_nlines = 0;
579 }
580 
581 void
582 mdb_iob_setflags(mdb_iob_t *iob, uint_t flags)
583 {
584 	iob->iob_flags |= flags;
585 	if (flags & MDB_IOB_INDENT)
586 		iob_indent(iob);
587 }
588 
589 void
590 mdb_iob_clrflags(mdb_iob_t *iob, uint_t flags)
591 {
592 	iob->iob_flags &= ~flags;
593 	if (flags & MDB_IOB_INDENT)
594 		iob_unindent(iob);
595 }
596 
597 uint_t
598 mdb_iob_getflags(mdb_iob_t *iob)
599 {
600 	return (iob->iob_flags);
601 }
602 
603 static uintmax_t
604 vec_arg(const mdb_arg_t **app)
605 {
606 	uintmax_t value;
607 
608 	if ((*app)->a_type == MDB_TYPE_STRING)
609 		value = (uintmax_t)(uintptr_t)(*app)->a_un.a_str;
610 	else
611 		value = (*app)->a_un.a_val;
612 
613 	(*app)++;
614 	return (value);
615 }
616 
617 static const char *
618 iob_size2str(intsize_t size)
619 {
620 	switch (size) {
621 	case SZ_SHORT:
622 		return ("short");
623 	case SZ_INT:
624 		return ("int");
625 	case SZ_LONG:
626 		return ("long");
627 	case SZ_LONGLONG:
628 		return ("long long");
629 	}
630 	return ("");
631 }
632 
633 /*
634  * In order to simplify maintenance of the ::formats display, we provide an
635  * unparser for mdb_printf format strings that converts a simple format
636  * string with one specifier into a descriptive representation, e.g.
637  * mdb_iob_format2str("%llx") returns "hexadecimal long long".
638  */
639 const char *
640 mdb_iob_format2str(const char *format)
641 {
642 	intsize_t size = SZ_INT;
643 	const char *p;
644 
645 	static char buf[64];
646 
647 	buf[0] = '\0';
648 
649 	if ((p = strchr(format, '%')) == NULL)
650 		goto done;
651 
652 fmt_switch:
653 	switch (*++p) {
654 	case '0': case '1': case '2': case '3': case '4':
655 	case '5': case '6': case '7': case '8': case '9':
656 		while (*p >= '0' && *p <= '9')
657 			p++;
658 		p--;
659 		goto fmt_switch;
660 
661 	case 'a':
662 	case 'A':
663 		return ("symbol");
664 
665 	case 'b':
666 		(void) strcpy(buf, "unsigned ");
667 		(void) strcat(buf, iob_size2str(size));
668 		(void) strcat(buf, " bitfield");
669 		break;
670 
671 	case 'c':
672 		return ("character");
673 
674 	case 'd':
675 	case 'i':
676 		(void) strcpy(buf, "decimal signed ");
677 		(void) strcat(buf, iob_size2str(size));
678 		break;
679 
680 	case 'e':
681 	case 'E':
682 	case 'g':
683 	case 'G':
684 		return ("double");
685 
686 	case 'h':
687 		size = SZ_SHORT;
688 		goto fmt_switch;
689 
690 	case 'I':
691 		return ("IPv4 address");
692 
693 	case 'l':
694 		if (size >= SZ_LONG)
695 			size = SZ_LONGLONG;
696 		else
697 			size = SZ_LONG;
698 		goto fmt_switch;
699 
700 	case 'm':
701 		return ("margin");
702 
703 	case 'N':
704 		return ("IPv6 address");
705 
706 	case 'o':
707 		(void) strcpy(buf, "octal unsigned ");
708 		(void) strcat(buf, iob_size2str(size));
709 		break;
710 
711 	case 'p':
712 		return ("pointer");
713 
714 	case 'q':
715 		(void) strcpy(buf, "octal signed ");
716 		(void) strcat(buf, iob_size2str(size));
717 		break;
718 
719 	case 'r':
720 		(void) strcpy(buf, "default radix unsigned ");
721 		(void) strcat(buf, iob_size2str(size));
722 		break;
723 
724 	case 'R':
725 		(void) strcpy(buf, "default radix signed ");
726 		(void) strcat(buf, iob_size2str(size));
727 		break;
728 
729 	case 's':
730 		return ("string");
731 
732 	case 't':
733 	case 'T':
734 		return ("tab");
735 
736 	case 'u':
737 		(void) strcpy(buf, "decimal unsigned ");
738 		(void) strcat(buf, iob_size2str(size));
739 		break;
740 
741 	case 'x':
742 	case 'X':
743 		(void) strcat(buf, "hexadecimal ");
744 		(void) strcat(buf, iob_size2str(size));
745 		break;
746 
747 	case 'Y':
748 		return ("time_t");
749 
750 	case '<':
751 		return ("terminal attribute");
752 
753 	case '?':
754 	case '#':
755 	case '+':
756 	case '-':
757 		goto fmt_switch;
758 	}
759 
760 done:
761 	if (buf[0] == '\0')
762 		(void) strcpy(buf, "text");
763 
764 	return ((const char *)buf);
765 }
766 
767 static const char *
768 iob_int2str(varglist_t *ap, intsize_t size, int base, uint_t flags, int *zero,
769     u_longlong_t *value)
770 {
771 	uintmax_t i;
772 
773 	switch (size) {
774 	case SZ_LONGLONG:
775 		if (flags & NTOS_UNSIGNED)
776 			i = (u_longlong_t)VA_ARG(ap, u_longlong_t);
777 		else
778 			i = (longlong_t)VA_ARG(ap, longlong_t);
779 		break;
780 
781 	case SZ_LONG:
782 		if (flags & NTOS_UNSIGNED)
783 			i = (ulong_t)VA_ARG(ap, ulong_t);
784 		else
785 			i = (long)VA_ARG(ap, long);
786 		break;
787 
788 	case SZ_SHORT:
789 		if (flags & NTOS_UNSIGNED)
790 			i = (ushort_t)VA_ARG(ap, uint_t);
791 		else
792 			i = (short)VA_ARG(ap, int);
793 		break;
794 
795 	default:
796 		if (flags & NTOS_UNSIGNED)
797 			i = (uint_t)VA_ARG(ap, uint_t);
798 		else
799 			i = (int)VA_ARG(ap, int);
800 	}
801 
802 	*zero = i == 0;	/* Return flag indicating if result was zero */
803 	*value = i;	/* Return value retrieved from va_list */
804 
805 	return (numtostr(i, base, flags));
806 }
807 
808 static const char *
809 iob_time2str(time_t *tmp)
810 {
811 	/*
812 	 * ctime(3c) returns a string of the form
813 	 * "Fri Sep 13 00:00:00 1986\n\0".  We turn this into the canonical
814 	 * adb /y format "1986 Sep 13 00:00:00" below.
815 	 */
816 	const char *src = ctime(tmp);
817 	static char buf[32];
818 	char *dst = buf;
819 	int i;
820 
821 	if (src == NULL)
822 		return (mdb_strerror(errno));
823 
824 	for (i = 20; i < 24; i++)
825 		*dst++ = src[i]; /* Copy the 4-digit year */
826 
827 	for (i = 3; i < 19; i++)
828 		*dst++ = src[i]; /* Copy month, day, and h:m:s */
829 
830 	*dst = '\0';
831 	return (buf);
832 }
833 
834 static const char *
835 iob_addr2str(uintptr_t addr)
836 {
837 	static char buf[MDB_TGT_SYM_NAMLEN];
838 	char *name = buf;
839 	longlong_t offset;
840 	GElf_Sym sym;
841 
842 	if (mdb_tgt_lookup_by_addr(mdb.m_target, addr,
843 	    MDB_TGT_SYM_FUZZY, buf, sizeof (buf), &sym, NULL) == -1)
844 		return (NULL);
845 
846 	if (mdb.m_demangler != NULL && (mdb.m_flags & MDB_FL_DEMANGLE))
847 		name = (char *)mdb_dem_convert(mdb.m_demangler, buf);
848 
849 	/*
850 	 * Here we provide a little cooperation between the %a formatting code
851 	 * and the proc target: if the initial address passed to %a is in fact
852 	 * a PLT address, the proc target's lookup_by_addr code will convert
853 	 * this to the PLT destination (a different address).  We do not want
854 	 * to append a "+/-offset" suffix based on comparison with the query
855 	 * symbol in this case because the proc target has really done a hidden
856 	 * query for us with a different address.  We detect this case by
857 	 * comparing the initial characters of buf to the special PLT= string.
858 	 */
859 	if (sym.st_value != addr && strncmp(name, "PLT=", 4) != 0) {
860 		if (sym.st_value > addr)
861 			offset = -(longlong_t)(sym.st_value - addr);
862 		else
863 			offset = (longlong_t)(addr - sym.st_value);
864 
865 		(void) strcat(name, numtostr(offset, mdb.m_radix,
866 		    NTOS_SIGNPOS | NTOS_SHOWBASE));
867 	}
868 
869 	return (name);
870 }
871 
872 static int
873 iob_setattr(mdb_iob_t *iob, const char *s, size_t nbytes)
874 {
875 	uint_t attr;
876 	int req;
877 
878 	if (iob->iob_pgp == NULL)
879 		return (set_errno(ENOTTY));
880 
881 	if (nbytes != 0 && *s == '/') {
882 		req = ATT_OFF;
883 		nbytes--;
884 		s++;
885 	} else
886 		req = ATT_ON;
887 
888 	if (nbytes != 1)
889 		return (set_errno(EINVAL));
890 
891 	switch (*s) {
892 	case 's':
893 		attr = ATT_STANDOUT;
894 		break;
895 	case 'u':
896 		attr = ATT_UNDERLINE;
897 		break;
898 	case 'r':
899 		attr = ATT_REVERSE;
900 		break;
901 	case 'b':
902 		attr = ATT_BOLD;
903 		break;
904 	case 'd':
905 		attr = ATT_DIM;
906 		break;
907 	case 'a':
908 		attr = ATT_ALTCHARSET;
909 		break;
910 	default:
911 		return (set_errno(EINVAL));
912 	}
913 
914 	/*
915 	 * We need to flush the current buffer contents before calling
916 	 * IOP_SETATTR because IOP_SETATTR may need to synchronously output
917 	 * terminal escape sequences directly to the underlying device.
918 	 */
919 	(void) iob_write(iob, iob->iob_iop, iob->iob_buf, iob->iob_nbytes);
920 	iob->iob_bufp = &iob->iob_buf[0];
921 	iob->iob_nbytes = 0;
922 
923 	return (IOP_SETATTR(iob->iob_pgp, req, attr));
924 }
925 
926 static void
927 iob_bits2str(mdb_iob_t *iob, u_longlong_t value, const mdb_bitmask_t *bmp,
928     mdb_bool_t altflag)
929 {
930 	mdb_bool_t delim = FALSE;
931 	const char *str;
932 	size_t width;
933 
934 	if (bmp == NULL)
935 		goto out;
936 
937 	for (; bmp->bm_name != NULL; bmp++) {
938 		if ((value & bmp->bm_mask) == bmp->bm_bits) {
939 			width = strlen(bmp->bm_name) + delim;
940 
941 			if (IOB_WRAPNOW(iob, width))
942 				mdb_iob_nl(iob);
943 
944 			if (delim)
945 				mdb_iob_putc(iob, ',');
946 			else
947 				delim = TRUE;
948 
949 			mdb_iob_puts(iob, bmp->bm_name);
950 			value &= ~bmp->bm_bits;
951 		}
952 	}
953 
954 out:
955 	if (altflag == TRUE && (delim == FALSE || value != 0)) {
956 		str = numtostr(value, 16, NTOS_UNSIGNED | NTOS_SHOWBASE);
957 		width = strlen(str) + delim;
958 
959 		if (IOB_WRAPNOW(iob, width))
960 			mdb_iob_nl(iob);
961 		if (delim)
962 			mdb_iob_putc(iob, ',');
963 		mdb_iob_puts(iob, str);
964 	}
965 }
966 
967 static const char *
968 iob_inaddr2str(uint32_t addr)
969 {
970 	static char buf[INET_ADDRSTRLEN];
971 
972 	(void) mdb_inet_ntop(AF_INET, &addr, buf, sizeof (buf));
973 
974 	return (buf);
975 }
976 
977 static const char *
978 iob_ipv6addr2str(void *addr)
979 {
980 	static char buf[INET6_ADDRSTRLEN];
981 
982 	(void) mdb_inet_ntop(AF_INET6, addr, buf, sizeof (buf));
983 
984 	return (buf);
985 }
986 
987 static const char *
988 iob_getvar(const char *s, size_t len)
989 {
990 	mdb_var_t *val;
991 	char *var;
992 
993 	if (len == 0) {
994 		(void) set_errno(EINVAL);
995 		return (NULL);
996 	}
997 
998 	var = strndup(s, len);
999 	val = mdb_nv_lookup(&mdb.m_nv, var);
1000 	strfree(var);
1001 
1002 	if (val == NULL) {
1003 		(void) set_errno(EINVAL);
1004 		return (NULL);
1005 	}
1006 
1007 	return (numtostr(mdb_nv_get_value(val), 10, 0));
1008 }
1009 
1010 /*
1011  * The iob_doprnt function forms the main engine of the debugger's output
1012  * formatting capabilities.  Note that this is NOT exactly compatible with
1013  * the printf(3S) family, nor is it intended to be so.  We support some
1014  * extensions and format characters not supported by printf(3S), and we
1015  * explicitly do NOT provide support for %C, %S, %ws (wide-character strings),
1016  * do NOT provide for the complete functionality of %f, %e, %E, %g, %G
1017  * (alternate double formats), and do NOT support %.x (precision specification).
1018  * Note that iob_doprnt consumes varargs off the original va_list.
1019  */
1020 static void
1021 iob_doprnt(mdb_iob_t *iob, const char *format, varglist_t *ap)
1022 {
1023 	char c[2] = { 0, 0 };	/* Buffer for single character output */
1024 	const char *p;		/* Current position in format string */
1025 	size_t len;		/* Length of format string to copy verbatim */
1026 	size_t altlen;		/* Length of alternate print format prefix */
1027 	const char *altstr;	/* Alternate print format prefix */
1028 	const char *symstr;	/* Symbol + offset string */
1029 
1030 	u_longlong_t val;	/* Current integer value */
1031 	intsize_t size;		/* Current integer value size */
1032 	uint_t flags;		/* Current flags to pass to iob_int2str */
1033 	size_t width;		/* Current field width */
1034 	int zero;		/* If != 0, then integer value == 0 */
1035 
1036 	mdb_bool_t f_alt;	/* Use alternate print format (%#) */
1037 	mdb_bool_t f_altsuff;	/* Alternate print format is a suffix */
1038 	mdb_bool_t f_zfill;	/* Zero-fill field (%0) */
1039 	mdb_bool_t f_left;	/* Left-adjust field (%-) */
1040 	mdb_bool_t f_digits;	/* Explicit digits used to set field width */
1041 
1042 	union {
1043 		const char *str;
1044 		uint32_t ui32;
1045 		void *ptr;
1046 		time_t tm;
1047 		char c;
1048 		double d;
1049 		long double ld;
1050 	} u;
1051 
1052 	ASSERT(iob->iob_flags & MDB_IOB_WRONLY);
1053 
1054 	while ((p = strchr(format, '%')) != NULL) {
1055 		/*
1056 		 * Output the format string verbatim up to the next '%' char
1057 		 */
1058 		if (p != format) {
1059 			len = p - format;
1060 			if (IOB_WRAPNOW(iob, len) && *format != '\n')
1061 				mdb_iob_nl(iob);
1062 			mdb_iob_nputs(iob, format, len);
1063 		}
1064 
1065 		/*
1066 		 * Now we need to parse the sequence of format characters
1067 		 * following the % marker and do the appropriate thing.
1068 		 */
1069 		size = SZ_INT;		/* Use normal-sized int by default */
1070 		flags = 0;		/* Clear numtostr() format flags */
1071 		width = 0;		/* No field width limit by default */
1072 		altlen = 0;		/* No alternate format string yet */
1073 		altstr = NULL;		/* No alternate format string yet */
1074 
1075 		f_alt = FALSE;		/* Alternate format off by default */
1076 		f_altsuff = FALSE;	/* Alternate format is a prefix */
1077 		f_zfill = FALSE;	/* Zero-fill off by default */
1078 		f_left = FALSE;		/* Left-adjust off by default */
1079 		f_digits = FALSE;	/* No digits for width specified yet */
1080 
1081 		fmt_switch:
1082 		switch (*++p) {
1083 		case '0': case '1': case '2': case '3': case '4':
1084 		case '5': case '6': case '7': case '8': case '9':
1085 			if (f_digits == FALSE && *p == '0') {
1086 				f_zfill = TRUE;
1087 				goto fmt_switch;
1088 			}
1089 
1090 			if (f_digits == FALSE)
1091 				width = 0; /* clear any other width specifier */
1092 
1093 			for (u.c = *p; u.c >= '0' && u.c <= '9'; u.c = *++p)
1094 				width = width * 10 + u.c - '0';
1095 
1096 			p--;
1097 			f_digits = TRUE;
1098 			goto fmt_switch;
1099 
1100 		case 'a':
1101 			if (size < SZ_LONG)
1102 				size = SZ_LONG;	/* Bump to size of uintptr_t */
1103 
1104 			u.str = iob_int2str(ap, size, 16,
1105 			    NTOS_UNSIGNED | NTOS_SHOWBASE, &zero, &val);
1106 
1107 			if ((symstr = iob_addr2str(val)) != NULL)
1108 				u.str = symstr;
1109 
1110 			if (f_alt == TRUE) {
1111 				f_altsuff = TRUE;
1112 				altstr = ":";
1113 				altlen = 1;
1114 			}
1115 			break;
1116 
1117 		case 'A':
1118 			if (size < SZ_LONG)
1119 				size = SZ_LONG;	/* Bump to size of uintptr_t */
1120 
1121 			(void) iob_int2str(ap, size, 16,
1122 			    NTOS_UNSIGNED, &zero, &val);
1123 
1124 			u.str = iob_addr2str(val);
1125 
1126 			if (f_alt == TRUE && u.str == NULL)
1127 				u.str = "?";
1128 			break;
1129 
1130 		case 'b':
1131 			u.str = iob_int2str(ap, size, 16,
1132 			    NTOS_UNSIGNED | NTOS_SHOWBASE, &zero, &val);
1133 
1134 			iob_bits2str(iob, val, VA_PTRARG(ap), f_alt);
1135 
1136 			format = ++p;
1137 			continue;
1138 
1139 		case 'c':
1140 			c[0] = (char)VA_ARG(ap, int);
1141 			u.str = c;
1142 			break;
1143 
1144 		case 'd':
1145 		case 'i':
1146 			if (f_alt)
1147 				flags |= NTOS_SHOWBASE;
1148 			u.str = iob_int2str(ap, size, 10, flags, &zero, &val);
1149 			break;
1150 
1151 		/* No floating point in kmdb */
1152 #ifndef _KMDB
1153 		case 'e':
1154 		case 'E':
1155 			u.d = VA_ARG(ap, double);
1156 			u.str = doubletos(u.d, 7, *p);
1157 			break;
1158 
1159 		case 'g':
1160 		case 'G':
1161 			if (size >= SZ_LONG) {
1162 				u.ld = VA_ARG(ap, long double);
1163 				u.str = longdoubletos(&u.ld, 16,
1164 				    (*p == 'g') ? 'e' : 'E');
1165 			} else {
1166 				u.d = VA_ARG(ap, double);
1167 				u.str = doubletos(u.d, 16,
1168 				    (*p == 'g') ? 'e' : 'E');
1169 			}
1170 			break;
1171 #endif
1172 
1173 		case 'h':
1174 			size = SZ_SHORT;
1175 			goto fmt_switch;
1176 
1177 		case 'I':
1178 			u.ui32 = VA_ARG(ap, uint32_t);
1179 			u.str = iob_inaddr2str(u.ui32);
1180 			break;
1181 
1182 		case 'l':
1183 			if (size >= SZ_LONG)
1184 				size = SZ_LONGLONG;
1185 			else
1186 				size = SZ_LONG;
1187 			goto fmt_switch;
1188 
1189 		case 'm':
1190 			if (iob->iob_nbytes == 0) {
1191 				mdb_iob_ws(iob, (width != 0) ? width :
1192 				    iob->iob_margin);
1193 			}
1194 			format = ++p;
1195 			continue;
1196 
1197 		case 'N':
1198 			u.ptr = VA_PTRARG(ap);
1199 			u.str = iob_ipv6addr2str(u.ptr);
1200 			break;
1201 
1202 		case 'o':
1203 			u.str = iob_int2str(ap, size, 8, NTOS_UNSIGNED,
1204 			    &zero, &val);
1205 
1206 			if (f_alt && !zero) {
1207 				altstr = "0";
1208 				altlen = 1;
1209 			}
1210 			break;
1211 
1212 		case 'p':
1213 			u.ptr = VA_PTRARG(ap);
1214 			u.str = numtostr((uintptr_t)u.ptr, 16, NTOS_UNSIGNED);
1215 			break;
1216 
1217 		case 'q':
1218 			u.str = iob_int2str(ap, size, 8, flags, &zero, &val);
1219 
1220 			if (f_alt && !zero) {
1221 				altstr = "0";
1222 				altlen = 1;
1223 			}
1224 			break;
1225 
1226 		case 'r':
1227 			if (f_alt)
1228 				flags |= NTOS_SHOWBASE;
1229 			u.str = iob_int2str(ap, size, mdb.m_radix,
1230 			    NTOS_UNSIGNED | flags, &zero, &val);
1231 			break;
1232 
1233 		case 'R':
1234 			if (f_alt)
1235 				flags |= NTOS_SHOWBASE;
1236 			u.str = iob_int2str(ap, size, mdb.m_radix, flags,
1237 			    &zero, &val);
1238 			break;
1239 
1240 		case 's':
1241 			u.str = VA_PTRARG(ap);
1242 			if (u.str == NULL)
1243 				u.str = "<NULL>"; /* Be forgiving of NULL */
1244 			break;
1245 
1246 		case 't':
1247 			if (width != 0) {
1248 				while (width-- > 0)
1249 					mdb_iob_tab(iob);
1250 			} else
1251 				mdb_iob_tab(iob);
1252 
1253 			format = ++p;
1254 			continue;
1255 
1256 		case 'T':
1257 			if (width != 0 && (iob->iob_nbytes % width) != 0) {
1258 				size_t ots = iob->iob_tabstop;
1259 				iob->iob_tabstop = width;
1260 				mdb_iob_tab(iob);
1261 				iob->iob_tabstop = ots;
1262 			}
1263 			format = ++p;
1264 			continue;
1265 
1266 		case 'u':
1267 			if (f_alt)
1268 				flags |= NTOS_SHOWBASE;
1269 			u.str = iob_int2str(ap, size, 10,
1270 			    flags | NTOS_UNSIGNED, &zero, &val);
1271 			break;
1272 
1273 		case 'x':
1274 			u.str = iob_int2str(ap, size, 16, NTOS_UNSIGNED,
1275 			    &zero, &val);
1276 
1277 			if (f_alt && !zero) {
1278 				altstr = "0x";
1279 				altlen = 2;
1280 			}
1281 			break;
1282 
1283 		case 'X':
1284 			u.str = iob_int2str(ap, size, 16,
1285 			    NTOS_UNSIGNED | NTOS_UPCASE, &zero, &val);
1286 
1287 			if (f_alt && !zero) {
1288 				altstr = "0X";
1289 				altlen = 2;
1290 			}
1291 			break;
1292 
1293 		case 'Y':
1294 			u.tm = VA_ARG(ap, time_t);
1295 			u.str = iob_time2str(&u.tm);
1296 			break;
1297 
1298 		case '<':
1299 			/*
1300 			 * Used to turn attributes on (<b>), to turn them
1301 			 * off (</b>), or to print variables (<_var>).
1302 			 */
1303 			for (u.str = ++p; *p != '\0' && *p != '>'; p++)
1304 				continue;
1305 
1306 			if (*p == '>') {
1307 				size_t paramlen = p - u.str;
1308 
1309 				if (paramlen > 0) {
1310 					if (*u.str == '_') {
1311 						u.str = iob_getvar(u.str + 1,
1312 						    paramlen - 1);
1313 						break;
1314 					} else {
1315 						(void) iob_setattr(iob, u.str,
1316 						    paramlen);
1317 					}
1318 				}
1319 
1320 				p++;
1321 			}
1322 
1323 			format = p;
1324 			continue;
1325 
1326 		case '*':
1327 			width = (size_t)(uint_t)VA_ARG(ap, int);
1328 			goto fmt_switch;
1329 
1330 		case '%':
1331 			u.str = "%";
1332 			break;
1333 
1334 		case '?':
1335 			width = sizeof (uintptr_t) * 2;
1336 			goto fmt_switch;
1337 
1338 		case '#':
1339 			f_alt = TRUE;
1340 			goto fmt_switch;
1341 
1342 		case '+':
1343 			flags |= NTOS_SIGNPOS;
1344 			goto fmt_switch;
1345 
1346 		case '-':
1347 			f_left = TRUE;
1348 			goto fmt_switch;
1349 
1350 		default:
1351 			c[0] = p[0];
1352 			u.str = c;
1353 		}
1354 
1355 		len = u.str != NULL ? strlen(u.str) : 0;
1356 
1357 		if (len + altlen > width)
1358 			width = len + altlen;
1359 
1360 		/*
1361 		 * If the string and the option altstr won't fit on this line
1362 		 * and auto-wrap is set (default), skip to the next line.
1363 		 */
1364 		if (IOB_WRAPNOW(iob, width))
1365 			mdb_iob_nl(iob);
1366 
1367 		/*
1368 		 * Optionally add whitespace or zeroes prefixing the value if
1369 		 * we haven't filled the minimum width and we're right-aligned.
1370 		 */
1371 		if (len < (width - altlen) && f_left == FALSE) {
1372 			mdb_iob_fill(iob, f_zfill ? '0' : ' ',
1373 			    width - altlen - len);
1374 		}
1375 
1376 		/*
1377 		 * Print the alternate string if it's a prefix, and then
1378 		 * print the value string itself.
1379 		 */
1380 		if (altstr != NULL && f_altsuff == FALSE)
1381 			mdb_iob_nputs(iob, altstr, altlen);
1382 		if (len != 0)
1383 			mdb_iob_nputs(iob, u.str, len);
1384 
1385 		/*
1386 		 * If we have an alternate string and it's a suffix, print it.
1387 		 */
1388 		if (altstr != NULL && f_altsuff == TRUE)
1389 			mdb_iob_nputs(iob, altstr, altlen);
1390 
1391 		/*
1392 		 * Finally, if we haven't filled the field width and we're
1393 		 * left-aligned, pad out the rest with whitespace.
1394 		 */
1395 		if ((len + altlen) < width && f_left == TRUE)
1396 			mdb_iob_ws(iob, width - altlen - len);
1397 
1398 		format = (*p != '\0') ? ++p : p;
1399 	}
1400 
1401 	/*
1402 	 * If there's anything left in the format string, output it now
1403 	 */
1404 	if (*format != '\0') {
1405 		len = strlen(format);
1406 		if (IOB_WRAPNOW(iob, len) && *format != '\n')
1407 			mdb_iob_nl(iob);
1408 		mdb_iob_nputs(iob, format, len);
1409 	}
1410 }
1411 
1412 void
1413 mdb_iob_vprintf(mdb_iob_t *iob, const char *format, va_list alist)
1414 {
1415 	varglist_t ap = { VAT_VARARGS };
1416 	va_copy(ap.val_valist, alist);
1417 	iob_doprnt(iob, format, &ap);
1418 }
1419 
1420 void
1421 mdb_iob_aprintf(mdb_iob_t *iob, const char *format, const mdb_arg_t *argv)
1422 {
1423 	varglist_t ap = { VAT_ARGVEC };
1424 	ap.val_argv = argv;
1425 	iob_doprnt(iob, format, &ap);
1426 }
1427 
1428 void
1429 mdb_iob_printf(mdb_iob_t *iob, const char *format, ...)
1430 {
1431 	va_list alist;
1432 
1433 	va_start(alist, format);
1434 	mdb_iob_vprintf(iob, format, alist);
1435 	va_end(alist);
1436 }
1437 
1438 /*
1439  * In order to handle the sprintf family of functions, we define a special
1440  * i/o backend known as a "sprintf buf" (or spbuf for short).  This back end
1441  * provides an IOP_WRITE entry point that concatenates each buffer sent from
1442  * mdb_iob_flush() onto the caller's buffer until the caller's buffer is
1443  * exhausted.  We also keep an absolute count of how many bytes were sent to
1444  * this function during the lifetime of the snprintf call.  This allows us
1445  * to provide the ability to (1) return the total size required for the given
1446  * format string and argument list, and (2) support a call to snprintf with a
1447  * NULL buffer argument with no special case code elsewhere.
1448  */
1449 static ssize_t
1450 spbuf_write(mdb_io_t *io, const void *buf, size_t buflen)
1451 {
1452 	spbuf_t *spb = io->io_data;
1453 
1454 	if (spb->spb_bufsiz != 0) {
1455 		size_t n = MIN(spb->spb_bufsiz, buflen);
1456 		bcopy(buf, spb->spb_buf, n);
1457 		spb->spb_buf += n;
1458 		spb->spb_bufsiz -= n;
1459 	}
1460 
1461 	spb->spb_total += buflen;
1462 	return (buflen);
1463 }
1464 
1465 static const mdb_io_ops_t spbuf_ops = {
1466 	no_io_read,
1467 	spbuf_write,
1468 	no_io_seek,
1469 	no_io_ctl,
1470 	no_io_close,
1471 	no_io_name,
1472 	no_io_link,
1473 	no_io_unlink,
1474 	no_io_setattr,
1475 	no_io_suspend,
1476 	no_io_resume
1477 };
1478 
1479 /*
1480  * The iob_spb_create function initializes an iob suitable for snprintf calls,
1481  * a spbuf i/o backend, and the spbuf private data, and then glues these
1482  * objects together.  The caller (either vsnprintf or asnprintf below) is
1483  * expected to have allocated the various structures on their stack.
1484  */
1485 static void
1486 iob_spb_create(mdb_iob_t *iob, char *iob_buf, size_t iob_len,
1487     mdb_io_t *io, spbuf_t *spb, char *spb_buf, size_t spb_len)
1488 {
1489 	spb->spb_buf = spb_buf;
1490 	spb->spb_bufsiz = spb_len;
1491 	spb->spb_total = 0;
1492 
1493 	io->io_ops = &spbuf_ops;
1494 	io->io_data = spb;
1495 	io->io_next = NULL;
1496 	io->io_refcnt = 1;
1497 
1498 	iob->iob_buf = iob_buf;
1499 	iob->iob_bufsiz = iob_len;
1500 	iob->iob_bufp = iob_buf;
1501 	iob->iob_nbytes = 0;
1502 	iob->iob_nlines = 0;
1503 	iob->iob_lineno = 1;
1504 	iob->iob_rows = MDB_IOB_DEFROWS;
1505 	iob->iob_cols = iob_len;
1506 	iob->iob_tabstop = MDB_IOB_DEFTAB;
1507 	iob->iob_margin = MDB_IOB_DEFMARGIN;
1508 	iob->iob_flags = MDB_IOB_WRONLY;
1509 	iob->iob_iop = io;
1510 	iob->iob_pgp = NULL;
1511 	iob->iob_next = NULL;
1512 }
1513 
1514 /*ARGSUSED*/
1515 ssize_t
1516 null_io_write(mdb_io_t *io, const void *buf, size_t nbytes)
1517 {
1518 	return (nbytes);
1519 }
1520 
1521 static const mdb_io_ops_t null_ops = {
1522 	no_io_read,
1523 	null_io_write,
1524 	no_io_seek,
1525 	no_io_ctl,
1526 	no_io_close,
1527 	no_io_name,
1528 	no_io_link,
1529 	no_io_unlink,
1530 	no_io_setattr,
1531 	no_io_suspend,
1532 	no_io_resume
1533 };
1534 
1535 mdb_io_t *
1536 mdb_nullio_create(void)
1537 {
1538 	static mdb_io_t null_io = {
1539 		&null_ops,
1540 		NULL,
1541 		NULL,
1542 		1
1543 	};
1544 
1545 	return (&null_io);
1546 }
1547 
1548 size_t
1549 mdb_iob_vsnprintf(char *buf, size_t nbytes, const char *format, va_list alist)
1550 {
1551 	varglist_t ap = { VAT_VARARGS };
1552 	char iob_buf[64];
1553 	mdb_iob_t iob;
1554 	mdb_io_t io;
1555 	spbuf_t spb;
1556 
1557 	ASSERT(buf != NULL || nbytes == 0);
1558 	iob_spb_create(&iob, iob_buf, sizeof (iob_buf), &io, &spb, buf, nbytes);
1559 	va_copy(ap.val_valist, alist);
1560 	iob_doprnt(&iob, format, &ap);
1561 	mdb_iob_flush(&iob);
1562 
1563 	if (spb.spb_bufsiz != 0)
1564 		*spb.spb_buf = '\0';
1565 	else if (buf != NULL && nbytes > 0)
1566 		*--spb.spb_buf = '\0';
1567 
1568 	return (spb.spb_total);
1569 }
1570 
1571 size_t
1572 mdb_iob_asnprintf(char *buf, size_t nbytes, const char *format,
1573     const mdb_arg_t *argv)
1574 {
1575 	varglist_t ap = { VAT_ARGVEC };
1576 	char iob_buf[64];
1577 	mdb_iob_t iob;
1578 	mdb_io_t io;
1579 	spbuf_t spb;
1580 
1581 	ASSERT(buf != NULL || nbytes == 0);
1582 	iob_spb_create(&iob, iob_buf, sizeof (iob_buf), &io, &spb, buf, nbytes);
1583 	ap.val_argv = argv;
1584 	iob_doprnt(&iob, format, &ap);
1585 	mdb_iob_flush(&iob);
1586 
1587 	if (spb.spb_bufsiz != 0)
1588 		*spb.spb_buf = '\0';
1589 	else if (buf != NULL && nbytes > 0)
1590 		*--spb.spb_buf = '\0';
1591 
1592 	return (spb.spb_total);
1593 }
1594 
1595 /*PRINTFLIKE3*/
1596 size_t
1597 mdb_iob_snprintf(char *buf, size_t nbytes, const char *format, ...)
1598 {
1599 	va_list alist;
1600 
1601 	va_start(alist, format);
1602 	nbytes = mdb_iob_vsnprintf(buf, nbytes, format, alist);
1603 	va_end(alist);
1604 
1605 	return (nbytes);
1606 }
1607 
1608 void
1609 mdb_iob_nputs(mdb_iob_t *iob, const char *s, size_t nbytes)
1610 {
1611 	size_t m, n, nleft = nbytes;
1612 	const char *p, *q = s;
1613 
1614 	ASSERT(iob->iob_flags & MDB_IOB_WRONLY);
1615 
1616 	if (nbytes == 0)
1617 		return; /* Return immediately if there is no work to do */
1618 
1619 	/*
1620 	 * If the string contains embedded newlines or tabs, invoke ourself
1621 	 * recursively for each string component, followed by a call to the
1622 	 * newline or tab routine.  This insures that strings with these
1623 	 * characters obey our wrapping and indenting rules, and that strings
1624 	 * with embedded newlines are flushed after each newline, allowing
1625 	 * the output pager to take over if it is enabled.
1626 	 */
1627 	while ((p = strnpbrk(q, "\t\n", nleft)) != NULL) {
1628 		if (p > q)
1629 			mdb_iob_nputs(iob, q, (size_t)(p - q));
1630 
1631 		if (*p == '\t')
1632 			mdb_iob_tab(iob);
1633 		else
1634 			mdb_iob_nl(iob);
1635 
1636 		nleft -= (size_t)(p - q) + 1;	/* Update byte count */
1637 		q = p + 1;			/* Advance past delimiter */
1638 	}
1639 
1640 	/*
1641 	 * For a given string component, we determine how many bytes (n) we can
1642 	 * copy into our buffer (limited by either cols or bufsiz depending
1643 	 * on whether AUTOWRAP is on), copy a chunk into the buffer, and
1644 	 * flush the buffer if we reach the end of a line.
1645 	 */
1646 	while (nleft != 0) {
1647 		if (iob->iob_flags & MDB_IOB_AUTOWRAP) {
1648 			ASSERT(iob->iob_cols >= iob->iob_nbytes);
1649 			n = iob->iob_cols - iob->iob_nbytes;
1650 		} else {
1651 			ASSERT(iob->iob_bufsiz >= iob->iob_nbytes);
1652 			n = iob->iob_bufsiz - iob->iob_nbytes;
1653 		}
1654 
1655 		m = MIN(nleft, n); /* copy at most n bytes in this pass */
1656 
1657 		bcopy(q, iob->iob_bufp, m);
1658 		nleft -= m;
1659 		q += m;
1660 
1661 		iob->iob_bufp += m;
1662 		iob->iob_nbytes += m;
1663 
1664 		if (m == n && nleft != 0) {
1665 			if (iob->iob_flags & MDB_IOB_AUTOWRAP)
1666 				mdb_iob_nl(iob);
1667 			else
1668 				mdb_iob_flush(iob);
1669 		}
1670 	}
1671 }
1672 
1673 void
1674 mdb_iob_puts(mdb_iob_t *iob, const char *s)
1675 {
1676 	mdb_iob_nputs(iob, s, strlen(s));
1677 }
1678 
1679 void
1680 mdb_iob_putc(mdb_iob_t *iob, int c)
1681 {
1682 	mdb_iob_fill(iob, c, 1);
1683 }
1684 
1685 void
1686 mdb_iob_tab(mdb_iob_t *iob)
1687 {
1688 	ASSERT(iob->iob_flags & MDB_IOB_WRONLY);
1689 
1690 	if (iob->iob_tabstop != 0) {
1691 		/*
1692 		 * Round up to the next multiple of the tabstop.  If this puts
1693 		 * us off the end of the line, just insert a newline; otherwise
1694 		 * insert sufficient whitespace to reach position n.
1695 		 */
1696 		size_t n = (iob->iob_nbytes + iob->iob_tabstop) /
1697 		    iob->iob_tabstop * iob->iob_tabstop;
1698 
1699 		if (n < iob->iob_cols)
1700 			mdb_iob_fill(iob, ' ', n - iob->iob_nbytes);
1701 		else
1702 			mdb_iob_nl(iob);
1703 	}
1704 }
1705 
1706 void
1707 mdb_iob_fill(mdb_iob_t *iob, int c, size_t nfill)
1708 {
1709 	size_t i, m, n;
1710 
1711 	ASSERT(iob->iob_flags & MDB_IOB_WRONLY);
1712 
1713 	while (nfill != 0) {
1714 		if (iob->iob_flags & MDB_IOB_AUTOWRAP) {
1715 			ASSERT(iob->iob_cols >= iob->iob_nbytes);
1716 			n = iob->iob_cols - iob->iob_nbytes;
1717 		} else {
1718 			ASSERT(iob->iob_bufsiz >= iob->iob_nbytes);
1719 			n = iob->iob_bufsiz - iob->iob_nbytes;
1720 		}
1721 
1722 		m = MIN(nfill, n); /* fill at most n bytes in this pass */
1723 
1724 		for (i = 0; i < m; i++)
1725 			*iob->iob_bufp++ = (char)c;
1726 
1727 		iob->iob_nbytes += m;
1728 		nfill -= m;
1729 
1730 		if (m == n && nfill != 0) {
1731 			if (iob->iob_flags & MDB_IOB_AUTOWRAP)
1732 				mdb_iob_nl(iob);
1733 			else
1734 				mdb_iob_flush(iob);
1735 		}
1736 	}
1737 }
1738 
1739 void
1740 mdb_iob_ws(mdb_iob_t *iob, size_t n)
1741 {
1742 	if (iob->iob_nbytes + n < iob->iob_cols)
1743 		mdb_iob_fill(iob, ' ', n);
1744 	else
1745 		mdb_iob_nl(iob);
1746 }
1747 
1748 void
1749 mdb_iob_nl(mdb_iob_t *iob)
1750 {
1751 	ASSERT(iob->iob_flags & MDB_IOB_WRONLY);
1752 
1753 	if (iob->iob_nbytes == iob->iob_bufsiz)
1754 		mdb_iob_flush(iob);
1755 
1756 	*iob->iob_bufp++ = '\n';
1757 	iob->iob_nbytes++;
1758 
1759 	mdb_iob_flush(iob);
1760 }
1761 
1762 ssize_t
1763 mdb_iob_ngets(mdb_iob_t *iob, char *buf, size_t n)
1764 {
1765 	ssize_t resid = n - 1;
1766 	ssize_t len;
1767 	int c;
1768 
1769 	if (iob->iob_flags & (MDB_IOB_WRONLY | MDB_IOB_EOF))
1770 		return (EOF); /* can't gets a write buf or a read buf at EOF */
1771 
1772 	if (n == 0)
1773 		return (0);   /* we need room for a terminating \0 */
1774 
1775 	while (resid != 0) {
1776 		if (iob->iob_nbytes == 0 && iob_read(iob, iob->iob_iop) <= 0)
1777 			goto done; /* failed to refill buffer */
1778 
1779 		for (len = MIN(iob->iob_nbytes, resid); len != 0; len--) {
1780 			c = *iob->iob_bufp++;
1781 			iob->iob_nbytes--;
1782 
1783 			if (c == EOF || c == '\n')
1784 				goto done;
1785 
1786 			*buf++ = (char)c;
1787 			resid--;
1788 		}
1789 	}
1790 done:
1791 	*buf = '\0';
1792 	return (n - resid - 1);
1793 }
1794 
1795 int
1796 mdb_iob_getc(mdb_iob_t *iob)
1797 {
1798 	int c;
1799 
1800 	if (iob->iob_flags & (MDB_IOB_WRONLY | MDB_IOB_EOF | MDB_IOB_ERR))
1801 		return (EOF); /* can't getc if write-only, EOF, or error bit */
1802 
1803 	if (iob->iob_nbytes == 0 && iob_read(iob, iob->iob_iop) <= 0)
1804 		return (EOF); /* failed to refill buffer */
1805 
1806 	c = (uchar_t)*iob->iob_bufp++;
1807 	iob->iob_nbytes--;
1808 
1809 	return (c);
1810 }
1811 
1812 int
1813 mdb_iob_ungetc(mdb_iob_t *iob, int c)
1814 {
1815 	if (iob->iob_flags & (MDB_IOB_WRONLY | MDB_IOB_ERR))
1816 		return (EOF); /* can't ungetc if write-only or error bit set */
1817 
1818 	if (c == EOF || iob->iob_nbytes == iob->iob_bufsiz)
1819 		return (EOF); /* can't ungetc EOF, or ungetc if buffer full */
1820 
1821 	*--iob->iob_bufp = (char)c;
1822 	iob->iob_nbytes++;
1823 	iob->iob_flags &= ~MDB_IOB_EOF;
1824 
1825 	return (c);
1826 }
1827 
1828 int
1829 mdb_iob_eof(mdb_iob_t *iob)
1830 {
1831 	return ((iob->iob_flags & (MDB_IOB_RDONLY | MDB_IOB_EOF)) ==
1832 	    (MDB_IOB_RDONLY | MDB_IOB_EOF));
1833 }
1834 
1835 int
1836 mdb_iob_err(mdb_iob_t *iob)
1837 {
1838 	return ((iob->iob_flags & MDB_IOB_ERR) == MDB_IOB_ERR);
1839 }
1840 
1841 ssize_t
1842 mdb_iob_read(mdb_iob_t *iob, void *buf, size_t n)
1843 {
1844 	ssize_t resid = n;
1845 	ssize_t len;
1846 
1847 	if (iob->iob_flags & (MDB_IOB_WRONLY | MDB_IOB_EOF | MDB_IOB_ERR))
1848 		return (0); /* can't read if write-only, eof, or error */
1849 
1850 	while (resid != 0) {
1851 		if (iob->iob_nbytes == 0 && iob_read(iob, iob->iob_iop) <= 0)
1852 			break; /* failed to refill buffer */
1853 
1854 		len = MIN(resid, iob->iob_nbytes);
1855 		bcopy(iob->iob_bufp, buf, len);
1856 
1857 		iob->iob_bufp += len;
1858 		iob->iob_nbytes -= len;
1859 
1860 		buf = (char *)buf + len;
1861 		resid -= len;
1862 	}
1863 
1864 	return (n - resid);
1865 }
1866 
1867 /*
1868  * For now, all binary writes are performed unbuffered.  This has the
1869  * side effect that the pager will not be triggered by mdb_iob_write.
1870  */
1871 ssize_t
1872 mdb_iob_write(mdb_iob_t *iob, const void *buf, size_t n)
1873 {
1874 	ssize_t ret;
1875 
1876 	if (iob->iob_flags & MDB_IOB_ERR)
1877 		return (set_errno(EIO));
1878 	if (iob->iob_flags & MDB_IOB_RDONLY)
1879 		return (set_errno(EMDB_IORO));
1880 
1881 	mdb_iob_flush(iob);
1882 	ret = iob_write(iob, iob->iob_iop, buf, n);
1883 
1884 	if (ret < 0 && iob == mdb.m_out)
1885 		longjmp(mdb.m_frame->f_pcb, MDB_ERR_OUTPUT);
1886 
1887 	return (ret);
1888 }
1889 
1890 int
1891 mdb_iob_ctl(mdb_iob_t *iob, int req, void *arg)
1892 {
1893 	return (IOP_CTL(iob->iob_iop, req, arg));
1894 }
1895 
1896 const char *
1897 mdb_iob_name(mdb_iob_t *iob)
1898 {
1899 	if (iob == NULL)
1900 		return ("<NULL>");
1901 
1902 	return (IOP_NAME(iob->iob_iop));
1903 }
1904 
1905 size_t
1906 mdb_iob_lineno(mdb_iob_t *iob)
1907 {
1908 	return (iob->iob_lineno);
1909 }
1910 
1911 size_t
1912 mdb_iob_gettabstop(mdb_iob_t *iob)
1913 {
1914 	return (iob->iob_tabstop);
1915 }
1916 
1917 size_t
1918 mdb_iob_getmargin(mdb_iob_t *iob)
1919 {
1920 	return (iob->iob_margin);
1921 }
1922 
1923 mdb_io_t *
1924 mdb_io_hold(mdb_io_t *io)
1925 {
1926 	io->io_refcnt++;
1927 	return (io);
1928 }
1929 
1930 void
1931 mdb_io_rele(mdb_io_t *io)
1932 {
1933 	ASSERT(io->io_refcnt != 0);
1934 
1935 	if (--io->io_refcnt == 0) {
1936 		IOP_CLOSE(io);
1937 		mdb_free(io, sizeof (mdb_io_t));
1938 	}
1939 }
1940 
1941 void
1942 mdb_io_destroy(mdb_io_t *io)
1943 {
1944 	ASSERT(io->io_refcnt == 0);
1945 	IOP_CLOSE(io);
1946 	mdb_free(io, sizeof (mdb_io_t));
1947 }
1948 
1949 void
1950 mdb_iob_stack_create(mdb_iob_stack_t *stk)
1951 {
1952 	stk->stk_top = NULL;
1953 	stk->stk_size = 0;
1954 }
1955 
1956 void
1957 mdb_iob_stack_destroy(mdb_iob_stack_t *stk)
1958 {
1959 	mdb_iob_t *top, *ntop;
1960 
1961 	for (top = stk->stk_top; top != NULL; top = ntop) {
1962 		ntop = top->iob_next;
1963 		mdb_iob_destroy(top);
1964 	}
1965 }
1966 
1967 void
1968 mdb_iob_stack_push(mdb_iob_stack_t *stk, mdb_iob_t *iob, size_t lineno)
1969 {
1970 	iob->iob_lineno = lineno;
1971 	iob->iob_next = stk->stk_top;
1972 	stk->stk_top = iob;
1973 	stk->stk_size++;
1974 	yylineno = 1;
1975 }
1976 
1977 mdb_iob_t *
1978 mdb_iob_stack_pop(mdb_iob_stack_t *stk)
1979 {
1980 	mdb_iob_t *top = stk->stk_top;
1981 
1982 	ASSERT(top != NULL);
1983 
1984 	stk->stk_top = top->iob_next;
1985 	top->iob_next = NULL;
1986 	stk->stk_size--;
1987 
1988 	return (top);
1989 }
1990 
1991 size_t
1992 mdb_iob_stack_size(mdb_iob_stack_t *stk)
1993 {
1994 	return (stk->stk_size);
1995 }
1996 
1997 /*
1998  * Stub functions for i/o backend implementors: these stubs either act as
1999  * pass-through no-ops or return ENOTSUP as appropriate.
2000  */
2001 ssize_t
2002 no_io_read(mdb_io_t *io, void *buf, size_t nbytes)
2003 {
2004 	if (io->io_next != NULL)
2005 		return (IOP_READ(io->io_next, buf, nbytes));
2006 
2007 	return (set_errno(EMDB_IOWO));
2008 }
2009 
2010 ssize_t
2011 no_io_write(mdb_io_t *io, const void *buf, size_t nbytes)
2012 {
2013 	if (io->io_next != NULL)
2014 		return (IOP_WRITE(io->io_next, buf, nbytes));
2015 
2016 	return (set_errno(EMDB_IORO));
2017 }
2018 
2019 off64_t
2020 no_io_seek(mdb_io_t *io, off64_t offset, int whence)
2021 {
2022 	if (io->io_next != NULL)
2023 		return (IOP_SEEK(io->io_next, offset, whence));
2024 
2025 	return (set_errno(ENOTSUP));
2026 }
2027 
2028 int
2029 no_io_ctl(mdb_io_t *io, int req, void *arg)
2030 {
2031 	if (io->io_next != NULL)
2032 		return (IOP_CTL(io->io_next, req, arg));
2033 
2034 	return (set_errno(ENOTSUP));
2035 }
2036 
2037 /*ARGSUSED*/
2038 void
2039 no_io_close(mdb_io_t *io)
2040 {
2041 /*
2042  * Note that we do not propagate IOP_CLOSE down the io stack.  IOP_CLOSE should
2043  * only be called by mdb_io_rele when an io's reference count has gone to zero.
2044  */
2045 }
2046 
2047 const char *
2048 no_io_name(mdb_io_t *io)
2049 {
2050 	if (io->io_next != NULL)
2051 		return (IOP_NAME(io->io_next));
2052 
2053 	return ("(anonymous)");
2054 }
2055 
2056 void
2057 no_io_link(mdb_io_t *io, mdb_iob_t *iob)
2058 {
2059 	if (io->io_next != NULL)
2060 		IOP_LINK(io->io_next, iob);
2061 }
2062 
2063 void
2064 no_io_unlink(mdb_io_t *io, mdb_iob_t *iob)
2065 {
2066 	if (io->io_next != NULL)
2067 		IOP_UNLINK(io->io_next, iob);
2068 }
2069 
2070 int
2071 no_io_setattr(mdb_io_t *io, int req, uint_t attrs)
2072 {
2073 	if (io->io_next != NULL)
2074 		return (IOP_SETATTR(io->io_next, req, attrs));
2075 
2076 	return (set_errno(ENOTSUP));
2077 }
2078 
2079 void
2080 no_io_suspend(mdb_io_t *io)
2081 {
2082 	if (io->io_next != NULL)
2083 		IOP_SUSPEND(io->io_next);
2084 }
2085 
2086 void
2087 no_io_resume(mdb_io_t *io)
2088 {
2089 	if (io->io_next != NULL)
2090 		IOP_RESUME(io->io_next);
2091 }
2092 
2093 /*
2094  * Iterate over the varargs. The first item indicates the mode:
2095  * MDB_TBL_PRNT
2096  * 	pull out the next vararg as a const char * and pass it and the
2097  * 	remaining varargs to iob_doprnt; if we want to print the column,
2098  * 	direct the output to mdb.m_out otherwise direct it to mdb.m_null
2099  *
2100  * MDB_TBL_FUNC
2101  * 	pull out the next vararg as type mdb_table_print_f and the
2102  * 	following one as a void * argument to the function; call the
2103  * 	function with the given argument if we want to print the column
2104  *
2105  * The second item indicates the flag; if the flag is set in the flags
2106  * argument, then the column is printed. A flag value of 0 indicates
2107  * that the column should always be printed.
2108  */
2109 void
2110 mdb_table_print(uint_t flags, const char *delimeter, ...)
2111 {
2112 	va_list alist;
2113 	uint_t flg;
2114 	uint_t type;
2115 	const char *fmt;
2116 	mdb_table_print_f *func;
2117 	void *arg;
2118 	mdb_iob_t *out;
2119 	mdb_bool_t first = TRUE;
2120 	mdb_bool_t print;
2121 
2122 	va_start(alist, delimeter);
2123 
2124 	while ((type = va_arg(alist, uint_t)) != MDB_TBL_DONE) {
2125 		flg = va_arg(alist, uint_t);
2126 
2127 		print = flg == 0 || (flg & flags) != 0;
2128 
2129 		if (print) {
2130 			if (first)
2131 				first = FALSE;
2132 			else
2133 				mdb_printf("%s", delimeter);
2134 		}
2135 
2136 		switch (type) {
2137 		case MDB_TBL_PRNT: {
2138 			varglist_t ap = { VAT_VARARGS };
2139 			fmt = va_arg(alist, const char *);
2140 			out = print ? mdb.m_out : mdb.m_null;
2141 			va_copy(ap.val_valist, alist);
2142 			iob_doprnt(out, fmt, &ap);
2143 			va_end(alist);
2144 			va_copy(alist, ap.val_valist);
2145 			break;
2146 		}
2147 
2148 		case MDB_TBL_FUNC:
2149 			func = va_arg(alist, mdb_table_print_f *);
2150 			arg = va_arg(alist, void *);
2151 
2152 			if (print)
2153 				func(arg);
2154 
2155 			break;
2156 
2157 		default:
2158 			warn("bad format type %x\n", type);
2159 			break;
2160 		}
2161 	}
2162 
2163 	va_end(alist);
2164 }
2165