| /* |
| * 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) 2013 by Delphix. All rights reserved. |
| * Copyright (c) 2012 Joyent, Inc. All rights reserved. |
| * Copyright (c) 2013 Josef 'Jeff' Sipek <jeffpc@josefsipek.net> |
| */ |
| /* |
| * 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 <mdb/mdb_modapi.h> |
| #include <mdb/mdb_ctf.h> |
| #include <mdb/mdb_ctf_impl.h> |
| #include <mdb/mdb_string.h> |
| #include <mdb/mdb_module.h> |
| #include <mdb/mdb_debug.h> |
| #include <mdb/mdb_print.h> |
| #include <mdb/mdb_nv.h> |
| #include <mdb/mdb_tab.h> |
| #include <mdb/mdb_target.h> |
| #include <mdb/mdb.h> |
| |
| #include <ctype.h> |
| |
| /* |
| * 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); |
| |
| /* |
| * Match against global symbols if the input is not a dcmd |
| */ |
| if (ret != 0) { |
| (void) mdb_tab_complete_global(mcp, buf); |
| 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 or a global symbol. 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) |
| { |
| if (dcmd != NULL) |
| 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 matches, index; |
| mdb_var_t *v; |
| |
| /* |
| * 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; |
| |
| (void) mdb_nv_insert(&mcp->mtc_nv, name, NULL, 0, MDB_NV_RDONLY); |
| |
| matches = mdb_tab_size(mcp); |
| if (matches == 1) { |
| (void) strlcpy(mcp->mtc_match, name, MDB_SYM_NAMLEN); |
| } else { |
| index = 0; |
| while (mcp->mtc_match[index] && |
| mcp->mtc_match[index] == name[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) |
| { |
| } |
| |
| /*ARGSUSED*/ |
| static int |
| tab_complete_global(void *arg, const GElf_Sym *sym, const char *name, |
| const mdb_syminfo_t *sip, const char *obj) |
| { |
| mdb_tab_cookie_t *mcp = arg; |
| mdb_tab_insert(mcp, name); |
| return (0); |
| } |
| |
| /* |
| * This function tab completes against all loaded global symbols. |
| */ |
| int |
| mdb_tab_complete_global(mdb_tab_cookie_t *mcp, const char *name) |
| { |
| mdb_tab_setmbase(mcp, name); |
| (void) mdb_tgt_symbol_iter(mdb.m_target, MDB_TGT_OBJ_EVERY, |
| MDB_TGT_SYMTAB, MDB_TGT_BIND_ANY | MDB_TGT_TYPE_OBJECT | |
| MDB_TGT_TYPE_FUNC, tab_complete_global, mcp); |
| return (0); |
| } |
| |
| /* |
| * 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); |
| (void) mdb_ctf_type_iter(MDB_CTF_SYNTHETIC_ITER, tab_complete_type, |
| 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); |
| } |