1 /**
2 * @file src/cmd.c Command Interface
3 *
4 * Copyright (C) 2010 - 2016 Creytiv.com
5 */
6 #include <ctype.h>
7 #include <string.h>
8 #include <re.h>
9 #include <baresip.h>
10 #include "core.h"
11
12
13 enum {
14 KEYCODE_DEL = 0x7f,
15 LONG_PREFIX = '/'
16 };
17
18
19 struct cmds {
20 struct le le;
21 const struct cmd *cmdv;
22 size_t cmdc;
23 };
24
25 struct cmd_ctx {
26 struct mbuf *mb;
27 const struct cmd *cmd;
28 bool is_long;
29 };
30
31 struct commands {
32 struct list cmdl; /**< List of command blocks (struct cmds) */
33 };
34
35
36 static int cmd_print_all(struct re_printf *pf,
37 const struct commands *commands,
38 bool print_long, bool print_short,
39 const char *match, size_t match_len);
40
41
destructor(void * arg)42 static void destructor(void *arg)
43 {
44 struct cmds *cmds = arg;
45
46 list_unlink(&cmds->le);
47 }
48
49
ctx_destructor(void * arg)50 static void ctx_destructor(void *arg)
51 {
52 struct cmd_ctx *ctx = arg;
53
54 mem_deref(ctx->mb);
55 }
56
57
commands_destructor(void * data)58 static void commands_destructor(void *data)
59 {
60 struct commands *commands = data;
61
62 list_flush(&commands->cmdl);
63 }
64
65
ctx_alloc(struct cmd_ctx ** ctxp,const struct cmd * cmd)66 static int ctx_alloc(struct cmd_ctx **ctxp, const struct cmd *cmd)
67 {
68 struct cmd_ctx *ctx;
69
70 ctx = mem_zalloc(sizeof(*ctx), ctx_destructor);
71 if (!ctx)
72 return ENOMEM;
73
74 ctx->mb = mbuf_alloc(32);
75 if (!ctx->mb) {
76 mem_deref(ctx);
77 return ENOMEM;
78 }
79
80 ctx->cmd = cmd;
81
82 *ctxp = ctx;
83
84 return 0;
85 }
86
87
88 /**
89 * Find a command block
90 *
91 * @param commands Commands container
92 * @param cmdv Command vector
93 *
94 * @return Command block if found, otherwise NULL
95 */
cmds_find(const struct commands * commands,const struct cmd * cmdv)96 struct cmds *cmds_find(const struct commands *commands,
97 const struct cmd *cmdv)
98 {
99 struct le *le;
100
101 if (!commands || !cmdv)
102 return NULL;
103
104 for (le = commands->cmdl.head; le; le = le->next) {
105 struct cmds *cmds = le->data;
106
107 if (cmds->cmdv == cmdv)
108 return cmds;
109 }
110
111 return NULL;
112 }
113
114
cmd_find_by_key(const struct commands * commands,char key)115 static const struct cmd *cmd_find_by_key(const struct commands *commands,
116 char key)
117 {
118 struct le *le;
119
120 if (!commands)
121 return NULL;
122
123 for (le = commands->cmdl.tail; le; le = le->prev) {
124
125 struct cmds *cmds = le->data;
126 size_t i;
127
128 for (i=0; i<cmds->cmdc; i++) {
129
130 const struct cmd *cmd = &cmds->cmdv[i];
131
132 if (cmd->key == key && cmd->h)
133 return cmd;
134 }
135 }
136
137 return NULL;
138 }
139
140
cmd_name(char * buf,size_t sz,const struct cmd * cmd)141 static const char *cmd_name(char *buf, size_t sz, const struct cmd *cmd)
142 {
143 switch (cmd->key) {
144
145 case ' ': return "SPACE";
146 case '\n': return "ENTER";
147 case KEYCODE_ESC: return "ESC";
148 }
149
150 buf[0] = cmd->key;
151 buf[1] = '\0';
152
153 if (cmd->flags & CMD_PRM)
154 strncat(buf, " ..", sz-1);
155
156 return buf;
157 }
158
159
get_match_long(const struct commands * commands,const struct cmd ** cmdp,const char * str,size_t len)160 static size_t get_match_long(const struct commands *commands,
161 const struct cmd **cmdp,
162 const char *str, size_t len)
163 {
164 struct le *le;
165 size_t nmatch = 0;
166
167 if (!commands)
168 return 0;
169
170 for (le = commands->cmdl.head; le; le = le->next) {
171
172 struct cmds *cmds = le->data;
173 size_t i;
174
175 for (i=0; i<cmds->cmdc; i++) {
176
177 const struct cmd *cmd = &cmds->cmdv[i];
178
179 if (!str_isset(cmd->name))
180 continue;
181
182 if (str_len(cmd->name) >= len &&
183 0 == memcmp(cmd->name, str, len)) {
184
185 ++nmatch;
186 *cmdp = cmd;
187 }
188 }
189 }
190
191 return nmatch;
192 }
193
194
editor_input(struct commands * commands,struct mbuf * mb,char key,struct re_printf * pf,bool * del,bool is_long)195 static int editor_input(struct commands *commands, struct mbuf *mb, char key,
196 struct re_printf *pf, bool *del, bool is_long)
197 {
198 int err = 0;
199
200 switch (key) {
201
202 case KEYCODE_ESC:
203 *del = true;
204 return re_hprintf(pf, "\nCancel\n");
205
206 case KEYCODE_NONE:
207 case KEYCODE_REL:
208 break;
209
210 case '\n':
211 *del = true;
212 return re_hprintf(pf, "\n");
213
214 case '\b':
215 case KEYCODE_DEL:
216 if (mb->pos > 0) {
217 err |= re_hprintf(pf, "\b ");
218 mb->pos = mb->end = (mb->pos - 1);
219 }
220 break;
221
222 case '\t':
223 if (is_long) {
224 const struct cmd *cmd = NULL;
225 size_t n;
226
227 err = re_hprintf(pf,
228 "TAB completion for \"%b\":\n",
229 mb->buf, mb->end);
230 if (err)
231 return err;
232
233 /* Find all long commands that matches the N
234 * first characters of the input string.
235 *
236 * If the number of matches is exactly one,
237 * we can regard it as TAB completion.
238 */
239
240 err = cmd_print_all(pf, commands, true, false,
241 (char *)mb->buf, mb->end);
242 if (err)
243 return err;
244
245 n = get_match_long(commands, &cmd,
246 (char *)mb->buf, mb->end);
247 if (n == 1 && cmd) {
248
249 mb->pos = 0;
250 mbuf_write_str(mb, cmd->name);
251 }
252 else if (n == 0) {
253 err = re_hprintf(pf, "(none)\n");
254 }
255 }
256 else {
257 err = mbuf_write_u8(mb, key);
258 }
259 break;
260
261 default:
262 err = mbuf_write_u8(mb, key);
263 break;
264 }
265
266 if (is_long) {
267 err |= re_hprintf(pf, "\r/%b",
268 mb->buf, mb->end);
269 }
270 else
271 err |= re_hprintf(pf, "\r> %32b", mb->buf, mb->end);
272
273 return err;
274 }
275
276
cmd_report(const struct cmd * cmd,struct re_printf * pf,struct mbuf * mb,bool compl,void * data)277 static int cmd_report(const struct cmd *cmd, struct re_printf *pf,
278 struct mbuf *mb, bool compl, void *data)
279 {
280 struct cmd_arg arg;
281 int err;
282
283 memset(&arg, 0, sizeof(arg));
284
285 mb->pos = 0;
286 err = mbuf_strdup(mb, &arg.prm, mb->end);
287 if (err)
288 return err;
289
290 arg.key = cmd->key;
291 arg.complete = compl;
292 arg.data = data;
293
294 err = cmd->h(pf, &arg);
295
296 mem_deref(arg.prm);
297
298 return err;
299 }
300
301
302 /**
303 * Process long commands
304 *
305 * @param commands Commands container
306 * @param str Input string
307 * @param len Length of input string
308 * @param pf_resp Print function for response
309 * @param data Application data
310 *
311 * @return 0 if success, otherwise errorcode
312 */
cmd_process_long(struct commands * commands,const char * str,size_t len,struct re_printf * pf_resp,void * data)313 int cmd_process_long(struct commands *commands, const char *str, size_t len,
314 struct re_printf *pf_resp, void *data)
315 {
316 struct cmd_arg arg;
317 const struct cmd *cmd_long;
318 char *name = NULL, *prm = NULL;
319 struct pl pl_name, pl_prm;
320 int err;
321
322 if (!str || !len)
323 return EINVAL;
324
325 memset(&arg, 0, sizeof(arg));
326
327 err = re_regex(str, len, "[^ ]+[ ]*[~]*", &pl_name, NULL, &pl_prm);
328 if (err) {
329 return err;
330 }
331
332 err = pl_strdup(&name, &pl_name);
333 if (pl_isset(&pl_prm))
334 err |= pl_strdup(&prm, &pl_prm);
335 if (err)
336 goto out;
337
338 cmd_long = cmd_find_long(commands, name);
339 if (cmd_long) {
340
341 arg.key = LONG_PREFIX;
342 arg.prm = prm;
343 arg.complete = true;
344 arg.data = data;
345
346 if (cmd_long->h)
347 err = cmd_long->h(pf_resp, &arg);
348 }
349 else {
350 err = re_hprintf(pf_resp, "command not found (%s)\n", name);
351 }
352
353 out:
354 mem_deref(name);
355 mem_deref(prm);
356
357 return err;
358 }
359
360
cmd_process_edit(struct commands * commands,struct cmd_ctx ** ctxp,char key,struct re_printf * pf,void * data)361 static int cmd_process_edit(struct commands *commands,
362 struct cmd_ctx **ctxp, char key,
363 struct re_printf *pf, void *data)
364 {
365 struct cmd_ctx *ctx;
366 bool compl = (key == '\n'), del = false;
367 int err;
368
369 if (!ctxp)
370 return EINVAL;
371
372 ctx = *ctxp;
373
374 err = editor_input(commands, ctx->mb, key, pf, &del, ctx->is_long);
375 if (err)
376 return err;
377
378 if (ctx->is_long) {
379
380 if (compl) {
381
382 err = cmd_process_long(commands,
383 (char *)ctx->mb->buf,
384 ctx->mb->end,
385 pf, data);
386 }
387 }
388 else {
389 if (compl ||
390 (ctx->cmd && ctx->cmd->flags & CMD_PROG))
391 err = cmd_report(ctx->cmd, pf, ctx->mb, compl, data);
392 }
393
394 if (del)
395 *ctxp = mem_deref(*ctxp);
396
397 return err;
398 }
399
400
401 /**
402 * Register commands
403 *
404 * @param commands Commands container
405 * @param cmdv Array of commands
406 * @param cmdc Number of commands
407 *
408 * @return 0 if success, otherwise errorcode
409 */
cmd_register(struct commands * commands,const struct cmd * cmdv,size_t cmdc)410 int cmd_register(struct commands *commands,
411 const struct cmd *cmdv, size_t cmdc)
412 {
413 struct cmds *cmds;
414 size_t i;
415
416 if (!commands || !cmdv || !cmdc)
417 return EINVAL;
418
419 cmds = cmds_find(commands, cmdv);
420 if (cmds)
421 return EALREADY;
422
423 /* verify that command is not registered */
424 for (i=0; i<cmdc; i++) {
425 const struct cmd *cmd = &cmdv[i];
426
427 if (cmd->key) {
428 const struct cmd *x = cmd_find_by_key(commands,
429 cmd->key);
430 if (x) {
431 warning("short command '%c' already"
432 " registered as \"%s\"\n",
433 x->key, x->desc);
434 return EALREADY;
435 }
436 }
437
438 if (cmd->key == LONG_PREFIX) {
439 warning("cmd: cannot register command with"
440 " short key '%c'\n", cmd->key);
441 return EINVAL;
442 }
443
444 if (str_isset(cmd->name) &&
445 cmd_find_long(commands, cmd->name)) {
446 warning("cmd: long command '%s' already registered\n",
447 cmd->name);
448 return EINVAL;
449 }
450 }
451
452 cmds = mem_zalloc(sizeof(*cmds), destructor);
453 if (!cmds)
454 return ENOMEM;
455
456 cmds->cmdv = cmdv;
457 cmds->cmdc = cmdc;
458
459 list_append(&commands->cmdl, &cmds->le, cmds);
460
461 return 0;
462 }
463
464
465 /**
466 * Unregister commands
467 *
468 * @param commands Commands container
469 * @param cmdv Array of commands
470 */
cmd_unregister(struct commands * commands,const struct cmd * cmdv)471 void cmd_unregister(struct commands *commands, const struct cmd *cmdv)
472 {
473 mem_deref(cmds_find(commands, cmdv));
474 }
475
476
477 /**
478 * Find a long command
479 *
480 * @param commands Commands container
481 * @param name Name of command, excluding prefix
482 *
483 * @return Command if found, NULL if not found
484 */
cmd_find_long(const struct commands * commands,const char * name)485 const struct cmd *cmd_find_long(const struct commands *commands,
486 const char *name)
487 {
488 struct le *le;
489
490 if (!commands || !name)
491 return NULL;
492
493 for (le = commands->cmdl.tail; le; le = le->prev) {
494
495 struct cmds *cmds = le->data;
496 size_t i;
497
498 for (i=0; i<cmds->cmdc; i++) {
499
500 const struct cmd *cmd = &cmds->cmdv[i];
501
502 if (0 == str_casecmp(name, cmd->name) && cmd->h)
503 return cmd;
504 }
505 }
506
507 return NULL;
508 }
509
510
511 /**
512 * Process input characters to the command system
513 *
514 * @param commands Commands container
515 * @param ctxp Pointer to context for editor (optional)
516 * @param key Input character
517 * @param pf Print function
518 * @param data Application data
519 *
520 * @return 0 if success, otherwise errorcode
521 */
cmd_process(struct commands * commands,struct cmd_ctx ** ctxp,char key,struct re_printf * pf,void * data)522 int cmd_process(struct commands *commands, struct cmd_ctx **ctxp, char key,
523 struct re_printf *pf, void *data)
524 {
525 const struct cmd *cmd;
526
527 if (!commands)
528 return EINVAL;
529
530 if (key == KEYCODE_NONE) {
531 warning("cmd: process: illegal keycode NONE\n");
532 return EINVAL;
533 }
534
535 /* are we in edit-mode? */
536 if (ctxp && *ctxp) {
537
538 if (key == KEYCODE_REL)
539 return 0;
540
541 return cmd_process_edit(commands, ctxp, key, pf, data);
542 }
543
544 cmd = cmd_find_by_key(commands, key);
545 if (cmd) {
546 struct cmd_arg arg;
547
548 /* check for parameters */
549 if (cmd->flags & CMD_PRM) {
550
551 int err = 0;
552
553 if (ctxp) {
554 err = ctx_alloc(ctxp, cmd);
555 if (err)
556 return err;
557 }
558
559 key = isdigit(key) ? key : KEYCODE_REL;
560
561 return cmd_process_edit(commands, ctxp, key, pf, data);
562 }
563
564 arg.key = key;
565 arg.prm = NULL;
566 arg.complete = true;
567 arg.data = data;
568
569 return cmd->h(pf, &arg);
570 }
571 else if (key == LONG_PREFIX) {
572
573 int err;
574
575 err = re_hprintf(pf, "%c", LONG_PREFIX);
576 if (err)
577 return err;
578
579 if (!ctxp) {
580 warning("cmd: ctxp is required\n");
581 return EINVAL;
582 }
583
584 err = ctx_alloc(ctxp, cmd);
585 if (err)
586 return err;
587
588 (*ctxp)->is_long = true;
589
590 return 0;
591 }
592 else if (key == '\t') {
593 return cmd_print_all(pf, commands, false, true, NULL, 0);
594 }
595
596 if (key == KEYCODE_REL)
597 return 0;
598
599 return cmd_print(pf, commands);
600 }
601
602
603 struct cmd_sort {
604 struct le le;
605 const struct cmd *cmd;
606 };
607
608
sort_handler(struct le * le1,struct le * le2,void * arg)609 static bool sort_handler(struct le *le1, struct le *le2, void *arg)
610 {
611 struct cmd_sort *cs1 = le1->data;
612 struct cmd_sort *cs2 = le2->data;
613 const struct cmd *cmd1 = cs1->cmd;
614 const struct cmd *cmd2 = cs2->cmd;
615 bool print_long = *(bool *)arg;
616
617 if (print_long) {
618 return str_casecmp(cs2->cmd->name ? cs2->cmd->name : "",
619 cs1->cmd->name ? cs1->cmd->name : "") >= 0;
620 }
621 else {
622 return tolower(cmd2->key) >= tolower(cmd1->key);
623 }
624 }
625
626
cmd_print_all(struct re_printf * pf,const struct commands * commands,bool print_long,bool print_short,const char * match,size_t match_len)627 static int cmd_print_all(struct re_printf *pf,
628 const struct commands *commands,
629 bool print_long, bool print_short,
630 const char *match, size_t match_len)
631 {
632 struct list sortedl = LIST_INIT;
633 struct le *le;
634 size_t width_long = 1;
635 size_t width_short = 5;
636 char fmt[64];
637 char buf[16];
638 int err = 0;
639
640 if (!commands)
641 return EINVAL;
642
643 for (le = commands->cmdl.head; le; le = le->next) {
644
645 struct cmds *cmds = le->data;
646 size_t i;
647
648 for (i=0; i<cmds->cmdc; i++) {
649
650 const struct cmd *cmd = &cmds->cmdv[i];
651 struct cmd_sort *cs;
652
653 if (match && match_len) {
654
655 if (str_len(cmd->name) >= match_len &&
656 0 == memcmp(cmd->name, match, match_len)) {
657 /* Match */
658 }
659 else {
660 continue;
661 }
662 }
663
664 if (!str_isset(cmd->desc))
665 continue;
666
667 if (print_short && !print_long) {
668
669 if (cmd->key == KEYCODE_NONE)
670 continue;
671 }
672
673 cs = mem_zalloc(sizeof(*cs), NULL);
674 if (!cs) {
675 err = ENOMEM;
676 goto out;
677 }
678 cs->cmd = cmd;
679
680 list_append(&sortedl, &cs->le, cs);
681
682 width_long = max(width_long, 1+str_len(cmd->name)+3);
683 }
684 }
685
686 list_sort(&sortedl, sort_handler, &print_long);
687
688 if (re_snprintf(fmt, sizeof(fmt),
689 " %%-%zus %%-%zus %%s\n",
690 width_long, width_short) < 0) {
691 err = ENOMEM;
692 goto out;
693 }
694
695 for (le = sortedl.head; le; le = le->next) {
696 struct cmd_sort *cs = le->data;
697 const struct cmd *cmd = cs->cmd;
698 char namep[64] = "";
699
700 if (print_long && str_isset(cmd->name)) {
701 re_snprintf(namep, sizeof(namep), "%c%s%s",
702 LONG_PREFIX, cmd->name,
703 (cmd->flags & CMD_PRM) ? " .." : "");
704 }
705
706 err |= re_hprintf(pf, fmt,
707 namep,
708 (print_short && cmd->key)
709 ? cmd_name(buf, sizeof(buf), cmd)
710 : "",
711 cmd->desc);
712 }
713
714 err |= re_hprintf(pf, "\n");
715
716 out:
717 list_flush(&sortedl);
718 return err;
719 }
720
721
722 /**
723 * Print a list of available commands
724 *
725 * @param pf Print function
726 * @param commands Commands container
727 *
728 * @return 0 if success, otherwise errorcode
729 */
cmd_print(struct re_printf * pf,const struct commands * commands)730 int cmd_print(struct re_printf *pf, const struct commands *commands)
731 {
732 int err = 0;
733
734 if (!pf)
735 return EINVAL;
736
737 err |= re_hprintf(pf, "--- Help ---\n");
738 err |= cmd_print_all(pf, commands, true, true, NULL, 0);
739 err |= re_hprintf(pf, "\n");
740
741 return err;
742 }
743
744
745 /**
746 * Initialize the commands subsystem.
747 *
748 * @param commandsp Pointer to allocated commands
749 *
750 * @return 0 if success, otherwise errorcode
751 */
cmd_init(struct commands ** commandsp)752 int cmd_init(struct commands **commandsp)
753 {
754 struct commands *commands;
755
756 if (!commandsp)
757 return EINVAL;
758
759 commands = mem_zalloc(sizeof(*commands), commands_destructor);
760 if (!commands)
761 return ENOMEM;
762
763 list_init(&commands->cmdl);
764
765 *commandsp = commands;
766
767 return 0;
768 }
769