blob: 94d14c515417a5a2e0b5be4356dc38acb03915dd [file] [log] [blame]
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001/* $Id$ */
2/*
3 * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com)
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19
20#include <pjlib-util/cli_imp.h>
21#include <pj/assert.h>
22#include <pj/errno.h>
23#include <pj/except.h>
24#include <pj/hash.h>
25#include <pj/os.h>
26#include <pj/pool.h>
27#include <pj/string.h>
28#include <pjlib-util/errno.h>
29#include <pjlib-util/scanner.h>
30#include <pjlib-util/xml.h>
31
32#define CMD_HASH_TABLE_SIZE 63 /* Hash table size */
33
34#define CLI_CMD_CHANGE_LOG 30000
35#define CLI_CMD_EXIT 30001
36
37#define MAX_CMD_HASH_NAME_LENGTH PJ_CLI_MAX_CMDBUF
38#define MAX_CMD_ID_LENGTH 16
39
40#if 1
41 /* Enable some tracing */
42 #define THIS_FILE "cli.c"
43 #define TRACE_(arg) PJ_LOG(3,arg)
44#else
45 #define TRACE_(arg)
46#endif
47
48/**
49 * This structure describes the full specification of a CLI command. A CLI
50 * command mainly consists of the name of the command, zero or more arguments,
51 * and a callback function to be called to execute the command.
52 *
53 * Application can create this specification by forming an XML document and
54 * calling pj_cli_add_cmd_from_xml() to instantiate the spec. A sample XML
55 * document containing a command spec is as follows:
56 *
57 \verbatim
58 <CMD name='makecall' id='101' sc='m,mc' desc='Make outgoing call'>
59 <ARGS>
60 <ARG name='target' type='text' desc='The destination'/>
61 </ARGS>
62 </CMD>
63 \endverbatim
64 */
65struct pj_cli_cmd_spec
66{
67 /**
68 * To make list of child cmds.
69 */
70 PJ_DECL_LIST_MEMBER(struct pj_cli_cmd_spec);
71
72 /**
73 * Command ID assigned to this command by the application during command
74 * creation. If this value is PJ_CLI_CMD_ID_GROUP (-2), then this is
75 * a command group and it can't be executed.
76 */
77 pj_cli_cmd_id id;
78
79 /**
80 * The command name.
81 */
82 pj_str_t name;
83
84 /**
85 * The full description of the command.
86 */
87 pj_str_t desc;
88
89 /**
90 * Number of optional shortcuts
91 */
92 unsigned sc_cnt;
93
94 /**
95 * Optional array of shortcuts, if any. Shortcut is a short name version
96 * of the command. If the command doesn't have any shortcuts, this
97 * will be initialized to NULL.
98 */
99 pj_str_t *sc;
100
101 /**
102 * The command handler, to be executed when a command matching this command
103 * specification is invoked by the end user. The value may be NULL if this
104 * is a command group.
105 */
106 pj_cli_cmd_handler handler;
107
108 /**
109 * Number of arguments.
110 */
111 unsigned arg_cnt;
112
113 /**
114 * Array of arguments.
115 */
116 pj_cli_arg_spec *arg;
117
118 /**
119 * Child commands, if any. A command will only have subcommands if it is
120 * a group. If the command doesn't have subcommands, this field will be
121 * initialized with NULL.
122 */
123 pj_cli_cmd_spec *sub_cmd;
124};
125
126struct pj_cli_t
127{
128 pj_pool_t *pool; /* Pool to allocate memory from */
129 pj_cli_cfg cfg; /* CLI configuration */
130 pj_cli_cmd_spec root; /* Root of command tree structure */
131 pj_cli_front_end fe_head; /* List of front-ends */
132 pj_hash_table_t *cmd_name_hash; /* Command name hash table, this will
133 include the command name and shortcut
134 as hash key */
135 pj_hash_table_t *cmd_id_hash; /* Command id hash table */
136
137 pj_bool_t is_quitting;
138 pj_bool_t is_restarting;
139};
140
141/**
142 * Reserved command id constants.
143 */
144typedef enum pj_cli_std_cmd_id
145{
146 /**
147 * Constant to indicate an invalid command id.
148 */
149 PJ_CLI_INVALID_CMD_ID = -1,
150
151 /**
152 * A special command id to indicate that a command id denotes
153 * a command group.
154 */
155 PJ_CLI_CMD_ID_GROUP = -2
156
157} pj_cli_std_cmd_id;
158
159/**
160 * This describes the type of an argument (pj_cli_arg_spec).
161 */
162typedef enum pj_cli_arg_type
163{
164 /**
165 * Unformatted string.
166 */
167 PJ_CLI_ARG_TEXT,
168
169 /**
170 * An integral number.
171 */
172 PJ_CLI_ARG_INT,
173
174 /**
175 * Choice type
176 */
177 PJ_CLI_ARG_CHOICE
178
179} pj_cli_arg_type;
180
181struct arg_type
182{
183 const pj_str_t msg;
184} arg_type[3] =
185{
186 {{"Text", 4}},
187 {{"Int", 3}},
188 {{"Choice", 6}}
189};
190
191/**
192 * This structure describe the specification of a command argument.
193 */
194struct pj_cli_arg_spec
195{
196 /**
197 * Argument id
198 */
199 pj_cli_arg_id id;
200
201 /**
202 * Argument name.
203 */
204 pj_str_t name;
205
206 /**
207 * Helpful description of the argument. This text will be used when
208 * displaying help texts for the command/argument.
209 */
210 pj_str_t desc;
211
212 /**
213 * Argument type, which will be used for rendering the argument and
214 * to perform basic validation against an input value.
215 */
216 pj_cli_arg_type type;
217
218 /**
219 * Argument status
220 */
221 pj_bool_t optional;
222
223 /**
224 * Validate choice values
225 */
226 pj_bool_t validate;
227
228 /**
229 * Static Choice Values count
230 */
231 unsigned stat_choice_cnt;
232
233 /**
234 * Static Choice Values
235 */
236 pj_cli_arg_choice_val *stat_choice_val;
237
238 /**
239 * Argument callback to get the valid values
240 */
241 pj_cli_get_dyn_choice get_dyn_choice;
242
243};
244
245/**
246 * This describe the parse mode of the command line
247 */
248typedef enum pj_cli_parse_mode {
249 PARSE_NONE,
250 PARSE_COMPLETION, /* Complete the command line */
251 PARSE_NEXT_AVAIL, /* Find the next available command line */
252 PARSE_EXEC /* Exec the command line */
253} pj_cli_parse_mode;
254
255/**
256 * This is used to get the matched command/argument from the
257 * command/argument structure.
258 *
259 * @param sess The session on which the command is execute on.
260 * @param cmd The active command.
261 * @param cmd_val The command value to match.
262 * @param argc The number of argument that the
263 * current active command have.
264 * @param pool The memory pool to allocate memory.
265 * @param get_cmd Set true to search matching command from sub command.
266 * @param parse_mode The parse mode.
267 * @param p_cmd The command that mathes the command value.
268 * @param info The output information containing any hints for
269 * matching command/arg.
270 * @return This function return the status of the
271 * matching process.Please see the return value
272 * of pj_cli_sess_parse() for possible return values.
273 */
274static pj_status_t get_available_cmds(pj_cli_sess *sess,
275 pj_cli_cmd_spec *cmd,
276 pj_str_t *cmd_val,
277 unsigned argc,
278 pj_pool_t *pool,
279 pj_bool_t get_cmd,
280 pj_cli_parse_mode parse_mode,
281 pj_cli_cmd_spec **p_cmd,
282 pj_cli_exec_info *info);
283
284PJ_DEF(pj_cli_cmd_id) pj_cli_get_cmd_id(const pj_cli_cmd_spec *cmd)
285{
286 return cmd->id;
287}
288
289PJ_DEF(void) pj_cli_cfg_default(pj_cli_cfg *param)
290{
291 pj_assert(param);
292 pj_bzero(param, sizeof(*param));
293 pj_strset2(&param->name, "");
294}
295
296PJ_DEF(void) pj_cli_exec_info_default(pj_cli_exec_info *param)
297{
298 pj_assert(param);
299 pj_bzero(param, sizeof(*param));
300 param->err_pos = -1;
301 param->cmd_id = PJ_CLI_INVALID_CMD_ID;
302 param->cmd_ret = PJ_SUCCESS;
303}
304
305PJ_DEF(void) pj_cli_write_log(pj_cli_t *cli,
306 int level,
307 const char *buffer,
308 int len)
309{
310 struct pj_cli_front_end *fe;
311
312 pj_assert(cli);
313
314 fe = cli->fe_head.next;
315 while (fe != &cli->fe_head) {
316 if (fe->op && fe->op->on_write_log)
317 (*fe->op->on_write_log)(fe, level, buffer, len);
318 fe = fe->next;
319 }
320}
321
322PJ_DEF(void) pj_cli_sess_write_msg(pj_cli_sess *sess,
323 const char *buffer,
324 pj_size_t len)
325{
326 struct pj_cli_front_end *fe;
327
328 pj_assert(sess);
329
330 fe = sess->fe;
331 if (fe->op && fe->op->on_write_log)
332 (*fe->op->on_write_log)(fe, 0, buffer, len);
333}
334
335/* Command handler */
336static pj_status_t cmd_handler(pj_cli_cmd_val *cval)
337{
338 unsigned level;
339
340 switch(cval->cmd->id) {
341 case CLI_CMD_CHANGE_LOG:
342 level = pj_strtoul(&cval->argv[1]);
343 if (!level && cval->argv[1].slen > 0 && (cval->argv[1].ptr[0] < '0' ||
344 cval->argv[1].ptr[0] > '9'))
345 {
346 return PJ_CLI_EINVARG;
347 }
348 cval->sess->log_level = level;
349 return PJ_SUCCESS;
350 case CLI_CMD_EXIT:
351 pj_cli_sess_end_session(cval->sess);
352 return PJ_CLI_EEXIT;
353 default:
354 return PJ_SUCCESS;
355 }
356}
357
358PJ_DEF(pj_status_t) pj_cli_create(pj_cli_cfg *cfg,
359 pj_cli_t **p_cli)
360{
361 pj_pool_t *pool;
362 pj_cli_t *cli;
363 unsigned i;
364
365 /* This is an example of the command structure */
366 char* cmd_xmls[] = {
367 "<CMD name='log' id='30000' sc='' desc='Change log level'>"
368 " <ARG name='level' type='int' optional='0' desc='Log level'/>"
369 "</CMD>",
370 "<CMD name='exit' id='30001' sc='' desc='Exit session'>"
371 "</CMD>",
372 };
373
374 PJ_ASSERT_RETURN(cfg && cfg->pf && p_cli, PJ_EINVAL);
375
376 pool = pj_pool_create(cfg->pf, "cli", PJ_CLI_POOL_SIZE,
377 PJ_CLI_POOL_INC, NULL);
378 if (!pool)
379 return PJ_ENOMEM;
380 cli = PJ_POOL_ZALLOC_T(pool, struct pj_cli_t);
381
382 pj_memcpy(&cli->cfg, cfg, sizeof(*cfg));
383 cli->pool = pool;
384 pj_list_init(&cli->fe_head);
385
386 cli->cmd_name_hash = pj_hash_create(pool, CMD_HASH_TABLE_SIZE);
387 cli->cmd_id_hash = pj_hash_create(pool, CMD_HASH_TABLE_SIZE);
388
389 cli->root.sub_cmd = PJ_POOL_ZALLOC_T(pool, pj_cli_cmd_spec);
390 pj_list_init(cli->root.sub_cmd);
391
392 /* Register some standard commands. */
393 for (i = 0; i < sizeof(cmd_xmls)/sizeof(cmd_xmls[0]); i++) {
394 pj_str_t xml = pj_str(cmd_xmls[i]);
395
396 if (pj_cli_add_cmd_from_xml(cli, NULL, &xml,
397 &cmd_handler, NULL, NULL) != PJ_SUCCESS)
398 {
399 TRACE_((THIS_FILE, "Failed to add command #%d", i));
400 }
401 }
402
403 *p_cli = cli;
404
405 return PJ_SUCCESS;
406}
407
408PJ_DEF(pj_cli_cfg*) pj_cli_get_param(pj_cli_t *cli)
409{
410 PJ_ASSERT_RETURN(cli, NULL);
411
412 return &cli->cfg;
413}
414
415PJ_DEF(void) pj_cli_register_front_end(pj_cli_t *cli,
416 pj_cli_front_end *fe)
417{
418 pj_list_push_back(&cli->fe_head, fe);
419}
420
421PJ_DEF(void) pj_cli_quit(pj_cli_t *cli, pj_cli_sess *req,
422 pj_bool_t restart)
423{
424 pj_cli_front_end *fe;
425
426 pj_assert(cli);
427 if (cli->is_quitting)
428 return;
429
430 cli->is_quitting = PJ_TRUE;
431 cli->is_restarting = restart;
432
433 fe = cli->fe_head.next;
434 while (fe != &cli->fe_head) {
435 if (fe->op && fe->op->on_quit)
436 (*fe->op->on_quit)(fe, req);
437 fe = fe->next;
438 }
439}
440
441PJ_DEF(pj_bool_t) pj_cli_is_quitting(pj_cli_t *cli)
442{
443 PJ_ASSERT_RETURN(cli, PJ_FALSE);
444
445 return cli->is_quitting;
446}
447
448PJ_DEF(pj_bool_t) pj_cli_is_restarting(pj_cli_t *cli)
449{
450 PJ_ASSERT_RETURN(cli, PJ_FALSE);
451
452 return cli->is_restarting;
453}
454
455PJ_DEF(void) pj_cli_destroy(pj_cli_t *cli)
456{
457 pj_cli_front_end *fe;
458
459 if (!cli)
460 return;
461
462 if (!pj_cli_is_quitting(cli))
463 pj_cli_quit(cli, NULL, PJ_FALSE);
464
465 fe = cli->fe_head.next;
466 while (fe != &cli->fe_head) {
467 pj_list_erase(fe);
468 if (fe->op && fe->op->on_destroy)
469 (*fe->op->on_destroy)(fe);
470
471 fe = cli->fe_head.next;
472 }
473 cli->is_quitting = PJ_FALSE;
474 pj_pool_release(cli->pool);
475}
476
477PJ_DEF(void) pj_cli_sess_end_session(pj_cli_sess *sess)
478{
479 pj_assert(sess);
480
481 if (sess->op && sess->op->destroy)
482 (*sess->op->destroy)(sess);
483}
484
485/* Syntax error handler for parser. */
486static void on_syntax_error(pj_scanner *scanner)
487{
488 PJ_UNUSED_ARG(scanner);
489 PJ_THROW(PJ_EINVAL);
490}
491
492/* Get the command from the command hash */
493static pj_cli_cmd_spec *get_cmd_name(const pj_cli_t *cli,
494 const pj_cli_cmd_spec *group,
495 const pj_str_t *cmd)
496{
497 pj_str_t cmd_val;
498 char cmd_ptr[MAX_CMD_HASH_NAME_LENGTH];
499
500 cmd_val.ptr = cmd_ptr;
501 cmd_val.slen = 0;
502
503 if (group) {
504 char cmd_str[MAX_CMD_ID_LENGTH];
505 pj_ansi_sprintf(cmd_str, "%d", group->id);
506 pj_strcat2(&cmd_val, cmd_str);
507 }
508 pj_strcat(&cmd_val, cmd);
509 return (pj_cli_cmd_spec *)pj_hash_get(cli->cmd_name_hash, cmd_val.ptr,
510 (unsigned)cmd_val.slen, NULL);
511}
512
513/* Add command to the command hash */
514static void add_cmd_name(pj_cli_t *cli, pj_cli_cmd_spec *group,
515 pj_cli_cmd_spec *cmd, pj_str_t *cmd_name)
516{
517 pj_str_t cmd_val;
518 pj_str_t add_cmd;
519 char cmd_ptr[MAX_CMD_HASH_NAME_LENGTH];
520
521 cmd_val.ptr = cmd_ptr;
522 cmd_val.slen = 0;
523
524 if (group) {
525 char cmd_str[MAX_CMD_ID_LENGTH];
526 pj_ansi_sprintf(cmd_str, "%d", group->id);
527 pj_strcat2(&cmd_val, cmd_str);
528 }
529 pj_strcat(&cmd_val, cmd_name);
530 pj_strdup(cli->pool, &add_cmd, &cmd_val);
531
532 pj_hash_set(cli->pool, cli->cmd_name_hash, cmd_val.ptr,
533 (unsigned)cmd_val.slen, 0, cmd);
534}
535
536/**
537 * This method is to parse and add the choice type
538 * argument values to command structure.
539 **/
540static pj_status_t add_choice_node(pj_cli_t *cli,
541 pj_xml_node *xml_node,
542 pj_cli_arg_spec *arg,
543 pj_cli_get_dyn_choice get_choice)
544{
545 pj_xml_node *choice_node;
546 pj_xml_node *sub_node;
547 pj_cli_arg_choice_val choice_values[PJ_CLI_MAX_CHOICE_VAL];
548 pj_status_t status = PJ_SUCCESS;
549
550 sub_node = xml_node;
551 arg->type = PJ_CLI_ARG_CHOICE;
552 arg->get_dyn_choice = get_choice;
553
554 choice_node = sub_node->node_head.next;
555 while (choice_node != (pj_xml_node*)&sub_node->node_head) {
556 pj_xml_attr *choice_attr;
557 unsigned *stat_cnt = &arg->stat_choice_cnt;
558 pj_cli_arg_choice_val *choice_val = &choice_values[*stat_cnt];
559 pj_bzero(choice_val, sizeof(*choice_val));
560
561 choice_attr = choice_node->attr_head.next;
562 while (choice_attr != &choice_node->attr_head) {
563 if (!pj_stricmp2(&choice_attr->name, "value")) {
564 pj_strassign(&choice_val->value, &choice_attr->value);
565 } else if (!pj_stricmp2(&choice_attr->name, "desc")) {
566 pj_strassign(&choice_val->desc, &choice_attr->value);
567 }
568 choice_attr = choice_attr->next;
569 }
570 if (++(*stat_cnt) >= PJ_CLI_MAX_CHOICE_VAL)
571 break;
572 choice_node = choice_node->next;
573 }
574 if (arg->stat_choice_cnt > 0) {
575 unsigned i;
576
577 arg->stat_choice_val = (pj_cli_arg_choice_val *)
578 pj_pool_zalloc(cli->pool,
579 arg->stat_choice_cnt *
580 sizeof(pj_cli_arg_choice_val));
581
582 for (i = 0; i < arg->stat_choice_cnt; i++) {
583 pj_strdup(cli->pool, &arg->stat_choice_val[i].value,
584 &choice_values[i].value);
585 pj_strdup(cli->pool, &arg->stat_choice_val[i].desc,
586 &choice_values[i].desc);
587 }
588 }
589 return status;
590}
591
592/**
593 * This method is to parse and add the argument attribute to command structure.
594 **/
595static pj_status_t add_arg_node(pj_cli_t *cli,
596 pj_xml_node *xml_node,
597 pj_cli_cmd_spec *cmd,
598 pj_cli_arg_spec *arg,
599 pj_cli_get_dyn_choice get_choice)
600{
601 pj_xml_attr *attr;
602 pj_status_t status = PJ_SUCCESS;
603 pj_xml_node *sub_node = xml_node;
604
605 if (cmd->arg_cnt >= PJ_CLI_MAX_ARGS)
606 return PJ_CLI_ETOOMANYARGS;
607
608 pj_bzero(arg, sizeof(*arg));
609 attr = sub_node->attr_head.next;
610 arg->optional = PJ_FALSE;
611 arg->validate = PJ_TRUE;
612 while (attr != &sub_node->attr_head) {
613 if (!pj_stricmp2(&attr->name, "name")) {
614 pj_strassign(&arg->name, &attr->value);
615 } else if (!pj_stricmp2(&attr->name, "id")) {
616 arg->id = pj_strtol(&attr->value);
617 } else if (!pj_stricmp2(&attr->name, "type")) {
618 if (!pj_stricmp2(&attr->value, "text")) {
619 arg->type = PJ_CLI_ARG_TEXT;
620 } else if (!pj_stricmp2(&attr->value, "int")) {
621 arg->type = PJ_CLI_ARG_INT;
622 } else if (!pj_stricmp2(&attr->value, "choice")) {
623 /* Get choice value */
624 add_choice_node(cli, xml_node, arg, get_choice);
625 }
626 } else if (!pj_stricmp2(&attr->name, "desc")) {
627 pj_strassign(&arg->desc, &attr->value);
628 } else if (!pj_stricmp2(&attr->name, "optional")) {
629 if (!pj_strcmp2(&attr->value, "1")) {
630 arg->optional = PJ_TRUE;
631 }
632 } else if (!pj_stricmp2(&attr->name, "validate")) {
633 if (!pj_strcmp2(&attr->value, "1")) {
634 arg->validate = PJ_TRUE;
635 } else {
636 arg->validate = PJ_FALSE;
637 }
638 }
639 attr = attr->next;
640 }
641 cmd->arg_cnt++;
642 return status;
643}
644
645/**
646 * This method is to parse and add the command attribute to command structure.
647 **/
648static pj_status_t add_cmd_node(pj_cli_t *cli,
649 pj_cli_cmd_spec *group,
650 pj_xml_node *xml_node,
651 pj_cli_cmd_handler handler,
652 pj_cli_cmd_spec **p_cmd,
653 pj_cli_get_dyn_choice get_choice)
654{
655 pj_xml_node *root = xml_node;
656 pj_xml_attr *attr;
657 pj_xml_node *sub_node;
658 pj_cli_cmd_spec *cmd;
659 pj_cli_arg_spec args[PJ_CLI_MAX_ARGS];
660 pj_str_t sc[PJ_CLI_MAX_SHORTCUTS];
661 pj_status_t status = PJ_SUCCESS;
662
663 if (pj_stricmp2(&root->name, "CMD"))
664 return PJ_EINVAL;
665
666 /* Initialize the command spec */
667 cmd = PJ_POOL_ZALLOC_T(cli->pool, struct pj_cli_cmd_spec);
668
669 /* Get the command attributes */
670 attr = root->attr_head.next;
671 while (attr != &root->attr_head) {
672 if (!pj_stricmp2(&attr->name, "name")) {
673 pj_strltrim(&attr->value);
674 if (!attr->value.slen ||
675 (get_cmd_name(cli, group, &attr->value)))
676 {
677 return PJ_CLI_EBADNAME;
678 }
679 pj_strdup(cli->pool, &cmd->name, &attr->value);
680 } else if (!pj_stricmp2(&attr->name, "id")) {
681 pj_bool_t is_valid = PJ_FALSE;
682 if (attr->value.slen) {
683 pj_cli_cmd_id cmd_id = pj_strtol(&attr->value);
684 if (!pj_hash_get(cli->cmd_id_hash, &cmd_id,
685 sizeof(pj_cli_cmd_id), NULL))
686 is_valid = PJ_TRUE;
687 }
688 if (!is_valid)
689 return PJ_CLI_EBADID;
690 cmd->id = (pj_cli_cmd_id)pj_strtol(&attr->value);
691 } else if (!pj_stricmp2(&attr->name, "sc")) {
692 pj_scanner scanner;
693 pj_str_t str;
694
695 PJ_USE_EXCEPTION;
696
697 pj_scan_init(&scanner, attr->value.ptr, attr->value.slen,
698 PJ_SCAN_AUTOSKIP_WS, &on_syntax_error);
699
700 PJ_TRY {
701 while (!pj_scan_is_eof(&scanner)) {
702 pj_scan_get_until_ch(&scanner, ',', &str);
703 pj_strrtrim(&str);
704 if (!pj_scan_is_eof(&scanner))
705 pj_scan_advance_n(&scanner, 1, PJ_TRUE);
706 if (!str.slen)
707 continue;
708
709 if (cmd->sc_cnt >= PJ_CLI_MAX_SHORTCUTS) {
710 PJ_THROW(PJ_CLI_ETOOMANYARGS);
711 }
712 /* Check whether the shortcuts are already used */
713 if (get_cmd_name(cli, &cli->root, &str)) {
714 PJ_THROW(PJ_CLI_EBADNAME);
715 }
716
717 pj_strassign(&sc[cmd->sc_cnt++], &str);
718 }
719 }
720 PJ_CATCH_ANY {
721 pj_scan_fini(&scanner);
722 return (PJ_GET_EXCEPTION());
723 }
724 PJ_END;
725
726 } else if (!pj_stricmp2(&attr->name, "desc")) {
727 pj_strdup(cli->pool, &cmd->desc, &attr->value);
728 }
729 attr = attr->next;
730 }
731
732 /* Get the command childs/arguments */
733 sub_node = root->node_head.next;
734 while (sub_node != (pj_xml_node*)&root->node_head) {
735 if (!pj_stricmp2(&sub_node->name, "CMD")) {
736 status = add_cmd_node(cli, cmd, sub_node, handler, NULL,
737 get_choice);
738 if (status != PJ_SUCCESS)
739 return status;
740 } else if (!pj_stricmp2(&sub_node->name, "ARG")) {
741 /* Get argument attribute */
742 status = add_arg_node(cli, sub_node,
743 cmd, &args[cmd->arg_cnt],
744 get_choice);
745
746 if (status != PJ_SUCCESS)
747 return status;
748 }
749 sub_node = sub_node->next;
750 }
751
752 if (!cmd->name.slen)
753 return PJ_CLI_EBADNAME;
754
755 if (!cmd->id)
756 return PJ_CLI_EBADID;
757
758 if (cmd->arg_cnt) {
759 unsigned i;
760
761 cmd->arg = (pj_cli_arg_spec *)pj_pool_zalloc(cli->pool, cmd->arg_cnt *
762 sizeof(pj_cli_arg_spec));
763
764 for (i = 0; i < cmd->arg_cnt; i++) {
765 pj_strdup(cli->pool, &cmd->arg[i].name, &args[i].name);
766 pj_strdup(cli->pool, &cmd->arg[i].desc, &args[i].desc);
767 cmd->arg[i].id = args[i].id;
768 cmd->arg[i].type = args[i].type;
769 cmd->arg[i].optional = args[i].optional;
770 cmd->arg[i].validate = args[i].validate;
771 cmd->arg[i].get_dyn_choice = args[i].get_dyn_choice;
772 cmd->arg[i].stat_choice_cnt = args[i].stat_choice_cnt;
773 cmd->arg[i].stat_choice_val = args[i].stat_choice_val;
774 }
775 }
776
777 if (cmd->sc_cnt) {
778 unsigned i;
779
780 cmd->sc = (pj_str_t *)pj_pool_zalloc(cli->pool, cmd->sc_cnt *
781 sizeof(pj_str_t));
782 for (i = 0; i < cmd->sc_cnt; i++) {
783 pj_strdup(cli->pool, &cmd->sc[i], &sc[i]);
784 /** Add shortcut to root command **/
785 add_cmd_name(cli, &cli->root, cmd, &sc[i]);
786 }
787 }
788
789 add_cmd_name(cli, group, cmd, &cmd->name);
790 pj_hash_set(cli->pool, cli->cmd_id_hash,
791 &cmd->id, sizeof(pj_cli_cmd_id), 0, cmd);
792
793 cmd->handler = handler;
794
795 if (group) {
796 if (!group->sub_cmd) {
797 group->sub_cmd = PJ_POOL_ALLOC_T(cli->pool, struct pj_cli_cmd_spec);
798 pj_list_init(group->sub_cmd);
799 }
800 pj_list_push_back(group->sub_cmd, cmd);
801 } else {
802 pj_list_push_back(cli->root.sub_cmd, cmd);
803 }
804
805 if (p_cmd)
806 *p_cmd = cmd;
807
808 return status;
809}
810
811PJ_DEF(pj_status_t) pj_cli_add_cmd_from_xml(pj_cli_t *cli,
812 pj_cli_cmd_spec *group,
813 const pj_str_t *xml,
814 pj_cli_cmd_handler handler,
815 pj_cli_cmd_spec **p_cmd,
816 pj_cli_get_dyn_choice get_choice)
817{
818 pj_pool_t *pool;
819 pj_xml_node *root;
820 pj_status_t status = PJ_SUCCESS;
821
822 PJ_ASSERT_RETURN(cli && xml, PJ_EINVAL);
823
824 /* Parse the xml */
825 pool = pj_pool_create(cli->cfg.pf, "xml", 1024, 1024, NULL);
826 if (!pool)
827 return PJ_ENOMEM;
828
829 root = pj_xml_parse(pool, xml->ptr, xml->slen);
830 if (!root) {
831 TRACE_((THIS_FILE, "Error: unable to parse XML"));
832 pj_pool_release(pool);
833 return PJ_CLI_EBADXML;
834 }
835 status = add_cmd_node(cli, group, root, handler, p_cmd, get_choice);
836 pj_pool_release(pool);
837 return status;
838}
839
840PJ_DEF(pj_status_t) pj_cli_sess_parse(pj_cli_sess *sess,
841 char *cmdline,
842 pj_cli_cmd_val *val,
843 pj_pool_t *pool,
844 pj_cli_exec_info *info)
845{
846 pj_scanner scanner;
847 pj_str_t str;
848 pj_size_t len;
849 pj_cli_cmd_spec *cmd;
850 pj_cli_cmd_spec *next_cmd;
851 pj_status_t status = PJ_SUCCESS;
852 pj_cli_parse_mode parse_mode = PARSE_NONE;
853
854 PJ_USE_EXCEPTION;
855
856 PJ_ASSERT_RETURN(sess && cmdline && val, PJ_EINVAL);
857
858 PJ_UNUSED_ARG(pool);
859
860 str.slen = 0;
861 pj_cli_exec_info_default(info);
862
863 /* Set the parse mode based on the latest char. */
864 len = pj_ansi_strlen(cmdline);
865 if (len > 0 && ((cmdline[len - 1] == '\r')||(cmdline[len - 1] == '\n'))) {
866 cmdline[--len] = 0;
867 parse_mode = PARSE_EXEC;
868 } else if (len > 0 &&
869 (cmdline[len - 1] == '\t' || cmdline[len - 1] == '?'))
870 {
871 cmdline[--len] = 0;
872 if (len == 0) {
873 parse_mode = PARSE_NEXT_AVAIL;
874 } else {
875 if (cmdline[len - 1] == ' ')
876 parse_mode = PARSE_NEXT_AVAIL;
877 else
878 parse_mode = PARSE_COMPLETION;
879 }
880 }
881 val->argc = 0;
882 info->err_pos = 0;
883 cmd = &sess->fe->cli->root;
884 if (len > 0) {
885 pj_scan_init(&scanner, cmdline, len, PJ_SCAN_AUTOSKIP_WS,
886 &on_syntax_error);
887 PJ_TRY {
888 val->argc = 0;
889 while (!pj_scan_is_eof(&scanner)) {
890 info->err_pos = (int)(scanner.curptr - scanner.begin);
891 if (*scanner.curptr == '\'' || *scanner.curptr == '"' ||
892 *scanner.curptr == '{')
893 {
894 pj_scan_get_quotes(&scanner, "'\"{", "'\"}", 3, &str);
895 /* Remove the quotes */
896 str.ptr++;
897 str.slen -= 2;
898 } else {
899 pj_scan_get_until_chr(&scanner, " \t\r\n", &str);
900 }
901 ++val->argc;
902 if (val->argc == PJ_CLI_MAX_ARGS)
903 PJ_THROW(PJ_CLI_ETOOMANYARGS);
904
905 status = get_available_cmds(sess, cmd, &str, val->argc-1,
906 pool, PJ_TRUE, parse_mode,
907 &next_cmd, info);
908
909 if (status != PJ_SUCCESS)
910 PJ_THROW(status);
911
912 if (cmd != next_cmd) {
913 /* Found new command, set it as the active command */
914 cmd = next_cmd;
915 val->argc = 1;
916 val->cmd = cmd;
917 }
918 if (parse_mode == PARSE_EXEC)
919 pj_strassign(&val->argv[val->argc-1], &info->hint->name);
920 else
921 pj_strassign(&val->argv[val->argc-1], &str);
922
923 }
924 if (!pj_scan_is_eof(&scanner))
925 PJ_THROW(PJ_CLI_EINVARG);
926
927 }
928 PJ_CATCH_ANY {
929 pj_scan_fini(&scanner);
930 return PJ_GET_EXCEPTION();
931 }
932 PJ_END;
933 }
934
935 if ((parse_mode == PARSE_NEXT_AVAIL) || (parse_mode == PARSE_EXEC)) {
936 /* If exec mode, just get the matching argument */
937 status = get_available_cmds(sess, cmd, NULL, val->argc, pool,
938 (parse_mode==PARSE_NEXT_AVAIL),
939 parse_mode,
940 NULL, info);
941 if ((status != PJ_SUCCESS) && (status != PJ_CLI_EINVARG)) {
942 pj_str_t data = pj_str(cmdline);
943 pj_strrtrim(&data);
944 data.ptr[data.slen] = ' ';
945 data.ptr[data.slen+1] = 0;
946
947 info->err_pos = (int)pj_ansi_strlen(cmdline);
948 }
949 }
950
951 val->sess = sess;
952 return status;
953}
954
955PJ_DECL(pj_status_t) pj_cli_sess_exec(pj_cli_sess *sess,
956 char *cmdline,
957 pj_pool_t *pool,
958 pj_cli_exec_info *info)
959{
960 pj_cli_cmd_val val;
961 pj_status_t status;
962 pj_cli_exec_info einfo;
963 pj_str_t cmd;
964
965 PJ_ASSERT_RETURN(sess && cmdline, PJ_EINVAL);
966
967 PJ_UNUSED_ARG(pool);
968
969 cmd.ptr = cmdline;
970 cmd.slen = pj_ansi_strlen(cmdline);
971
972 if (pj_strtrim(&cmd)->slen == 0)
973 return PJ_SUCCESS;
974
975 if (!info)
976 info = &einfo;
977
978 status = pj_cli_sess_parse(sess, cmdline, &val, pool, info);
979 if (status != PJ_SUCCESS)
980 return status;
981
982 if ((val.argc > 0) && (val.cmd->handler)) {
983 info->cmd_ret = (*val.cmd->handler)(&val);
984 if (info->cmd_ret == PJ_CLI_EINVARG ||
985 info->cmd_ret == PJ_CLI_EEXIT)
986 {
987 return info->cmd_ret;
988 }
989 }
990
991 return PJ_SUCCESS;
992}
993
994static pj_bool_t hint_inserted(const pj_str_t *name,
995 const pj_str_t *desc,
996 const pj_str_t *type,
997 pj_cli_exec_info *info)
998{
999 unsigned i;
1000 for(i=0; i<info->hint_cnt; ++i) {
1001 pj_cli_hint_info *hint = &info->hint[i];
1002 if ((!pj_strncmp(&hint->name, name, hint->name.slen)) &&
1003 (!pj_strncmp(&hint->desc, desc, hint->desc.slen)) &&
1004 (!pj_strncmp(&hint->type, type, hint->type.slen)))
1005 {
1006 return PJ_TRUE;
1007 }
1008 }
1009 return PJ_FALSE;
1010}
1011
1012/** This will insert new hint with the option to check for the same
1013 previous entry **/
1014static pj_status_t insert_new_hint2(pj_pool_t *pool,
1015 pj_bool_t unique_insert,
1016 const pj_str_t *name,
1017 const pj_str_t *desc,
1018 const pj_str_t *type,
1019 pj_cli_exec_info *info)
1020{
1021 pj_cli_hint_info *hint;
1022 PJ_ASSERT_RETURN(pool && info, PJ_EINVAL);
1023 PJ_ASSERT_RETURN((info->hint_cnt < PJ_CLI_MAX_HINTS), PJ_EINVAL);
1024
1025 if ((unique_insert) && (hint_inserted(name, desc, type, info)))
1026 return PJ_SUCCESS;
1027
1028 hint = &info->hint[info->hint_cnt];
1029
1030 pj_strdup(pool, &hint->name, name);
1031
1032 if (desc && (desc->slen > 0)) {
1033 pj_strdup(pool, &hint->desc, desc);
1034 } else {
1035 hint->desc.slen = 0;
1036 }
1037
1038 if (type && (type->slen > 0)) {
1039 pj_strdup(pool, &hint->type, type);
1040 } else {
1041 hint->type.slen = 0;
1042 }
1043
1044 ++info->hint_cnt;
1045 return PJ_SUCCESS;
1046}
1047
1048/** This will insert new hint without checking for the same previous entry **/
1049static pj_status_t insert_new_hint(pj_pool_t *pool,
1050 const pj_str_t *name,
1051 const pj_str_t *desc,
1052 const pj_str_t *type,
1053 pj_cli_exec_info *info)
1054{
1055 return insert_new_hint2(pool, PJ_FALSE, name, desc, type, info);
1056}
1057
1058/** This will get a complete/exact match of a command from the cmd hash **/
1059static pj_status_t get_comp_match_cmds(const pj_cli_t *cli,
1060 const pj_cli_cmd_spec *group,
1061 const pj_str_t *cmd_val,
1062 pj_pool_t *pool,
1063 pj_cli_cmd_spec **p_cmd,
1064 pj_cli_exec_info *info)
1065{
1066 pj_cli_cmd_spec *cmd;
1067 PJ_ASSERT_RETURN(cli && group && cmd_val && pool && info, PJ_EINVAL);
1068
1069 cmd = get_cmd_name(cli, group, cmd_val);
1070
1071 if (cmd) {
1072 pj_status_t status;
1073 status = insert_new_hint(pool, cmd_val, &cmd->desc, NULL, info);
1074
1075 if (status != PJ_SUCCESS)
1076 return status;
1077
1078 *p_cmd = cmd;
1079 }
1080
1081 return PJ_SUCCESS;
1082}
1083
1084/** This method will search for any shortcut with pattern match to the input
1085 command. This method should be called from root command, as shortcut could
1086 only be executed from root **/
1087static pj_status_t get_pattern_match_shortcut(const pj_cli_t *cli,
1088 const pj_str_t *cmd_val,
1089 pj_pool_t *pool,
1090 pj_cli_cmd_spec **p_cmd,
1091 pj_cli_exec_info *info)
1092{
1093 pj_hash_iterator_t it_buf, *it;
1094 pj_status_t status;
1095 PJ_ASSERT_RETURN(cli && pool && cmd_val && info, PJ_EINVAL);
1096
1097 it = pj_hash_first(cli->cmd_name_hash, &it_buf);
1098 while (it) {
1099 unsigned i;
1100 pj_cli_cmd_spec *cmd = (pj_cli_cmd_spec *)
1101 pj_hash_this(cli->cmd_name_hash, it);
1102
1103 PJ_ASSERT_RETURN(cmd, PJ_EINVAL);
1104
1105 for (i=0; i < cmd->sc_cnt; ++i) {
1106 static const pj_str_t SHORTCUT = {"SC", 2};
1107 pj_str_t *sc = &cmd->sc[i];
1108 PJ_ASSERT_RETURN(sc, PJ_EINVAL);
1109
1110 if (!pj_strncmp(sc, cmd_val, cmd_val->slen)) {
1111 /** Unique hints needed because cmd hash contain command name
1112 and shortcut referencing to the same command **/
1113 status = insert_new_hint2(pool, PJ_TRUE, sc, &cmd->desc,
1114 &SHORTCUT, info);
1115 if (status != PJ_SUCCESS)
1116 return status;
1117
1118 if (p_cmd)
1119 *p_cmd = cmd;
1120 }
1121 }
1122
1123 it = pj_hash_next(cli->cmd_name_hash, it);
1124 }
1125
1126 return PJ_SUCCESS;
1127}
1128
1129/** This method will search a pattern match to the input command from the child
1130 command list of the current/active command. **/
1131static pj_status_t get_pattern_match_cmds(pj_cli_cmd_spec *cmd,
1132 const pj_str_t *cmd_val,
1133 pj_pool_t *pool,
1134 pj_cli_cmd_spec **p_cmd,
1135 pj_cli_parse_mode parse_mode,
1136 pj_cli_exec_info *info)
1137{
1138 pj_status_t status;
1139 PJ_ASSERT_RETURN(cmd && pool && info && cmd_val, PJ_EINVAL);
1140
1141 if (p_cmd)
1142 *p_cmd = cmd;
1143
1144 /* Get matching command */
1145 if (cmd->sub_cmd) {
1146 pj_cli_cmd_spec *child_cmd = cmd->sub_cmd->next;
1147 while (child_cmd != cmd->sub_cmd) {
1148 pj_bool_t found = PJ_FALSE;
1149 if (!pj_strncmp(&child_cmd->name, cmd_val, cmd_val->slen)) {
1150 status = insert_new_hint(pool, &child_cmd->name,
1151 &child_cmd->desc, NULL, info);
1152 if (status != PJ_SUCCESS)
1153 return status;
1154
1155 found = PJ_TRUE;
1156 }
1157 if (found) {
1158 if (parse_mode == PARSE_NEXT_AVAIL) {
1159 /** Only insert shortcut on next available commands mode **/
1160 unsigned i;
1161 for (i=0; i < child_cmd->sc_cnt; ++i) {
1162 static const pj_str_t SHORTCUT = {"SC", 2};
1163 pj_str_t *sc = &child_cmd->sc[i];
1164 PJ_ASSERT_RETURN(sc, PJ_EINVAL);
1165
1166 status = insert_new_hint(pool, sc,
1167 &child_cmd->desc, &SHORTCUT,
1168 info);
1169 if (status != PJ_SUCCESS)
1170 return status;
1171 }
1172 }
1173
1174 if (p_cmd)
1175 *p_cmd = child_cmd;
1176 }
1177 child_cmd = child_cmd->next;
1178 }
1179 }
1180 return PJ_SUCCESS;
1181}
1182
1183/** This will match the arguments passed to the command with the argument list
1184 of the specified command list. **/
1185static pj_status_t get_match_args(pj_cli_sess *sess,
1186 pj_cli_cmd_spec *cmd,
1187 const pj_str_t *cmd_val,
1188 unsigned argc,
1189 pj_pool_t *pool,
1190 pj_cli_parse_mode parse_mode,
1191 pj_cli_exec_info *info)
1192{
1193 pj_cli_arg_spec *arg;
1194 pj_status_t status = PJ_SUCCESS;
1195
1196 PJ_ASSERT_RETURN(cmd && pool && cmd_val && info, PJ_EINVAL);
1197
1198 if ((argc > cmd->arg_cnt) && (!cmd->sub_cmd)) {
1199 if (cmd_val->slen > 0)
1200 return PJ_CLI_ETOOMANYARGS;
1201 else
1202 return PJ_SUCCESS;
1203 }
1204
1205 if (cmd->arg_cnt > 0) {
1206 arg = &cmd->arg[argc-1];
1207 PJ_ASSERT_RETURN(arg, PJ_EINVAL);
1208 if (arg->type == PJ_CLI_ARG_CHOICE) {
1209 unsigned j;
1210
1211 if ((parse_mode == PARSE_EXEC) && (!arg->validate)) {
1212 /* If no validation needed, then insert the values */
1213 status = insert_new_hint(pool, cmd_val, NULL, NULL, info);
1214 return status;
1215 }
1216
1217 for (j=0; j < arg->stat_choice_cnt; ++j) {
1218 pj_cli_arg_choice_val *choice_val = &arg->stat_choice_val[j];
1219
1220 PJ_ASSERT_RETURN(choice_val, PJ_EINVAL);
1221
1222 if (!pj_strncmp(&choice_val->value, cmd_val, cmd_val->slen)) {
1223 status = insert_new_hint(pool,
1224 &choice_val->value,
1225 &choice_val->desc,
1226 &arg_type[PJ_CLI_ARG_CHOICE].msg,
1227 info);
1228 if (status != PJ_SUCCESS)
1229 return status;
1230 }
1231 }
1232 if (arg->get_dyn_choice) {
1233 pj_cli_dyn_choice_param dyn_choice_param;
1234 static pj_str_t choice_str = {"choice", 6};
1235
1236 /* Get the dynamic choice values */
1237 dyn_choice_param.sess = sess;
1238 dyn_choice_param.cmd = cmd;
1239 dyn_choice_param.arg_id = arg->id;
1240 dyn_choice_param.max_cnt = PJ_CLI_MAX_CHOICE_VAL;
1241 dyn_choice_param.pool = pool;
1242 dyn_choice_param.cnt = 0;
1243
1244 (*arg->get_dyn_choice)(&dyn_choice_param);
1245 for (j=0; j < dyn_choice_param.cnt; ++j) {
1246 pj_cli_arg_choice_val *choice = &dyn_choice_param.choice[j];
1247 if (!pj_strncmp(&choice->value, cmd_val, cmd_val->slen)) {
1248 pj_strassign(&info->hint[info->hint_cnt].name,
1249 &choice->value);
1250 pj_strassign(&info->hint[info->hint_cnt].type,
1251 &choice_str);
1252 pj_strassign(&info->hint[info->hint_cnt].desc,
1253 &choice->desc);
1254 if ((++info->hint_cnt) >= PJ_CLI_MAX_HINTS)
1255 break;
1256 }
1257 }
1258 if ((info->hint_cnt == 0) && (!arg->optional))
1259 return PJ_CLI_EMISSINGARG;
1260 }
1261 } else {
1262 if (cmd_val->slen == 0) {
1263 if (info->hint_cnt == 0) {
1264 if (!((parse_mode == PARSE_EXEC) && (arg->optional))) {
1265 /* For exec mode,no need to insert hint if optional */
1266 status = insert_new_hint(pool,
1267 &arg->name,
1268 &arg->desc,
1269 &arg_type[arg->type].msg,
1270 info);
1271 if (status != PJ_SUCCESS)
1272 return status;
1273 }
1274 if (!arg->optional)
1275 return PJ_CLI_EMISSINGARG;
1276 }
1277 } else {
1278 return insert_new_hint(pool, cmd_val, NULL, NULL, info);
1279 }
1280 }
1281 }
1282 return status;
1283}
1284
1285/** This will check for a match of the commands/arguments input. Any match
1286 will be inserted to the hint data. **/
1287static pj_status_t get_available_cmds(pj_cli_sess *sess,
1288 pj_cli_cmd_spec *cmd,
1289 pj_str_t *cmd_val,
1290 unsigned argc,
1291 pj_pool_t *pool,
1292 pj_bool_t get_cmd,
1293 pj_cli_parse_mode parse_mode,
1294 pj_cli_cmd_spec **p_cmd,
1295 pj_cli_exec_info *info)
1296{
1297 pj_status_t status = PJ_SUCCESS;
1298 pj_str_t *prefix;
1299 pj_str_t EMPTY_STR = {NULL, 0};
1300
1301 prefix = cmd_val?(pj_strtrim(cmd_val)):(&EMPTY_STR);
1302
1303 info->hint_cnt = 0;
1304
1305 if (get_cmd) {
1306 status = get_comp_match_cmds(sess->fe->cli, cmd, prefix, pool, p_cmd,
1307 info);
1308 if (status != PJ_SUCCESS)
1309 return status;
1310
1311 /** If exact match found, then no need to search for pattern match **/
1312 if (info->hint_cnt == 0) {
1313 if ((parse_mode != PARSE_NEXT_AVAIL) &&
1314 (cmd == &sess->fe->cli->root))
1315 {
1316 /** Pattern match for shortcut needed on root command only **/
1317 status = get_pattern_match_shortcut(sess->fe->cli, prefix, pool,
1318 p_cmd, info);
1319
1320 if (status != PJ_SUCCESS)
1321 return status;
1322 }
1323
1324 status = get_pattern_match_cmds(cmd, prefix, pool, p_cmd,
1325 parse_mode, info);
1326 }
1327
1328 if (status != PJ_SUCCESS)
1329 return status;
1330 }
1331
1332 if (argc > 0)
1333 status = get_match_args(sess, cmd, prefix, argc,
1334 pool, parse_mode, info);
1335
1336 if (status == PJ_SUCCESS) {
1337 if (prefix->slen > 0) {
1338 /** If a command entered is not a an empty command, and have a
1339 single match in the command list then it is a valid command **/
1340 if (info->hint_cnt == 0) {
1341 status = PJ_CLI_EINVARG;
1342 } else if (info->hint_cnt > 1) {
1343 status = PJ_CLI_EAMBIGUOUS;
1344 }
1345 } else {
1346 if (info->hint_cnt > 0)
1347 status = PJ_CLI_EAMBIGUOUS;
1348 }
1349 }
1350
1351 return status;
1352}
1353