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