/* * 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) 2018, Joyent, Inc. * Copyright (c) 2013 Josef 'Jeff' Sipek */ /* * 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 #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); /* * 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 (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) { return (mcp->mtc_match + strlen(mcp->mtc_base)); } 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); }