1 /*
2  * Copyright (c) 2018 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <string.h>
8 #include <zephyr/posix/fnmatch.h>
9 #include "shell_wildcard.h"
10 #include "shell_utils.h"
11 #include "shell_ops.h"
12 
command_add(char * buff,uint16_t * buff_len,char const * cmd,char const * pattern)13 static enum shell_wildcard_status command_add(char *buff, uint16_t *buff_len,
14 					      char const *cmd,
15 					      char const *pattern)
16 {
17 	uint16_t cmd_len = z_shell_strlen(cmd);
18 	char *completion_addr;
19 	uint16_t shift;
20 
21 	/* +1 for space */
22 	if ((*buff_len + cmd_len + 1) > CONFIG_SHELL_CMD_BUFF_SIZE) {
23 		return SHELL_WILDCARD_CMD_MISSING_SPACE;
24 	}
25 
26 	completion_addr = strstr(buff, pattern);
27 
28 	if (!completion_addr) {
29 		return SHELL_WILDCARD_CMD_NO_MATCH_FOUND;
30 	}
31 
32 	shift = z_shell_strlen(completion_addr);
33 
34 	/* make place for new command: + 1 for space + 1 for EOS */
35 	memmove(completion_addr + cmd_len + 1, completion_addr, shift + 1);
36 	memcpy(completion_addr, cmd, cmd_len);
37 	/* adding space to not brake next command in the buffer */
38 	completion_addr[cmd_len] = ' ';
39 
40 	*buff_len += cmd_len + 1; /* + 1 for space */
41 
42 	return SHELL_WILDCARD_CMD_ADDED;
43 }
44 
45 /**
46  * @internal @brief Function for searching and adding commands to the temporary
47  * shell buffer matching to wildcard pattern.
48  *
49  * Function will search commands tree for commands matching wildcard pattern
50  * stored in argv[cmd_lvl]. When match is found wildcard pattern will be
51  * replaced by matching commands. If there is no space in the buffer to add all
52  * matching commands function will add as many as possible. Next it will
53  * continue to search for next wildcard pattern and it will try to add matching
54  * commands.
55  *
56  *
57  * This function is internal to shell module and shall be not called directly.
58  *
59  * @param[in/out] shell		Pointer to the CLI instance.
60  * @param[in]	  cmd		Pointer to command which will be processed
61  * @param[in]	  pattern	Pointer to wildcard pattern.
62  *
63  * @retval WILDCARD_CMD_ADDED	All matching commands added to the buffer.
64  * @retval WILDCARD_CMD_ADDED_MISSING_SPACE  Not all matching commands added
65  *					     because CONFIG_SHELL_CMD_BUFF_SIZE
66  *					     is too small.
67  * @retval WILDCARD_CMD_NO_MATCH_FOUND No matching command found.
68  */
commands_expand(const struct shell * sh,const struct shell_static_entry * cmd,const char * pattern)69 static enum shell_wildcard_status commands_expand(const struct shell *sh,
70 					const struct shell_static_entry *cmd,
71 					const char *pattern)
72 {
73 	enum shell_wildcard_status ret_val = SHELL_WILDCARD_CMD_NO_MATCH_FOUND;
74 	struct shell_static_entry const *entry = NULL;
75 	struct shell_static_entry dloc;
76 	size_t cmd_idx = 0;
77 	size_t cnt = 0;
78 
79 	while ((entry = z_shell_cmd_get(cmd, cmd_idx++, &dloc)) != NULL) {
80 
81 		if (fnmatch(pattern, entry->syntax, 0) == 0) {
82 			ret_val = command_add(sh->ctx->temp_buff,
83 					      &sh->ctx->cmd_tmp_buff_len,
84 					      entry->syntax, pattern);
85 			if (ret_val == SHELL_WILDCARD_CMD_MISSING_SPACE) {
86 				z_shell_fprintf(sh, SHELL_WARNING,
87 					"Command buffer is too short to"
88 					" expand all commands matching"
89 					" wildcard pattern: %s\n", pattern);
90 				break;
91 			} else if (ret_val != SHELL_WILDCARD_CMD_ADDED) {
92 				break;
93 			}
94 			cnt++;
95 		}
96 	}
97 
98 	if (cnt > 0) {
99 		z_shell_pattern_remove(sh->ctx->temp_buff,
100 				       &sh->ctx->cmd_tmp_buff_len, pattern);
101 	}
102 
103 	return ret_val;
104 }
105 
z_shell_has_wildcard(const char * str)106 bool z_shell_has_wildcard(const char *str)
107 {
108 	uint16_t str_len = z_shell_strlen(str);
109 
110 	for (size_t i = 0; i < str_len; i++) {
111 		if ((str[i] == '?') || (str[i] == '*')) {
112 			return true;
113 		}
114 	}
115 
116 	return false;
117 }
118 
z_shell_wildcard_prepare(const struct shell * sh)119 void z_shell_wildcard_prepare(const struct shell *sh)
120 {
121 	/* Wildcard can be correctly handled under following conditions:
122 	 * - wildcard command does not have a handler
123 	 * - wildcard command is on the deepest commands level
124 	 * - other commands on the same level as wildcard command shall also not
125 	 *   have a handler
126 	 *
127 	 * Algorithm:
128 	 * 1. Command buffer: ctx->cmd_buff is copied to temporary buffer:
129 	 *    ctx->temp_buff.
130 	 * 2. Algorithm goes through command buffer to find handlers and
131 	 *    subcommands.
132 	 * 3. If algorithm will find a wildcard character it switches to
133 	 *    Temporary buffer.
134 	 * 4. In the Temporary buffer command containing wildcard character is
135 	 *    replaced by matching command(s).
136 	 * 5. Algorithm switch back to Command buffer and analyzes next command.
137 	 * 6. When all arguments are analyzed from Command buffer, Temporary
138 	 *    buffer with all expanded commands is copied to Command buffer.
139 	 * 7. Deepest found handler is executed and all lower level commands,
140 	 *    including expanded commands, are passed as arguments.
141 	 */
142 
143 	memset(sh->ctx->temp_buff, 0, sizeof(sh->ctx->temp_buff));
144 	memcpy(sh->ctx->temp_buff,
145 			sh->ctx->cmd_buff,
146 			sh->ctx->cmd_buff_len);
147 
148 	/* Function shell_spaces_trim must be used instead of shell_make_argv.
149 	 * At this point it is important to keep temp_buff as one string.
150 	 * It will allow to find wildcard commands easily with strstr function.
151 	 */
152 	z_shell_spaces_trim(sh->ctx->temp_buff);
153 
154 	/* +1 for EOS*/
155 	sh->ctx->cmd_tmp_buff_len = z_shell_strlen(sh->ctx->temp_buff) + 1;
156 }
157 
158 
z_shell_wildcard_process(const struct shell * sh,const struct shell_static_entry * cmd,const char * pattern)159 enum shell_wildcard_status z_shell_wildcard_process(const struct shell *sh,
160 					const struct shell_static_entry *cmd,
161 					const char *pattern)
162 {
163 	enum shell_wildcard_status ret_val = SHELL_WILDCARD_NOT_FOUND;
164 
165 	if (cmd == NULL) {
166 		return ret_val;
167 	}
168 
169 	if (!z_shell_has_wildcard(pattern)) {
170 		return ret_val;
171 	}
172 
173 	/* Function will search commands tree for commands matching wildcard
174 	 * pattern stored in argv[cmd_lvl]. When match is found wildcard pattern
175 	 * will be replaced by matching commands. If there is no space in the
176 	 * buffer to add all matching commands function will add as many as
177 	 * possible. Next it will continue to search for next wildcard pattern
178 	 * and it will try to add matching commands.
179 	 */
180 	ret_val = commands_expand(sh, cmd, pattern);
181 
182 	return ret_val;
183 }
184 
z_shell_wildcard_finalize(const struct shell * sh)185 void z_shell_wildcard_finalize(const struct shell *sh)
186 {
187 	memcpy(sh->ctx->cmd_buff,
188 	       sh->ctx->temp_buff,
189 	       sh->ctx->cmd_tmp_buff_len);
190 	sh->ctx->cmd_buff_len = sh->ctx->cmd_tmp_buff_len;
191 }
192