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