1 /*
2 This file is part of UFFS, the Ultra-low-cost Flash File System.
3
4 Copyright (C) 2005-2009 Ricky Zheng <[email protected]>
5
6 UFFS is free software; you can redistribute it and/or modify it under
7 the GNU Library General Public License as published by the Free Software
8 Foundation; either version 2 of the License, or (at your option) any
9 later version.
10
11 UFFS is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 or GNU Library General Public License, as applicable, for more details.
15
16 You should have received a copy of the GNU General Public License
17 and GNU Library General Public License along with UFFS; if not, write
18 to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
20
21 As a special exception, if other files instantiate templates or use
22 macros or inline functions from this file, or you compile this file
23 and link it with other works to produce a work based on this file,
24 this file does not by itself cause the resulting work to be covered
25 by the GNU General Public License. However the source code for this
26 file must still be made available in accordance with section (3) of
27 the GNU General Public License v2.
28
29 This exception does not invalidate any other reasons why a work based
30 on this file might be covered by the GNU General Public License.
31 */
32
33 /**
34 * \file cmdline.c
35 * \brief command line test interface
36 * \author Ricky Zheng, created in 22th Feb, 2007
37 */
38
39 #include <string.h>
40 #include <stdio.h>
41 //#include <conio.h>
42 #include "uffs_config.h"
43 #include "cmdline.h"
44 #include "uffs/uffs_fs.h"
45
46 #define PROMPT "UFFS>"
47
48 #define PFX "cli : "
49 #define MSGLN(msg,...) uffs_Perror(UFFS_MSG_NORMAL, msg, ## __VA_ARGS__)
50 #define MSG(msg,...) uffs_PerrorRaw(UFFS_MSG_NORMAL, msg, ## __VA_ARGS__)
51
52 #define MAX_CLI_ARGS_BUF_LEN 120
53 #define MAX_CLI_ARGS_NUM 20
54 #define MAX_CLI_ENV_NUM 11 // '?', '0' - '9'
55
56
57 struct cli_arg {
58 int argc;
59 char *argv[MAX_CLI_ARGS_NUM];
60 char _buf[MAX_CLI_ARGS_BUF_LEN];
61 };
62
63 static BOOL m_exit = FALSE;
64 static BOOL m_abort = FALSE;
65 static struct cli_commandset *m_cmdset_head = NULL;
66
67 // Note: last command return code stored in env 0.
68 static int m_cli_envs[MAX_CLI_ENV_NUM] = {0}; // cli environment variables
69
70 static const struct cli_command * cli_find(const char *cmd);
71 static int cmd_help(int argc, char *argv[]);
72
73
74 #define FOR_EACH_CLI_CMD(set, cmd) \
75 for (set = m_cmdset_head; set && set->cmds; set = set->next) \
76 for (cmd = set->cmds; cmd && cmd->handler; cmd++)
77
78
79 /*** filter out leading and tailing spaces, discard comments
80 * return pointer to start of new command line
81 */
cli_process_line(char * p)82 static char * cli_process_line(char *p)
83 {
84 char *s;
85 char *x;
86
87 if (!p)
88 return NULL;
89
90 // skip leading spaces
91 while (p && (*p == ' ' || *p == '\t'))
92 p++;
93
94 for (s = x = p; *p; x++, p++) {
95 switch(*p) {
96 case '\\':
97 p++;
98 if (*p) {
99 switch(*p) {
100 case 'n':
101 *x = '\n';
102 break;
103 case 'r':
104 *x = '\r';
105 break;
106 case 't':
107 *x = '\t';
108 break;
109 case 'b':
110 *x = '\b';
111 break;
112 default:
113 if (*p >= '0' && *p <= '9')
114 *x = *p - '0';
115 else
116 *x = *p;
117 break;
118 }
119 }
120 break;
121 default:
122 if (*p == '\r' || *p == '\n' || *p == '#') *p = '\0';
123 *x = *p;
124 break;
125 }
126
127 if (*p == 0)
128 break;
129 }
130
131 // trim tailing spaces
132 p--;
133 while (p > s && (*p == ' ' || *p == '\t'))
134 *p-- = '\0';
135
136 return s;
137 }
138
cli_env_to_idx(char env)139 static int cli_env_to_idx(char env)
140 {
141 int idx = -1;
142
143 if (env >= '0' && env <= '9') {
144 idx = env - '0' + 1;
145 }
146 else if (env == '?') {
147 idx = 0;
148 }
149
150 return idx;
151 }
152
cli_env_set(char env,int val)153 int cli_env_set(char env, int val)
154 {
155 int idx = cli_env_to_idx(env);
156
157 if (idx >= 0) {
158 m_cli_envs[idx] = val;
159 return 0;
160 }
161 else
162 return -1;
163 }
164
cli_env_get(char env)165 int cli_env_get(char env)
166 {
167 int idx = cli_env_to_idx(env);
168
169 return idx >= 0 ? m_cli_envs[idx] : 0;
170 }
171
172 /** exec command <n> times:
173 * exec <n> <cmd> [...]
174 */
cmd_exec(int argc,char * argv[])175 static int cmd_exec(int argc, char *argv[])
176 {
177 int n = 0;
178 const struct cli_command *cmd;
179
180 CHK_ARGC(3, 0);
181
182 if (sscanf(argv[1], "%d", &n) != 1)
183 return CLI_INVALID_ARG;
184 if (n <= 0)
185 return CLI_INVALID_ARG;
186
187 cmd = cli_find(argv[2]);
188 if (cmd == NULL) {
189 MSG("Unknown command '%s'\n", argv[2]);
190 return -1;
191 }
192 else {
193 argv += 2;
194 while (n-- >= 0) {
195 if (cmd->handler(argc - 2, argv) != 0)
196 return -1;
197 }
198 }
199
200 return 0;
201 }
202
203 /**
204 * test expression
205 * test <a> <op> <b>
206 * for example:
207 * test 1 > 0 ==> return 0
208 * test 1 <= 0 ==> return -1
209 */
cmd_test(int argc,char * argv[])210 static int cmd_test(int argc, char *argv[])
211 {
212 int a, b;
213 char *op;
214 BOOL tst = FALSE;
215
216 CHK_ARGC(4, 4);
217
218 if (sscanf(argv[1], "%d", &a) != 1 ||
219 sscanf(argv[3], "%d", &b) != 1)
220 {
221 return CLI_INVALID_ARG;
222 }
223
224 op = argv[2];
225 if (!strcmp(op, ">")) {
226 tst = (a > b);
227 }
228 else if (!strcmp(op, "<")) {
229 tst = (a < b);
230 }
231 else if (!strcmp(op, "==")) {
232 tst = (a == b);
233 }
234 else if (!strcmp(op, ">=")) {
235 tst = (a >= b);
236 }
237 else if (!strcmp(op, "<=")) {
238 tst = (a <= b);
239 }
240 else if (!strcmp(op, "!=")) {
241 tst = (a != b);
242 }
243 else {
244 return CLI_INVALID_ARG;
245 }
246
247 return tst ? 0 : -1;
248 }
249
250 /** if last command failed (return != 0), run <cmd>
251 * ! <cmd>
252 */
cmd_failed(int argc,char * argv[])253 static int cmd_failed(int argc, char *argv[])
254 {
255 const struct cli_command *cmd;
256
257 CHK_ARGC(2, 0);
258
259 cmd = cli_find(argv[1]);
260 if (cmd == NULL) {
261 MSG("Unknown command '%s'\n", argv[1]);
262 return -1;
263 }
264 else {
265 argv++;
266 return (cli_env_get('?') == 0 ? 0 : cmd->handler(argc - 1, argv));
267 }
268 }
269
270 /** print messages
271 * echo [<arg> ...]
272 */
cmd_echo(int argc,char * argv[])273 static int cmd_echo(int argc, char *argv[])
274 {
275 int i;
276
277 for (i = 1; i < argc; i++) {
278 MSG("%s%s", i > 1 ? " " : "", argv[i]);
279 }
280 MSG("\n");
281
282 return 0;
283 }
284
285 /** set cli environment variable
286 * set <env> <value>
287 */
cmd_set(int argc,char * argv[])288 static int cmd_set(int argc, char *argv[])
289 {
290 int val;
291 int ret = -1;
292
293 CHK_ARGC(3, 0);
294
295 if (sscanf(argv[2], "%d", &val) == 1) {
296 ret = cli_env_set(argv[1][0], val);
297 }
298
299 return ret;
300 }
301
302 /** evaluation the expresstion, result to $1
303 * evl <value> <op> <value>
304 */
cmd_evl(int argc,char * argv[])305 static int cmd_evl(int argc, char *argv[])
306 {
307 int val1, val2, result = 0;
308 int ret = -1;
309
310 CHK_ARGC(4, 4);
311
312 if (sscanf(argv[1], "%d", &val1) == 1 &&
313 sscanf(argv[3], "%d", &val2) == 1) {
314 ret = 0;
315 switch(argv[2][0]) {
316 case '+':
317 result = val1 + val2;
318 break;
319 case '-':
320 result = val1 - val2;
321 break;
322 case '*':
323 result = val1 * val2;
324 break;
325 case '/':
326 if (val2 == 0)
327 ret = -1;
328 else
329 result = val1 / val2;
330 break;
331 case '%':
332 if (val2 == 0)
333 ret = -1;
334 else
335 result = val1 % val2;
336 break;
337 default:
338 ret = CLI_INVALID_ARG;
339 break;
340 }
341 }
342
343 if (ret == 0)
344 ret = cli_env_set('1', result);
345
346 return ret;
347 }
348
cmd_exit(int argc,char * argv[])349 static int cmd_exit(int argc, char *argv[])
350 {
351 m_exit = TRUE;
352 return 0;
353 }
354
355 /** Abort current script
356 * abort [...]
357 */
cmd_abort(int argc,char * argv[])358 static int cmd_abort(int argc, char *argv[])
359 {
360 if (argc > 1) {
361 cmd_echo(argc, argv);
362 }
363
364 m_abort = TRUE;
365
366 return 0;
367 }
368
369 /** run local file system's script
370 * script <filename>
371 */
cmd_script(int argc,char * argv[])372 static int cmd_script(int argc, char *argv[])
373 {
374 char line_buf[256];
375 char *p;
376 FILE *fp;
377 const char *name;
378 int ret = 0;
379 static int stack = 0;
380
381 CHK_ARGC(2, 0);
382
383 if (stack++ == 0)
384 m_abort = FALSE;
385
386 name = argv[1];
387 fp = fopen(name, "r");
388
389 if (fp) {
390 memset(line_buf, 0, sizeof(line_buf));
391 while (!m_abort && fgets(line_buf, sizeof(line_buf) - 1, fp)) {
392 p = line_buf + sizeof(line_buf) - 1;
393 while (*p == 0 && p > line_buf)
394 p--;
395 while ((*p == '\r' || *p == '\n') && p > line_buf) {
396 *p-- = 0;
397 }
398 p = cli_process_line(line_buf);
399 if (*p)
400 ret = cli_interpret(p);
401 memset(line_buf, 0, sizeof(line_buf));
402 }
403 fclose(fp);
404 }
405 else {
406 MSG("Can't open host script file '%s' for read\n", name);
407 ret = -1;
408 }
409
410 stack--;
411
412 return ret;
413 }
414
415
416
417 static const struct cli_command default_cmds[] =
418 {
419 { cmd_help, "help|?", "[<command>]", "show commands or help on one command" },
420 { cmd_exit, "exit", NULL, "exit command line" },
421 { cmd_exec, "*", "<n> <cmd> [...]>", "run <cmd> <n> times" },
422 { cmd_failed, "!", "<cmd> [...]", "run <cmd> if last command failed" },
423 { cmd_echo, "echo", "[...]", "print messages" },
424 { cmd_set, "set", "<env> <val>", "set env variable" },
425 { cmd_evl, "evl", "<val> <op> <val>", "evaluation expresstion" },
426 { cmd_test, "test", "<a> <op> <b>", "test expression: <a> <op> <b>" },
427 { cmd_script, "script", "<file>", "run host script <file>" },
428 { cmd_abort, "abort", NULL, "abort from the running script" },
429 { NULL, NULL, NULL, NULL }
430 };
431
432 static struct cli_commandset default_cmdset = {
433 default_cmds,
434 };
435
match_cmd(const char * src,int start,int end,const char * des)436 static BOOL match_cmd(const char *src, int start, int end, const char *des)
437 {
438 while (src[start] == ' ' && start < end)
439 start++;
440
441 while (src[end] == ' ' && start < end)
442 end--;
443
444 if ((int)strlen(des) == (end - start + 1)) {
445 if (memcmp(src + start, des, end - start + 1) == 0) {
446 return TRUE;
447 }
448 }
449
450 return FALSE;
451 }
452
check_cmd(const char * cmds,const char * cmd)453 static BOOL check_cmd(const char *cmds, const char *cmd)
454 {
455 int start, end;
456
457 for (start = end = 0; cmds[end] != 0 && cmds[end] != '|'; end++);
458
459 while (end > start) {
460 if (match_cmd(cmds, start, end - 1, cmd) == TRUE)
461 return TRUE;
462 if (cmds[end] == 0)
463 break;
464 if (cmds[end] == '|') {
465 end++;
466 for (start = end; cmds[end] != 0 && cmds[end] != '|'; end++);
467 }
468 }
469
470 return FALSE;
471 }
472
cli_find(const char * cmd)473 static const struct cli_command * cli_find(const char *cmd)
474 {
475 struct cli_commandset *work;
476 const struct cli_command *s;
477
478 FOR_EACH_CLI_CMD(work, s) {
479 if (check_cmd(s->cmd, cmd) == TRUE)
480 return s;
481 }
482
483 return NULL;
484 }
485
show_cmd_usage(const struct cli_command * cmd)486 static void show_cmd_usage(const struct cli_command *cmd)
487 {
488 MSG("%s: %s\n", cmd->cmd, cmd->descr);
489 MSG("Usage: %s %s\n", cmd->cmd, cmd->args ? cmd->args : "");
490 }
491
cmd_help(int argc,char * argv[])492 static int cmd_help(int argc, char *argv[])
493 {
494 const struct cli_command *cmd;
495 struct cli_commandset *cmdset;
496 int i, n;
497
498 if (argc < 2) {
499 MSG("Available commands:\n");
500 n = 0;
501 FOR_EACH_CLI_CMD(cmdset, cmd) {
502 MSG("%s", cmd->cmd);
503 for (i = strlen(cmd->cmd); i%10; i++, MSG(" "));
504 if ((++n % 5) == 0) MSG("\n");
505 }
506 MSG("\n");
507 }
508 else {
509 cmd = cli_find(argv[1]);
510 if (cmd == NULL) {
511 MSG("No such command\n");
512 return -1;
513 }
514 else {
515 show_cmd_usage(cmd);
516 }
517 }
518
519 return 0;
520 }
521
cli_parse_args(const char * cmd,struct cli_arg * arg)522 static void cli_parse_args(const char *cmd, struct cli_arg *arg)
523 {
524 char *p;
525 int val;
526
527 if (arg) {
528 arg->argc = 0;
529 if (cmd) {
530 p = arg->_buf;
531 while (*cmd && arg->argc < MAX_CLI_ARGS_NUM && (p - arg->_buf < MAX_CLI_ARGS_BUF_LEN)) {
532 while(*cmd && (*cmd == ' ' || *cmd == '\t'))
533 cmd++;
534
535 arg->argv[arg->argc] = p;
536 while (*cmd && (*cmd != ' ' && *cmd != '\t') && (p - arg->_buf < MAX_CLI_ARGS_BUF_LEN)) {
537 if (*cmd == '$') {
538 // command env replacement
539 cmd++;
540 val = cli_env_get(*cmd++);
541 if (p - arg->_buf < MAX_CLI_ARGS_BUF_LEN - 12) { // 12 is long enough for 32bit 'int'
542 p += sprintf(p, "%d", val & 0xFFFFFFFF);
543 }
544 }
545 else
546 *p++ = *cmd++;
547 }
548 *p++ = '\0';
549
550 if (*(arg->argv[arg->argc]) == '\0')
551 break;
552 arg->argc++;
553 }
554 }
555 }
556 }
557
cli_interpret(const char * line)558 int cli_interpret(const char *line)
559 {
560 struct cli_arg arg = {0};
561 const struct cli_command *cmd;
562 int ret = -1;
563
564 cli_parse_args(line, &arg);
565
566 if (arg.argc > 0) {
567 cmd = cli_find(arg.argv[0]);
568 if (cmd == NULL) {
569 MSG("Unknown command '%s'\n", arg.argv[0]);
570 }
571 else {
572 ret = cmd->handler(arg.argc, arg.argv);
573 if (ret == CLI_INVALID_ARG) {
574 MSG("\n");
575 show_cmd_usage(cmd);
576 }
577 }
578 }
579
580 cli_env_set('?', ret); // $? = last command return code
581
582 return ret;
583 }
584
cli_add_commandset(struct cli_commandset * set)585 void cli_add_commandset(struct cli_commandset *set)
586 {
587 if (set) {
588 set->next = m_cmdset_head;
589 m_cmdset_head = set;
590 }
591 }
592
cli_main_entry()593 void cli_main_entry()
594 {
595 char line[80];
596 int linelen = 0;
597 char *p;
598
599 MSG("$ ");
600 cli_add_commandset(&default_cmdset);
601
602 while (!m_exit) {
603 char ch;
604 if (linelen >= sizeof(line))
605 continue;
606 ch = getc(stdin);
607 switch (ch) {
608 case 8:
609 case 127:
610 if (linelen > 0) {
611 --linelen;
612 MSG("\x08 \x08");
613 }
614 break;
615
616 case '\r':
617 case '\n':
618 //MSG("\r\n");
619 if (linelen > 0) {
620 line[linelen] = 0;
621 p = cli_process_line(line);
622 if (*p)
623 cli_interpret(p);
624 linelen = 0;
625 }
626 MSG("$ ");
627 break;
628
629 case 21:
630 while (linelen > 0) {
631 --linelen;
632 MSG("\x08 \x08");
633 }
634 break;
635
636 default:
637 if (ch >= ' ' && ch < 127 && linelen < sizeof(line) - 1) {
638 line[linelen++] = ch;
639 //MSG("%c", ch);
640 }
641 }
642 }
643 }
644