xref: /illumos-gate/usr/src/cmd/mdb/common/mdb/mdb_tab.c (revision d75f3745)
13b6e0a59SMatt Amdur /*
23b6e0a59SMatt Amdur  * CDDL HEADER START
33b6e0a59SMatt Amdur  *
43b6e0a59SMatt Amdur  * The contents of this file are subject to the terms of the
53b6e0a59SMatt Amdur  * Common Development and Distribution License (the "License").
63b6e0a59SMatt Amdur  * You may not use this file except in compliance with the License.
73b6e0a59SMatt Amdur  *
83b6e0a59SMatt Amdur  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
93b6e0a59SMatt Amdur  * or http://www.opensolaris.org/os/licensing.
103b6e0a59SMatt Amdur  * See the License for the specific language governing permissions
113b6e0a59SMatt Amdur  * and limitations under the License.
123b6e0a59SMatt Amdur  *
133b6e0a59SMatt Amdur  * When distributing Covered Code, include this CDDL HEADER in each
143b6e0a59SMatt Amdur  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
153b6e0a59SMatt Amdur  * If applicable, add the following below this CDDL HEADER, with the
163b6e0a59SMatt Amdur  * fields enclosed by brackets "[]" replaced with your own identifying
173b6e0a59SMatt Amdur  * information: Portions Copyright [yyyy] [name of copyright owner]
183b6e0a59SMatt Amdur  *
193b6e0a59SMatt Amdur  * CDDL HEADER END
203b6e0a59SMatt Amdur  */
213b6e0a59SMatt Amdur /*
2272a1114bSHenrik Mattsson  * Copyright (c) 2013 by Delphix. All rights reserved.
23*d75f3745SJohn Levon  * Copyright (c) 2018, Joyent, Inc.
24065c692aSJosef 'Jeff' Sipek  * Copyright (c) 2013 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
253b6e0a59SMatt Amdur  */
263b6e0a59SMatt Amdur /*
273b6e0a59SMatt Amdur  * This file contains all of the interfaces for mdb's tab completion engine.
283b6e0a59SMatt Amdur  * Currently some interfaces are private to mdb and its internal implementation,
293b6e0a59SMatt Amdur  * those are in mdb_tab.h. Other pieces are public interfaces. Those are in
303b6e0a59SMatt Amdur  * mdb_modapi.h.
313b6e0a59SMatt Amdur  *
323b6e0a59SMatt Amdur  * Memory allocations in tab completion context have to be done very carefully.
333b6e0a59SMatt Amdur  * We need to think of ourselves as the same as any other command that is being
343b6e0a59SMatt Amdur  * executed by the user, which means we must use UM_GC to handle being
353b6e0a59SMatt Amdur  * interrupted.
363b6e0a59SMatt Amdur  */
373b6e0a59SMatt Amdur 
383b6e0a59SMatt Amdur #include <mdb/mdb_modapi.h>
393b6e0a59SMatt Amdur #include <mdb/mdb_ctf.h>
403b6e0a59SMatt Amdur #include <mdb/mdb_ctf_impl.h>
413b6e0a59SMatt Amdur #include <mdb/mdb_string.h>
423b6e0a59SMatt Amdur #include <mdb/mdb_module.h>
433b6e0a59SMatt Amdur #include <mdb/mdb_debug.h>
443b6e0a59SMatt Amdur #include <mdb/mdb_print.h>
453b6e0a59SMatt Amdur #include <mdb/mdb_nv.h>
463b6e0a59SMatt Amdur #include <mdb/mdb_tab.h>
4772a1114bSHenrik Mattsson #include <mdb/mdb_target.h>
483b6e0a59SMatt Amdur #include <mdb/mdb.h>
493b6e0a59SMatt Amdur 
503b6e0a59SMatt Amdur #include <ctype.h>
513b6e0a59SMatt Amdur 
523b6e0a59SMatt Amdur /*
533b6e0a59SMatt Amdur  * There may be another way to do this, but this works well enough.
543b6e0a59SMatt Amdur  */
553b6e0a59SMatt Amdur #define	COMMAND_SEPARATOR "::"
563b6e0a59SMatt Amdur 
573b6e0a59SMatt Amdur /*
583b6e0a59SMatt Amdur  * find_command_start --
593b6e0a59SMatt Amdur  *
60*d75f3745SJohn Levon  *	Given a buffer find the start of the last command.
613b6e0a59SMatt Amdur  */
623b6e0a59SMatt Amdur static char *
tab_find_command_start(char * buf)633b6e0a59SMatt Amdur tab_find_command_start(char *buf)
643b6e0a59SMatt Amdur {
653b6e0a59SMatt Amdur 	char *offset = strstr(buf, COMMAND_SEPARATOR);
663b6e0a59SMatt Amdur 
673b6e0a59SMatt Amdur 	if (offset == NULL)
683b6e0a59SMatt Amdur 		return (NULL);
693b6e0a59SMatt Amdur 
703b6e0a59SMatt Amdur 	for (;;) {
713b6e0a59SMatt Amdur 		char *next = strstr(offset + strlen(COMMAND_SEPARATOR),
723b6e0a59SMatt Amdur 		    COMMAND_SEPARATOR);
733b6e0a59SMatt Amdur 
743b6e0a59SMatt Amdur 		if (next == NULL) {
753b6e0a59SMatt Amdur 			return (offset);
763b6e0a59SMatt Amdur 		}
773b6e0a59SMatt Amdur 
783b6e0a59SMatt Amdur 		offset = next;
793b6e0a59SMatt Amdur 	}
803b6e0a59SMatt Amdur }
813b6e0a59SMatt Amdur 
823b6e0a59SMatt Amdur /*
833b6e0a59SMatt Amdur  * get_dcmd --
843b6e0a59SMatt Amdur  *
85*d75f3745SJohn Levon  *	Given a buffer containing a command and its argument return
86*d75f3745SJohn Levon  *	the name of the command and the offset in the buffer where
87*d75f3745SJohn Levon  *	the command arguments start.
883b6e0a59SMatt Amdur  *
89*d75f3745SJohn Levon  *	Note: This will modify the buffer.
903b6e0a59SMatt Amdur  */
913b6e0a59SMatt Amdur char *
tab_get_dcmd(char * buf,char ** args,uint_t * flags)923b6e0a59SMatt Amdur tab_get_dcmd(char *buf, char **args, uint_t *flags)
933b6e0a59SMatt Amdur {
943b6e0a59SMatt Amdur 	char *start = buf + strlen(COMMAND_SEPARATOR);
953b6e0a59SMatt Amdur 	char *separator = start;
963b6e0a59SMatt Amdur 	const char *end = buf + strlen(buf);
973b6e0a59SMatt Amdur 	uint_t space = 0;
983b6e0a59SMatt Amdur 
993b6e0a59SMatt Amdur 	while (separator < end && !isspace(*separator))
1003b6e0a59SMatt Amdur 		separator++;
1013b6e0a59SMatt Amdur 
1023b6e0a59SMatt Amdur 	if (separator == end) {
1033b6e0a59SMatt Amdur 		*args = NULL;
1043b6e0a59SMatt Amdur 	} else {
1053b6e0a59SMatt Amdur 		if (isspace(*separator))
1063b6e0a59SMatt Amdur 			space = 1;
1073b6e0a59SMatt Amdur 
1083b6e0a59SMatt Amdur 		*separator++ = '\0';
1093b6e0a59SMatt Amdur 		*args = separator;
1103b6e0a59SMatt Amdur 	}
1113b6e0a59SMatt Amdur 
1123b6e0a59SMatt Amdur 	if (space)
1133b6e0a59SMatt Amdur 		*flags |= DCMD_TAB_SPACE;
1143b6e0a59SMatt Amdur 
1153b6e0a59SMatt Amdur 	return (start);
1163b6e0a59SMatt Amdur }
1173b6e0a59SMatt Amdur 
1183b6e0a59SMatt Amdur /*
1193b6e0a59SMatt Amdur  * count_args --
1203b6e0a59SMatt Amdur  *
121*d75f3745SJohn Levon  *	Given a buffer containing dmcd arguments return the total number
122*d75f3745SJohn Levon  *	of arguments.
1233b6e0a59SMatt Amdur  *
124*d75f3745SJohn Levon  *	While parsing arguments we need to keep track of whether or not the last
125*d75f3745SJohn Levon  *	arguments ends with a trailing space.
1263b6e0a59SMatt Amdur  */
1273b6e0a59SMatt Amdur static int
tab_count_args(const char * input,uint_t * flags)1283b6e0a59SMatt Amdur tab_count_args(const char *input, uint_t *flags)
1293b6e0a59SMatt Amdur {
1303b6e0a59SMatt Amdur 	const char *index;
1313b6e0a59SMatt Amdur 	int argc = 0;
1323b6e0a59SMatt Amdur 	uint_t space = *flags & DCMD_TAB_SPACE;
1333b6e0a59SMatt Amdur 	index = input;
1343b6e0a59SMatt Amdur 
1353b6e0a59SMatt Amdur 	while (*index != '\0') {
1363b6e0a59SMatt Amdur 		while (*index != '\0' && isspace(*index)) {
1373b6e0a59SMatt Amdur 			index++;
1383b6e0a59SMatt Amdur 			space = 1;
1393b6e0a59SMatt Amdur 		}
1403b6e0a59SMatt Amdur 
1413b6e0a59SMatt Amdur 		if (*index != '\0' && !isspace(*index)) {
1423b6e0a59SMatt Amdur 			argc++;
1433b6e0a59SMatt Amdur 			space = 0;
1443b6e0a59SMatt Amdur 			while (*index != '\0' && !isspace (*index)) {
1453b6e0a59SMatt Amdur 				index++;
1463b6e0a59SMatt Amdur 			}
1473b6e0a59SMatt Amdur 		}
1483b6e0a59SMatt Amdur 	}
1493b6e0a59SMatt Amdur 
1503b6e0a59SMatt Amdur 	if (space)
1513b6e0a59SMatt Amdur 		*flags |= DCMD_TAB_SPACE;
1523b6e0a59SMatt Amdur 	else
1533b6e0a59SMatt Amdur 		*flags &= ~DCMD_TAB_SPACE;
1543b6e0a59SMatt Amdur 
1553b6e0a59SMatt Amdur 	return (argc);
1563b6e0a59SMatt Amdur }
1573b6e0a59SMatt Amdur 
1583b6e0a59SMatt Amdur /*
1593b6e0a59SMatt Amdur  * copy_args --
1603b6e0a59SMatt Amdur  *
161*d75f3745SJohn Levon  *	Given a buffer containing dcmd arguments and an array of mdb_arg_t's
162*d75f3745SJohn Levon  *	initialize the string value of each mdb_arg_t.
1633b6e0a59SMatt Amdur  *
164*d75f3745SJohn Levon  *	Note: This will modify the buffer.
1653b6e0a59SMatt Amdur  */
1663b6e0a59SMatt Amdur static int
tab_copy_args(char * input,int argc,mdb_arg_t * argv)1673b6e0a59SMatt Amdur tab_copy_args(char *input, int argc, mdb_arg_t *argv)
1683b6e0a59SMatt Amdur {
1693b6e0a59SMatt Amdur 	int i = 0;
1703b6e0a59SMatt Amdur 	char *index;
1713b6e0a59SMatt Amdur 
1723b6e0a59SMatt Amdur 	index = input;
1733b6e0a59SMatt Amdur 
1743b6e0a59SMatt Amdur 	while (*index) {
1753b6e0a59SMatt Amdur 		while (*index && isspace(*index)) {
1763b6e0a59SMatt Amdur 			index++;
1773b6e0a59SMatt Amdur 		}
1783b6e0a59SMatt Amdur 
1793b6e0a59SMatt Amdur 		if (*index && !isspace(*index)) {
1803b6e0a59SMatt Amdur 			char *end = index;
1813b6e0a59SMatt Amdur 
1823b6e0a59SMatt Amdur 			while (*end && !isspace(*end)) {
1833b6e0a59SMatt Amdur 				end++;
1843b6e0a59SMatt Amdur 			}
1853b6e0a59SMatt Amdur 
1863b6e0a59SMatt Amdur 			if (*end) {
1873b6e0a59SMatt Amdur 				*end++ = '\0';
1883b6e0a59SMatt Amdur 			}
1893b6e0a59SMatt Amdur 
1903b6e0a59SMatt Amdur 			argv[i].a_type = MDB_TYPE_STRING;
1913b6e0a59SMatt Amdur 			argv[i].a_un.a_str = index;
1923b6e0a59SMatt Amdur 
1933b6e0a59SMatt Amdur 			index = end;
1943b6e0a59SMatt Amdur 			i++;
1953b6e0a59SMatt Amdur 		}
1963b6e0a59SMatt Amdur 	}
1973b6e0a59SMatt Amdur 
1983b6e0a59SMatt Amdur 	if (i != argc)
1993b6e0a59SMatt Amdur 		return (-1);
2003b6e0a59SMatt Amdur 
2013b6e0a59SMatt Amdur 	return (0);
2023b6e0a59SMatt Amdur }
2033b6e0a59SMatt Amdur 
2043b6e0a59SMatt Amdur /*
2053b6e0a59SMatt Amdur  * parse-buf --
2063b6e0a59SMatt Amdur  *
207*d75f3745SJohn Levon  *	Parse the given buffer and return the specified dcmd, the number
208*d75f3745SJohn Levon  *	of arguments, and array of mdb_arg_t containing the argument
209*d75f3745SJohn Levon  *	values.
2103b6e0a59SMatt Amdur  *
211*d75f3745SJohn Levon  *	Note: this will modify the specified buffer. Caller is responisble
212*d75f3745SJohn Levon  *	for freeing argvp.
2133b6e0a59SMatt Amdur  */
2143b6e0a59SMatt Amdur static int
tab_parse_buf(char * buf,char ** dcmdp,int * argcp,mdb_arg_t ** argvp,uint_t * flags)2153b6e0a59SMatt Amdur tab_parse_buf(char *buf, char **dcmdp, int *argcp, mdb_arg_t **argvp,
2163b6e0a59SMatt Amdur     uint_t *flags)
2173b6e0a59SMatt Amdur {
2183b6e0a59SMatt Amdur 	char *data = tab_find_command_start(buf);
2193b6e0a59SMatt Amdur 	char *args_data = NULL;
2203b6e0a59SMatt Amdur 	char *dcmd = NULL;
2213b6e0a59SMatt Amdur 	int argc = 0;
2223b6e0a59SMatt Amdur 	mdb_arg_t *argv = NULL;
2233b6e0a59SMatt Amdur 
2243b6e0a59SMatt Amdur 	if (data == NULL) {
2253b6e0a59SMatt Amdur 		return (-1);
2263b6e0a59SMatt Amdur 	}
2273b6e0a59SMatt Amdur 
2283b6e0a59SMatt Amdur 	dcmd = tab_get_dcmd(data, &args_data, flags);
2293b6e0a59SMatt Amdur 
2303b6e0a59SMatt Amdur 	if (dcmd == NULL) {
2313b6e0a59SMatt Amdur 		return (-1);
2323b6e0a59SMatt Amdur 	}
2333b6e0a59SMatt Amdur 
2343b6e0a59SMatt Amdur 	if (args_data != NULL) {
2353b6e0a59SMatt Amdur 		argc = tab_count_args(args_data, flags);
2363b6e0a59SMatt Amdur 
2373b6e0a59SMatt Amdur 		if (argc != 0) {
2383b6e0a59SMatt Amdur 			argv = mdb_alloc(sizeof (mdb_arg_t) * argc,
2393b6e0a59SMatt Amdur 			    UM_SLEEP | UM_GC);
2403b6e0a59SMatt Amdur 
2413b6e0a59SMatt Amdur 			if (tab_copy_args(args_data, argc, argv) == -1)
2423b6e0a59SMatt Amdur 				return (-1);
2433b6e0a59SMatt Amdur 		}
2443b6e0a59SMatt Amdur 	}
2453b6e0a59SMatt Amdur 
2463b6e0a59SMatt Amdur 	*dcmdp = dcmd;
2473b6e0a59SMatt Amdur 	*argcp = argc;
2483b6e0a59SMatt Amdur 	*argvp = argv;
2493b6e0a59SMatt Amdur 
2503b6e0a59SMatt Amdur 	return (0);
2513b6e0a59SMatt Amdur }
2523b6e0a59SMatt Amdur 
2533b6e0a59SMatt Amdur /*
2543b6e0a59SMatt Amdur  * tab_command --
2553b6e0a59SMatt Amdur  *
256*d75f3745SJohn Levon  *	This function is executed anytime a tab is entered. It checks
257*d75f3745SJohn Levon  *	the current buffer to determine if there is a valid dmcd,
258*d75f3745SJohn Levon  *	if that dcmd has a tab completion handler it will invoke it.
2593b6e0a59SMatt Amdur  *
2603b6e0a59SMatt Amdur  *	This function returns the string (if any) that should be added to the
2613b6e0a59SMatt Amdur  *	existing buffer to complete it.
2623b6e0a59SMatt Amdur  */
2633b6e0a59SMatt Amdur int
mdb_tab_command(mdb_tab_cookie_t * mcp,const char * buf)2643b6e0a59SMatt Amdur mdb_tab_command(mdb_tab_cookie_t *mcp, const char *buf)
2653b6e0a59SMatt Amdur {
2663b6e0a59SMatt Amdur 	char *data;
2673b6e0a59SMatt Amdur 	char *dcmd = NULL;
2683b6e0a59SMatt Amdur 	int argc = 0;
2693b6e0a59SMatt Amdur 	mdb_arg_t *argv = NULL;
2703b6e0a59SMatt Amdur 	int ret = 0;
2713b6e0a59SMatt Amdur 	mdb_idcmd_t *cp;
2723b6e0a59SMatt Amdur 	uint_t flags = 0;
2733b6e0a59SMatt Amdur 
2743b6e0a59SMatt Amdur 	/*
2753b6e0a59SMatt Amdur 	 * Parsing the command and arguments will modify the buffer
2763b6e0a59SMatt Amdur 	 * (replacing spaces with \0), so make a copy of the specified
2773b6e0a59SMatt Amdur 	 * buffer first.
2783b6e0a59SMatt Amdur 	 */
2793b6e0a59SMatt Amdur 	data = mdb_alloc(strlen(buf) + 1, UM_SLEEP | UM_GC);
2803b6e0a59SMatt Amdur 	(void) strcpy(data, buf);
2813b6e0a59SMatt Amdur 
2823b6e0a59SMatt Amdur 	/*
2833b6e0a59SMatt Amdur 	 * Get the specified dcmd and arguments from the buffer.
2843b6e0a59SMatt Amdur 	 */
2853b6e0a59SMatt Amdur 	ret = tab_parse_buf(data, &dcmd, &argc, &argv, &flags);
2863b6e0a59SMatt Amdur 
28772a1114bSHenrik Mattsson 	/*
28872a1114bSHenrik Mattsson 	 * Match against global symbols if the input is not a dcmd
28972a1114bSHenrik Mattsson 	 */
2903b6e0a59SMatt Amdur 	if (ret != 0) {
29172a1114bSHenrik Mattsson 		(void) mdb_tab_complete_global(mcp, buf);
2923b6e0a59SMatt Amdur 		goto out;
2933b6e0a59SMatt Amdur 	}
2943b6e0a59SMatt Amdur 
2953b6e0a59SMatt Amdur 	/*
2963b6e0a59SMatt Amdur 	 * Check to see if the buffer contains a valid dcmd
2973b6e0a59SMatt Amdur 	 */
2983b6e0a59SMatt Amdur 	cp = mdb_dcmd_lookup(dcmd);
2993b6e0a59SMatt Amdur 
3003b6e0a59SMatt Amdur 	/*
3013b6e0a59SMatt Amdur 	 * When argc is zero it indicates that we are trying to tab complete
30272a1114bSHenrik Mattsson 	 * a dcmd or a global symbol. Note, that if there isn't the start of
30372a1114bSHenrik Mattsson 	 * a dcmd, i.e. ::, then we will have already bailed in the call to
30472a1114bSHenrik Mattsson 	 * tab_parse_buf.
3053b6e0a59SMatt Amdur 	 */
3063b6e0a59SMatt Amdur 	if (cp == NULL && argc != 0) {
3073b6e0a59SMatt Amdur 		goto out;
3083b6e0a59SMatt Amdur 	}
3093b6e0a59SMatt Amdur 
3103b6e0a59SMatt Amdur 	/*
3113b6e0a59SMatt Amdur 	 * Invoke the command specific tab completion handler or the built in
3123b6e0a59SMatt Amdur 	 * dcmd one if there is no dcmd.
3133b6e0a59SMatt Amdur 	 */
3143b6e0a59SMatt Amdur 	if (cp == NULL)
3153b6e0a59SMatt Amdur 		(void) mdb_tab_complete_dcmd(mcp, dcmd);
3163b6e0a59SMatt Amdur 	else
3173b6e0a59SMatt Amdur 		mdb_call_tab(cp, mcp, flags, argc, argv);
3183b6e0a59SMatt Amdur 
3193b6e0a59SMatt Amdur out:
3203b6e0a59SMatt Amdur 	return (mdb_tab_size(mcp));
3213b6e0a59SMatt Amdur }
3223b6e0a59SMatt Amdur 
3233b6e0a59SMatt Amdur static int
tab_complete_dcmd(mdb_var_t * v,void * arg)3243b6e0a59SMatt Amdur tab_complete_dcmd(mdb_var_t *v, void *arg)
3253b6e0a59SMatt Amdur {
3263b6e0a59SMatt Amdur 	mdb_idcmd_t *idcp = mdb_nv_get_cookie(mdb_nv_get_cookie(v));
3273b6e0a59SMatt Amdur 	mdb_tab_cookie_t *mcp = (mdb_tab_cookie_t *)arg;
3283b6e0a59SMatt Amdur 
3293b6e0a59SMatt Amdur 	/*
3303b6e0a59SMatt Amdur 	 * The way that mdb is implemented, even commands like $C will show up
3313b6e0a59SMatt Amdur 	 * here. As such, we don't want to match anything that doesn't start
3323b6e0a59SMatt Amdur 	 * with an alpha or number. While nothing currently appears (via a
3333b6e0a59SMatt Amdur 	 * cursory search with mdb -k) to start with a capital letter or a
3343b6e0a59SMatt Amdur 	 * number, we'll support them anyways.
3353b6e0a59SMatt Amdur 	 */
3363b6e0a59SMatt Amdur 	if (!isalnum(idcp->idc_name[0]))
3373b6e0a59SMatt Amdur 		return (0);
3383b6e0a59SMatt Amdur 
3393b6e0a59SMatt Amdur 	mdb_tab_insert(mcp, idcp->idc_name);
3403b6e0a59SMatt Amdur 	return (0);
3413b6e0a59SMatt Amdur }
3423b6e0a59SMatt Amdur 
3433b6e0a59SMatt Amdur int
mdb_tab_complete_dcmd(mdb_tab_cookie_t * mcp,const char * dcmd)3443b6e0a59SMatt Amdur mdb_tab_complete_dcmd(mdb_tab_cookie_t *mcp, const char *dcmd)
3453b6e0a59SMatt Amdur {
3462f045fd6SRobert Mustacchi 	if (dcmd != NULL)
3472f045fd6SRobert Mustacchi 		mdb_tab_setmbase(mcp, dcmd);
3483b6e0a59SMatt Amdur 	mdb_nv_sort_iter(&mdb.m_dcmds, tab_complete_dcmd, mcp,
3493b6e0a59SMatt Amdur 	    UM_GC | UM_SLEEP);
3503b6e0a59SMatt Amdur 	return (0);
3513b6e0a59SMatt Amdur }
3523b6e0a59SMatt Amdur 
3533b6e0a59SMatt Amdur static int
tab_complete_walker(mdb_var_t * v,void * arg)3543b6e0a59SMatt Amdur tab_complete_walker(mdb_var_t *v, void *arg)
3553b6e0a59SMatt Amdur {
3563b6e0a59SMatt Amdur 	mdb_iwalker_t *iwp = mdb_nv_get_cookie(mdb_nv_get_cookie(v));
3573b6e0a59SMatt Amdur 	mdb_tab_cookie_t *mcp = arg;
3583b6e0a59SMatt Amdur 
3593b6e0a59SMatt Amdur 	mdb_tab_insert(mcp, iwp->iwlk_name);
3603b6e0a59SMatt Amdur 	return (0);
3613b6e0a59SMatt Amdur }
3623b6e0a59SMatt Amdur 
3633b6e0a59SMatt Amdur int
mdb_tab_complete_walker(mdb_tab_cookie_t * mcp,const char * walker)3643b6e0a59SMatt Amdur mdb_tab_complete_walker(mdb_tab_cookie_t *mcp, const char *walker)
3653b6e0a59SMatt Amdur {
3663b6e0a59SMatt Amdur 	if (walker != NULL)
3673b6e0a59SMatt Amdur 		mdb_tab_setmbase(mcp, walker);
3683b6e0a59SMatt Amdur 	mdb_nv_sort_iter(&mdb.m_walkers, tab_complete_walker, mcp,
3693b6e0a59SMatt Amdur 	    UM_GC | UM_SLEEP);
3703b6e0a59SMatt Amdur 
3713b6e0a59SMatt Amdur 	return (0);
3723b6e0a59SMatt Amdur }
3733b6e0a59SMatt Amdur 
3743b6e0a59SMatt Amdur mdb_tab_cookie_t *
mdb_tab_init(void)3753b6e0a59SMatt Amdur mdb_tab_init(void)
3763b6e0a59SMatt Amdur {
3773b6e0a59SMatt Amdur 	mdb_tab_cookie_t *mcp;
3783b6e0a59SMatt Amdur 
3793b6e0a59SMatt Amdur 	mcp = mdb_zalloc(sizeof (mdb_tab_cookie_t), UM_SLEEP | UM_GC);
3803b6e0a59SMatt Amdur 	(void) mdb_nv_create(&mcp->mtc_nv, UM_SLEEP | UM_GC);
3813b6e0a59SMatt Amdur 
3823b6e0a59SMatt Amdur 	return (mcp);
3833b6e0a59SMatt Amdur }
3843b6e0a59SMatt Amdur 
3853b6e0a59SMatt Amdur size_t
mdb_tab_size(mdb_tab_cookie_t * mcp)3863b6e0a59SMatt Amdur mdb_tab_size(mdb_tab_cookie_t *mcp)
3873b6e0a59SMatt Amdur {
3883b6e0a59SMatt Amdur 	return (mdb_nv_size(&mcp->mtc_nv));
3893b6e0a59SMatt Amdur }
3903b6e0a59SMatt Amdur 
3913b6e0a59SMatt Amdur /*
3923b6e0a59SMatt Amdur  * Determine whether the specified name is a valid tab completion for
3933b6e0a59SMatt Amdur  * the given command. If the name is a valid tab completion then
3943b6e0a59SMatt Amdur  * it will be saved in the mdb_tab_cookie_t.
3953b6e0a59SMatt Amdur  */
3963b6e0a59SMatt Amdur void
mdb_tab_insert(mdb_tab_cookie_t * mcp,const char * name)3973b6e0a59SMatt Amdur mdb_tab_insert(mdb_tab_cookie_t *mcp, const char *name)
3983b6e0a59SMatt Amdur {
399065c692aSJosef 'Jeff' Sipek 	size_t matches, index;
4003b6e0a59SMatt Amdur 	mdb_var_t *v;
4013b6e0a59SMatt Amdur 
4023b6e0a59SMatt Amdur 	/*
4033b6e0a59SMatt Amdur 	 * If we have a match set, then we want to verify that we actually match
4043b6e0a59SMatt Amdur 	 * it.
4053b6e0a59SMatt Amdur 	 */
406*d75f3745SJohn Levon 	if (strncmp(name, mcp->mtc_base, strlen(mcp->mtc_base)) != 0)
4073b6e0a59SMatt Amdur 		return;
4083b6e0a59SMatt Amdur 
4093b6e0a59SMatt Amdur 	v = mdb_nv_lookup(&mcp->mtc_nv, name);
4103b6e0a59SMatt Amdur 	if (v != NULL)
4113b6e0a59SMatt Amdur 		return;
4123b6e0a59SMatt Amdur 
413065c692aSJosef 'Jeff' Sipek 	(void) mdb_nv_insert(&mcp->mtc_nv, name, NULL, 0, MDB_NV_RDONLY);
4143b6e0a59SMatt Amdur 
4153b6e0a59SMatt Amdur 	matches = mdb_tab_size(mcp);
4163b6e0a59SMatt Amdur 	if (matches == 1) {
417065c692aSJosef 'Jeff' Sipek 		(void) strlcpy(mcp->mtc_match, name, MDB_SYM_NAMLEN);
4183b6e0a59SMatt Amdur 	} else {
4193b6e0a59SMatt Amdur 		index = 0;
4203b6e0a59SMatt Amdur 		while (mcp->mtc_match[index] &&
421065c692aSJosef 'Jeff' Sipek 		    mcp->mtc_match[index] == name[index])
4223b6e0a59SMatt Amdur 			index++;
4233b6e0a59SMatt Amdur 
4243b6e0a59SMatt Amdur 		mcp->mtc_match[index] = '\0';
4253b6e0a59SMatt Amdur 	}
4263b6e0a59SMatt Amdur }
4273b6e0a59SMatt Amdur 
4283b6e0a59SMatt Amdur /*ARGSUSED*/
4293b6e0a59SMatt Amdur static int
tab_print_cb(mdb_var_t * v,void * ignored)4303b6e0a59SMatt Amdur tab_print_cb(mdb_var_t *v, void *ignored)
4313b6e0a59SMatt Amdur {
4323b6e0a59SMatt Amdur 	mdb_printf("%s\n", mdb_nv_get_name(v));
4333b6e0a59SMatt Amdur 	return (0);
4343b6e0a59SMatt Amdur }
4353b6e0a59SMatt Amdur 
4363b6e0a59SMatt Amdur void
mdb_tab_print(mdb_tab_cookie_t * mcp)4373b6e0a59SMatt Amdur mdb_tab_print(mdb_tab_cookie_t *mcp)
4383b6e0a59SMatt Amdur {
4393b6e0a59SMatt Amdur 	mdb_nv_sort_iter(&mcp->mtc_nv, tab_print_cb, NULL, UM_SLEEP | UM_GC);
4403b6e0a59SMatt Amdur }
4413b6e0a59SMatt Amdur 
4423b6e0a59SMatt Amdur const char *
mdb_tab_match(mdb_tab_cookie_t * mcp)4433b6e0a59SMatt Amdur mdb_tab_match(mdb_tab_cookie_t *mcp)
4443b6e0a59SMatt Amdur {
445*d75f3745SJohn Levon 	return (mcp->mtc_match + strlen(mcp->mtc_base));
4463b6e0a59SMatt Amdur }
4473b6e0a59SMatt Amdur 
4483b6e0a59SMatt Amdur void
mdb_tab_setmbase(mdb_tab_cookie_t * mcp,const char * base)4493b6e0a59SMatt Amdur mdb_tab_setmbase(mdb_tab_cookie_t *mcp, const char *base)
4503b6e0a59SMatt Amdur {
4513b6e0a59SMatt Amdur 	(void) strlcpy(mcp->mtc_base, base, MDB_SYM_NAMLEN);
4523b6e0a59SMatt Amdur }
4533b6e0a59SMatt Amdur 
4543b6e0a59SMatt Amdur /*
4553b6e0a59SMatt Amdur  * This function is currently a no-op due to the fact that we have to GC because
4563b6e0a59SMatt Amdur  * we're in command context.
4573b6e0a59SMatt Amdur  */
4583b6e0a59SMatt Amdur /*ARGSUSED*/
4593b6e0a59SMatt Amdur void
mdb_tab_fini(mdb_tab_cookie_t * mcp)4603b6e0a59SMatt Amdur mdb_tab_fini(mdb_tab_cookie_t *mcp)
4613b6e0a59SMatt Amdur {
4623b6e0a59SMatt Amdur }
4633b6e0a59SMatt Amdur 
46472a1114bSHenrik Mattsson /*ARGSUSED*/
46572a1114bSHenrik Mattsson static int
tab_complete_global(void * arg,const GElf_Sym * sym,const char * name,const mdb_syminfo_t * sip,const char * obj)46672a1114bSHenrik Mattsson tab_complete_global(void *arg, const GElf_Sym *sym, const char *name,
46772a1114bSHenrik Mattsson     const mdb_syminfo_t *sip, const char *obj)
46872a1114bSHenrik Mattsson {
46972a1114bSHenrik Mattsson 	mdb_tab_cookie_t *mcp = arg;
47072a1114bSHenrik Mattsson 	mdb_tab_insert(mcp, name);
47172a1114bSHenrik Mattsson 	return (0);
47272a1114bSHenrik Mattsson }
47372a1114bSHenrik Mattsson 
47472a1114bSHenrik Mattsson /*
47572a1114bSHenrik Mattsson  * This function tab completes against all loaded global symbols.
47672a1114bSHenrik Mattsson  */
47772a1114bSHenrik Mattsson int
mdb_tab_complete_global(mdb_tab_cookie_t * mcp,const char * name)47872a1114bSHenrik Mattsson mdb_tab_complete_global(mdb_tab_cookie_t *mcp, const char *name)
47972a1114bSHenrik Mattsson {
48072a1114bSHenrik Mattsson 	mdb_tab_setmbase(mcp, name);
48172a1114bSHenrik Mattsson 	(void) mdb_tgt_symbol_iter(mdb.m_target, MDB_TGT_OBJ_EVERY,
48222ce0148SMatthew Ahrens 	    MDB_TGT_SYMTAB, MDB_TGT_BIND_ANY | MDB_TGT_TYPE_OBJECT |
48372a1114bSHenrik Mattsson 	    MDB_TGT_TYPE_FUNC, tab_complete_global, mcp);
48472a1114bSHenrik Mattsson 	return (0);
48572a1114bSHenrik Mattsson }
48672a1114bSHenrik Mattsson 
4873b6e0a59SMatt Amdur /*
4883b6e0a59SMatt Amdur  * This function takes a ctf id and determines whether or not the associated
4893b6e0a59SMatt Amdur  * type should be considered as a potential match for the given tab
4903b6e0a59SMatt Amdur  * completion command. We verify that the type itself is valid
4913b6e0a59SMatt Amdur  * for completion given the current context of the command, resolve
4923b6e0a59SMatt Amdur  * its actual name, and then pass it off to mdb_tab_insert to determine
4933b6e0a59SMatt Amdur  * if it's an actual match.
4943b6e0a59SMatt Amdur  */
4953b6e0a59SMatt Amdur static int
tab_complete_type(mdb_ctf_id_t id,void * arg)4963b6e0a59SMatt Amdur tab_complete_type(mdb_ctf_id_t id, void *arg)
4973b6e0a59SMatt Amdur {
4983b6e0a59SMatt Amdur 	int rkind;
4993b6e0a59SMatt Amdur 	char buf[MDB_SYM_NAMLEN];
5003b6e0a59SMatt Amdur 	mdb_ctf_id_t rid;
5013b6e0a59SMatt Amdur 	mdb_tab_cookie_t *mcp = arg;
5023b6e0a59SMatt Amdur 	uint_t flags = (uint_t)(uintptr_t)mcp->mtc_cba;
5033b6e0a59SMatt Amdur 
5043b6e0a59SMatt Amdur 	/*
5053b6e0a59SMatt Amdur 	 * CTF data includes types that mdb commands don't understand. Before
5063b6e0a59SMatt Amdur 	 * we resolve the actual type prune any entry that is a type we
5073b6e0a59SMatt Amdur 	 * don't care about.
5083b6e0a59SMatt Amdur 	 */
5093b6e0a59SMatt Amdur 	switch (mdb_ctf_type_kind(id)) {
5103b6e0a59SMatt Amdur 	case CTF_K_CONST:
5113b6e0a59SMatt Amdur 	case CTF_K_RESTRICT:
5123b6e0a59SMatt Amdur 	case CTF_K_VOLATILE:
5133b6e0a59SMatt Amdur 		return (0);
5143b6e0a59SMatt Amdur 	}
5153b6e0a59SMatt Amdur 
5163b6e0a59SMatt Amdur 	if (mdb_ctf_type_resolve(id, &rid) != 0)
5173b6e0a59SMatt Amdur 		return (1);
5183b6e0a59SMatt Amdur 
5193b6e0a59SMatt Amdur 	rkind = mdb_ctf_type_kind(rid);
5203b6e0a59SMatt Amdur 
5213b6e0a59SMatt Amdur 	if ((flags & MDB_TABC_MEMBERS) && rkind != CTF_K_STRUCT &&
5223b6e0a59SMatt Amdur 	    rkind != CTF_K_UNION)
5233b6e0a59SMatt Amdur 		return (0);
5243b6e0a59SMatt Amdur 
5253b6e0a59SMatt Amdur 	if ((flags & MDB_TABC_NOPOINT) && rkind == CTF_K_POINTER)
5263b6e0a59SMatt Amdur 		return (0);
5273b6e0a59SMatt Amdur 
5283b6e0a59SMatt Amdur 	if ((flags & MDB_TABC_NOARRAY) && rkind == CTF_K_ARRAY)
5293b6e0a59SMatt Amdur 		return (0);
5303b6e0a59SMatt Amdur 
5313b6e0a59SMatt Amdur 	(void) mdb_ctf_type_name(id, buf, sizeof (buf));
5323b6e0a59SMatt Amdur 
5333b6e0a59SMatt Amdur 	mdb_tab_insert(mcp, buf);
5343b6e0a59SMatt Amdur 	return (0);
5353b6e0a59SMatt Amdur }
5363b6e0a59SMatt Amdur 
5373b6e0a59SMatt Amdur /*ARGSUSED*/
5383b6e0a59SMatt Amdur static int
mdb_tab_complete_module(void * data,const mdb_map_t * mp,const char * name)5393b6e0a59SMatt Amdur mdb_tab_complete_module(void *data, const mdb_map_t *mp, const char *name)
5403b6e0a59SMatt Amdur {
5413b6e0a59SMatt Amdur 	(void) mdb_ctf_type_iter(name, tab_complete_type, data);
5423b6e0a59SMatt Amdur 	return (0);
5433b6e0a59SMatt Amdur }
5443b6e0a59SMatt Amdur 
5453b6e0a59SMatt Amdur int
mdb_tab_complete_type(mdb_tab_cookie_t * mcp,const char * name,uint_t flags)5463b6e0a59SMatt Amdur mdb_tab_complete_type(mdb_tab_cookie_t *mcp, const char *name, uint_t flags)
5473b6e0a59SMatt Amdur {
5483b6e0a59SMatt Amdur 	mdb_tgt_t *t = mdb.m_target;
5493b6e0a59SMatt Amdur 
5503b6e0a59SMatt Amdur 	mcp->mtc_cba = (void *)(uintptr_t)flags;
5513b6e0a59SMatt Amdur 	if (name != NULL)
5523b6e0a59SMatt Amdur 		mdb_tab_setmbase(mcp, name);
5533b6e0a59SMatt Amdur 
5543b6e0a59SMatt Amdur 	(void) mdb_tgt_object_iter(t, mdb_tab_complete_module, mcp);
5550a47c91cSRobert Mustacchi 	(void) mdb_ctf_type_iter(MDB_CTF_SYNTHETIC_ITER, tab_complete_type,
5560a47c91cSRobert Mustacchi 	    mcp);
5573b6e0a59SMatt Amdur 	return (0);
5583b6e0a59SMatt Amdur }
5593b6e0a59SMatt Amdur 
5603b6e0a59SMatt Amdur /*ARGSUSED*/
5613b6e0a59SMatt Amdur static int
tab_complete_member(const char * name,mdb_ctf_id_t id,ulong_t off,void * arg)5623b6e0a59SMatt Amdur tab_complete_member(const char *name, mdb_ctf_id_t id, ulong_t off, void *arg)
5633b6e0a59SMatt Amdur {
5643b6e0a59SMatt Amdur 	mdb_tab_cookie_t *mcp = arg;
5653b6e0a59SMatt Amdur 	mdb_tab_insert(mcp, name);
5663b6e0a59SMatt Amdur 	return (0);
5673b6e0a59SMatt Amdur }
5683b6e0a59SMatt Amdur 
5693b6e0a59SMatt Amdur int
mdb_tab_complete_member_by_id(mdb_tab_cookie_t * mcp,mdb_ctf_id_t id,const char * member)5703b6e0a59SMatt Amdur mdb_tab_complete_member_by_id(mdb_tab_cookie_t *mcp, mdb_ctf_id_t id,
5713b6e0a59SMatt Amdur     const char *member)
5723b6e0a59SMatt Amdur {
5733b6e0a59SMatt Amdur 	if (member != NULL)
5743b6e0a59SMatt Amdur 		mdb_tab_setmbase(mcp, member);
5753b6e0a59SMatt Amdur 	(void) mdb_ctf_member_iter(id, tab_complete_member, mcp);
5763b6e0a59SMatt Amdur 	return (0);
5773b6e0a59SMatt Amdur }
5783b6e0a59SMatt Amdur 
5793b6e0a59SMatt Amdur int
mdb_tab_complete_member(mdb_tab_cookie_t * mcp,const char * type,const char * member)5803b6e0a59SMatt Amdur mdb_tab_complete_member(mdb_tab_cookie_t *mcp, const char *type,
5813b6e0a59SMatt Amdur     const char *member)
5823b6e0a59SMatt Amdur {
5833b6e0a59SMatt Amdur 	mdb_ctf_id_t id;
5843b6e0a59SMatt Amdur 
5853b6e0a59SMatt Amdur 	if (mdb_ctf_lookup_by_name(type, &id) != 0)
5863b6e0a59SMatt Amdur 		return (-1);
5873b6e0a59SMatt Amdur 
5883b6e0a59SMatt Amdur 	return (mdb_tab_complete_member_by_id(mcp, id, member));
5893b6e0a59SMatt Amdur }
5903b6e0a59SMatt Amdur 
5913b6e0a59SMatt Amdur int
mdb_tab_complete_mt(mdb_tab_cookie_t * mcp,uint_t flags,int argc,const mdb_arg_t * argv)5923b6e0a59SMatt Amdur mdb_tab_complete_mt(mdb_tab_cookie_t *mcp, uint_t flags, int argc,
5933b6e0a59SMatt Amdur     const mdb_arg_t *argv)
5943b6e0a59SMatt Amdur {
5953b6e0a59SMatt Amdur 	char tn[MDB_SYM_NAMLEN];
5963b6e0a59SMatt Amdur 	int ret;
5973b6e0a59SMatt Amdur 
5983b6e0a59SMatt Amdur 	if (argc == 0 && !(flags & DCMD_TAB_SPACE))
5993b6e0a59SMatt Amdur 		return (0);
6003b6e0a59SMatt Amdur 
6013b6e0a59SMatt Amdur 	if (argc == 0)
6023b6e0a59SMatt Amdur 		return (mdb_tab_complete_type(mcp, NULL, MDB_TABC_MEMBERS));
6033b6e0a59SMatt Amdur 
6043b6e0a59SMatt Amdur 	if ((ret = mdb_tab_typename(&argc, &argv, tn, sizeof (tn))) < 0)
6053b6e0a59SMatt Amdur 		return (ret);
6063b6e0a59SMatt Amdur 
6073b6e0a59SMatt Amdur 	if (argc == 1 && (!(flags & DCMD_TAB_SPACE) || ret == 1))
6083b6e0a59SMatt Amdur 		return (mdb_tab_complete_type(mcp, tn, MDB_TABC_MEMBERS));
6093b6e0a59SMatt Amdur 
6103b6e0a59SMatt Amdur 	if (argc == 1 && (flags & DCMD_TAB_SPACE))
6113b6e0a59SMatt Amdur 		return (mdb_tab_complete_member(mcp, tn, NULL));
6123b6e0a59SMatt Amdur 
6133b6e0a59SMatt Amdur 	if (argc == 2)
6143b6e0a59SMatt Amdur 		return (mdb_tab_complete_member(mcp, tn, argv[1].a_un.a_str));
6153b6e0a59SMatt Amdur 
6163b6e0a59SMatt Amdur 	return (0);
6173b6e0a59SMatt Amdur }
6183b6e0a59SMatt Amdur 
6193b6e0a59SMatt Amdur /*
6203b6e0a59SMatt Amdur  * This is similar to mdb_print.c's args_to_typename, but it has subtle
6213b6e0a59SMatt Amdur  * differences surrounding how the strings of one element are handled that have
6223b6e0a59SMatt Amdur  * 'struct', 'enum', or 'union' in them and instead works with them for tab
6233b6e0a59SMatt Amdur  * completion purposes.
6243b6e0a59SMatt Amdur  */
6253b6e0a59SMatt Amdur int
mdb_tab_typename(int * argcp,const mdb_arg_t ** argvp,char * buf,size_t len)6263b6e0a59SMatt Amdur mdb_tab_typename(int *argcp, const mdb_arg_t **argvp, char *buf, size_t len)
6273b6e0a59SMatt Amdur {
6283b6e0a59SMatt Amdur 	int argc = *argcp;
6293b6e0a59SMatt Amdur 	const mdb_arg_t *argv = *argvp;
6303b6e0a59SMatt Amdur 
6313b6e0a59SMatt Amdur 	if (argc < 1 || argv->a_type != MDB_TYPE_STRING)
6323b6e0a59SMatt Amdur 		return (DCMD_USAGE);
6333b6e0a59SMatt Amdur 
6343b6e0a59SMatt Amdur 	if (strcmp(argv->a_un.a_str, "struct") == 0 ||
6353b6e0a59SMatt Amdur 	    strcmp(argv->a_un.a_str, "enum") == 0 ||
6363b6e0a59SMatt Amdur 	    strcmp(argv->a_un.a_str, "union") == 0) {
6373b6e0a59SMatt Amdur 		if (argc == 1) {
6383b6e0a59SMatt Amdur 			(void) mdb_snprintf(buf, len, "%s ",
6393b6e0a59SMatt Amdur 			    argv[0].a_un.a_str);
6403b6e0a59SMatt Amdur 			return (1);
6413b6e0a59SMatt Amdur 		}
6423b6e0a59SMatt Amdur 
6433b6e0a59SMatt Amdur 		if (argv[1].a_type != MDB_TYPE_STRING)
6443b6e0a59SMatt Amdur 			return (DCMD_USAGE);
6453b6e0a59SMatt Amdur 
6463b6e0a59SMatt Amdur 		(void) mdb_snprintf(buf, len, "%s %s",
6473b6e0a59SMatt Amdur 		    argv[0].a_un.a_str, argv[1].a_un.a_str);
6483b6e0a59SMatt Amdur 
6493b6e0a59SMatt Amdur 		*argcp = argc - 1;
6503b6e0a59SMatt Amdur 		*argvp = argv + 1;
6513b6e0a59SMatt Amdur 	} else {
6523b6e0a59SMatt Amdur 		(void) mdb_snprintf(buf, len, "%s", argv[0].a_un.a_str);
6533b6e0a59SMatt Amdur 	}
6543b6e0a59SMatt Amdur 
6553b6e0a59SMatt Amdur 	return (0);
6563b6e0a59SMatt Amdur }
657