# HG changeset patch # User Matt Amdur # Date 1336790293 14400 # Node ID 32dde99890907b9e75d7a371556bfce399a2e5f4 # Parent 72ce76fa37fb74d5293870aedb14b56791ec836e 2701 Add tab completion support for mdb Reviewed by: Jerry Jelinek Reviewed by: Adam Leventhal Reviewed by: Sebastien Roy Reviewed by: Darren Reed Reviewed by: Gordon Ross Approved by: Richard Lowe diff -r 72ce76fa37fb -r 32dde9989090 usr/src/cmd/mdb/Makefile.kmdb.files --- a/usr/src/cmd/mdb/Makefile.kmdb.files Thu May 03 05:27:43 2012 -0500 +++ b/usr/src/cmd/mdb/Makefile.kmdb.files Fri May 11 22:38:13 2012 -0400 @@ -23,6 +23,11 @@ # Use is subject to license terms. # +# +# Copyright (c) 2012 by Delphix. All rights reserved. +# Copyright (c) 2012 Joyent, Inc. All rights reserved. +# + KMDBSRCS += \ ffs.c \ kaif_start.c \ @@ -75,6 +80,7 @@ mdb_string.c \ mdb_strio.c \ kmdb_stubs.c \ + mdb_tab.c \ mdb_target.c \ kmdb_terminfo.c \ mdb_termio.c \ diff -r 72ce76fa37fb -r 32dde9989090 usr/src/cmd/mdb/Makefile.mdb --- a/usr/src/cmd/mdb/Makefile.mdb Thu May 03 05:27:43 2012 -0500 +++ b/usr/src/cmd/mdb/Makefile.mdb Fri May 11 22:38:13 2012 -0400 @@ -19,11 +19,16 @@ # CDDL HEADER END # # -# Copyright 2011 Nexenta Systems, Inc. All rights reserved. # Copyright 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # +# +# Copyright 2011 Nexenta Systems, Inc. All rights reserved. +# Copyright (c) 2012 by Delphix. All rights reserved. +# Copyright (c) 2012 Joyent, Inc. All rights reserved. +# + .KEEP_STATE: .SUFFIXES: @@ -76,6 +81,7 @@ mdb_stdlib.c \ mdb_string.c \ mdb_strio.c \ + mdb_tab.c \ mdb_target.c \ mdb_tdb.c \ mdb_termio.c \ diff -r 72ce76fa37fb -r 32dde9989090 usr/src/cmd/mdb/common/mdb/mdb.c --- a/usr/src/cmd/mdb/common/mdb/mdb.c Thu May 03 05:27:43 2012 -0500 +++ b/usr/src/cmd/mdb/common/mdb/mdb.c Fri May 11 22:38:13 2012 -0400 @@ -24,6 +24,11 @@ */ /* + * Copyright (c) 2012 by Delphix. All rights reserved. + * Copyright (c) 2012 Joyent, Inc. All rights reserved. + */ + +/* * Modular Debugger (MDB) * * Refer to the white paper "A Modular Debugger for Solaris" for information @@ -1132,6 +1137,16 @@ return (status); } +void +mdb_call_tab(mdb_idcmd_t *idcp, mdb_tab_cookie_t *mcp, uint_t flags, + uintmax_t argc, mdb_arg_t *argv) +{ + if (idcp->idc_tabp == NULL) + return; + + idcp->idc_tabp(mcp, flags, argc, argv); +} + /* * Call an internal dcmd directly: this code is used by module API functions * that need to execute dcmds, and by mdb_call() above. diff -r 72ce76fa37fb -r 32dde9989090 usr/src/cmd/mdb/common/mdb/mdb.h --- a/usr/src/cmd/mdb/common/mdb/mdb.h Thu May 03 05:27:43 2012 -0500 +++ b/usr/src/cmd/mdb/common/mdb/mdb.h Fri May 11 22:38:13 2012 -0400 @@ -23,6 +23,11 @@ * Use is subject to license terms. */ +/* + * Copyright (c) 2012 by Delphix. All rights reserved. + * Copyright (c) 2012 Joyent, Inc. All rights reserved. + */ + #ifndef _MDB_H #define _MDB_H @@ -38,6 +43,7 @@ #include #include #include +#include #ifdef _KMDB #include #endif @@ -206,6 +212,8 @@ extern int mdb_call_idcmd(mdb_idcmd_t *, uintmax_t, uintmax_t, uint_t, mdb_argvec_t *, mdb_addrvec_t *, mdb_vcb_t *); +extern void mdb_call_tab(mdb_idcmd_t *, mdb_tab_cookie_t *, uint_t, uintmax_t, + mdb_arg_t *); extern int mdb_call(uintmax_t, uintmax_t, uint_t); extern int mdb_run(void); diff -r 72ce76fa37fb -r 32dde9989090 usr/src/cmd/mdb/common/mdb/mdb_cmds.c --- a/usr/src/cmd/mdb/common/mdb/mdb_cmds.c Thu May 03 05:27:43 2012 -0500 +++ b/usr/src/cmd/mdb/common/mdb/mdb_cmds.c Fri May 11 22:38:13 2012 -0400 @@ -24,6 +24,11 @@ * Use is subject to license terms. */ +/* + * Copyright (c) 2012 by Delphix. All rights reserved. + * Copyright (c) 2012 Joyent, Inc. All rights reserved. + */ + #include #include @@ -61,6 +66,7 @@ #include #include #include +#include #ifdef _KMDB #include #endif @@ -2129,6 +2135,24 @@ return (DCMD_OK); } +static int +cmd_walk_tab(mdb_tab_cookie_t *mcp, uint_t flags, int argc, + const mdb_arg_t *argv) +{ + if (argc > 1) + return (1); + + if (argc == 1) { + ASSERT(argv[0].a_type == MDB_TYPE_STRING); + return (mdb_tab_complete_walker(mcp, argv[0].a_un.a_str)); + } + + if (argc == 0 && flags & DCMD_TAB_SPACE) + return (mdb_tab_complete_walker(mcp, NULL)); + + return (1); +} + static ssize_t mdb_partial_xread(void *buf, size_t nbytes, uintptr_t addr, void *arg) { @@ -2924,7 +2948,8 @@ head_help }, { "help", "[cmd]", "list commands/command help", cmd_help }, { "list", "?type member [variable]", - "walk list using member as link pointer", cmd_list }, + "walk list using member as link pointer", cmd_list, NULL, + mdb_tab_complete_mt }, { "map", "?expr", "print dot after evaluating expression", cmd_map }, { "mappings", "?[name]", "print address space mappings", cmd_mappings }, { "nm", "?[-DPdghnopuvx] [-f format] [-t types] [object]", @@ -2935,14 +2960,16 @@ { "obey", NULL, NULL, cmd_obey }, { "objects", "[-v]", "print load objects information", cmd_objects }, { "offsetof", "type member", "print the offset of a given struct " - "or union member", cmd_offsetof }, + "or union member", cmd_offsetof, NULL, mdb_tab_complete_mt }, { "print", "?[-aCdhiLptx] [-c lim] [-l lim] [type] [member|offset ...]", - "print the contents of a data structure", cmd_print, print_help }, + "print the contents of a data structure", cmd_print, print_help, + cmd_print_tab }, { "regs", NULL, "print general purpose registers", cmd_notsup }, { "set", "[-wF] [+/-o opt] [-s dist] [-I path] [-L path] [-P prompt]", "get/set debugger properties", cmd_set }, { "showrev", "[-pv]", "print version information", cmd_showrev }, - { "sizeof", "type", "print the size of a type", cmd_sizeof }, + { "sizeof", "type", "print the size of a type", cmd_sizeof, NULL, + cmd_sizeof_tab }, { "stack", "?[cnt]", "print stack backtrace", cmd_notsup }, { "stackregs", "?", "print stack backtrace and registers", cmd_notsup }, @@ -2954,7 +2981,8 @@ { "version", NULL, "print debugger version string", cmd_version }, { "vtop", ":[-a as]", "print physical mapping of virtual address", cmd_vtop }, - { "walk", "?name [variable]", "walk data structure", cmd_walk }, + { "walk", "?name [variable]", "walk data structure", cmd_walk, NULL, + cmd_walk_tab }, { "walkers", NULL, "list available walkers", cmd_walkers }, { "whatis", ":[-aikqv]", "given an address, return information", cmd_whatis, whatis_help }, diff -r 72ce76fa37fb -r 32dde9989090 usr/src/cmd/mdb/common/mdb/mdb_modapi.h --- a/usr/src/cmd/mdb/common/mdb/mdb_modapi.h Thu May 03 05:27:43 2012 -0500 +++ b/usr/src/cmd/mdb/common/mdb/mdb_modapi.h Fri May 11 22:38:13 2012 -0400 @@ -21,6 +21,8 @@ /* * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012 by Delphix. All rights reserved. + * Copyright (c) 2012 Joyent, Inc. All rights reserved. */ #ifndef _MDB_MODAPI_H @@ -69,7 +71,7 @@ #define MAX(x, y) ((x) > (y) ? (x) : (y)) #endif -#define MDB_API_VERSION 3 /* Current API version number */ +#define MDB_API_VERSION 4 /* Current API version number */ /* * Debugger command function flags: @@ -83,6 +85,11 @@ #define DCMD_HDRSPEC(fl) (((fl) & DCMD_LOOPFIRST) || !((fl) & DCMD_LOOP)) /* + * Debugger tab command function flags + */ +#define DCMD_TAB_SPACE 0x01 /* Tab cb invoked with trailing space */ + +/* * Debugger command function return values: */ #define DCMD_OK 0 /* Dcmd completed successfully */ @@ -111,7 +118,10 @@ } a_un; } mdb_arg_t; +typedef struct mdb_tab_cookie mdb_tab_cookie_t; typedef int mdb_dcmd_f(uintptr_t, uint_t, int, const mdb_arg_t *); +typedef int mdb_dcmd_tab_f(mdb_tab_cookie_t *, uint_t, int, + const mdb_arg_t *); typedef struct mdb_dcmd { const char *dc_name; /* Command name */ @@ -119,6 +129,7 @@ const char *dc_descr; /* Description */ mdb_dcmd_f *dc_funcp; /* Command function */ void (*dc_help)(void); /* Command help function (or NULL) */ + mdb_dcmd_tab_f *dc_tabp; /* Tab completion function */ } mdb_dcmd_t; #define WALK_ERR -1 /* Walk fatal error (terminate walk) */ @@ -302,6 +313,31 @@ extern void *mdb_callback_add(int, mdb_callback_f, void *); extern void mdb_callback_remove(void *); +#define MDB_TABC_ALL_TYPES 0x1 /* Include array types in type output */ +#define MDB_TABC_MEMBERS 0x2 /* Tab comp. types with members */ +#define MDB_TABC_NOPOINT 0x4 /* Tab comp. everything but pointers */ +#define MDB_TABC_NOARRAY 0x8 /* Don't include array data in output */ + +/* + * Module's interaction path + */ +extern void mdb_tab_insert(mdb_tab_cookie_t *, const char *); +extern void mdb_tab_setmbase(mdb_tab_cookie_t *, const char *); + +/* + * Tab completion utility functions for modules. + */ +extern int mdb_tab_complete_type(mdb_tab_cookie_t *, const char *, uint_t); +extern int mdb_tab_complete_member(mdb_tab_cookie_t *, const char *, + const char *); +extern int mdb_tab_typename(int *, const mdb_arg_t **, char *buf, size_t len); + +/* + * Tab completion functions for common signatures. + */ +extern int mdb_tab_complete_mt(mdb_tab_cookie_t *, uint_t, int, + const mdb_arg_t *); + extern size_t strlcat(char *, const char *, size_t); extern char *strcat(char *, const char *); extern char *strcpy(char *, const char *); diff -r 72ce76fa37fb -r 32dde9989090 usr/src/cmd/mdb/common/mdb/mdb_module.c --- a/usr/src/cmd/mdb/common/mdb/mdb_module.c Thu May 03 05:27:43 2012 -0500 +++ b/usr/src/cmd/mdb/common/mdb/mdb_module.c Fri May 11 22:38:13 2012 -0400 @@ -21,6 +21,8 @@ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. + * Copyright (c) 2012 by Delphix. All rights reserved. + * Copyright (c) 2012 Joyent, Inc. All rights reserved. */ #include @@ -43,6 +45,20 @@ #include /* + * The format of an mdb dcmd changed between MDB_API_VERSION 3 and 4, with an + * addition of a new field to the public interface. To maintain backwards + * compatibility with older versions, we know to keep around the old version of + * the structure so we can correctly read the set of dcmds passed in. + */ +typedef struct mdb_dcmd_v3 { + const char *dco_name; /* Command name */ + const char *dco_usage; /* Usage message (optional) */ + const char *dco_descr; /* Description */ + mdb_dcmd_f *dco_funcp; /* Command function */ + void (*dco_help)(void); /* Command help function (or NULL) */ +} mdb_dcmd_v3_t; + +/* * For builtin modules, we set mod_init to this function, which just * returns a constant modinfo struct with no dcmds and walkers. */ @@ -92,6 +108,9 @@ const mdb_dcmd_t *dcp; const mdb_walker_t *wp; + const mdb_dcmd_v3_t *dcop; + mdb_dcmd_t *dctp = NULL; + mdb_module_t *mod; mod = mdb_zalloc(sizeof (mdb_module_t), UM_SLEEP); @@ -164,6 +183,7 @@ */ switch (info->mi_dvers) { case MDB_API_VERSION: + case 3: case 2: case 1: /* @@ -186,6 +206,35 @@ } /* + * In MDB_API_VERSION 4, the size of the mdb_dcmd_t struct changed. If + * our module is from an earlier version, we need to walk it in the old + * structure and convert it to the new one. + * + * Note that we purposefully don't predicate on whether or not we have + * the empty list case and duplicate it anyways. That case is rare and + * it makes our logic simpler when we need to unload the module. + */ + if (info->mi_dvers < 4) { + int ii = 0; + for (dcop = (mdb_dcmd_v3_t *)&mod->mod_info->mi_dcmds[0]; + dcop->dco_name != NULL; dcop++) + ii++; + /* Don't forget null terminated one at the end */ + dctp = mdb_zalloc(sizeof (mdb_dcmd_t) * (ii + 1), UM_SLEEP); + ii = 0; + for (dcop = (mdb_dcmd_v3_t *)&mod->mod_info->mi_dcmds[0]; + dcop->dco_name != NULL; dcop++, ii++) { + dctp[ii].dc_name = dcop->dco_name; + dctp[ii].dc_usage = dcop->dco_usage; + dctp[ii].dc_descr = dcop->dco_descr; + dctp[ii].dc_funcp = dcop->dco_funcp; + dctp[ii].dc_help = dcop->dco_help; + dctp[ii].dc_tabp = NULL; + } + mod->mod_info->mi_dcmds = dctp; + } + + /* * Before we actually go ahead with the load, we need to check * each dcmd and walk structure for any invalid values: */ @@ -300,6 +349,7 @@ { mdb_var_t *v = mdb_nv_lookup(&mdb.m_modules, name); mdb_module_t *mod; + const mdb_dcmd_t *dcp; if (v == NULL) return (set_errno(EMDB_NOMOD)); @@ -358,6 +408,18 @@ mdb_nv_destroy(&mod->mod_dcmds); strfree((char *)mod->mod_name); + + if (mod->mod_info->mi_dvers < 4) { + int ii = 0; + + for (dcp = &mod->mod_info->mi_dcmds[0]; dcp->dc_name != NULL; + dcp++) + ii++; + + mdb_free((void *)mod->mod_info->mi_dcmds, + sizeof (mdb_dcmd_t) * (ii + 1)); + } + mdb_free(mod->mod_info, sizeof (mdb_modinfo_t)); mdb_free(mod, sizeof (mdb_module_t)); @@ -384,6 +446,7 @@ idcp->idc_descr = dcp->dc_descr; idcp->idc_help = dcp->dc_help; idcp->idc_funcp = dcp->dc_funcp; + idcp->idc_tabp = dcp->dc_tabp; idcp->idc_modp = mod; v = mdb_nv_insert(&mod->mod_dcmds, dcp->dc_name, NULL, diff -r 72ce76fa37fb -r 32dde9989090 usr/src/cmd/mdb/common/mdb/mdb_module.h --- a/usr/src/cmd/mdb/common/mdb/mdb_module.h Thu May 03 05:27:43 2012 -0500 +++ b/usr/src/cmd/mdb/common/mdb/mdb_module.h Fri May 11 22:38:13 2012 -0400 @@ -24,11 +24,14 @@ * Use is subject to license terms. */ +/* + * Copyright (c) 2012 by Delphix. All rights reserved. + * Copyright (c) 2012 Joyent, Inc. All rights reserved. + */ + #ifndef _MDB_MODULE_H #define _MDB_MODULE_H -#pragma ident "%Z%%M% %I% %E% SMI" - #include #include #include @@ -67,6 +70,7 @@ const char *idc_descr; /* Description */ mdb_dcmd_f *idc_funcp; /* Command function */ void (*idc_help)(void); /* Help function */ + mdb_dcmd_tab_f *idc_tabp; /* Tab completion pointer */ mdb_module_t *idc_modp; /* Backpointer to module */ mdb_var_t *idc_var; /* Backpointer to global variable */ } mdb_idcmd_t; diff -r 72ce76fa37fb -r 32dde9989090 usr/src/cmd/mdb/common/mdb/mdb_module_load.c --- a/usr/src/cmd/mdb/common/mdb/mdb_module_load.c Thu May 03 05:27:43 2012 -0500 +++ b/usr/src/cmd/mdb/common/mdb/mdb_module_load.c Fri May 11 22:38:13 2012 -0400 @@ -21,6 +21,8 @@ /* * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012 by Delphix. All rights reserved. + * Copyright (c) 2012 Joyent, Inc. All rights reserved. */ #include @@ -201,6 +203,7 @@ mdb_iob_setflags(mdb.m_out, oflag); } +/*ARGSUSED*/ int mdb_module_unload(const char *name, int mode) { diff -r 72ce76fa37fb -r 32dde9989090 usr/src/cmd/mdb/common/mdb/mdb_print.c --- a/usr/src/cmd/mdb/common/mdb/mdb_print.c Thu May 03 05:27:43 2012 -0500 +++ b/usr/src/cmd/mdb/common/mdb/mdb_print.c Fri May 11 22:38:13 2012 -0400 @@ -23,6 +23,11 @@ * Use is subject to license terms. */ +/* + * Copyright (c) 2012 by Delphix. All rights reserved. + * Copyright (c) 2012 Joyent, Inc. All rights reserved. + */ + #include #include #include @@ -34,6 +39,7 @@ #include #include #include +#include #include #include @@ -209,6 +215,28 @@ return (DCMD_OK); } +int +cmd_sizeof_tab(mdb_tab_cookie_t *mcp, uint_t flags, int argc, + const mdb_arg_t *argv) +{ + char tn[MDB_SYM_NAMLEN]; + int ret; + + if (argc == 0 && !(flags & DCMD_TAB_SPACE)) + return (0); + + if (argc == 0 && (flags & DCMD_TAB_SPACE)) + return (mdb_tab_complete_type(mcp, NULL, MDB_TABC_NOPOINT)); + + if ((ret = mdb_tab_typename(&argc, &argv, tn, sizeof (tn))) < 0) + return (ret); + + if (argc == 1) + return (mdb_tab_complete_type(mcp, tn, MDB_TABC_NOPOINT)); + + return (0); +} + /*ARGSUSED*/ int cmd_offsetof(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) @@ -1882,7 +1910,8 @@ return (-1); } - (void) mdb_snprintf(member, end - start + 1, start); + (void) mdb_snprintf(member, end - start + 1, "%s", + start); index = mdb_strtoull(member); @@ -1959,7 +1988,7 @@ for (end = start + 1; isalnum(*end) || *end == '_'; end++) continue; - (void) mdb_snprintf(member, end - start + 1, start); + (void) mdb_snprintf(member, end - start + 1, "%s", start); if (mdb_ctf_member_info(rid, member, &off, &id) != 0) { mdb_warn("failed to find member %s of %s", member, @@ -1981,6 +2010,170 @@ return (0); } +int +cmd_print_tab(mdb_tab_cookie_t *mcp, uint_t flags, int argc, + const mdb_arg_t *argv) +{ + char tn[MDB_SYM_NAMLEN]; + char member[64]; + int i, dummy, delim, kind; + int ret = 0; + mdb_ctf_id_t id, rid; + mdb_ctf_arinfo_t ar; + char *start, *end; + ulong_t dul; + + /* + * This getopts is only here to make the tab completion work better when + * including options in the ::print arguments. None of the values should + * be used. This should only be updated with additional arguments, if + * they are added to cmd_print. + */ + i = mdb_getopts(argc, argv, + 'a', MDB_OPT_SETBITS, PA_SHOWADDR, &dummy, + 'C', MDB_OPT_SETBITS, TRUE, &dummy, + 'c', MDB_OPT_UINTPTR, &dummy, + 'd', MDB_OPT_SETBITS, PA_INTDEC, &dummy, + 'h', MDB_OPT_SETBITS, PA_SHOWHOLES, &dummy, + 'i', MDB_OPT_SETBITS, TRUE, &dummy, + 'L', MDB_OPT_SETBITS, TRUE, &dummy, + 'l', MDB_OPT_UINTPTR, &dummy, + 'n', MDB_OPT_SETBITS, PA_NOSYMBOLIC, &dummy, + 'p', MDB_OPT_SETBITS, TRUE, &dummy, + 's', MDB_OPT_UINTPTR, &dummy, + 'T', MDB_OPT_SETBITS, PA_SHOWTYPE | PA_SHOWBASETYPE, &dummy, + 't', MDB_OPT_SETBITS, PA_SHOWTYPE, &dummy, + 'x', MDB_OPT_SETBITS, PA_INTHEX, &dummy, + NULL); + + argc -= i; + argv += i; + + if (argc == 0 && !(flags & DCMD_TAB_SPACE)) + return (0); + + if (argc == 0 && (flags & DCMD_TAB_SPACE)) + return (mdb_tab_complete_type(mcp, NULL, MDB_TABC_NOPOINT | + MDB_TABC_NOARRAY)); + + if ((ret = mdb_tab_typename(&argc, &argv, tn, sizeof (tn))) < 0) + return (ret); + + if (argc == 1 && (!(flags & DCMD_TAB_SPACE) || ret == 1)) + return (mdb_tab_complete_type(mcp, tn, MDB_TABC_NOPOINT | + MDB_TABC_NOARRAY)); + + if (argc == 1 && (flags & DCMD_TAB_SPACE)) + return (mdb_tab_complete_member(mcp, tn, NULL)); + + /* + * This is the reason that tab completion was created. We're going to go + * along and walk the delimiters until we find something a member that + * we don't recognize, at which point we'll try and tab complete it. + * Note that ::print takes multiple args, so this is going to operate on + * whatever the last arg that we have is. + */ + if (mdb_ctf_lookup_by_name(tn, &id) != 0) + return (1); + + (void) mdb_ctf_type_resolve(id, &rid); + start = (char *)argv[argc-1].a_un.a_str; + delim = parse_delimiter(&start); + + /* + * If we hit the case where we actually have no delimiters, than we need + * to make sure that we properly set up the fields the loops would. + */ + if (delim == MEMBER_DELIM_DONE) + (void) mdb_snprintf(member, sizeof (member), "%s", start); + + while (delim != MEMBER_DELIM_DONE) { + switch (delim) { + case MEMBER_DELIM_PTR: + kind = mdb_ctf_type_kind(rid); + if (kind != CTF_K_POINTER) + return (1); + + (void) mdb_ctf_type_reference(rid, &id); + (void) mdb_ctf_type_resolve(id, &rid); + break; + case MEMBER_DELIM_DOT: + kind = mdb_ctf_type_kind(rid); + if (kind != CTF_K_STRUCT && kind != CTF_K_UNION) + return (1); + break; + case MEMBER_DELIM_LBR: + end = strchr(start, ']'); + /* + * We're not going to try and tab complete the indexes + * here. So for now, punt on it. Also, we're not going + * to try and validate you're within the bounds, just + * that you get the type you asked for. + */ + if (end == NULL) + return (1); + + switch (mdb_ctf_type_kind(rid)) { + case CTF_K_POINTER: + (void) mdb_ctf_type_reference(rid, &id); + (void) mdb_ctf_type_resolve(id, &rid); + break; + case CTF_K_ARRAY: + (void) mdb_ctf_array_info(rid, &ar); + id = ar.mta_contents; + (void) mdb_ctf_type_resolve(id, &rid); + break; + default: + return (1); + } + + start = end + 1; + delim = parse_delimiter(&start); + break; + case MEMBER_DELIM_ERR: + default: + break; + } + + for (end = start + 1; isalnum(*end) || *end == '_'; end++) + continue; + + (void) mdb_snprintf(member, end - start + 1, start); + + /* + * We are going to try to resolve this name as a member. There + * are a few two different questions that we need to answer. The + * first is do we recognize this member. The second is are we at + * the end of the string. If we encounter a member that we don't + * recognize before the end, then we have to error out and can't + * complete it. But if there are no more delimiters then we can + * try and complete it. + */ + ret = mdb_ctf_member_info(rid, member, &dul, &id); + start = end; + delim = parse_delimiter(&start); + if (ret != 0 && errno == EMDB_CTFNOMEMB) { + if (delim != MEMBER_DELIM_DONE) + return (1); + continue; + } else if (ret != 0) + return (1); + + if (delim == MEMBER_DELIM_DONE) + return (mdb_tab_complete_member_by_id(mcp, rid, + member)); + + (void) mdb_ctf_type_resolve(id, &rid); + } + + /* + * If we've reached here, then we need to try and tab complete the last + * field, which is currently member, based on the ctf type id that we + * already have in rid. + */ + return (mdb_tab_complete_member_by_id(mcp, rid, member)); +} + /* * Recursively descend a print a given data structure. We create a struct of * the relevant print arguments and then call mdb_ctf_type_visit() to do the @@ -2005,6 +2198,10 @@ mdb_syminfo_t s_info; GElf_Sym sym; + /* + * If a new option is added, make sure the getopts above in + * cmd_print_tab is also updated. + */ i = mdb_getopts(argc, argv, 'a', MDB_OPT_SETBITS, PA_SHOWADDR, &uflags, 'C', MDB_OPT_SETBITS, TRUE, &opt_C, diff -r 72ce76fa37fb -r 32dde9989090 usr/src/cmd/mdb/common/mdb/mdb_print.h --- a/usr/src/cmd/mdb/common/mdb/mdb_print.h Thu May 03 05:27:43 2012 -0500 +++ b/usr/src/cmd/mdb/common/mdb/mdb_print.h Fri May 11 22:38:13 2012 -0400 @@ -23,9 +23,16 @@ * Use is subject to license terms. */ +/* + * Copyright (c) 2012 by Delphix. All rights reserved. + * Copyright (c) 2012 Joyent, Inc. All rights reserved. + */ + #ifndef _MDB_PRINT_H #define _MDB_PRINT_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -35,10 +42,12 @@ extern int cmd_enum(uintptr_t, uint_t, int, const mdb_arg_t *); extern void enum_help(void); extern int cmd_sizeof(uintptr_t, uint_t, int, const mdb_arg_t *); +extern int cmd_sizeof_tab(mdb_tab_cookie_t *, uint_t, int, const mdb_arg_t *); extern int cmd_offsetof(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_list(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_array(uintptr_t, uint_t, int, const mdb_arg_t *); extern int cmd_print(uintptr_t, uint_t, int, const mdb_arg_t *); +extern int cmd_print_tab(mdb_tab_cookie_t *, uint_t, int, const mdb_arg_t *); extern void print_help(void); #endif /* _MDB */ diff -r 72ce76fa37fb -r 32dde9989090 usr/src/cmd/mdb/common/mdb/mdb_tab.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/cmd/mdb/common/mdb/mdb_tab.c Fri May 11 22:38:13 2012 -0400 @@ -0,0 +1,652 @@ +/* + * 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 (c) 2012 by Delphix. All rights reserved. + * Copyright (c) 2012 Joyent, Inc. All rights reserved. + */ +/* + * This file contains all of the interfaces for mdb's tab completion engine. + * Currently some interfaces are private to mdb and its internal implementation, + * those are in mdb_tab.h. Other pieces are public interfaces. Those are in + * mdb_modapi.h. + * + * Memory allocations in tab completion context have to be done very carefully. + * We need to think of ourselves as the same as any other command that is being + * executed by the user, which means we must use UM_GC to handle being + * interrupted. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* + * There may be another way to do this, but this works well enough. + */ +#define COMMAND_SEPARATOR "::" + +/* + * find_command_start -- + * + * Given a buffer find the start of the last command. + */ +static char * +tab_find_command_start(char *buf) +{ + char *offset = strstr(buf, COMMAND_SEPARATOR); + + if (offset == NULL) + return (NULL); + + for (;;) { + char *next = strstr(offset + strlen(COMMAND_SEPARATOR), + COMMAND_SEPARATOR); + + if (next == NULL) { + return (offset); + } + + offset = next; + } +} + +/* + * get_dcmd -- + * + * Given a buffer containing a command and its argument return + * the name of the command and the offset in the buffer where + * the command arguments start. + * + * Note: This will modify the buffer. + */ +char * +tab_get_dcmd(char *buf, char **args, uint_t *flags) +{ + char *start = buf + strlen(COMMAND_SEPARATOR); + char *separator = start; + const char *end = buf + strlen(buf); + uint_t space = 0; + + while (separator < end && !isspace(*separator)) + separator++; + + if (separator == end) { + *args = NULL; + } else { + if (isspace(*separator)) + space = 1; + + *separator++ = '\0'; + *args = separator; + } + + if (space) + *flags |= DCMD_TAB_SPACE; + + return (start); +} + +/* + * count_args -- + * + * Given a buffer containing dmcd arguments return the total number + * of arguments. + * + * While parsing arguments we need to keep track of whether or not the last + * arguments ends with a trailing space. + */ +static int +tab_count_args(const char *input, uint_t *flags) +{ + const char *index; + int argc = 0; + uint_t space = *flags & DCMD_TAB_SPACE; + index = input; + + while (*index != '\0') { + while (*index != '\0' && isspace(*index)) { + index++; + space = 1; + } + + if (*index != '\0' && !isspace(*index)) { + argc++; + space = 0; + while (*index != '\0' && !isspace (*index)) { + index++; + } + } + } + + if (space) + *flags |= DCMD_TAB_SPACE; + else + *flags &= ~DCMD_TAB_SPACE; + + return (argc); +} + +/* + * copy_args -- + * + * Given a buffer containing dcmd arguments and an array of mdb_arg_t's + * initialize the string value of each mdb_arg_t. + * + * Note: This will modify the buffer. + */ +static int +tab_copy_args(char *input, int argc, mdb_arg_t *argv) +{ + int i = 0; + char *index; + + index = input; + + while (*index) { + while (*index && isspace(*index)) { + index++; + } + + if (*index && !isspace(*index)) { + char *end = index; + + while (*end && !isspace(*end)) { + end++; + } + + if (*end) { + *end++ = '\0'; + } + + argv[i].a_type = MDB_TYPE_STRING; + argv[i].a_un.a_str = index; + + index = end; + i++; + } + } + + if (i != argc) + return (-1); + + return (0); +} + +/* + * parse-buf -- + * + * Parse the given buffer and return the specified dcmd, the number + * of arguments, and array of mdb_arg_t containing the argument + * values. + * + * Note: this will modify the specified buffer. Caller is responisble + * for freeing argvp. + */ +static int +tab_parse_buf(char *buf, char **dcmdp, int *argcp, mdb_arg_t **argvp, + uint_t *flags) +{ + char *data = tab_find_command_start(buf); + char *args_data = NULL; + char *dcmd = NULL; + int argc = 0; + mdb_arg_t *argv = NULL; + + if (data == NULL) { + return (-1); + } + + dcmd = tab_get_dcmd(data, &args_data, flags); + + if (dcmd == NULL) { + return (-1); + } + + if (args_data != NULL) { + argc = tab_count_args(args_data, flags); + + if (argc != 0) { + argv = mdb_alloc(sizeof (mdb_arg_t) * argc, + UM_SLEEP | UM_GC); + + if (tab_copy_args(args_data, argc, argv) == -1) + return (-1); + } + } + + *dcmdp = dcmd; + *argcp = argc; + *argvp = argv; + + return (0); +} + +/* + * tab_command -- + * + * This function is executed anytime a tab is entered. It checks + * the current buffer to determine if there is a valid dmcd, + * if that dcmd has a tab completion handler it will invoke it. + * + * This function returns the string (if any) that should be added to the + * existing buffer to complete it. + */ +int +mdb_tab_command(mdb_tab_cookie_t *mcp, const char *buf) +{ + char *data; + char *dcmd = NULL; + int argc = 0; + mdb_arg_t *argv = NULL; + int ret = 0; + mdb_idcmd_t *cp; + uint_t flags = 0; + + /* + * Parsing the command and arguments will modify the buffer + * (replacing spaces with \0), so make a copy of the specified + * buffer first. + */ + data = mdb_alloc(strlen(buf) + 1, UM_SLEEP | UM_GC); + (void) strcpy(data, buf); + + /* + * Get the specified dcmd and arguments from the buffer. + */ + ret = tab_parse_buf(data, &dcmd, &argc, &argv, &flags); + + if (ret != 0) { + goto out; + } + + /* + * Check to see if the buffer contains a valid dcmd + */ + cp = mdb_dcmd_lookup(dcmd); + + /* + * When argc is zero it indicates that we are trying to tab complete + * a dcmd. Note, that if there isn't the start of a dcmd, i.e. ::, then + * we will have already bailed in the call to tab_parse_buf. + */ + if (cp == NULL && argc != 0) { + goto out; + } + + /* + * Invoke the command specific tab completion handler or the built in + * dcmd one if there is no dcmd. + */ + if (cp == NULL) + (void) mdb_tab_complete_dcmd(mcp, dcmd); + else + mdb_call_tab(cp, mcp, flags, argc, argv); + +out: + return (mdb_tab_size(mcp)); +} + +static int +tab_complete_dcmd(mdb_var_t *v, void *arg) +{ + mdb_idcmd_t *idcp = mdb_nv_get_cookie(mdb_nv_get_cookie(v)); + mdb_tab_cookie_t *mcp = (mdb_tab_cookie_t *)arg; + + /* + * The way that mdb is implemented, even commands like $C will show up + * here. As such, we don't want to match anything that doesn't start + * with an alpha or number. While nothing currently appears (via a + * cursory search with mdb -k) to start with a capital letter or a + * number, we'll support them anyways. + */ + if (!isalnum(idcp->idc_name[0])) + return (0); + + mdb_tab_insert(mcp, idcp->idc_name); + return (0); +} + +int +mdb_tab_complete_dcmd(mdb_tab_cookie_t *mcp, const char *dcmd) +{ + mdb_tab_setmbase(mcp, dcmd); + mdb_nv_sort_iter(&mdb.m_dcmds, tab_complete_dcmd, mcp, + UM_GC | UM_SLEEP); + return (0); +} + +static int +tab_complete_walker(mdb_var_t *v, void *arg) +{ + mdb_iwalker_t *iwp = mdb_nv_get_cookie(mdb_nv_get_cookie(v)); + mdb_tab_cookie_t *mcp = arg; + + mdb_tab_insert(mcp, iwp->iwlk_name); + return (0); +} + +int +mdb_tab_complete_walker(mdb_tab_cookie_t *mcp, const char *walker) +{ + if (walker != NULL) + mdb_tab_setmbase(mcp, walker); + mdb_nv_sort_iter(&mdb.m_walkers, tab_complete_walker, mcp, + UM_GC | UM_SLEEP); + + return (0); +} + +mdb_tab_cookie_t * +mdb_tab_init(void) +{ + mdb_tab_cookie_t *mcp; + + mcp = mdb_zalloc(sizeof (mdb_tab_cookie_t), UM_SLEEP | UM_GC); + (void) mdb_nv_create(&mcp->mtc_nv, UM_SLEEP | UM_GC); + + return (mcp); +} + +size_t +mdb_tab_size(mdb_tab_cookie_t *mcp) +{ + return (mdb_nv_size(&mcp->mtc_nv)); +} + +/* + * Determine whether the specified name is a valid tab completion for + * the given command. If the name is a valid tab completion then + * it will be saved in the mdb_tab_cookie_t. + */ +void +mdb_tab_insert(mdb_tab_cookie_t *mcp, const char *name) +{ + size_t len, matches, index; + uint_t flags; + mdb_var_t *v; + char *n; + const char *nvn; + + /* + * If we have a match set, then we want to verify that we actually match + * it. + */ + if (mcp->mtc_base != NULL && + strncmp(name, mcp->mtc_base, strlen(mcp->mtc_base)) != 0) + return; + + v = mdb_nv_lookup(&mcp->mtc_nv, name); + if (v != NULL) + return; + + /* + * Names that we get passed in may be longer than MDB_NV_NAMELEN which + * is currently 31 including the null terminator. If that is the case, + * then we're going to take care of allocating a string and holding it + * for our caller. Note that we don't need to free it, because we're + * allocating this with UM_GC. + */ + flags = 0; + len = strlen(name); + if (len > MDB_NV_NAMELEN - 1) { + n = mdb_alloc(len + 1, UM_SLEEP | UM_GC); + (void) strcpy(n, name); + nvn = n; + flags |= MDB_NV_EXTNAME; + } else { + nvn = name; + } + flags |= MDB_NV_RDONLY; + + (void) mdb_nv_insert(&mcp->mtc_nv, nvn, NULL, 0, flags); + + matches = mdb_tab_size(mcp); + if (matches == 1) { + (void) strlcpy(mcp->mtc_match, nvn, MDB_SYM_NAMLEN); + } else { + index = 0; + while (mcp->mtc_match[index] && + mcp->mtc_match[index] == nvn[index]) + index++; + + mcp->mtc_match[index] = '\0'; + } +} + +/*ARGSUSED*/ +static int +tab_print_cb(mdb_var_t *v, void *ignored) +{ + mdb_printf("%s\n", mdb_nv_get_name(v)); + return (0); +} + +void +mdb_tab_print(mdb_tab_cookie_t *mcp) +{ + mdb_nv_sort_iter(&mcp->mtc_nv, tab_print_cb, NULL, UM_SLEEP | UM_GC); +} + +const char * +mdb_tab_match(mdb_tab_cookie_t *mcp) +{ + size_t blen; + + if (mcp->mtc_base == NULL) + blen = 0; + else + blen = strlen(mcp->mtc_base); + return (mcp->mtc_match + blen); +} + +void +mdb_tab_setmbase(mdb_tab_cookie_t *mcp, const char *base) +{ + (void) strlcpy(mcp->mtc_base, base, MDB_SYM_NAMLEN); +} + +/* + * This function is currently a no-op due to the fact that we have to GC because + * we're in command context. + */ +/*ARGSUSED*/ +void +mdb_tab_fini(mdb_tab_cookie_t *mcp) +{ +} + +/* + * This function takes a ctf id and determines whether or not the associated + * type should be considered as a potential match for the given tab + * completion command. We verify that the type itself is valid + * for completion given the current context of the command, resolve + * its actual name, and then pass it off to mdb_tab_insert to determine + * if it's an actual match. + */ +static int +tab_complete_type(mdb_ctf_id_t id, void *arg) +{ + int rkind; + char buf[MDB_SYM_NAMLEN]; + mdb_ctf_id_t rid; + mdb_tab_cookie_t *mcp = arg; + uint_t flags = (uint_t)(uintptr_t)mcp->mtc_cba; + + /* + * CTF data includes types that mdb commands don't understand. Before + * we resolve the actual type prune any entry that is a type we + * don't care about. + */ + switch (mdb_ctf_type_kind(id)) { + case CTF_K_CONST: + case CTF_K_RESTRICT: + case CTF_K_VOLATILE: + return (0); + } + + if (mdb_ctf_type_resolve(id, &rid) != 0) + return (1); + + rkind = mdb_ctf_type_kind(rid); + + if ((flags & MDB_TABC_MEMBERS) && rkind != CTF_K_STRUCT && + rkind != CTF_K_UNION) + return (0); + + if ((flags & MDB_TABC_NOPOINT) && rkind == CTF_K_POINTER) + return (0); + + if ((flags & MDB_TABC_NOARRAY) && rkind == CTF_K_ARRAY) + return (0); + + (void) mdb_ctf_type_name(id, buf, sizeof (buf)); + + mdb_tab_insert(mcp, buf); + return (0); +} + +/*ARGSUSED*/ +static int +mdb_tab_complete_module(void *data, const mdb_map_t *mp, const char *name) +{ + (void) mdb_ctf_type_iter(name, tab_complete_type, data); + return (0); +} + +int +mdb_tab_complete_type(mdb_tab_cookie_t *mcp, const char *name, uint_t flags) +{ + mdb_tgt_t *t = mdb.m_target; + + mcp->mtc_cba = (void *)(uintptr_t)flags; + if (name != NULL) + mdb_tab_setmbase(mcp, name); + + (void) mdb_tgt_object_iter(t, mdb_tab_complete_module, mcp); + return (0); +} + +/*ARGSUSED*/ +static int +tab_complete_member(const char *name, mdb_ctf_id_t id, ulong_t off, void *arg) +{ + mdb_tab_cookie_t *mcp = arg; + mdb_tab_insert(mcp, name); + return (0); +} + +int +mdb_tab_complete_member_by_id(mdb_tab_cookie_t *mcp, mdb_ctf_id_t id, + const char *member) +{ + if (member != NULL) + mdb_tab_setmbase(mcp, member); + (void) mdb_ctf_member_iter(id, tab_complete_member, mcp); + return (0); +} + +int +mdb_tab_complete_member(mdb_tab_cookie_t *mcp, const char *type, + const char *member) +{ + mdb_ctf_id_t id; + + if (mdb_ctf_lookup_by_name(type, &id) != 0) + return (-1); + + return (mdb_tab_complete_member_by_id(mcp, id, member)); +} + +int +mdb_tab_complete_mt(mdb_tab_cookie_t *mcp, uint_t flags, int argc, + const mdb_arg_t *argv) +{ + char tn[MDB_SYM_NAMLEN]; + int ret; + + if (argc == 0 && !(flags & DCMD_TAB_SPACE)) + return (0); + + if (argc == 0) + return (mdb_tab_complete_type(mcp, NULL, MDB_TABC_MEMBERS)); + + if ((ret = mdb_tab_typename(&argc, &argv, tn, sizeof (tn))) < 0) + return (ret); + + if (argc == 1 && (!(flags & DCMD_TAB_SPACE) || ret == 1)) + return (mdb_tab_complete_type(mcp, tn, MDB_TABC_MEMBERS)); + + if (argc == 1 && (flags & DCMD_TAB_SPACE)) + return (mdb_tab_complete_member(mcp, tn, NULL)); + + if (argc == 2) + return (mdb_tab_complete_member(mcp, tn, argv[1].a_un.a_str)); + + return (0); +} + +/* + * This is similar to mdb_print.c's args_to_typename, but it has subtle + * differences surrounding how the strings of one element are handled that have + * 'struct', 'enum', or 'union' in them and instead works with them for tab + * completion purposes. + */ +int +mdb_tab_typename(int *argcp, const mdb_arg_t **argvp, char *buf, size_t len) +{ + int argc = *argcp; + const mdb_arg_t *argv = *argvp; + + if (argc < 1 || argv->a_type != MDB_TYPE_STRING) + return (DCMD_USAGE); + + if (strcmp(argv->a_un.a_str, "struct") == 0 || + strcmp(argv->a_un.a_str, "enum") == 0 || + strcmp(argv->a_un.a_str, "union") == 0) { + if (argc == 1) { + (void) mdb_snprintf(buf, len, "%s ", + argv[0].a_un.a_str); + return (1); + } + + if (argv[1].a_type != MDB_TYPE_STRING) + return (DCMD_USAGE); + + (void) mdb_snprintf(buf, len, "%s %s", + argv[0].a_un.a_str, argv[1].a_un.a_str); + + *argcp = argc - 1; + *argvp = argv + 1; + } else { + (void) mdb_snprintf(buf, len, "%s", argv[0].a_un.a_str); + } + + return (0); +} diff -r 72ce76fa37fb -r 32dde9989090 usr/src/cmd/mdb/common/mdb/mdb_tab.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/cmd/mdb/common/mdb/mdb_tab.h Fri May 11 22:38:13 2012 -0400 @@ -0,0 +1,71 @@ +/* + * 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 (c) 2012 by Delphix. All rights reserved. + * Copyright (c) 2012 Joyent, Inc. All rights reserved. + */ +/* + * This file contains mdb private tab completion related functions. Public + * functions for modules are put into the module API, see mdb_modapi.h. Note + * that the mdb_ctf_id_t value is private to mdb and not a part of the module + * api, hence it has to stay in here. + */ + +#ifndef _MDB_TAB_H +#define _MDB_TAB_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _MDB + +struct mdb_tab_cookie { + mdb_nv_t mtc_nv; + char mtc_match[MDB_SYM_NAMLEN]; + char mtc_base[MDB_SYM_NAMLEN]; + void *mtc_cba; +}; + +extern mdb_tab_cookie_t *mdb_tab_init(void); +extern size_t mdb_tab_size(mdb_tab_cookie_t *); +extern const char *mdb_tab_match(mdb_tab_cookie_t *); +extern void mdb_tab_print(mdb_tab_cookie_t *); +extern void mdb_tab_fini(mdb_tab_cookie_t *); + +extern int mdb_tab_complete_dcmd(mdb_tab_cookie_t *, const char *); +extern int mdb_tab_complete_walker(mdb_tab_cookie_t *, const char *); +extern int mdb_tab_complete_member_by_id(mdb_tab_cookie_t *, mdb_ctf_id_t, + const char *); +extern int mdb_tab_command(mdb_tab_cookie_t *, const char *); + +#endif /* _MDB */ + +#ifdef __cplusplus +} +#endif + +#endif /* _MDB_TAB_H */ diff -r 72ce76fa37fb -r 32dde9989090 usr/src/cmd/mdb/common/mdb/mdb_termio.c --- a/usr/src/cmd/mdb/common/mdb/mdb_termio.c Thu May 03 05:27:43 2012 -0500 +++ b/usr/src/cmd/mdb/common/mdb/mdb_termio.c Fri May 11 22:38:13 2012 -0400 @@ -25,6 +25,11 @@ */ /* + * Copyright (c) 2012 by Delphix. All rights reserved. + * Copyright (c) 2012 Joyent, Inc. All rights reserved. + */ + +/* * Terminal I/O Backend * * Terminal editing backend for standard input. The terminal i/o backend is @@ -76,6 +81,7 @@ #include #include #include +#include #include #ifdef ERR @@ -174,6 +180,7 @@ #define TIO_TTYWARN 0x20 /* Warnings about tty issued */ #define TIO_CAPWARN 0x40 /* Warnings about terminfo issued */ #define TIO_XTERM 0x80 /* Terminal is xterm compatible */ +#define TIO_TAB 0x100 /* Tab completion mode */ static const mdb_bitmask_t tio_flag_masks[] = { { "FINDHIST", TIO_FINDHIST, TIO_FINDHIST }, @@ -184,6 +191,7 @@ { "TTYWARN", TIO_TTYWARN, TIO_TTYWARN }, { "CAPWARN", TIO_CAPWARN, TIO_CAPWARN }, { "XTERM", TIO_XTERM, TIO_XTERM }, + { "TAB", TIO_TAB, TIO_TAB}, { NULL, 0, 0 } }; @@ -256,6 +264,7 @@ static void termio_redraw(termio_data_t *); static void termio_prompt(termio_data_t *); +static const char *termio_tab(termio_data_t *, int); static const char *termio_insert(termio_data_t *, int); static const char *termio_accept(termio_data_t *, int); static const char *termio_backspace(termio_data_t *, int); @@ -398,7 +407,10 @@ goto out; } - termio_prompt(td); + if (td->tio_flags & TIO_TAB) + termio_redraw(td); + else + termio_prompt(td); /* * We need to redraw the entire command-line and restart our read loop @@ -428,6 +440,13 @@ td->tio_active = TRUE; + /* + * We may have had some error while in tab completion mode which sent us + * longjmping all over the place. If that's the case, come back here and + * make sure the flag is off. + */ + td->tio_flags &= ~TIO_TAB; + do { char_loop: if ((c = mdb_iob_getc(td->tio_in)) == EOF) { @@ -1488,6 +1507,11 @@ td->tio_keymap['['] = termio_accel; td->tio_keymap[']'] = termio_accel; + /* + * Grab tabs + */ + td->tio_keymap['\t'] = termio_tab; + td->tio_x = 0; td->tio_y = 0; td->tio_max_x = 0; @@ -1590,6 +1614,67 @@ return (NULL); } +/* + * This function may end up calling termio_read recursively as part of invoking + * the mdb pager. To work around this fact, we need to go through and make sure + * that we change the underlying terminal settings before and after this + * function call. If we don't do this, we invoke the pager, and don't abort + * (which will longjmp us elsewhere) we're going to return to the read loop with + * the wrong termio settings. + * + * Furthermore, because of the fact that we're being invoked in a user context + * that allows us to be interrupted, we need to actually allocate the memory + * that we're using with GC so that it gets cleaned up in case of the pager + * resetting us and never reaching the end. + */ +/*ARGSUSED*/ +static const char * +termio_tab(termio_data_t *td, int c) +{ + char *buf; + const char *result; + int nres; + mdb_tab_cookie_t *mtp; + + if (termio_ctl(td->tio_io, TCSETSW, &td->tio_dtios) == -1) + warn("failed to restore terminal attributes"); + + buf = mdb_alloc(td->tio_cmdbuf.cmd_bufidx + 1, UM_SLEEP | UM_GC); + (void) strncpy(buf, td->tio_cmdbuf.cmd_buf, td->tio_cmdbuf.cmd_bufidx); + buf[td->tio_cmdbuf.cmd_bufidx] = '\0'; + td->tio_flags |= TIO_TAB; + mtp = mdb_tab_init(); + nres = mdb_tab_command(mtp, buf); + + if (nres == 0) { + result = NULL; + } else { + result = mdb_tab_match(mtp); + if (nres != 1) { + mdb_printf("\n"); + mdb_tab_print(mtp); + } + } + + if (result != NULL) { + int index = 0; + + while (result[index] != '\0') { + (void) termio_insert(td, result[index]); + index++; + } + } + + termio_redraw(td); + mdb_tab_fini(mtp); + td->tio_flags &= ~TIO_TAB; + if (termio_ctl(td->tio_io, TCSETSW, &td->tio_rtios) == -1) + warn("failed to set terminal attributes"); + + + return (NULL); +} + static const char * termio_delchar(termio_data_t *td, int c) { diff -r 72ce76fa37fb -r 32dde9989090 usr/src/cmd/mdb/common/mdb/mdb_whatis.c --- a/usr/src/cmd/mdb/common/mdb/mdb_whatis.c Thu May 03 05:27:43 2012 -0500 +++ b/usr/src/cmd/mdb/common/mdb/mdb_whatis.c Fri May 11 22:38:13 2012 -0400 @@ -23,7 +23,12 @@ * Use is subject to license terms. */ -#include +/* + * Copyright (c) 2012 by Delphix. All rights reserved. + * Copyright (c) 2012 Joyent, Inc. All rights reserved. + */ + +#include #include #include #include