1 /*
2  * Copyright (c) 2018 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 #include <zephyr/shell/shell.h>
7 #include <zephyr/sys/iterable_sections.h>
8 
9 #include "shell_utils.h"
10 #include "shell_help.h"
11 #include "shell_ops.h"
12 #include "shell_vt100.h"
13 
14 #define SHELL_MSG_CMD_NOT_SUPPORTED	"Command not supported.\n"
15 #define SHELL_HELP_COMMENT		"Ignore lines beginning with 'rem '"
16 #define SHELL_HELP_RETVAL		"Print return value of most recent command"
17 #define SHELL_HELP_CLEAR		"Clear screen."
18 #define SHELL_HELP_BACKENDS		"List active shell backends.\n"
19 #define SHELL_HELP_BACKSPACE_MODE	"Toggle backspace key mode.\n"	      \
20 	"Some terminals are not sending separate escape code for "	      \
21 	"backspace and delete button. This command forces shell to interpret" \
22 	" delete key as backspace."
23 #define SHELL_HELP_BACKSPACE_MODE_BACKSPACE	"Set different escape"	 \
24 	" code for backspace and delete key."
25 #define SHELL_HELP_BACKSPACE_MODE_DELETE	"Set the same escape"	 \
26 	" code for backspace and delete key."
27 
28 #define SHELL_HELP_COLORS		"Toggle colored syntax."
29 #define SHELL_HELP_COLORS_OFF		"Disable colored syntax."
30 #define SHELL_HELP_COLORS_ON		"Enable colored syntax."
31 #define SHELL_HELP_VT100		"Toggle vt100 commands."
32 #define SHELL_HELP_VT100_OFF		"Disable vt100 commands."
33 #define SHELL_HELP_VT100_ON		"Enable vt100 commands."
34 #define SHELL_HELP_PROMPT		"Toggle prompt."
35 #define SHELL_HELP_PROMPT_OFF		"Disable prompt."
36 #define SHELL_HELP_PROMPT_ON		"Enable prompt."
37 #define SHELL_HELP_STATISTICS		"Shell statistics."
38 #define SHELL_HELP_STATISTICS_SHOW	\
39 	"Get shell statistics for the Logger module."
40 #define SHELL_HELP_STATISTICS_RESET	\
41 	"Reset shell statistics for the Logger module."
42 #define SHELL_HELP_RESIZE						\
43 	"Console gets terminal screen size or assumes default in case"	\
44 	" the readout fails. It must be executed after each terminal"	\
45 	" width change to ensure correct text display."
46 #define SHELL_HELP_RESIZE_DEFAULT				\
47 	"Assume 80 chars screen width and send this setting "	\
48 	"to the terminal."
49 #define SHELL_HELP_HISTORY	"Command history."
50 #define SHELL_HELP_ECHO		"Toggle shell echo."
51 #define SHELL_HELP_ECHO_ON	"Enable shell echo."
52 #define SHELL_HELP_ECHO_OFF	\
53 	"Disable shell echo. Editing keys and meta-keys are not handled"
54 
55 #define SHELL_HELP_SELECT	"Selects new root command. In order for the " \
56 	"command to be selected, it must meet the criteria:\n"		      \
57 	" - it is a static command\n"					      \
58 	" - it is not preceded by a dynamic command\n"			      \
59 	" - it accepts arguments\n"					      \
60 	"Return to the main command tree is done by pressing alt+r."
61 
62 #define SHELL_HELP_SHELL		"Useful, not Unix-like shell commands."
63 #define SHELL_HELP_HELP			"Prints help message."
64 
65 #define SHELL_MSG_UNKNOWN_PARAMETER	" unknown parameter: "
66 
67 #define SHELL_MAX_TERMINAL_SIZE		(250u)
68 
69 /* 10 == {esc, [, 2, 5, 0, ;, 2, 5, 0, '\0'} */
70 #define SHELL_CURSOR_POSITION_BUFFER	(10u)
71 
72 /* Function reads cursor position from terminal. */
cursor_position_get(const struct shell * sh,uint16_t * x,uint16_t * y)73 static int cursor_position_get(const struct shell *sh, uint16_t *x, uint16_t *y)
74 {
75 	uint16_t buff_idx = 0U;
76 	size_t cnt;
77 	char c = 0;
78 
79 	*x = 0U;
80 	*y = 0U;
81 
82 	memset(sh->ctx->temp_buff, 0, sizeof(sh->ctx->temp_buff));
83 
84 	/* escape code asking terminal about its size */
85 	static char const cmd_get_terminal_size[] = "\033[6n";
86 
87 	z_shell_raw_fprintf(sh->fprintf_ctx, cmd_get_terminal_size);
88 
89 	/* fprintf buffer needs to be flushed to start sending prepared
90 	 * escape code to the terminal.
91 	 */
92 	z_transport_buffer_flush(sh);
93 
94 	/* timeout for terminal response = ~1s */
95 	for (uint16_t i = 0; i < 1000; i++) {
96 		do {
97 			(void)sh->iface->api->read(sh->iface, &c,
98 						      sizeof(c), &cnt);
99 			if (cnt == 0) {
100 				k_busy_wait(1000);
101 				break;
102 			}
103 			if ((c != SHELL_VT100_ASCII_ESC) &&
104 			    (sh->ctx->temp_buff[0] !=
105 					    SHELL_VT100_ASCII_ESC)) {
106 				continue;
107 			}
108 
109 			if (c == 'R') { /* End of response from the terminal. */
110 				sh->ctx->temp_buff[buff_idx] = '\0';
111 				if (sh->ctx->temp_buff[1] != '[') {
112 					sh->ctx->temp_buff[0] = 0;
113 					return -EIO;
114 				}
115 
116 				/* Index start position in the buffer where 'y'
117 				 * is stored.
118 				 */
119 				buff_idx = 2U;
120 
121 				while (sh->ctx->temp_buff[buff_idx] != ';') {
122 					*y = *y * 10U +
123 					(sh->ctx->temp_buff[buff_idx++] -
124 									  '0');
125 					if (buff_idx >=
126 						CONFIG_SHELL_CMD_BUFF_SIZE) {
127 						return -EMSGSIZE;
128 					}
129 				}
130 
131 				if (++buff_idx >= CONFIG_SHELL_CMD_BUFF_SIZE) {
132 					return -EIO;
133 				}
134 
135 				while (sh->ctx->temp_buff[buff_idx]
136 							     != '\0') {
137 					*x = *x * 10U +
138 					(sh->ctx->temp_buff[buff_idx++] -
139 									   '0');
140 
141 					if (buff_idx >=
142 						CONFIG_SHELL_CMD_BUFF_SIZE) {
143 						return -EMSGSIZE;
144 					}
145 				}
146 				/* horizontal cursor position */
147 				if (*x > SHELL_MAX_TERMINAL_SIZE) {
148 					*x = SHELL_MAX_TERMINAL_SIZE;
149 				}
150 
151 				/* vertical cursor position */
152 				if (*y > SHELL_MAX_TERMINAL_SIZE) {
153 					*y = SHELL_MAX_TERMINAL_SIZE;
154 				}
155 
156 				sh->ctx->temp_buff[0] = 0;
157 
158 				return 0;
159 			}
160 
161 			sh->ctx->temp_buff[buff_idx] = c;
162 
163 			if (++buff_idx > SHELL_CURSOR_POSITION_BUFFER - 1) {
164 				sh->ctx->temp_buff[0] = 0;
165 				/* data_buf[SHELL_CURSOR_POSITION_BUFFER - 1]
166 				 * is reserved for '\0'
167 				 */
168 				return -ENOMEM;
169 			}
170 
171 		} while (cnt > 0);
172 	}
173 
174 	return -ETIMEDOUT;
175 }
176 
177 /* Function gets terminal width and height. */
terminal_size_get(const struct shell * sh)178 static int terminal_size_get(const struct shell *sh)
179 {
180 	uint16_t x; /* horizontal position */
181 	uint16_t y; /* vertical position */
182 	int ret_val = 0;
183 
184 	z_cursor_save(sh);
185 
186 	/* Assumption: terminal width and height < 999. */
187 	/* Move to last column. */
188 	z_shell_op_cursor_vert_move(sh, -SHELL_MAX_TERMINAL_SIZE);
189 	/* Move to last row. */
190 	z_shell_op_cursor_horiz_move(sh, SHELL_MAX_TERMINAL_SIZE);
191 
192 	if (cursor_position_get(sh, &x, &y) == 0) {
193 		sh->ctx->vt100_ctx.cons.terminal_wid = x;
194 		sh->ctx->vt100_ctx.cons.terminal_hei = y;
195 	} else {
196 		ret_val = -ENOTSUP;
197 	}
198 
199 	z_cursor_restore(sh);
200 	return ret_val;
201 }
202 
cmd_comment(const struct shell * sh,size_t argc,char ** argv)203 static int cmd_comment(const struct shell *sh, size_t argc, char **argv)
204 {
205 	ARG_UNUSED(sh);
206 	ARG_UNUSED(argc);
207 	ARG_UNUSED(argv);
208 
209 	return 0;
210 }
211 
cmd_clear(const struct shell * sh,size_t argc,char ** argv)212 static int cmd_clear(const struct shell *sh, size_t argc, char **argv)
213 {
214 	ARG_UNUSED(argv);
215 
216 	Z_SHELL_VT100_CMD(sh, SHELL_VT100_CURSORHOME);
217 	Z_SHELL_VT100_CMD(sh, SHELL_VT100_CLEARSCREEN);
218 
219 	return 0;
220 }
221 
cmd_backends(const struct shell * sh,size_t argc,char ** argv)222 static int cmd_backends(const struct shell *sh, size_t argc, char **argv)
223 {
224 	ARG_UNUSED(argc);
225 	ARG_UNUSED(argv);
226 
227 	uint16_t cnt = 0;
228 
229 	shell_print(sh, "Active shell backends:");
230 	STRUCT_SECTION_FOREACH(shell, obj) {
231 		shell_print(sh, "  %2d. :%s (%s)", cnt++, obj->ctx->prompt, obj->name);
232 	}
233 
234 	return 0;
235 }
236 
cmd_bacskpace_mode_backspace(const struct shell * sh,size_t argc,char ** argv)237 static int cmd_bacskpace_mode_backspace(const struct shell *sh, size_t argc,
238 					char **argv)
239 {
240 	ARG_UNUSED(argc);
241 	ARG_UNUSED(argv);
242 
243 	z_flag_mode_delete_set(sh, false);
244 
245 	return 0;
246 }
247 
cmd_bacskpace_mode_delete(const struct shell * sh,size_t argc,char ** argv)248 static int cmd_bacskpace_mode_delete(const struct shell *sh, size_t argc,
249 				      char **argv)
250 {
251 	ARG_UNUSED(argc);
252 	ARG_UNUSED(argv);
253 
254 	z_flag_mode_delete_set(sh, true);
255 
256 	return 0;
257 }
258 
cmd_colors_off(const struct shell * sh,size_t argc,char ** argv)259 static int cmd_colors_off(const struct shell *sh, size_t argc, char **argv)
260 {
261 	ARG_UNUSED(argc);
262 	ARG_UNUSED(argv);
263 
264 	z_flag_use_colors_set(sh, false);
265 
266 	return 0;
267 }
268 
cmd_colors_on(const struct shell * sh,size_t argc,char ** argv)269 static int cmd_colors_on(const struct shell *sh, size_t argc, char **argv)
270 {
271 	ARG_UNUSED(argv);
272 	ARG_UNUSED(argv);
273 
274 	z_flag_use_colors_set(sh, true);
275 
276 	return 0;
277 }
278 
cmd_vt100_off(const struct shell * sh,size_t argc,char ** argv)279 static int cmd_vt100_off(const struct shell *sh, size_t argc, char **argv)
280 {
281 	ARG_UNUSED(argc);
282 	ARG_UNUSED(argv);
283 
284 	z_flag_use_vt100_set(sh, false);
285 
286 	return 0;
287 }
288 
cmd_vt100_on(const struct shell * sh,size_t argc,char ** argv)289 static int cmd_vt100_on(const struct shell *sh, size_t argc, char **argv)
290 {
291 	ARG_UNUSED(argv);
292 	ARG_UNUSED(argv);
293 
294 	z_flag_use_vt100_set(sh, true);
295 
296 	return 0;
297 }
298 
cmd_prompt_off(const struct shell * sh,size_t argc,char ** argv)299 static int cmd_prompt_off(const struct shell *sh, size_t argc, char **argv)
300 {
301 	ARG_UNUSED(argc);
302 	ARG_UNUSED(argv);
303 
304 	shell_prompt_change(sh, "");
305 
306 	return 0;
307 }
308 
cmd_prompt_on(const struct shell * sh,size_t argc,char ** argv)309 static int cmd_prompt_on(const struct shell *sh, size_t argc, char **argv)
310 {
311 	ARG_UNUSED(argv);
312 	ARG_UNUSED(argv);
313 
314 	shell_prompt_change(sh, sh->default_prompt);
315 
316 	return 0;
317 }
318 
cmd_echo_off(const struct shell * sh,size_t argc,char ** argv)319 static int cmd_echo_off(const struct shell *sh, size_t argc, char **argv)
320 {
321 	ARG_UNUSED(argc);
322 	ARG_UNUSED(argv);
323 
324 	z_flag_echo_set(sh, false);
325 
326 	return 0;
327 }
328 
cmd_echo_on(const struct shell * sh,size_t argc,char ** argv)329 static int cmd_echo_on(const struct shell *sh, size_t argc, char **argv)
330 {
331 	ARG_UNUSED(argc);
332 	ARG_UNUSED(argv);
333 
334 	z_flag_echo_set(sh, true);
335 
336 	return 0;
337 }
338 
cmd_echo(const struct shell * sh,size_t argc,char ** argv)339 static int cmd_echo(const struct shell *sh, size_t argc, char **argv)
340 {
341 	if (argc == 2) {
342 		shell_error(sh, "%s:%s%s", argv[0],
343 			    SHELL_MSG_UNKNOWN_PARAMETER, argv[1]);
344 		return -EINVAL;
345 	}
346 
347 	shell_print(sh, "Echo status: %s",
348 		    z_flag_echo_get(sh) ? "on" : "off");
349 
350 	return 0;
351 }
352 
cmd_history(const struct shell * sh,size_t argc,char ** argv)353 static int cmd_history(const struct shell *sh, size_t argc, char **argv)
354 {
355 	ARG_UNUSED(argc);
356 	ARG_UNUSED(argv);
357 
358 	size_t i = 0;
359 	uint16_t len;
360 
361 	while (1) {
362 		z_shell_history_get(sh->history, true,
363 				    sh->ctx->temp_buff, &len);
364 
365 		if (len) {
366 			shell_print(sh, "[%3d] %s",
367 				    (int)i++, sh->ctx->temp_buff);
368 
369 		} else {
370 			break;
371 		}
372 	}
373 
374 	sh->ctx->temp_buff[0] = '\0';
375 
376 	return 0;
377 }
378 
cmd_shell_stats_show(const struct shell * sh,size_t argc,char ** argv)379 static int cmd_shell_stats_show(const struct shell *sh, size_t argc,
380 				char **argv)
381 {
382 	ARG_UNUSED(argc);
383 	ARG_UNUSED(argv);
384 
385 	shell_print(sh, "Lost logs: %lu", sh->stats->log_lost_cnt);
386 
387 	return 0;
388 }
389 
cmd_shell_stats_reset(const struct shell * sh,size_t argc,char ** argv)390 static int cmd_shell_stats_reset(const struct shell *sh,
391 				 size_t argc, char **argv)
392 {
393 	ARG_UNUSED(argc);
394 	ARG_UNUSED(argv);
395 
396 	sh->stats->log_lost_cnt = 0;
397 
398 	return 0;
399 }
400 
cmd_resize_default(const struct shell * sh,size_t argc,char ** argv)401 static int cmd_resize_default(const struct shell *sh,
402 			      size_t argc, char **argv)
403 {
404 	ARG_UNUSED(argc);
405 	ARG_UNUSED(argv);
406 
407 	Z_SHELL_VT100_CMD(sh, SHELL_VT100_SETCOL_80);
408 	sh->ctx->vt100_ctx.cons.terminal_wid = CONFIG_SHELL_DEFAULT_TERMINAL_WIDTH;
409 	sh->ctx->vt100_ctx.cons.terminal_hei = CONFIG_SHELL_DEFAULT_TERMINAL_HEIGHT;
410 
411 	return 0;
412 }
413 
cmd_resize(const struct shell * sh,size_t argc,char ** argv)414 static int cmd_resize(const struct shell *sh, size_t argc, char **argv)
415 {
416 	int err;
417 
418 	if (argc != 1) {
419 		shell_error(sh, "%s:%s%s", argv[0],
420 			    SHELL_MSG_UNKNOWN_PARAMETER, argv[1]);
421 		return -EINVAL;
422 	}
423 
424 	err = terminal_size_get(sh);
425 	if (err != 0) {
426 		sh->ctx->vt100_ctx.cons.terminal_wid =
427 				CONFIG_SHELL_DEFAULT_TERMINAL_WIDTH;
428 		sh->ctx->vt100_ctx.cons.terminal_hei =
429 				CONFIG_SHELL_DEFAULT_TERMINAL_HEIGHT;
430 		shell_warn(sh, "No response from the terminal, assumed 80x24"
431 			   " screen size");
432 		return -ENOEXEC;
433 	}
434 
435 	return 0;
436 }
437 
cmd_get_retval(const struct shell * sh,size_t argc,char ** argv)438 static int cmd_get_retval(const struct shell *sh, size_t argc, char **argv)
439 {
440 	ARG_UNUSED(argc);
441 	ARG_UNUSED(argv);
442 
443 	shell_print(sh, "%d", shell_get_return_value(sh));
444 	return 0;
445 }
446 
no_args(const struct shell_static_entry * entry)447 static bool no_args(const struct shell_static_entry *entry)
448 {
449 	return (entry->args.mandatory == 1) && (entry->args.optional == 0);
450 }
451 
cmd_select(const struct shell * sh,size_t argc,char ** argv)452 static int cmd_select(const struct shell *sh, size_t argc, char **argv)
453 {
454 	const struct shell_static_entry *candidate = NULL;
455 	struct shell_static_entry entry;
456 	size_t matching_argc;
457 
458 	argc--;
459 	argv = argv + 1;
460 	candidate = z_shell_get_last_command(sh->ctx->selected_cmd,
461 					     argc, (const char **)argv,
462 					     &matching_argc, &entry, true);
463 
464 	if ((candidate != NULL) && !no_args(candidate)
465 	    && (argc == matching_argc)) {
466 		sh->ctx->selected_cmd = candidate;
467 		return 0;
468 	}
469 
470 	shell_error(sh, "Cannot select command");
471 
472 	return -EINVAL;
473 }
474 
475 SHELL_STATIC_SUBCMD_SET_CREATE(m_sub_colors,
476 	SHELL_COND_CMD_ARG(CONFIG_SHELL_VT100_COMMANDS, off, NULL,
477 			   SHELL_HELP_COLORS_OFF, cmd_colors_off, 1, 0),
478 	SHELL_COND_CMD_ARG(CONFIG_SHELL_VT100_COMMANDS, on, NULL,
479 			   SHELL_HELP_COLORS_ON, cmd_colors_on, 1, 0),
480 	SHELL_SUBCMD_SET_END
481 );
482 
483 SHELL_STATIC_SUBCMD_SET_CREATE(m_sub_vt100,
484 	SHELL_COND_CMD_ARG(CONFIG_SHELL_VT100_COMMANDS, off, NULL,
485 			   SHELL_HELP_VT100_OFF, cmd_vt100_off, 1, 0),
486 	SHELL_COND_CMD_ARG(CONFIG_SHELL_VT100_COMMANDS, on, NULL,
487 			   SHELL_HELP_VT100_ON, cmd_vt100_on, 1, 0),
488 	SHELL_SUBCMD_SET_END
489 );
490 
491 SHELL_STATIC_SUBCMD_SET_CREATE(m_sub_prompt,
492 	SHELL_CMD_ARG(off, NULL, SHELL_HELP_PROMPT_OFF, cmd_prompt_off, 1, 0),
493 	SHELL_CMD_ARG(on, NULL, SHELL_HELP_PROMPT_ON, cmd_prompt_on, 1, 0),
494 	SHELL_SUBCMD_SET_END
495 );
496 
497 SHELL_STATIC_SUBCMD_SET_CREATE(m_sub_echo,
498 	SHELL_CMD_ARG(off, NULL, SHELL_HELP_ECHO_OFF, cmd_echo_off, 1, 0),
499 	SHELL_CMD_ARG(on, NULL, SHELL_HELP_ECHO_ON, cmd_echo_on, 1, 0),
500 	SHELL_SUBCMD_SET_END
501 );
502 
503 SHELL_STATIC_SUBCMD_SET_CREATE(m_sub_shell_stats,
504 	SHELL_CMD_ARG(reset, NULL, SHELL_HELP_STATISTICS_RESET,
505 			cmd_shell_stats_reset, 1, 0),
506 	SHELL_CMD_ARG(show, NULL, SHELL_HELP_STATISTICS_SHOW,
507 			cmd_shell_stats_show, 1, 0),
508 	SHELL_SUBCMD_SET_END
509 );
510 
511 SHELL_STATIC_SUBCMD_SET_CREATE(m_sub_backspace_mode,
512 	SHELL_CMD_ARG(backspace, NULL, SHELL_HELP_BACKSPACE_MODE_BACKSPACE,
513 			cmd_bacskpace_mode_backspace, 1, 0),
514 	SHELL_CMD_ARG(delete, NULL, SHELL_HELP_BACKSPACE_MODE_DELETE,
515 			cmd_bacskpace_mode_delete, 1, 0),
516 	SHELL_SUBCMD_SET_END
517 );
518 
519 SHELL_STATIC_SUBCMD_SET_CREATE(m_sub_shell,
520 	SHELL_CMD_ARG(backends, NULL, SHELL_HELP_BACKENDS, cmd_backends, 1, 0),
521 	SHELL_CMD(backspace_mode, &m_sub_backspace_mode,
522 			SHELL_HELP_BACKSPACE_MODE, NULL),
523 	SHELL_COND_CMD(CONFIG_SHELL_VT100_COMMANDS, colors, &m_sub_colors,
524 		       SHELL_HELP_COLORS, NULL),
525 	SHELL_COND_CMD(CONFIG_SHELL_VT100_COMMANDS, vt100, &m_sub_vt100,
526 		       SHELL_HELP_VT100, NULL),
527 	SHELL_CMD(prompt, &m_sub_prompt, SHELL_HELP_PROMPT, NULL),
528 	SHELL_CMD_ARG(echo, &m_sub_echo, SHELL_HELP_ECHO, cmd_echo, 1, 1),
529 	SHELL_COND_CMD(CONFIG_SHELL_STATS, stats, &m_sub_shell_stats,
530 			SHELL_HELP_STATISTICS, NULL),
531 	SHELL_SUBCMD_SET_END
532 );
533 
534 SHELL_STATIC_SUBCMD_SET_CREATE(m_sub_resize,
535 	SHELL_CMD_ARG(default, NULL, SHELL_HELP_RESIZE_DEFAULT,
536 			cmd_resize_default, 1, 0),
537 	SHELL_SUBCMD_SET_END
538 );
539 
540 SHELL_COND_CMD_REGISTER(CONFIG_SHELL_VT100_COMMANDS, rem, NULL,
541 				SHELL_HELP_COMMENT, cmd_comment);
542 SHELL_COND_CMD_ARG_REGISTER(CONFIG_SHELL_VT100_COMMANDS, clear, NULL,
543 			    SHELL_HELP_CLEAR, cmd_clear, 1, 0);
544 SHELL_CMD_REGISTER(shell, &m_sub_shell, SHELL_HELP_SHELL, NULL);
545 SHELL_COND_CMD_ARG_REGISTER(CONFIG_SHELL_HISTORY, history, NULL,
546 			SHELL_HELP_HISTORY, cmd_history, 1, 0);
547 SHELL_COND_CMD_ARG_REGISTER(CONFIG_SHELL_CMDS_RESIZE, resize, &m_sub_resize,
548 			SHELL_HELP_RESIZE, cmd_resize, 1, 1);
549 SHELL_COND_CMD_ARG_REGISTER(CONFIG_SHELL_CMDS_SELECT, select, NULL,
550 			    SHELL_HELP_SELECT, cmd_select, 2,
551 			    SHELL_OPT_ARG_CHECK_SKIP);
552 SHELL_COND_CMD_ARG_REGISTER(CONFIG_SHELL_CMDS_RETURN_VALUE, retval, NULL,
553 			    SHELL_HELP_RETVAL, cmd_get_retval, 1, 0);
554