/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2020 Joyent, Inc. * Copyright (c) 2016 by Delphix. All rights reserved. * Copyright 2022 Oxide Computer Company */ /* * MDB uses its own enhanced standard i/o mechanism for all input and output. * This file provides the underpinnings of this mechanism, including the * printf-style formatting code, the output pager, and APIs for raw input * and output. This mechanism is used throughout the debugger for everything * from simple sprintf and printf-style formatting, to input to the lexer * and parser, to raw file i/o for reading ELF files. In general, we divide * our i/o implementation into two parts: * * (1) An i/o buffer (mdb_iob_t) provides buffered read or write capabilities, * as well as access to formatting and the ability to invoke a pager. The * buffer is constructed explicitly for use in either reading or writing; it * may not be used for both simultaneously. * * (2) Each i/o buffer is associated with an underlying i/o backend (mdb_io_t). * The backend provides, through an ops-vector, equivalents for the standard * read, write, lseek, ioctl, and close operations. In addition, the backend * can provide an IOP_NAME entry point for returning a name for the backend, * IOP_LINK and IOP_UNLINK entry points that are called when the backend is * connected or disconnected from an mdb_iob_t, and an IOP_SETATTR entry point * for manipulating terminal attributes. * * The i/o objects themselves are reference counted so that more than one i/o * buffer may make use of the same i/o backend. In addition, each buffer * provides the ability to push or pop backends to interpose on input or output * behavior. We make use of this, for example, to implement interactive * session logging. Normally, the stdout iob has a backend that is either * file descriptor 1, or a terminal i/o backend associated with the tty. * However, we can push a log i/o backend on top that multiplexes stdout to * the original back-end and another backend that writes to a log file. The * use of i/o backends is also used for simplifying tasks such as making * lex and yacc read from strings for mdb_eval(), and making our ELF file * processing code read executable "files" from a crash dump via kvm_uread. * * Additionally, the formatting code provides auto-wrap and indent facilities * that are necessary for compatibility with adb macro formatting. In auto- * wrap mode, the formatting code examines each new chunk of output to determine * if it will fit on the current line. If not, instead of having the chunk * divided between the current line of output and the next, the auto-wrap * code will automatically output a newline, auto-indent the next line, * and then continue. Auto-indent is implemented by simply prepending a number * of blanks equal to iob_margin to the start of each line. The margin is * inserted when the iob is created, and following each flush of the buffer. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Define list of possible integer sizes for conversion routines: */ typedef enum { SZ_SHORT, /* format %h? */ SZ_INT, /* format %? */ SZ_LONG, /* format %l? */ SZ_LONGLONG /* format %ll? */ } intsize_t; /* * The iob snprintf family of functions makes use of a special "sprintf * buffer" i/o backend in order to provide the appropriate snprintf semantics. * This structure is maintained as the backend-specific private storage, * and its use is described in more detail below (see spbuf_write()). */ typedef struct { char *spb_buf; /* pointer to underlying buffer */ size_t spb_bufsiz; /* length of underlying buffer */ size_t spb_total; /* total of all bytes passed via IOP_WRITE */ } spbuf_t; /* * Define VA_ARG macro for grabbing the next datum to format for the printf * family of functions. We use VA_ARG so that we can support two kinds of * argument lists: the va_list type supplied by used for printf and * vprintf, and an array of mdb_arg_t structures, which we expect will be * either type STRING or IMMEDIATE. The vec_arg function takes care of * handling the mdb_arg_t case. */ typedef enum { VAT_VARARGS, /* va_list is a va_list */ VAT_ARGVEC /* va_list is a const mdb_arg_t[] in disguise */ } vatype_t; typedef struct { vatype_t val_type; union { va_list _val_valist; const mdb_arg_t *_val_argv; } _val_u; } varglist_t; #define val_valist _val_u._val_valist #define val_argv _val_u._val_argv #define VA_ARG(ap, type) ((ap->val_type == VAT_VARARGS) ? \ va_arg(ap->val_valist, type) : (type)vec_arg(&ap->val_argv)) #define VA_PTRARG(ap) ((ap->val_type == VAT_VARARGS) ? \ (void *)va_arg(ap->val_valist, uintptr_t) : \ (void *)(uintptr_t)vec_arg(&ap->val_argv)) /* * Define macro for converting char constant to Ctrl-char equivalent: */ #ifndef CTRL #define CTRL(c) ((c) & 0x01f) #endif #define IOB_AUTOWRAP(iob) \ ((mdb.m_flags & MDB_FL_AUTOWRAP) && \ ((iob)->iob_flags & MDB_IOB_AUTOWRAP)) /* * Define macro for determining if we should automatically wrap to the next * line of output, based on the amount of consumed buffer space and the * specified size of the next thing to be inserted (n) -- being careful to * not force a spurious wrap if we're autoindented and already at the margin. */ #define IOB_WRAPNOW(iob, n) \ (IOB_AUTOWRAP(iob) && (iob)->iob_nbytes != 0 && \ ((n) + (iob)->iob_nbytes > (iob)->iob_cols) && \ !(((iob)->iob_flags & MDB_IOB_INDENT) && \ (iob)->iob_nbytes == (iob)->iob_margin)) /* * Define prompt string and string to erase prompt string for iob_pager * function, which is invoked if the pager is enabled on an i/o buffer * and we're about to print a line which would be the last on the screen. */ static const char io_prompt[] = ">> More [, , q, n, c, a] ? "; static const char io_perase[] = " "; static const char io_pbcksp[] = /*CSTYLED*/ "\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"; static const size_t io_promptlen = sizeof (io_prompt) - 1; static const size_t io_peraselen = sizeof (io_perase) - 1; static const size_t io_pbcksplen = sizeof (io_pbcksp) - 1; static ssize_t iob_write(mdb_iob_t *iob, mdb_io_t *io, const void *buf, size_t n) { ssize_t resid = n; ssize_t len; while (resid != 0) { if ((len = IOP_WRITE(io, buf, resid)) <= 0) break; buf = (char *)buf + len; resid -= len; } /* * Note that if we had a partial write before an error, we still want * to return the fact something was written. The caller will get an * error next time it tries to write anything. */ if (resid == n && n != 0) { iob->iob_flags |= MDB_IOB_ERR; return (-1); } return (n - resid); } static ssize_t iob_read(mdb_iob_t *iob, mdb_io_t *io) { ssize_t len; ASSERT(iob->iob_nbytes == 0); len = IOP_READ(io, iob->iob_buf, iob->iob_bufsiz); iob->iob_bufp = &iob->iob_buf[0]; switch (len) { case -1: iob->iob_flags |= MDB_IOB_ERR; break; case 0: iob->iob_flags |= MDB_IOB_EOF; break; default: iob->iob_nbytes = len; } return (len); } /*ARGSUSED*/ static void iob_winch(int sig, siginfo_t *sip, ucontext_t *ucp, void *data) { siglongjmp(*((sigjmp_buf *)data), sig); } static int iob_pager(mdb_iob_t *iob) { int status = 0; sigjmp_buf env; uchar_t c; mdb_signal_f *termio_winch; void *termio_data; size_t old_rows; if (iob->iob_pgp == NULL || (iob->iob_flags & MDB_IOB_PGCONT)) return (0); termio_winch = mdb_signal_gethandler(SIGWINCH, &termio_data); (void) mdb_signal_sethandler(SIGWINCH, iob_winch, &env); if (sigsetjmp(env, 1) != 0) { /* * Reset the cursor back to column zero before printing a new * prompt, since its position is unreliable after a SIGWINCH. */ (void) iob_write(iob, iob->iob_pgp, "\r", sizeof (char)); old_rows = iob->iob_rows; /* * If an existing SIGWINCH handler was present, call it. We * expect that this will be termio: the handler will read the * new window size, and then resize this iob appropriately. */ if (termio_winch != (mdb_signal_f *)NULL) termio_winch(SIGWINCH, NULL, NULL, termio_data); /* * If the window has increased in size, we treat this like a * request to fill out the new remainder of the page. */ if (iob->iob_rows > old_rows) { iob->iob_flags &= ~MDB_IOB_PGSINGLE; iob->iob_nlines = old_rows; status = 0; goto winch; } } (void) iob_write(iob, iob->iob_pgp, io_prompt, io_promptlen); for (;;) { if (IOP_READ(iob->iob_pgp, &c, sizeof (c)) != sizeof (c)) { status = MDB_ERR_PAGER; break; } switch (c) { case 'N': case 'n': case '\n': case '\r': iob->iob_flags |= MDB_IOB_PGSINGLE; goto done; case CTRL('c'): case CTRL('\\'): case 'Q': case 'q': mdb_iob_discard(iob); status = MDB_ERR_PAGER; goto done; case 'A': case 'a': mdb_iob_discard(iob); status = MDB_ERR_ABORT; goto done; case 'C': case 'c': iob->iob_flags |= MDB_IOB_PGCONT; /*FALLTHRU*/ case ' ': iob->iob_flags &= ~MDB_IOB_PGSINGLE; goto done; } } done: (void) iob_write(iob, iob->iob_pgp, io_pbcksp, io_pbcksplen); winch: (void) iob_write(iob, iob->iob_pgp, io_perase, io_peraselen); (void) iob_write(iob, iob->iob_pgp, io_pbcksp, io_pbcksplen); (void) mdb_signal_sethandler(SIGWINCH, termio_winch, termio_data); if ((iob->iob_flags & MDB_IOB_ERR) && status == 0) status = MDB_ERR_OUTPUT; return (status); } static void iob_indent(mdb_iob_t *iob) { if (iob->iob_nbytes == 0 && iob->iob_margin != 0 && (iob->iob_flags & MDB_IOB_INDENT)) { size_t i; ASSERT(iob->iob_margin < iob->iob_cols); ASSERT(iob->iob_bufp == iob->iob_buf); for (i = 0; i < iob->iob_margin; i++) *iob->iob_bufp++ = ' '; iob->iob_nbytes = iob->iob_margin; } } static void iob_unindent(mdb_iob_t *iob) { if (iob->iob_nbytes != 0 && iob->iob_nbytes == iob->iob_margin) { const char *p = iob->iob_buf; while (p < &iob->iob_buf[iob->iob_margin]) { if (*p++ != ' ') return; } iob->iob_bufp = &iob->iob_buf[0]; iob->iob_nbytes = 0; } } mdb_iob_t * mdb_iob_create(mdb_io_t *io, uint_t flags) { mdb_iob_t *iob = mdb_alloc(sizeof (mdb_iob_t), UM_SLEEP); iob->iob_buf = mdb_alloc(BUFSIZ, UM_SLEEP); iob->iob_bufsiz = BUFSIZ; iob->iob_bufp = &iob->iob_buf[0]; iob->iob_nbytes = 0; iob->iob_nlines = 0; iob->iob_lineno = 1; iob->iob_rows = MDB_IOB_DEFROWS; iob->iob_cols = MDB_IOB_DEFCOLS; iob->iob_tabstop = MDB_IOB_DEFTAB; iob->iob_margin = MDB_IOB_DEFMARGIN; iob->iob_flags = flags & ~(MDB_IOB_EOF|MDB_IOB_ERR) | MDB_IOB_AUTOWRAP; iob->iob_iop = mdb_io_hold(io); iob->iob_pgp = NULL; iob->iob_next = NULL; IOP_LINK(io, iob); iob_indent(iob); return (iob); } void mdb_iob_pipe(mdb_iob_t **iobs, mdb_iobsvc_f *rdsvc, mdb_iobsvc_f *wrsvc) { mdb_io_t *pio = mdb_pipeio_create(rdsvc, wrsvc); int i; iobs[0] = mdb_iob_create(pio, MDB_IOB_RDONLY); iobs[1] = mdb_iob_create(pio, MDB_IOB_WRONLY); for (i = 0; i < 2; i++) { iobs[i]->iob_flags &= ~MDB_IOB_AUTOWRAP; iobs[i]->iob_cols = iobs[i]->iob_bufsiz; } } void mdb_iob_destroy(mdb_iob_t *iob) { /* * Don't flush a pipe, since it may cause a context switch when the * other side has already been destroyed. */ if (!mdb_iob_isapipe(iob)) mdb_iob_flush(iob); if (iob->iob_pgp != NULL) mdb_io_rele(iob->iob_pgp); while (iob->iob_iop != NULL) { IOP_UNLINK(iob->iob_iop, iob); (void) mdb_iob_pop_io(iob); } mdb_free(iob->iob_buf, iob->iob_bufsiz); mdb_free(iob, sizeof (mdb_iob_t)); } void mdb_iob_discard(mdb_iob_t *iob) { iob->iob_bufp = &iob->iob_buf[0]; iob->iob_nbytes = 0; } void mdb_iob_flush(mdb_iob_t *iob) { int pgerr = 0; if (iob->iob_nbytes == 0) return; /* Nothing to do if buffer is empty */ if (iob->iob_flags & MDB_IOB_WRONLY) { if (iob->iob_flags & MDB_IOB_PGSINGLE) { iob->iob_flags &= ~MDB_IOB_PGSINGLE; iob->iob_nlines = 0; pgerr = iob_pager(iob); } else if (iob->iob_nlines >= iob->iob_rows - 1) { iob->iob_nlines = 0; if (iob->iob_flags & MDB_IOB_PGENABLE) pgerr = iob_pager(iob); } if (pgerr == 0) { /* * We only jump out of the dcmd on error if the iob is * m_out. Presumably, if a dcmd has opened a special * file and is writing to it, it will handle errors * properly. */ if (iob_write(iob, iob->iob_iop, iob->iob_buf, iob->iob_nbytes) < 0 && iob == mdb.m_out) pgerr = MDB_ERR_OUTPUT; iob->iob_nlines++; } } iob->iob_bufp = &iob->iob_buf[0]; iob->iob_nbytes = 0; iob_indent(iob); if (pgerr) longjmp(mdb.m_frame->f_pcb, pgerr); } void mdb_iob_nlflush(mdb_iob_t *iob) { iob_unindent(iob); if (iob->iob_nbytes != 0) mdb_iob_nl(iob); else iob_indent(iob); } void mdb_iob_push_io(mdb_iob_t *iob, mdb_io_t *io) { ASSERT(io->io_next == NULL); io->io_next = iob->iob_iop; iob->iob_iop = mdb_io_hold(io); } mdb_io_t * mdb_iob_pop_io(mdb_iob_t *iob) { mdb_io_t *io = iob->iob_iop; if (io != NULL) { iob->iob_iop = io->io_next; io->io_next = NULL; mdb_io_rele(io); } return (io); } void mdb_iob_resize(mdb_iob_t *iob, size_t rows, size_t cols) { if (cols > iob->iob_bufsiz) iob->iob_cols = iob->iob_bufsiz; else iob->iob_cols = cols != 0 ? cols : MDB_IOB_DEFCOLS; iob->iob_rows = rows != 0 ? rows : MDB_IOB_DEFROWS; } void mdb_iob_setpager(mdb_iob_t *iob, mdb_io_t *pgio) { struct winsize winsz; if (iob->iob_pgp != NULL) { IOP_UNLINK(iob->iob_pgp, iob); mdb_io_rele(iob->iob_pgp); } iob->iob_flags |= MDB_IOB_PGENABLE; iob->iob_flags &= ~(MDB_IOB_PGSINGLE | MDB_IOB_PGCONT); iob->iob_pgp = mdb_io_hold(pgio); IOP_LINK(iob->iob_pgp, iob); if (IOP_CTL(pgio, TIOCGWINSZ, &winsz) == 0) mdb_iob_resize(iob, (size_t)winsz.ws_row, (size_t)winsz.ws_col); } void mdb_iob_tabstop(mdb_iob_t *iob, size_t tabstop) { iob->iob_tabstop = MIN(tabstop, iob->iob_cols - 1); } void mdb_iob_margin(mdb_iob_t *iob, size_t margin) { iob_unindent(iob); iob->iob_margin = MIN(margin, iob->iob_cols - 1); iob_indent(iob); } void mdb_iob_setbuf(mdb_iob_t *iob, void *buf, size_t bufsiz) { ASSERT(buf != NULL && bufsiz != 0); mdb_free(iob->iob_buf, iob->iob_bufsiz); iob->iob_buf = buf; iob->iob_bufsiz = bufsiz; if (iob->iob_flags & MDB_IOB_WRONLY) iob->iob_cols = MIN(iob->iob_cols, iob->iob_bufsiz); } void mdb_iob_clearlines(mdb_iob_t *iob) { iob->iob_flags &= ~(MDB_IOB_PGSINGLE | MDB_IOB_PGCONT); iob->iob_nlines = 0; } void mdb_iob_setflags(mdb_iob_t *iob, uint_t flags) { iob->iob_flags |= flags; if (flags & MDB_IOB_INDENT) iob_indent(iob); } void mdb_iob_clrflags(mdb_iob_t *iob, uint_t flags) { iob->iob_flags &= ~flags; if (flags & MDB_IOB_INDENT) iob_unindent(iob); } uint_t mdb_iob_getflags(mdb_iob_t *iob) { return (iob->iob_flags); } static uintmax_t vec_arg(const mdb_arg_t **app) { uintmax_t value; if ((*app)->a_type == MDB_TYPE_STRING) value = (uintmax_t)(uintptr_t)(*app)->a_un.a_str; else value = (*app)->a_un.a_val; (*app)++; return (value); } static const char * iob_size2str(intsize_t size) { switch (size) { case SZ_SHORT: return ("short"); case SZ_INT: return ("int"); case SZ_LONG: return ("long"); case SZ_LONGLONG: return ("long long"); } return (""); } /* * In order to simplify maintenance of the ::formats display, we provide an * unparser for mdb_printf format strings that converts a simple format * string with one specifier into a descriptive representation, e.g. * mdb_iob_format2str("%llx") returns "hexadecimal long long". */ const char * mdb_iob_format2str(const char *format) { intsize_t size = SZ_INT; const char *p; static char buf[64]; buf[0] = '\0'; if ((p = strchr(format, '%')) == NULL) goto done; fmt_switch: switch (*++p) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': while (*p >= '0' && *p <= '9') p++; p--; goto fmt_switch; case 'a': case 'A': return ("symbol"); case 'b': (void) strcpy(buf, "unsigned "); (void) strcat(buf, iob_size2str(size)); (void) strcat(buf, " bitfield"); break; case 'c': return ("character"); case 'd': case 'i': (void) strcpy(buf, "decimal signed "); (void) strcat(buf, iob_size2str(size)); break; case 'e': case 'E': case 'g': case 'G': return ("double"); case 'h': size = SZ_SHORT; goto fmt_switch; case 'H': return ("human-readable size"); case 'I': return ("IPv4 address"); case 'l': if (size >= SZ_LONG) size = SZ_LONGLONG; else size = SZ_LONG; goto fmt_switch; case 'm': return ("margin"); case 'N': return ("IPv6 address"); case 'o': (void) strcpy(buf, "octal unsigned "); (void) strcat(buf, iob_size2str(size)); break; case 'p': return ("pointer"); case 'q': (void) strcpy(buf, "octal signed "); (void) strcat(buf, iob_size2str(size)); break; case 'r': (void) strcpy(buf, "default radix unsigned "); (void) strcat(buf, iob_size2str(size)); break; case 'R': (void) strcpy(buf, "default radix signed "); (void) strcat(buf, iob_size2str(size)); break; case 's': return ("string"); case 't': case 'T': return ("tab"); case 'u': (void) strcpy(buf, "decimal unsigned "); (void) strcat(buf, iob_size2str(size)); break; case 'x': case 'X': (void) strcat(buf, "hexadecimal "); (void) strcat(buf, iob_size2str(size)); break; case 'Y': return ("time_t"); case '<': return ("terminal attribute"); case '?': case '#': case '+': case '-': goto fmt_switch; } done: if (buf[0] == '\0') (void) strcpy(buf, "text"); return ((const char *)buf); } static const char * iob_int2str(varglist_t *ap, intsize_t size, int base, uint_t flags, int *zero, u_longlong_t *value) { uintmax_t i; switch (size) { case SZ_LONGLONG: if (flags & NTOS_UNSIGNED) i = (u_longlong_t)VA_ARG(ap, u_longlong_t); else i = (longlong_t)VA_ARG(ap, longlong_t); break; case SZ_LONG: if (flags & NTOS_UNSIGNED) i = (ulong_t)VA_ARG(ap, ulong_t); else i = (long)VA_ARG(ap, long); break; case SZ_SHORT: if (flags & NTOS_UNSIGNED) i = (ushort_t)VA_ARG(ap, uint_t); else i = (short)VA_ARG(ap, int); break; default: if (flags & NTOS_UNSIGNED) i = (uint_t)VA_ARG(ap, uint_t); else i = (int)VA_ARG(ap, int); } *zero = i == 0; /* Return flag indicating if result was zero */ *value = i; /* Return value retrieved from va_list */ return (numtostr(i, base, flags)); } static const char * iob_time2str(time_t *tmp) { /* * ctime(3c) returns a string of the form * "Fri Sep 13 00:00:00 1986\n\0". We turn this into the canonical * adb /y format "1986 Sep 13 00:00:00" below. */ const char *src = ctime(tmp); static char buf[32]; char *dst = buf; int i; if (src == NULL) return (numtostr((uintmax_t)*tmp, mdb.m_radix, 0)); for (i = 20; i < 24; i++) *dst++ = src[i]; /* Copy the 4-digit year */ for (i = 3; i < 19; i++) *dst++ = src[i]; /* Copy month, day, and h:m:s */ *dst = '\0'; return (buf); } static const char * iob_addr2str(uintptr_t addr) { static char buf[MDB_TGT_SYM_NAMLEN]; size_t buflen = sizeof (buf); longlong_t offset; GElf_Sym sym; if (mdb_tgt_lookup_by_addr(mdb.m_target, addr, MDB_TGT_SYM_FUZZY, buf, sizeof (buf), &sym, NULL) == -1) return (NULL); if (mdb.m_demangler != NULL && (mdb.m_flags & MDB_FL_DEMANGLE)) { /* * The mdb demangler attempts to either return us our original * name or a pointer to something it has changed. If it has * returned our original name, we want to update buf with that * so we can later modify it. Unfortunately if we find we exceed * the buffer, there's not an easy way to warn the user about * this, so we just truncate the symbol with a '???' and return * it. To someone finding this due to having seen that in a * symbol, sorry. */ const char *dem = mdb_dem_convert(mdb.m_demangler, buf); if (dem != buf) { if (strlcpy(buf, dem, buflen) >= buflen) { buf[buflen - 1] = '?'; buf[buflen - 2] = '?'; buf[buflen - 3] = '?'; return (buf); } } } /* * Here we provide a little cooperation between the %a formatting code * and the proc target: if the initial address passed to %a is in fact * a PLT address, the proc target's lookup_by_addr code will convert * this to the PLT destination (a different address). We do not want * to append a "+/-offset" suffix based on comparison with the query * symbol in this case because the proc target has really done a hidden * query for us with a different address. We detect this case by * comparing the initial characters of buf to the special PLT= string. */ if (sym.st_value != addr && strncmp(buf, "PLT=", 4) != 0) { if (sym.st_value > addr) offset = -(longlong_t)(sym.st_value - addr); else offset = (longlong_t)(addr - sym.st_value); /* * See the earlier note in this function about how we handle * demangler output for why we've dealt with things this way. */ if (strlcat(buf, numtostr(offset, mdb.m_radix, NTOS_SIGNPOS | NTOS_SHOWBASE), buflen) >= buflen) { buf[buflen - 1] = '?'; buf[buflen - 2] = '?'; buf[buflen - 3] = '?'; } } return (buf); } /* * Produce human-readable size, similar in spirit (and identical in output) * to libzfs's zfs_nicenum() -- but made significantly more complicated by * the constraint that we cannot use snprintf() as an implementation detail. * Recall, floating point is verboten in kmdb. */ static const char * iob_bytes2str(varglist_t *ap, intsize_t size) { #ifndef _KMDB const int sigfig = 3; uint64_t orig; #endif uint64_t n; static char buf[68], *c; int index = 0; char u; switch (size) { case SZ_LONGLONG: n = (u_longlong_t)VA_ARG(ap, u_longlong_t); break; case SZ_LONG: n = (ulong_t)VA_ARG(ap, ulong_t); break; case SZ_SHORT: n = (ushort_t)VA_ARG(ap, uint_t); break; default: n = (uint_t)VA_ARG(ap, uint_t); } #ifndef _KMDB orig = n; #endif while (n >= 1024) { n /= 1024; index++; } u = " KMGTPE"[index]; buf[0] = '\0'; if (index == 0) { return (numtostr(n, 10, 0)); #ifndef _KMDB } else if ((orig & ((1ULL << 10 * index) - 1)) == 0) { #else } else { #endif /* * If this is an even multiple of the base or we are in an * environment where floating point is verboten (i.e., kmdb), * always display without any decimal precision. */ (void) strcat(buf, numtostr(n, 10, 0)); #ifndef _KMDB } else { /* * We want to choose a precision that results in the specified * number of significant figures (by default, 3). This is * similar to the output that one would get specifying the %.*g * format specifier (where the asterisk denotes the number of * significant digits), but (1) we include trailing zeros if * the there are non-zero digits beyond the number of * significant digits (that is, 10241 is '10.0K', not the * '10K' that it would be with %.3g) and (2) we never resort * to %e notation when the number of digits exceeds the * number of significant figures (that is, 1043968 is '1020K', * not '1.02e+03K'). This is also made somewhat complicated * by the fact that we need to deal with rounding (10239 is * '10.0K', not '9.99K'), for which we perform nearest-even * rounding. */ double val = (double)orig / (1ULL << 10 * index); int i, mag = 1, thresh; for (i = 0; i < sigfig - 1; i++) mag *= 10; for (thresh = mag * 10; mag >= 1; mag /= 10, i--) { double mult = val * (double)mag; uint32_t v; /* * Note that we cast mult to a 32-bit value. We know * that val is less than 1024 due to the logic above, * and that mag is at most 10^(sigfig - 1). This means * that as long as sigfig is 9 or lower, this will not * overflow. (We perform this cast because it assures * that we are never converting a double to a uint64_t, * which for some compilers requires a call to a * function not guaranteed to be in libstand.) */ if (mult - (double)(uint32_t)mult != 0.5) { v = (uint32_t)(mult + 0.5); } else { /* * We are exactly between integer multiples * of units; perform nearest-even rounding * to be consistent with the behavior of * printf(). */ if ((v = (uint32_t)mult) & 1) v++; } if (mag == 1) { (void) strcat(buf, numtostr(v, 10, 0)); break; } if (v < thresh) { (void) strcat(buf, numtostr(v / mag, 10, 0)); (void) strcat(buf, "."); c = (char *)numtostr(v % mag, 10, 0); i -= strlen(c); /* * We need to zero-fill from the right of the * decimal point to the first significant digit * of the fractional component. */ while (i--) (void) strcat(buf, "0"); (void) strcat(buf, c); break; } } #endif } c = &buf[strlen(buf)]; *c++ = u; *c++ = '\0'; return (buf); } static int iob_setattr(mdb_iob_t *iob, const char *s, size_t nbytes) { uint_t attr; int req; if (iob->iob_pgp == NULL) return (set_errno(ENOTTY)); if (nbytes != 0 && *s == '/') { req = ATT_OFF; nbytes--; s++; } else req = ATT_ON; if (nbytes != 1) return (set_errno(EINVAL)); switch (*s) { case 's': attr = ATT_STANDOUT; break; case 'u': attr = ATT_UNDERLINE; break; case 'r': attr = ATT_REVERSE; break; case 'b': attr = ATT_BOLD; break; case 'd': attr = ATT_DIM; break; case 'a': attr = ATT_ALTCHARSET; break; default: return (set_errno(EINVAL)); } /* * We need to flush the current buffer contents before calling * IOP_SETATTR because IOP_SETATTR may need to synchronously output * terminal escape sequences directly to the underlying device. */ (void) iob_write(iob, iob->iob_iop, iob->iob_buf, iob->iob_nbytes); iob->iob_bufp = &iob->iob_buf[0]; iob->iob_nbytes = 0; return (IOP_SETATTR(iob->iob_pgp, req, attr)); } static void iob_bits2str(mdb_iob_t *iob, u_longlong_t value, const mdb_bitmask_t *bmp, mdb_bool_t altflag) { mdb_bool_t delim = FALSE; const char *str; size_t width; if (bmp == NULL) goto out; for (; bmp->bm_name != NULL; bmp++) { if ((value & bmp->bm_mask) == bmp->bm_bits) { width = strlen(bmp->bm_name) + delim; if (IOB_WRAPNOW(iob, width)) mdb_iob_nl(iob); if (delim) mdb_iob_putc(iob, ','); else delim = TRUE; mdb_iob_puts(iob, bmp->bm_name); value &= ~bmp->bm_bits; } } out: if (altflag == TRUE && (delim == FALSE || value != 0)) { str = numtostr(value, 16, NTOS_UNSIGNED | NTOS_SHOWBASE); width = strlen(str) + delim; if (IOB_WRAPNOW(iob, width)) mdb_iob_nl(iob); if (delim) mdb_iob_putc(iob, ','); mdb_iob_puts(iob, str); } } static const char * iob_inaddr2str(uint32_t addr) { static char buf[INET_ADDRSTRLEN]; (void) mdb_inet_ntop(AF_INET, &addr, buf, sizeof (buf)); return (buf); } static const char * iob_ipv6addr2str(void *addr) { static char buf[INET6_ADDRSTRLEN]; (void) mdb_inet_ntop(AF_INET6, addr, buf, sizeof (buf)); return (buf); } static const char * iob_getvar(const char *s, size_t len) { mdb_var_t *val; char *var; if (len == 0) { (void) set_errno(EINVAL); return (NULL); } var = strndup(s, len); val = mdb_nv_lookup(&mdb.m_nv, var); strfree(var); if (val == NULL) { (void) set_errno(EINVAL); return (NULL); } return (numtostr(mdb_nv_get_value(val), 10, 0)); } /* * The iob_doprnt function forms the main engine of the debugger's output * formatting capabilities. Note that this is NOT exactly compatible with * the printf(3C) family, nor is it intended to be so. We support some * extensions and format characters not supported by printf(3C), and we * explicitly do NOT provide support for %C, %S, %ws (wide-character strings), * do NOT provide for the complete functionality of %f, %e, %E, %g, %G * (alternate double formats), and do NOT support %.x (precision specification). * Note that iob_doprnt consumes varargs off the original va_list. */ static void iob_doprnt(mdb_iob_t *iob, const char *format, varglist_t *ap) { char c[2] = { 0, 0 }; /* Buffer for single character output */ const char *p; /* Current position in format string */ size_t len; /* Length of format string to copy verbatim */ size_t altlen; /* Length of alternate print format prefix */ const char *altstr; /* Alternate print format prefix */ const char *symstr; /* Symbol + offset string */ u_longlong_t val; /* Current integer value */ intsize_t size; /* Current integer value size */ uint_t flags; /* Current flags to pass to iob_int2str */ size_t width; /* Current field width */ int zero; /* If != 0, then integer value == 0 */ mdb_bool_t f_alt; /* Use alternate print format (%#) */ mdb_bool_t f_altsuff; /* Alternate print format is a suffix */ mdb_bool_t f_zfill; /* Zero-fill field (%0) */ mdb_bool_t f_left; /* Left-adjust field (%-) */ mdb_bool_t f_digits; /* Explicit digits used to set field width */ union { const char *str; uint32_t ui32; void *ptr; time_t tm; char c; double d; long double ld; } u; ASSERT(iob->iob_flags & MDB_IOB_WRONLY); while ((p = strchr(format, '%')) != NULL) { /* * Output the format string verbatim up to the next '%' char */ if (p != format) { len = p - format; if (IOB_WRAPNOW(iob, len) && *format != '\n') mdb_iob_nl(iob); mdb_iob_nputs(iob, format, len); } /* * Now we need to parse the sequence of format characters * following the % marker and do the appropriate thing. */ size = SZ_INT; /* Use normal-sized int by default */ flags = 0; /* Clear numtostr() format flags */ width = 0; /* No field width limit by default */ altlen = 0; /* No alternate format string yet */ altstr = NULL; /* No alternate format string yet */ f_alt = FALSE; /* Alternate format off by default */ f_altsuff = FALSE; /* Alternate format is a prefix */ f_zfill = FALSE; /* Zero-fill off by default */ f_left = FALSE; /* Left-adjust off by default */ f_digits = FALSE; /* No digits for width specified yet */ fmt_switch: switch (*++p) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (f_digits == FALSE && *p == '0') { f_zfill = TRUE; goto fmt_switch; } if (f_digits == FALSE) width = 0; /* clear any other width specifier */ for (u.c = *p; u.c >= '0' && u.c <= '9'; u.c = *++p) width = width * 10 + u.c - '0'; p--; f_digits = TRUE; goto fmt_switch; case 'a': if (size < SZ_LONG) size = SZ_LONG; /* Bump to size of uintptr_t */ u.str = iob_int2str(ap, size, 16, NTOS_UNSIGNED | NTOS_SHOWBASE, &zero, &val); if ((symstr = iob_addr2str(val)) != NULL) u.str = symstr; if (f_alt == TRUE) { f_altsuff = TRUE; altstr = ":"; altlen = 1; } break; case 'A': if (size < SZ_LONG) size = SZ_LONG; /* Bump to size of uintptr_t */ (void) iob_int2str(ap, size, 16, NTOS_UNSIGNED, &zero, &val); u.str = iob_addr2str(val); if (f_alt == TRUE && u.str == NULL) u.str = "?"; break; case 'b': u.str = iob_int2str(ap, size, 16, NTOS_UNSIGNED | NTOS_SHOWBASE, &zero, &val); iob_bits2str(iob, val, VA_PTRARG(ap), f_alt); format = ++p; continue; case 'c': c[0] = (char)VA_ARG(ap, int); u.str = c; break; case 'd': case 'i': if (f_alt) flags |= NTOS_SHOWBASE; u.str = iob_int2str(ap, size, 10, flags, &zero, &val); break; /* No floating point in kmdb */ #ifndef _KMDB case 'e': case 'E': u.d = VA_ARG(ap, double); u.str = doubletos(u.d, 7, *p); break; case 'g': case 'G': if (size >= SZ_LONG) { u.ld = VA_ARG(ap, long double); u.str = longdoubletos(&u.ld, 16, (*p == 'g') ? 'e' : 'E'); } else { u.d = VA_ARG(ap, double); u.str = doubletos(u.d, 16, (*p == 'g') ? 'e' : 'E'); } break; #endif case 'h': size = SZ_SHORT; goto fmt_switch; case 'H': u.str = iob_bytes2str(ap, size); break; case 'I': u.ui32 = VA_ARG(ap, uint32_t); u.str = iob_inaddr2str(u.ui32); break; case 'l': if (size >= SZ_LONG) size = SZ_LONGLONG; else size = SZ_LONG; goto fmt_switch; case 'm': if (iob->iob_nbytes == 0) { mdb_iob_ws(iob, (width != 0) ? width : iob->iob_margin); } format = ++p; continue; case 'N': u.ptr = VA_PTRARG(ap); u.str = iob_ipv6addr2str(u.ptr); break; case 'o': u.str = iob_int2str(ap, size, 8, NTOS_UNSIGNED, &zero, &val); if (f_alt && !zero) { altstr = "0"; altlen = 1; } break; case 'p': u.ptr = VA_PTRARG(ap); u.str = numtostr((uintptr_t)u.ptr, 16, NTOS_UNSIGNED); break; case 'q': u.str = iob_int2str(ap, size, 8, flags, &zero, &val); if (f_alt && !zero) { altstr = "0"; altlen = 1; } break; case 'r': if (f_alt) flags |= NTOS_SHOWBASE; u.str = iob_int2str(ap, size, mdb.m_radix, NTOS_UNSIGNED | flags, &zero, &val); break; case 'R': if (f_alt) flags |= NTOS_SHOWBASE; u.str = iob_int2str(ap, size, mdb.m_radix, flags, &zero, &val); break; case 's': u.str = VA_PTRARG(ap); if (u.str == NULL) u.str = ""; /* Be forgiving of NULL */ break; case 't': if (width != 0) { while (width-- > 0) mdb_iob_tab(iob); } else mdb_iob_tab(iob); format = ++p; continue; case 'T': if (width != 0 && (iob->iob_nbytes % width) != 0) { size_t ots = iob->iob_tabstop; iob->iob_tabstop = width; mdb_iob_tab(iob); iob->iob_tabstop = ots; } format = ++p; continue; case 'u': if (f_alt) flags |= NTOS_SHOWBASE; u.str = iob_int2str(ap, size, 10, flags | NTOS_UNSIGNED, &zero, &val); break; case 'x': u.str = iob_int2str(ap, size, 16, NTOS_UNSIGNED, &zero, &val); if (f_alt && !zero) { altstr = "0x"; altlen = 2; } break; case 'X': u.str = iob_int2str(ap, size, 16, NTOS_UNSIGNED | NTOS_UPCASE, &zero, &val); if (f_alt && !zero) { altstr = "0X"; altlen = 2; } break; case 'Y': u.tm = VA_ARG(ap, time_t); u.str = iob_time2str(&u.tm); break; case '<': /* * Used to turn attributes on (), to turn them * off (), or to print variables (<_var>). */ for (u.str = ++p; *p != '\0' && *p != '>'; p++) continue; if (*p == '>') { size_t paramlen = p - u.str; if (paramlen > 0) { if (*u.str == '_') { u.str = iob_getvar(u.str + 1, paramlen - 1); break; } else { (void) iob_setattr(iob, u.str, paramlen); } } p++; } format = p; continue; case '*': width = (size_t)(uint_t)VA_ARG(ap, int); goto fmt_switch; case '%': u.str = "%"; break; case '?': width = sizeof (uintptr_t) * 2; goto fmt_switch; case '#': f_alt = TRUE; goto fmt_switch; case '+': flags |= NTOS_SIGNPOS; goto fmt_switch; case '-': f_left = TRUE; goto fmt_switch; default: c[0] = p[0]; u.str = c; } len = u.str != NULL ? strlen(u.str) : 0; if (len + altlen > width) width = len + altlen; /* * If the string and the option altstr won't fit on this line * and auto-wrap is set (default), skip to the next line. * If the string contains \n, and the \n terminated substring * + altstr is shorter than the above, use the shorter lf_len. */ if (u.str != NULL) { char *np = strchr(u.str, '\n'); if (np != NULL) { int lf_len = (np - u.str) + altlen; if (lf_len < width) width = lf_len; } } if (IOB_WRAPNOW(iob, width)) mdb_iob_nl(iob); /* * Optionally add whitespace or zeroes prefixing the value if * we haven't filled the minimum width and we're right-aligned. */ if (len < (width - altlen) && f_left == FALSE) { mdb_iob_fill(iob, f_zfill ? '0' : ' ', width - altlen - len); } /* * Print the alternate string if it's a prefix, and then * print the value string itself. */ if (altstr != NULL && f_altsuff == FALSE) mdb_iob_nputs(iob, altstr, altlen); if (len != 0) mdb_iob_nputs(iob, u.str, len); /* * If we have an alternate string and it's a suffix, print it. */ if (altstr != NULL && f_altsuff == TRUE) mdb_iob_nputs(iob, altstr, altlen); /* * Finally, if we haven't filled the field width and we're * left-aligned, pad out the rest with whitespace. */ if ((len + altlen) < width && f_left == TRUE) mdb_iob_ws(iob, width - altlen - len); format = (*p != '\0') ? ++p : p; } /* * If there's anything left in the format string, output it now */ if (*format != '\0') { len = strlen(format); if (IOB_WRAPNOW(iob, len) && *format != '\n') mdb_iob_nl(iob); mdb_iob_nputs(iob, format, len); } } void mdb_iob_vprintf(mdb_iob_t *iob, const char *format, va_list alist) { varglist_t ap = { VAT_VARARGS }; va_copy(ap.val_valist, alist); iob_doprnt(iob, format, &ap); } void mdb_iob_aprintf(mdb_iob_t *iob, const char *format, const mdb_arg_t *argv) { varglist_t ap = { VAT_ARGVEC }; ap.val_argv = argv; iob_doprnt(iob, format, &ap); } void mdb_iob_printf(mdb_iob_t *iob, const char *format, ...) { va_list alist; va_start(alist, format); mdb_iob_vprintf(iob, format, alist); va_end(alist); } /* * In order to handle the sprintf family of functions, we define a special * i/o backend known as a "sprintf buf" (or spbuf for short). This back end * provides an IOP_WRITE entry point that concatenates each buffer sent from * mdb_iob_flush() onto the caller's buffer until the caller's buffer is * exhausted. We also keep an absolute count of how many bytes were sent to * this function during the lifetime of the snprintf call. This allows us * to provide the ability to (1) return the total size required for the given * format string and argument list, and (2) support a call to snprintf with a * NULL buffer argument with no special case code elsewhere. */ static ssize_t spbuf_write(mdb_io_t *io, const void *buf, size_t buflen) { spbuf_t *spb = io->io_data; if (spb->spb_bufsiz != 0) { size_t n = MIN(spb->spb_bufsiz, buflen); bcopy(buf, spb->spb_buf, n); spb->spb_buf += n; spb->spb_bufsiz -= n; } spb->spb_total += buflen; return (buflen); } static const mdb_io_ops_t spbuf_ops = { .io_read = no_io_read, .io_write = spbuf_write, .io_seek = no_io_seek, .io_ctl = no_io_ctl, .io_close = no_io_close, .io_name = no_io_name, .io_link = no_io_link, .io_unlink = no_io_unlink, .io_setattr = no_io_setattr, .io_suspend = no_io_suspend, .io_resume = no_io_resume }; /* * The iob_spb_create function initializes an iob suitable for snprintf calls, * a spbuf i/o backend, and the spbuf private data, and then glues these * objects together. The caller (either vsnprintf or asnprintf below) is * expected to have allocated the various structures on their stack. */ static void iob_spb_create(mdb_iob_t *iob, char *iob_buf, size_t iob_len, mdb_io_t *io, spbuf_t *spb, char *spb_buf, size_t spb_len) { spb->spb_buf = spb_buf; spb->spb_bufsiz = spb_len; spb->spb_total = 0; io->io_ops = &spbuf_ops; io->io_data = spb; io->io_next = NULL; io->io_refcnt = 1; iob->iob_buf = iob_buf; iob->iob_bufsiz = iob_len; iob->iob_bufp = iob_buf; iob->iob_nbytes = 0; iob->iob_nlines = 0; iob->iob_lineno = 1; iob->iob_rows = MDB_IOB_DEFROWS; iob->iob_cols = iob_len; iob->iob_tabstop = MDB_IOB_DEFTAB; iob->iob_margin = MDB_IOB_DEFMARGIN; iob->iob_flags = MDB_IOB_WRONLY; iob->iob_iop = io; iob->iob_pgp = NULL; iob->iob_next = NULL; } /*ARGSUSED*/ ssize_t null_io_write(mdb_io_t *io, const void *buf, size_t nbytes) { return (nbytes); } static const mdb_io_ops_t null_ops = { .io_read = no_io_read, .io_write = null_io_write, .io_seek = no_io_seek, .io_ctl = no_io_ctl, .io_close = no_io_close, .io_name = no_io_name, .io_link = no_io_link, .io_unlink = no_io_unlink, .io_setattr = no_io_setattr, .io_suspend = no_io_suspend, .io_resume = no_io_resume, }; mdb_io_t * mdb_nullio_create(void) { static mdb_io_t null_io = { &null_ops, NULL, NULL, 1 }; return (&null_io); } size_t mdb_iob_vsnprintf(char *buf, size_t nbytes, const char *format, va_list alist) { varglist_t ap = { VAT_VARARGS }; char iob_buf[64]; mdb_iob_t iob; mdb_io_t io; spbuf_t spb; ASSERT(buf != NULL || nbytes == 0); iob_spb_create(&iob, iob_buf, sizeof (iob_buf), &io, &spb, buf, nbytes); va_copy(ap.val_valist, alist); iob_doprnt(&iob, format, &ap); mdb_iob_flush(&iob); if (spb.spb_bufsiz != 0) *spb.spb_buf = '\0'; else if (buf != NULL && nbytes > 0) *--spb.spb_buf = '\0'; return (spb.spb_total); } size_t mdb_iob_asnprintf(char *buf, size_t nbytes, const char *format, const mdb_arg_t *argv) { varglist_t ap = { VAT_ARGVEC }; char iob_buf[64]; mdb_iob_t iob; mdb_io_t io; spbuf_t spb; ASSERT(buf != NULL || nbytes == 0); iob_spb_create(&iob, iob_buf, sizeof (iob_buf), &io, &spb, buf, nbytes); ap.val_argv = argv; iob_doprnt(&iob, format, &ap); mdb_iob_flush(&iob); if (spb.spb_bufsiz != 0) *spb.spb_buf = '\0'; else if (buf != NULL && nbytes > 0) *--spb.spb_buf = '\0'; return (spb.spb_total); } /*PRINTFLIKE3*/ size_t mdb_iob_snprintf(char *buf, size_t nbytes, const char *format, ...) { va_list alist; va_start(alist, format); nbytes = mdb_iob_vsnprintf(buf, nbytes, format, alist); va_end(alist); return (nbytes); } /* * Return how many bytes we can copy into our buffer, limited by either cols or * bufsiz depending on whether AUTOWRAP is on. Note that typically, * mdb_iob_set_autowrap() will have already checked for an existing * "->iob_nbytes > ->iob_cols" situation, but we double check here anyway. */ static size_t iob_bufleft(mdb_iob_t *iob) { if (IOB_AUTOWRAP(iob)) { if (iob->iob_cols < iob->iob_nbytes) { mdb_iob_nl(iob); ASSERT(iob->iob_cols >= iob->iob_nbytes); } return (iob->iob_cols - iob->iob_nbytes); } ASSERT(iob->iob_bufsiz >= iob->iob_nbytes); return (iob->iob_bufsiz - iob->iob_nbytes); } void mdb_iob_nputs(mdb_iob_t *iob, const char *s, size_t nbytes) { size_t m, n, nleft = nbytes; const char *p, *q = s; ASSERT(iob->iob_flags & MDB_IOB_WRONLY); if (nbytes == 0) return; /* Return immediately if there is no work to do */ /* * If the string contains embedded newlines or tabs, invoke ourself * recursively for each string component, followed by a call to the * newline or tab routine. This insures that strings with these * characters obey our wrapping and indenting rules, and that strings * with embedded newlines are flushed after each newline, allowing * the output pager to take over if it is enabled. */ while ((p = strnpbrk(q, "\t\n", nleft)) != NULL) { if (p > q) mdb_iob_nputs(iob, q, (size_t)(p - q)); if (*p == '\t') mdb_iob_tab(iob); else mdb_iob_nl(iob); nleft -= (size_t)(p - q) + 1; /* Update byte count */ q = p + 1; /* Advance past delimiter */ } /* * For a given string component, we copy a chunk into the buffer, and * flush the buffer if we reach the end of a line. */ while (nleft != 0) { n = iob_bufleft(iob); m = MIN(nleft, n); /* copy at most n bytes in this pass */ bcopy(q, iob->iob_bufp, m); nleft -= m; q += m; iob->iob_bufp += m; iob->iob_nbytes += m; if (m == n && nleft != 0) { if (IOB_AUTOWRAP(iob)) { mdb_iob_nl(iob); } else { mdb_iob_flush(iob); } } } } void mdb_iob_puts(mdb_iob_t *iob, const char *s) { mdb_iob_nputs(iob, s, strlen(s)); } void mdb_iob_putc(mdb_iob_t *iob, int c) { mdb_iob_fill(iob, c, 1); } void mdb_iob_tab(mdb_iob_t *iob) { ASSERT(iob->iob_flags & MDB_IOB_WRONLY); if (iob->iob_tabstop != 0) { /* * Round up to the next multiple of the tabstop. If this puts * us off the end of the line, just insert a newline; otherwise * insert sufficient whitespace to reach position n. */ size_t n = (iob->iob_nbytes + iob->iob_tabstop) / iob->iob_tabstop * iob->iob_tabstop; if (n < iob->iob_cols) mdb_iob_fill(iob, ' ', n - iob->iob_nbytes); else mdb_iob_nl(iob); } } void mdb_iob_fill(mdb_iob_t *iob, int c, size_t nfill) { size_t i, m, n; ASSERT(iob->iob_flags & MDB_IOB_WRONLY); while (nfill != 0) { n = iob_bufleft(iob); m = MIN(nfill, n); /* fill at most n bytes in this pass */ for (i = 0; i < m; i++) *iob->iob_bufp++ = (char)c; iob->iob_nbytes += m; nfill -= m; if (m == n && nfill != 0) { if (IOB_AUTOWRAP(iob)) { mdb_iob_nl(iob); } else { mdb_iob_flush(iob); } } } } void mdb_iob_ws(mdb_iob_t *iob, size_t n) { if (!IOB_AUTOWRAP(iob) || iob->iob_nbytes + n < iob->iob_cols) mdb_iob_fill(iob, ' ', n); else mdb_iob_nl(iob); } void mdb_iob_nl(mdb_iob_t *iob) { ASSERT(iob->iob_flags & MDB_IOB_WRONLY); if (iob->iob_nbytes == iob->iob_bufsiz) mdb_iob_flush(iob); *iob->iob_bufp++ = '\n'; iob->iob_nbytes++; mdb_iob_flush(iob); } ssize_t mdb_iob_ngets(mdb_iob_t *iob, char *buf, size_t n) { ssize_t resid = n - 1; ssize_t len; int c; if (iob->iob_flags & (MDB_IOB_WRONLY | MDB_IOB_EOF)) return (EOF); /* can't gets a write buf or a read buf at EOF */ if (n == 0) return (0); /* we need room for a terminating \0 */ while (resid != 0) { if (iob->iob_nbytes == 0 && iob_read(iob, iob->iob_iop) <= 0) goto done; /* failed to refill buffer */ for (len = MIN(iob->iob_nbytes, resid); len != 0; len--) { c = *iob->iob_bufp++; iob->iob_nbytes--; if (c == EOF || c == '\n') goto done; *buf++ = (char)c; resid--; } } done: *buf = '\0'; return (n - resid - 1); } int mdb_iob_getc(mdb_iob_t *iob) { int c; if (iob->iob_flags & (MDB_IOB_WRONLY | MDB_IOB_EOF | MDB_IOB_ERR)) return (EOF); /* can't getc if write-only, EOF, or error bit */ if (iob->iob_nbytes == 0 && iob_read(iob, iob->iob_iop) <= 0) return (EOF); /* failed to refill buffer */ c = (uchar_t)*iob->iob_bufp++; iob->iob_nbytes--; return (c); } int mdb_iob_ungetc(mdb_iob_t *iob, int c) { if (iob->iob_flags & (MDB_IOB_WRONLY | MDB_IOB_ERR)) return (EOF); /* can't ungetc if write-only or error bit set */ if (c == EOF || iob->iob_nbytes == iob->iob_bufsiz) return (EOF); /* can't ungetc EOF, or ungetc if buffer full */ *--iob->iob_bufp = (char)c; iob->iob_nbytes++; iob->iob_flags &= ~MDB_IOB_EOF; return (c); } int mdb_iob_eof(mdb_iob_t *iob) { return ((iob->iob_flags & (MDB_IOB_RDONLY | MDB_IOB_EOF)) == (MDB_IOB_RDONLY | MDB_IOB_EOF)); } int mdb_iob_err(mdb_iob_t *iob) { return ((iob->iob_flags & MDB_IOB_ERR) == MDB_IOB_ERR); } ssize_t mdb_iob_read(mdb_iob_t *iob, void *buf, size_t n) { ssize_t resid = n; ssize_t len; if (iob->iob_flags & (MDB_IOB_WRONLY | MDB_IOB_EOF | MDB_IOB_ERR)) return (0); /* can't read if write-only, eof, or error */ while (resid != 0) { if (iob->iob_nbytes == 0 && iob_read(iob, iob->iob_iop) <= 0) break; /* failed to refill buffer */ len = MIN(resid, iob->iob_nbytes); bcopy(iob->iob_bufp, buf, len); iob->iob_bufp += len; iob->iob_nbytes -= len; buf = (char *)buf + len; resid -= len; } return (n - resid); } /* * For now, all binary writes are performed unbuffered. This has the * side effect that the pager will not be triggered by mdb_iob_write. */ ssize_t mdb_iob_write(mdb_iob_t *iob, const void *buf, size_t n) { ssize_t ret; if (iob->iob_flags & MDB_IOB_ERR) return (set_errno(EIO)); if (iob->iob_flags & MDB_IOB_RDONLY) return (set_errno(EMDB_IORO)); mdb_iob_flush(iob); ret = iob_write(iob, iob->iob_iop, buf, n); if (ret < 0 && iob == mdb.m_out) longjmp(mdb.m_frame->f_pcb, MDB_ERR_OUTPUT); return (ret); } int mdb_iob_ctl(mdb_iob_t *iob, int req, void *arg) { return (IOP_CTL(iob->iob_iop, req, arg)); } const char * mdb_iob_name(mdb_iob_t *iob) { if (iob == NULL) return (""); return (IOP_NAME(iob->iob_iop)); } size_t mdb_iob_lineno(mdb_iob_t *iob) { return (iob->iob_lineno); } size_t mdb_iob_gettabstop(mdb_iob_t *iob) { return (iob->iob_tabstop); } size_t mdb_iob_getmargin(mdb_iob_t *iob) { return (iob->iob_margin); } mdb_io_t * mdb_io_hold(mdb_io_t *io) { io->io_refcnt++; return (io); } void mdb_io_rele(mdb_io_t *io) { ASSERT(io->io_refcnt != 0); if (--io->io_refcnt == 0) { IOP_CLOSE(io); mdb_free(io, sizeof (mdb_io_t)); } } void mdb_io_destroy(mdb_io_t *io) { ASSERT(io->io_refcnt == 0); IOP_CLOSE(io); mdb_free(io, sizeof (mdb_io_t)); } void mdb_iob_stack_create(mdb_iob_stack_t *stk) { stk->stk_top = NULL; stk->stk_size = 0; } void mdb_iob_stack_destroy(mdb_iob_stack_t *stk) { mdb_iob_t *top, *ntop; for (top = stk->stk_top; top != NULL; top = ntop) { ntop = top->iob_next; mdb_iob_destroy(top); } } void mdb_iob_stack_push(mdb_iob_stack_t *stk, mdb_iob_t *iob, size_t lineno) { iob->iob_lineno = lineno; iob->iob_next = stk->stk_top; stk->stk_top = iob; stk->stk_size++; yylineno = 1; } mdb_iob_t * mdb_iob_stack_pop(mdb_iob_stack_t *stk) { mdb_iob_t *top = stk->stk_top; ASSERT(top != NULL); stk->stk_top = top->iob_next; top->iob_next = NULL; stk->stk_size--; return (top); } size_t mdb_iob_stack_size(mdb_iob_stack_t *stk) { return (stk->stk_size); } /* * This only enables autowrap for iobs that are already autowrap themselves such * as mdb.m_out typically. * * Note that we might be the middle of the iob buffer at this point, and * specifically, iob->iob_nbytes could be more than iob->iob_cols. As that's * not a valid situation, we may need to do an autowrap *now*. * * In theory, we would need to do this across all MDB_IOB_AUTOWRAP iob's; * instead, we have a failsafe in iob_bufleft(). */ void mdb_iob_set_autowrap(mdb_iob_t *iob) { mdb.m_flags |= MDB_FL_AUTOWRAP; if (IOB_WRAPNOW(iob, 0)) mdb_iob_nl(iob); ASSERT(iob->iob_cols >= iob->iob_nbytes); } /* * Stub functions for i/o backend implementors: these stubs either act as * pass-through no-ops or return ENOTSUP as appropriate. */ ssize_t no_io_read(mdb_io_t *io, void *buf, size_t nbytes) { if (io->io_next != NULL) return (IOP_READ(io->io_next, buf, nbytes)); return (set_errno(EMDB_IOWO)); } ssize_t no_io_write(mdb_io_t *io, const void *buf, size_t nbytes) { if (io->io_next != NULL) return (IOP_WRITE(io->io_next, buf, nbytes)); return (set_errno(EMDB_IORO)); } off64_t no_io_seek(mdb_io_t *io, off64_t offset, int whence) { if (io->io_next != NULL) return (IOP_SEEK(io->io_next, offset, whence)); return (set_errno(ENOTSUP)); } int no_io_ctl(mdb_io_t *io, int req, void *arg) { if (io->io_next != NULL) return (IOP_CTL(io->io_next, req, arg)); return (set_errno(ENOTSUP)); } /*ARGSUSED*/ void no_io_close(mdb_io_t *io) { /* * Note that we do not propagate IOP_CLOSE down the io stack. IOP_CLOSE should * only be called by mdb_io_rele when an io's reference count has gone to zero. */ } const char * no_io_name(mdb_io_t *io) { if (io->io_next != NULL) return (IOP_NAME(io->io_next)); return ("(anonymous)"); } void no_io_link(mdb_io_t *io, mdb_iob_t *iob) { if (io->io_next != NULL) IOP_LINK(io->io_next, iob); } void no_io_unlink(mdb_io_t *io, mdb_iob_t *iob) { if (io->io_next != NULL) IOP_UNLINK(io->io_next, iob); } int no_io_setattr(mdb_io_t *io, int req, uint_t attrs) { if (io->io_next != NULL) return (IOP_SETATTR(io->io_next, req, attrs)); return (set_errno(ENOTSUP)); } void no_io_suspend(mdb_io_t *io) { if (io->io_next != NULL) IOP_SUSPEND(io->io_next); } void no_io_resume(mdb_io_t *io) { if (io->io_next != NULL) IOP_RESUME(io->io_next); } /* * Iterate over the varargs. The first item indicates the mode: * MDB_TBL_PRNT * pull out the next vararg as a const char * and pass it and the * remaining varargs to iob_doprnt; if we want to print the column, * direct the output to mdb.m_out otherwise direct it to mdb.m_null * * MDB_TBL_FUNC * pull out the next vararg as type mdb_table_print_f and the * following one as a void * argument to the function; call the * function with the given argument if we want to print the column * * The second item indicates the flag; if the flag is set in the flags * argument, then the column is printed. A flag value of 0 indicates * that the column should always be printed. */ void mdb_table_print(uint_t flags, const char *delimeter, ...) { va_list alist; uint_t flg; uint_t type; const char *fmt; mdb_table_print_f *func; void *arg; mdb_iob_t *out; mdb_bool_t first = TRUE; mdb_bool_t print; va_start(alist, delimeter); while ((type = va_arg(alist, uint_t)) != MDB_TBL_DONE) { flg = va_arg(alist, uint_t); print = flg == 0 || (flg & flags) != 0; if (print) { if (first) first = FALSE; else mdb_printf("%s", delimeter); } switch (type) { case MDB_TBL_PRNT: { varglist_t ap = { VAT_VARARGS }; fmt = va_arg(alist, const char *); out = print ? mdb.m_out : mdb.m_null; va_copy(ap.val_valist, alist); iob_doprnt(out, fmt, &ap); va_end(alist); va_copy(alist, ap.val_valist); break; } case MDB_TBL_FUNC: func = va_arg(alist, mdb_table_print_f *); arg = va_arg(alist, void *); if (print) func(arg); break; default: warn("bad format type %x\n", type); break; } } va_end(alist); }