Fix a memory leak in func_curl. Every thread that used this function leaked
[asterisk-bristuff.git] / main / cli.c
blob36f20a511bd140d7b39793664950194a26028129
1 /*
2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2006, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
19 /*! \file
21 * \brief Standard Command Line Interface
23 * \author Mark Spencer <markster@digium.com>
26 #include "asterisk.h"
28 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
30 #include <unistd.h>
31 #include <stdlib.h>
32 #include <sys/signal.h>
33 #include <stdio.h>
34 #include <signal.h>
35 #include <string.h>
36 #include <ctype.h>
37 #include <regex.h>
39 #include "asterisk/logger.h"
40 #include "asterisk/options.h"
41 #include "asterisk/cli.h"
42 #include "asterisk/linkedlists.h"
43 #include "asterisk/module.h"
44 #include "asterisk/pbx.h"
45 #include "asterisk/channel.h"
46 #include "asterisk/utils.h"
47 #include "asterisk/app.h"
48 #include "asterisk/lock.h"
49 #include "editline/readline/readline.h"
50 #include "asterisk/threadstorage.h"
52 extern unsigned long global_fin, global_fout;
54 AST_THREADSTORAGE(ast_cli_buf, ast_cli_buf_init);
56 /*! \brief Initial buffer size for resulting strings in ast_cli() */
57 #define AST_CLI_INITLEN 256
59 void ast_cli(int fd, char *fmt, ...)
61 int res;
62 struct ast_dynamic_str *buf;
63 va_list ap;
65 if (!(buf = ast_dynamic_str_thread_get(&ast_cli_buf, AST_CLI_INITLEN)))
66 return;
68 va_start(ap, fmt);
69 res = ast_dynamic_str_thread_set_va(&buf, 0, &ast_cli_buf, fmt, ap);
70 va_end(ap);
72 if (res != AST_DYNSTR_BUILD_FAILED)
73 ast_carefulwrite(fd, buf->str, strlen(buf->str), 100);
76 static AST_LIST_HEAD_STATIC(helpers, ast_cli_entry);
78 static char load_help[] =
79 "Usage: module load <module name>\n"
80 " Loads the specified module into Asterisk.\n";
82 static char unload_help[] =
83 "Usage: module unload [-f|-h] <module name>\n"
84 " Unloads the specified module from Asterisk. The -f\n"
85 " option causes the module to be unloaded even if it is\n"
86 " in use (may cause a crash) and the -h module causes the\n"
87 " module to be unloaded even if the module says it cannot, \n"
88 " which almost always will cause a crash.\n";
90 static char help_help[] =
91 "Usage: help [topic]\n"
92 " When called with a topic as an argument, displays usage\n"
93 " information on the given command. If called without a\n"
94 " topic, it provides a list of commands.\n";
96 static char chanlist_help[] =
97 "Usage: core show channels [concise|verbose]\n"
98 " Lists currently defined channels and some information about them. If\n"
99 " 'concise' is specified, the format is abridged and in a more easily\n"
100 " machine parsable format. If 'verbose' is specified, the output includes\n"
101 " more and longer fields.\n";
103 static char reload_help[] =
104 "Usage: module reload [module ...]\n"
105 " Reloads configuration files for all listed modules which support\n"
106 " reloading, or for all supported modules if none are listed.\n";
108 static char verbose_help[] =
109 "Usage: core set verbose <level>\n"
110 " Sets level of verbose messages to be displayed. 0 means\n"
111 " no messages should be displayed. Equivalent to -v[v[v...]]\n"
112 " on startup\n";
114 static char debug_help[] =
115 "Usage: core set debug <level> [filename]\n"
116 " Sets level of core debug messages to be displayed. 0 means\n"
117 " no messages should be displayed. Equivalent to -d[d[d...]]\n"
118 " on startup. If filename is specified, debugging will be\n"
119 " limited to just that file.\n";
121 static char nodebug_help[] =
122 "Usage: core set debug off\n"
123 " Turns off core debug messages.\n";
125 static char logger_mute_help[] =
126 "Usage: logger mute\n"
127 " Disables logging output to the current console, making it possible to\n"
128 " gather information without being disturbed by scrolling lines.\n";
130 static char softhangup_help[] =
131 "Usage: soft hangup <channel>\n"
132 " Request that a channel be hung up. The hangup takes effect\n"
133 " the next time the driver reads or writes from the channel\n";
135 static char group_show_channels_help[] =
136 "Usage: group show channels [pattern]\n"
137 " Lists all currently active channels with channel group(s) specified.\n"
138 " Optional regular expression pattern is matched to group names for each\n"
139 " channel.\n";
141 static int handle_load_deprecated(int fd, int argc, char *argv[])
143 if (argc != 2)
144 return RESULT_SHOWUSAGE;
145 if (ast_load_resource(argv[1])) {
146 ast_cli(fd, "Unable to load module %s\n", argv[1]);
147 return RESULT_FAILURE;
149 return RESULT_SUCCESS;
152 static int handle_load(int fd, int argc, char *argv[])
154 if (argc != 3)
155 return RESULT_SHOWUSAGE;
156 if (ast_load_resource(argv[2])) {
157 ast_cli(fd, "Unable to load module %s\n", argv[2]);
158 return RESULT_FAILURE;
160 return RESULT_SUCCESS;
163 static int handle_reload_deprecated(int fd, int argc, char *argv[])
165 int x;
166 int res;
167 if (argc < 1)
168 return RESULT_SHOWUSAGE;
169 if (argc > 1) {
170 for (x = 1; x < argc; x++) {
171 res = ast_module_reload(argv[x]);
172 switch(res) {
173 case 0:
174 ast_cli(fd, "No such module '%s'\n", argv[x]);
175 break;
176 case 1:
177 ast_cli(fd, "Module '%s' does not support reload\n", argv[x]);
178 break;
181 } else
182 ast_module_reload(NULL);
183 return RESULT_SUCCESS;
186 static int handle_reload(int fd, int argc, char *argv[])
188 int x;
189 int res;
190 if (argc < 2)
191 return RESULT_SHOWUSAGE;
192 if (argc > 2) {
193 for (x = 2; x < argc; x++) {
194 res = ast_module_reload(argv[x]);
195 switch(res) {
196 case 0:
197 ast_cli(fd, "No such module '%s'\n", argv[x]);
198 break;
199 case 1:
200 ast_cli(fd, "Module '%s' does not support reload\n", argv[x]);
201 break;
204 } else
205 ast_module_reload(NULL);
206 return RESULT_SUCCESS;
209 static int handle_set_verbose_deprecated(int fd, int argc, char *argv[])
211 int val = 0;
212 int oldval = option_verbose;
214 /* "set verbose [atleast] N" */
215 if (argc == 3)
216 option_verbose = atoi(argv[2]);
217 else if (argc == 4) {
218 if (strcasecmp(argv[2], "atleast"))
219 return RESULT_SHOWUSAGE;
220 val = atoi(argv[3]);
221 if (val > option_verbose)
222 option_verbose = val;
223 } else
224 return RESULT_SHOWUSAGE;
226 if (oldval != option_verbose && option_verbose > 0)
227 ast_cli(fd, "Verbosity was %d and is now %d\n", oldval, option_verbose);
228 else if (oldval > 0 && option_verbose > 0)
229 ast_cli(fd, "Verbosity is at least %d\n", option_verbose);
230 else if (oldval > 0 && option_verbose == 0)
231 ast_cli(fd, "Verbosity is now OFF\n");
233 return RESULT_SUCCESS;
236 static int handle_verbose(int fd, int argc, char *argv[])
238 int oldval = option_verbose;
239 int newlevel;
240 int atleast = 0;
242 if ((argc < 4) || (argc > 5))
243 return RESULT_SHOWUSAGE;
245 if (!strcasecmp(argv[3], "atleast"))
246 atleast = 1;
248 if (!atleast) {
249 if (argc > 4)
250 return RESULT_SHOWUSAGE;
252 option_verbose = atoi(argv[3]);
253 } else {
254 if (argc < 5)
255 return RESULT_SHOWUSAGE;
257 newlevel = atoi(argv[4]);
258 if (newlevel > option_verbose)
259 option_verbose = newlevel;
261 if (oldval > 0 && option_verbose == 0)
262 ast_cli(fd, "Verbosity is now OFF\n");
263 else if (option_verbose > 0) {
264 if (oldval == option_verbose)
265 ast_cli(fd, "Verbosity is at least %d\n", option_verbose);
266 else
267 ast_cli(fd, "Verbosity was %d and is now %d\n", oldval, option_verbose);
270 return RESULT_SUCCESS;
273 static int handle_set_debug_deprecated(int fd, int argc, char *argv[])
275 int val = 0;
276 int oldval = option_debug;
278 /* "set debug [atleast] N" */
279 if (argc == 3)
280 option_debug = atoi(argv[2]);
281 else if (argc == 4) {
282 if (strcasecmp(argv[2], "atleast"))
283 return RESULT_SHOWUSAGE;
284 val = atoi(argv[3]);
285 if (val > option_debug)
286 option_debug = val;
287 } else
288 return RESULT_SHOWUSAGE;
290 if (oldval != option_debug && option_debug > 0)
291 ast_cli(fd, "Core debug was %d and is now %d\n", oldval, option_debug);
292 else if (oldval > 0 && option_debug > 0)
293 ast_cli(fd, "Core debug is at least %d\n", option_debug);
294 else if (oldval > 0 && option_debug == 0)
295 ast_cli(fd, "Core debug is now OFF\n");
297 return RESULT_SUCCESS;
300 static int handle_set_debug(int fd, int argc, char *argv[])
302 int oldval = option_debug;
303 int newlevel;
304 int atleast = 0;
305 char *filename = '\0';
307 /* 'core set debug <level>'
308 * 'core set debug <level> <fn>'
309 * 'core set debug atleast <level>'
310 * 'core set debug atleast <level> <fn>'
312 if ((argc < 4) || (argc > 6))
313 return RESULT_SHOWUSAGE;
315 if (!strcasecmp(argv[3], "atleast"))
316 atleast = 1;
318 if (!atleast) {
319 if (argc > 5)
320 return RESULT_SHOWUSAGE;
322 if (sscanf(argv[3], "%d", &newlevel) != 1)
323 return RESULT_SHOWUSAGE;
325 if (argc == 4) {
326 debug_filename[0] = '\0';
327 } else {
328 filename = argv[4];
329 ast_copy_string(debug_filename, filename, sizeof(debug_filename));
332 option_debug = newlevel;
333 } else {
334 if (argc < 5 || argc > 6)
335 return RESULT_SHOWUSAGE;
337 if (sscanf(argv[4], "%d", &newlevel) != 1)
338 return RESULT_SHOWUSAGE;
340 if (argc == 5) {
341 debug_filename[0] = '\0';
342 } else {
343 filename = argv[5];
344 ast_copy_string(debug_filename, filename, sizeof(debug_filename));
347 if (newlevel > option_debug)
348 option_debug = newlevel;
351 if (oldval > 0 && option_debug == 0)
352 ast_cli(fd, "Core debug is now OFF\n");
353 else if (option_debug > 0) {
354 if (filename) {
355 if (oldval == option_debug)
356 ast_cli(fd, "Core debug is at least %d, file '%s'\n", option_debug, filename);
357 else
358 ast_cli(fd, "Core debug was %d and is now %d, file '%s'\n", oldval, option_debug, filename);
359 } else {
360 if (oldval == option_debug)
361 ast_cli(fd, "Core debug is at least %d\n", option_debug);
362 else
363 ast_cli(fd, "Core debug was %d and is now %d\n", oldval, option_debug);
367 return RESULT_SUCCESS;
370 static int handle_nodebug(int fd, int argc, char *argv[])
372 int oldval = option_debug;
373 if (argc != 4)
374 return RESULT_SHOWUSAGE;
376 option_debug = 0;
377 debug_filename[0] = '\0';
379 if (oldval > 0)
380 ast_cli(fd, "Core debug is now OFF\n");
381 return RESULT_SUCCESS;
384 static int handle_debuglevel_deprecated(int fd, int argc, char *argv[])
386 int newlevel;
387 char *filename = "<any>";
388 if ((argc < 3) || (argc > 4))
389 return RESULT_SHOWUSAGE;
390 if (sscanf(argv[2], "%d", &newlevel) != 1)
391 return RESULT_SHOWUSAGE;
392 option_debug = newlevel;
393 if (argc == 4) {
394 filename = argv[3];
395 ast_copy_string(debug_filename, filename, sizeof(debug_filename));
396 } else {
397 debug_filename[0] = '\0';
399 ast_cli(fd, "Debugging level set to %d, file '%s'\n", newlevel, filename);
400 return RESULT_SUCCESS;
403 static int handle_logger_mute(int fd, int argc, char *argv[])
405 if (argc < 2 || argc > 3)
406 return RESULT_SHOWUSAGE;
407 if (argc == 3 && !strcasecmp(argv[2], "silent"))
408 ast_console_toggle_mute(fd, 1);
409 else
410 ast_console_toggle_mute(fd, 0);
411 return RESULT_SUCCESS;
414 static int handle_unload_deprecated(int fd, int argc, char *argv[])
416 int x;
417 int force = AST_FORCE_SOFT;
418 if (argc < 2)
419 return RESULT_SHOWUSAGE;
420 for (x = 1; x < argc; x++) {
421 if (argv[x][0] == '-') {
422 switch(argv[x][1]) {
423 case 'f':
424 force = AST_FORCE_FIRM;
425 break;
426 case 'h':
427 force = AST_FORCE_HARD;
428 break;
429 default:
430 return RESULT_SHOWUSAGE;
432 } else if (x != argc - 1)
433 return RESULT_SHOWUSAGE;
434 else if (ast_unload_resource(argv[x], force)) {
435 ast_cli(fd, "Unable to unload resource %s\n", argv[x]);
436 return RESULT_FAILURE;
439 return RESULT_SUCCESS;
442 static int handle_unload(int fd, int argc, char *argv[])
444 int x;
445 int force = AST_FORCE_SOFT;
446 if (argc < 3)
447 return RESULT_SHOWUSAGE;
448 for (x = 2; x < argc; x++) {
449 if (argv[x][0] == '-') {
450 switch(argv[x][1]) {
451 case 'f':
452 force = AST_FORCE_FIRM;
453 break;
454 case 'h':
455 force = AST_FORCE_HARD;
456 break;
457 default:
458 return RESULT_SHOWUSAGE;
460 } else if (x != argc - 1)
461 return RESULT_SHOWUSAGE;
462 else if (ast_unload_resource(argv[x], force)) {
463 ast_cli(fd, "Unable to unload resource %s\n", argv[x]);
464 return RESULT_FAILURE;
467 return RESULT_SUCCESS;
470 #define MODLIST_FORMAT "%-30s %-40.40s %-10d\n"
471 #define MODLIST_FORMAT2 "%-30s %-40.40s %-10s\n"
473 AST_MUTEX_DEFINE_STATIC(climodentrylock);
474 static int climodentryfd = -1;
476 static int modlist_modentry(const char *module, const char *description, int usecnt, const char *like)
478 /* Comparing the like with the module */
479 if (strcasestr(module, like) ) {
480 ast_cli(climodentryfd, MODLIST_FORMAT, module, description, usecnt);
481 return 1;
483 return 0;
486 static char modlist_help[] =
487 "Usage: module show [like <keyword>]\n"
488 " Shows Asterisk modules currently in use, and usage statistics.\n";
490 static char uptime_help[] =
491 "Usage: core show uptime [seconds]\n"
492 " Shows Asterisk uptime information.\n"
493 " The seconds word returns the uptime in seconds only.\n";
495 static void print_uptimestr(int fd, time_t timeval, const char *prefix, int printsec)
497 int x; /* the main part - years, weeks, etc. */
498 char timestr[256]="", *s = timestr;
499 size_t maxbytes = sizeof(timestr);
501 #define SECOND (1)
502 #define MINUTE (SECOND*60)
503 #define HOUR (MINUTE*60)
504 #define DAY (HOUR*24)
505 #define WEEK (DAY*7)
506 #define YEAR (DAY*365)
507 #define ESS(x) ((x == 1) ? "" : "s") /* plural suffix */
508 #define NEEDCOMMA(x) ((x)? ",": "") /* define if we need a comma */
509 if (timeval < 0) /* invalid, nothing to show */
510 return;
511 if (printsec) { /* plain seconds output */
512 ast_build_string(&s, &maxbytes, "%lu", (u_long)timeval);
513 timeval = 0; /* bypass the other cases */
515 if (timeval > YEAR) {
516 x = (timeval / YEAR);
517 timeval -= (x * YEAR);
518 ast_build_string(&s, &maxbytes, "%d year%s%s ", x, ESS(x),NEEDCOMMA(timeval));
520 if (timeval > WEEK) {
521 x = (timeval / WEEK);
522 timeval -= (x * WEEK);
523 ast_build_string(&s, &maxbytes, "%d week%s%s ", x, ESS(x),NEEDCOMMA(timeval));
525 if (timeval > DAY) {
526 x = (timeval / DAY);
527 timeval -= (x * DAY);
528 ast_build_string(&s, &maxbytes, "%d day%s%s ", x, ESS(x),NEEDCOMMA(timeval));
530 if (timeval > HOUR) {
531 x = (timeval / HOUR);
532 timeval -= (x * HOUR);
533 ast_build_string(&s, &maxbytes, "%d hour%s%s ", x, ESS(x),NEEDCOMMA(timeval));
535 if (timeval > MINUTE) {
536 x = (timeval / MINUTE);
537 timeval -= (x * MINUTE);
538 ast_build_string(&s, &maxbytes, "%d minute%s%s ", x, ESS(x),NEEDCOMMA(timeval));
540 x = timeval;
541 if (x > 0)
542 ast_build_string(&s, &maxbytes, "%d second%s ", x, ESS(x));
543 if (timestr[0] != '\0')
544 ast_cli(fd, "%s: %s\n", prefix, timestr);
547 static int handle_showuptime_deprecated(int fd, int argc, char *argv[])
549 /* 'show uptime [seconds]' */
550 time_t curtime = time(NULL);
551 int printsec = (argc == 3 && !strcasecmp(argv[2],"seconds"));
553 if (argc != 2 && !printsec)
554 return RESULT_SHOWUSAGE;
555 if (ast_startuptime)
556 print_uptimestr(fd, curtime - ast_startuptime, "System uptime", printsec);
557 if (ast_lastreloadtime)
558 print_uptimestr(fd, curtime - ast_lastreloadtime, "Last reload", printsec);
559 return RESULT_SUCCESS;
562 static int handle_showuptime(int fd, int argc, char *argv[])
564 /* 'core show uptime [seconds]' */
565 time_t curtime = time(NULL);
566 int printsec = (argc == 4 && !strcasecmp(argv[3],"seconds"));
568 if (argc != 3 && !printsec)
569 return RESULT_SHOWUSAGE;
570 if (ast_startuptime)
571 print_uptimestr(fd, curtime - ast_startuptime, "System uptime", printsec);
572 if (ast_lastreloadtime)
573 print_uptimestr(fd, curtime - ast_lastreloadtime, "Last reload", printsec);
574 return RESULT_SUCCESS;
577 static int handle_modlist(int fd, int argc, char *argv[])
579 char *like = "";
580 if (argc == 3)
581 return RESULT_SHOWUSAGE;
582 else if (argc >= 4) {
583 if (strcmp(argv[2],"like"))
584 return RESULT_SHOWUSAGE;
585 like = argv[3];
588 ast_mutex_lock(&climodentrylock);
589 climodentryfd = fd; /* global, protected by climodentrylock */
590 ast_cli(fd, MODLIST_FORMAT2, "Module", "Description", "Use Count");
591 ast_cli(fd,"%d modules loaded\n", ast_update_module_list(modlist_modentry, like));
592 climodentryfd = -1;
593 ast_mutex_unlock(&climodentrylock);
594 return RESULT_SUCCESS;
596 #undef MODLIST_FORMAT
597 #undef MODLIST_FORMAT2
599 #define FORMAT_STRING "%-20.20s %-20.20s %-7.7s %-30.30s\n"
600 #define FORMAT_STRING2 "%-20.20s %-20.20s %-7.7s %-30.30s\n"
601 #define CONCISE_FORMAT_STRING "%s!%s!%s!%d!%s!%s!%s!%s!%s!%d!%s!%s\n"
602 #define VERBOSE_FORMAT_STRING "%-20.20s %-20.20s %-16.16s %4d %-7.7s %-12.12s %-25.25s %-15.15s %8.8s %-11.11s %-20.20s\n"
603 #define VERBOSE_FORMAT_STRING2 "%-20.20s %-20.20s %-16.16s %-4.4s %-7.7s %-12.12s %-25.25s %-15.15s %8.8s %-11.11s %-20.20s\n"
605 static int handle_chanlist_deprecated(int fd, int argc, char *argv[])
607 struct ast_channel *c = NULL;
608 char durbuf[10] = "-";
609 char locbuf[40];
610 char appdata[40];
611 int duration;
612 int durh, durm, durs;
613 int numchans = 0, concise = 0, verbose = 0;
615 concise = (argc == 3 && (!strcasecmp(argv[2],"concise")));
616 verbose = (argc == 3 && (!strcasecmp(argv[2],"verbose")));
618 if (argc < 2 || argc > 3 || (argc == 3 && !concise && !verbose))
619 return RESULT_SHOWUSAGE;
621 if (!concise && !verbose)
622 ast_cli(fd, FORMAT_STRING2, "Channel", "Location", "State", "Application(Data)");
623 else if (verbose)
624 ast_cli(fd, VERBOSE_FORMAT_STRING2, "Channel", "Context", "Extension", "Priority", "State", "Application", "Data",
625 "CallerID", "Duration", "Accountcode", "BridgedTo");
627 while ((c = ast_channel_walk_locked(c)) != NULL) {
628 struct ast_channel *bc = ast_bridged_channel(c);
629 if ((concise || verbose) && c->cdr && !ast_tvzero(c->cdr->start)) {
630 duration = (int)(ast_tvdiff_ms(ast_tvnow(), c->cdr->start) / 1000);
631 if (verbose) {
632 durh = duration / 3600;
633 durm = (duration % 3600) / 60;
634 durs = duration % 60;
635 snprintf(durbuf, sizeof(durbuf), "%02d:%02d:%02d", durh, durm, durs);
636 } else {
637 snprintf(durbuf, sizeof(durbuf), "%d", duration);
639 } else {
640 durbuf[0] = '\0';
642 if (concise) {
643 ast_cli(fd, CONCISE_FORMAT_STRING, c->name, c->context, c->exten, c->priority, ast_state2str(c->_state),
644 c->appl ? c->appl : "(None)",
645 S_OR(c->data, ""), /* XXX different from verbose ? */
646 S_OR(c->cid.cid_num, ""),
647 S_OR(c->accountcode, ""),
648 c->amaflags,
649 durbuf,
650 bc ? bc->name : "(None)");
651 } else if (verbose) {
652 ast_cli(fd, VERBOSE_FORMAT_STRING, c->name, c->context, c->exten, c->priority, ast_state2str(c->_state),
653 c->appl ? c->appl : "(None)",
654 c->data ? S_OR(c->data, "(Empty)" ): "(None)",
655 S_OR(c->cid.cid_num, ""),
656 durbuf,
657 S_OR(c->accountcode, ""),
658 bc ? bc->name : "(None)");
659 } else {
660 if (!ast_strlen_zero(c->context) && !ast_strlen_zero(c->exten))
661 snprintf(locbuf, sizeof(locbuf), "%s@%s:%d", c->exten, c->context, c->priority);
662 else
663 strcpy(locbuf, "(None)");
664 if (c->appl)
665 snprintf(appdata, sizeof(appdata), "%s(%s)", c->appl, c->data ? c->data : "");
666 else
667 strcpy(appdata, "(None)");
668 ast_cli(fd, FORMAT_STRING, c->name, locbuf, ast_state2str(c->_state), appdata);
670 numchans++;
671 ast_channel_unlock(c);
673 if (!concise) {
674 ast_cli(fd, "%d active channel%s\n", numchans, ESS(numchans));
675 if (option_maxcalls)
676 ast_cli(fd, "%d of %d max active call%s (%5.2f%% of capacity)\n",
677 ast_active_calls(), option_maxcalls, ESS(ast_active_calls()),
678 ((double)ast_active_calls() / (double)option_maxcalls) * 100.0);
679 else
680 ast_cli(fd, "%d active call%s\n", ast_active_calls(), ESS(ast_active_calls()));
682 return RESULT_SUCCESS;
685 static int handle_chanlist(int fd, int argc, char *argv[])
687 struct ast_channel *c = NULL;
688 char durbuf[10] = "-";
689 char locbuf[40];
690 char appdata[40];
691 int duration;
692 int durh, durm, durs;
693 int numchans = 0, concise = 0, verbose = 0;
695 concise = (argc == 4 && (!strcasecmp(argv[3],"concise")));
696 verbose = (argc == 4 && (!strcasecmp(argv[3],"verbose")));
698 if (argc < 3 || argc > 4 || (argc == 4 && !concise && !verbose))
699 return RESULT_SHOWUSAGE;
701 if (!concise && !verbose)
702 ast_cli(fd, FORMAT_STRING2, "Channel", "Location", "State", "Application(Data)");
703 else if (verbose)
704 ast_cli(fd, VERBOSE_FORMAT_STRING2, "Channel", "Context", "Extension", "Priority", "State", "Application", "Data",
705 "CallerID", "Duration", "Accountcode", "BridgedTo");
707 while ((c = ast_channel_walk_locked(c)) != NULL) {
708 struct ast_channel *bc = ast_bridged_channel(c);
709 if ((concise || verbose) && c->cdr && !ast_tvzero(c->cdr->start)) {
710 duration = (int)(ast_tvdiff_ms(ast_tvnow(), c->cdr->start) / 1000);
711 if (verbose) {
712 durh = duration / 3600;
713 durm = (duration % 3600) / 60;
714 durs = duration % 60;
715 snprintf(durbuf, sizeof(durbuf), "%02d:%02d:%02d", durh, durm, durs);
716 } else {
717 snprintf(durbuf, sizeof(durbuf), "%d", duration);
719 } else {
720 durbuf[0] = '\0';
722 if (concise) {
723 ast_cli(fd, CONCISE_FORMAT_STRING, c->name, c->context, c->exten, c->priority, ast_state2str(c->_state),
724 c->appl ? c->appl : "(None)",
725 S_OR(c->data, ""), /* XXX different from verbose ? */
726 S_OR(c->cid.cid_num, ""),
727 S_OR(c->accountcode, ""),
728 c->amaflags,
729 durbuf,
730 bc ? bc->name : "(None)");
731 } else if (verbose) {
732 ast_cli(fd, VERBOSE_FORMAT_STRING, c->name, c->context, c->exten, c->priority, ast_state2str(c->_state),
733 c->appl ? c->appl : "(None)",
734 c->data ? S_OR(c->data, "(Empty)" ): "(None)",
735 S_OR(c->cid.cid_num, ""),
736 durbuf,
737 S_OR(c->accountcode, ""),
738 bc ? bc->name : "(None)");
739 } else {
740 if (!ast_strlen_zero(c->context) && !ast_strlen_zero(c->exten))
741 snprintf(locbuf, sizeof(locbuf), "%s@%s:%d", c->exten, c->context, c->priority);
742 else
743 strcpy(locbuf, "(None)");
744 if (c->appl)
745 snprintf(appdata, sizeof(appdata), "%s(%s)", c->appl, c->data ? c->data : "");
746 else
747 strcpy(appdata, "(None)");
748 ast_cli(fd, FORMAT_STRING, c->name, locbuf, ast_state2str(c->_state), appdata);
750 numchans++;
751 ast_channel_unlock(c);
753 if (!concise) {
754 ast_cli(fd, "%d active channel%s\n", numchans, ESS(numchans));
755 if (option_maxcalls)
756 ast_cli(fd, "%d of %d max active call%s (%5.2f%% of capacity)\n",
757 ast_active_calls(), option_maxcalls, ESS(ast_active_calls()),
758 ((double)ast_active_calls() / (double)option_maxcalls) * 100.0);
759 else
760 ast_cli(fd, "%d active call%s\n", ast_active_calls(), ESS(ast_active_calls()));
762 return RESULT_SUCCESS;
765 #undef FORMAT_STRING
766 #undef FORMAT_STRING2
767 #undef CONCISE_FORMAT_STRING
768 #undef VERBOSE_FORMAT_STRING
769 #undef VERBOSE_FORMAT_STRING2
771 static char showchan_help[] =
772 "Usage: core show channel <channel>\n"
773 " Shows lots of information about the specified channel.\n";
775 static char debugchan_help[] =
776 "Usage: core set debug channel <channel> [off]\n"
777 " Enables/disables debugging on a specific channel.\n";
779 static char commandcomplete_help[] =
780 "Usage: _command complete \"<line>\" text state\n"
781 " This function is used internally to help with command completion and should.\n"
782 " never be called by the user directly.\n";
784 static char commandnummatches_help[] =
785 "Usage: _command nummatches \"<line>\" text \n"
786 " This function is used internally to help with command completion and should.\n"
787 " never be called by the user directly.\n";
789 static char commandmatchesarray_help[] =
790 "Usage: _command matchesarray \"<line>\" text \n"
791 " This function is used internally to help with command completion and should.\n"
792 " never be called by the user directly.\n";
794 static int handle_softhangup(int fd, int argc, char *argv[])
796 struct ast_channel *c=NULL;
797 if (argc != 3)
798 return RESULT_SHOWUSAGE;
799 c = ast_get_channel_by_name_locked(argv[2]);
800 if (c) {
801 ast_cli(fd, "Requested Hangup on channel '%s'\n", c->name);
802 ast_softhangup(c, AST_SOFTHANGUP_EXPLICIT);
803 ast_channel_unlock(c);
804 } else
805 ast_cli(fd, "%s is not a known channel\n", argv[2]);
806 return RESULT_SUCCESS;
809 static char *__ast_cli_generator(const char *text, const char *word, int state, int lock);
811 static int handle_commandmatchesarray(int fd, int argc, char *argv[])
813 char *buf, *obuf;
814 int buflen = 2048;
815 int len = 0;
816 char **matches;
817 int x, matchlen;
819 if (argc != 4)
820 return RESULT_SHOWUSAGE;
821 if (!(buf = ast_malloc(buflen)))
822 return RESULT_FAILURE;
823 buf[len] = '\0';
824 matches = ast_cli_completion_matches(argv[2], argv[3]);
825 if (matches) {
826 for (x=0; matches[x]; x++) {
827 matchlen = strlen(matches[x]) + 1;
828 if (len + matchlen >= buflen) {
829 buflen += matchlen * 3;
830 obuf = buf;
831 if (!(buf = ast_realloc(obuf, buflen)))
832 /* Memory allocation failure... Just free old buffer and be done */
833 free(obuf);
835 if (buf)
836 len += sprintf( buf + len, "%s ", matches[x]);
837 free(matches[x]);
838 matches[x] = NULL;
840 free(matches);
843 if (buf) {
844 ast_cli(fd, "%s%s",buf, AST_CLI_COMPLETE_EOF);
845 free(buf);
846 } else
847 ast_cli(fd, "NULL\n");
849 return RESULT_SUCCESS;
854 static int handle_commandnummatches(int fd, int argc, char *argv[])
856 int matches = 0;
858 if (argc != 4)
859 return RESULT_SHOWUSAGE;
861 matches = ast_cli_generatornummatches(argv[2], argv[3]);
863 ast_cli(fd, "%d", matches);
865 return RESULT_SUCCESS;
868 static int handle_commandcomplete(int fd, int argc, char *argv[])
870 char *buf;
872 if (argc != 5)
873 return RESULT_SHOWUSAGE;
874 buf = __ast_cli_generator(argv[2], argv[3], atoi(argv[4]), 0);
875 if (buf) {
876 ast_cli(fd, buf);
877 free(buf);
878 } else
879 ast_cli(fd, "NULL\n");
880 return RESULT_SUCCESS;
883 static int handle_debugchan_deprecated(int fd, int argc, char *argv[])
885 struct ast_channel *c=NULL;
886 int is_all;
888 /* 'debug channel {all|chan_id}' */
889 if (argc != 3)
890 return RESULT_SHOWUSAGE;
892 is_all = !strcasecmp("all", argv[2]);
893 if (is_all) {
894 global_fin |= DEBUGCHAN_FLAG;
895 global_fout |= DEBUGCHAN_FLAG;
896 c = ast_channel_walk_locked(NULL);
897 } else {
898 c = ast_get_channel_by_name_locked(argv[2]);
899 if (c == NULL)
900 ast_cli(fd, "No such channel %s\n", argv[2]);
902 while (c) {
903 if (!(c->fin & DEBUGCHAN_FLAG) || !(c->fout & DEBUGCHAN_FLAG)) {
904 c->fin |= DEBUGCHAN_FLAG;
905 c->fout |= DEBUGCHAN_FLAG;
906 ast_cli(fd, "Debugging enabled on channel %s\n", c->name);
908 ast_channel_unlock(c);
909 if (!is_all)
910 break;
911 c = ast_channel_walk_locked(c);
913 ast_cli(fd, "Debugging on new channels is enabled\n");
914 return RESULT_SUCCESS;
917 static int handle_core_set_debug_channel(int fd, int argc, char *argv[])
919 struct ast_channel *c = NULL;
920 int is_all, is_off = 0;
922 /* 'core set debug channel {all|chan_id}' */
923 if (argc == 6 && strcmp(argv[5], "off") == 0)
924 is_off = 1;
925 else if (argc != 5)
926 return RESULT_SHOWUSAGE;
928 is_all = !strcasecmp("all", argv[4]);
929 if (is_all) {
930 if (is_off) {
931 global_fin &= ~DEBUGCHAN_FLAG;
932 global_fout &= ~DEBUGCHAN_FLAG;
933 } else {
934 global_fin |= DEBUGCHAN_FLAG;
935 global_fout |= DEBUGCHAN_FLAG;
937 c = ast_channel_walk_locked(NULL);
938 } else {
939 c = ast_get_channel_by_name_locked(argv[4]);
940 if (c == NULL)
941 ast_cli(fd, "No such channel %s\n", argv[4]);
943 while (c) {
944 if (!(c->fin & DEBUGCHAN_FLAG) || !(c->fout & DEBUGCHAN_FLAG)) {
945 if (is_off) {
946 c->fin &= ~DEBUGCHAN_FLAG;
947 c->fout &= ~DEBUGCHAN_FLAG;
948 } else {
949 c->fin |= DEBUGCHAN_FLAG;
950 c->fout |= DEBUGCHAN_FLAG;
952 ast_cli(fd, "Debugging %s on channel %s\n", is_off ? "disabled" : "enabled", c->name);
954 ast_channel_unlock(c);
955 if (!is_all)
956 break;
957 c = ast_channel_walk_locked(c);
959 ast_cli(fd, "Debugging on new channels is %s\n", is_off ? "disabled" : "enabled");
960 return RESULT_SUCCESS;
963 static int handle_nodebugchan_deprecated(int fd, int argc, char *argv[])
965 struct ast_channel *c=NULL;
966 int is_all;
967 /* 'no debug channel {all|chan_id}' */
968 if (argc != 4)
969 return RESULT_SHOWUSAGE;
970 is_all = !strcasecmp("all", argv[3]);
971 if (is_all) {
972 global_fin &= ~DEBUGCHAN_FLAG;
973 global_fout &= ~DEBUGCHAN_FLAG;
974 c = ast_channel_walk_locked(NULL);
975 } else {
976 c = ast_get_channel_by_name_locked(argv[3]);
977 if (c == NULL)
978 ast_cli(fd, "No such channel %s\n", argv[3]);
980 while(c) {
981 if ((c->fin & DEBUGCHAN_FLAG) || (c->fout & DEBUGCHAN_FLAG)) {
982 c->fin &= ~DEBUGCHAN_FLAG;
983 c->fout &= ~DEBUGCHAN_FLAG;
984 ast_cli(fd, "Debugging disabled on channel %s\n", c->name);
986 ast_channel_unlock(c);
987 if (!is_all)
988 break;
989 c = ast_channel_walk_locked(c);
991 ast_cli(fd, "Debugging on new channels is disabled\n");
992 return RESULT_SUCCESS;
995 static int handle_showchan_deprecated(int fd, int argc, char *argv[])
997 struct ast_channel *c=NULL;
998 struct timeval now;
999 char buf[2048];
1000 char cdrtime[256];
1001 char nf[256], wf[256], rf[256];
1002 long elapsed_seconds=0;
1003 int hour=0, min=0, sec=0;
1005 if (argc != 3)
1006 return RESULT_SHOWUSAGE;
1007 now = ast_tvnow();
1008 c = ast_get_channel_by_name_locked(argv[2]);
1009 if (!c) {
1010 ast_cli(fd, "%s is not a known channel\n", argv[2]);
1011 return RESULT_SUCCESS;
1013 if(c->cdr) {
1014 elapsed_seconds = now.tv_sec - c->cdr->start.tv_sec;
1015 hour = elapsed_seconds / 3600;
1016 min = (elapsed_seconds % 3600) / 60;
1017 sec = elapsed_seconds % 60;
1018 snprintf(cdrtime, sizeof(cdrtime), "%dh%dm%ds", hour, min, sec);
1019 } else
1020 strcpy(cdrtime, "N/A");
1021 ast_cli(fd,
1022 " -- General --\n"
1023 " Name: %s\n"
1024 " Type: %s\n"
1025 " UniqueID: %s\n"
1026 " Caller ID: %s\n"
1027 " Caller ID Name: %s\n"
1028 " DNID Digits: %s\n"
1029 " State: %s (%d)\n"
1030 " Rings: %d\n"
1031 " NativeFormats: %s\n"
1032 " WriteFormat: %s\n"
1033 " ReadFormat: %s\n"
1034 " WriteTranscode: %s\n"
1035 " ReadTranscode: %s\n"
1036 "1st File Descriptor: %d\n"
1037 " Frames in: %d%s\n"
1038 " Frames out: %d%s\n"
1039 " Time to Hangup: %ld\n"
1040 " Elapsed Time: %s\n"
1041 " Direct Bridge: %s\n"
1042 "Indirect Bridge: %s\n"
1043 " -- PBX --\n"
1044 " Context: %s\n"
1045 " Extension: %s\n"
1046 " Priority: %d\n"
1047 " Call Group: %llu\n"
1048 " Pickup Group: %llu\n"
1049 " Application: %s\n"
1050 " Data: %s\n"
1051 " Blocking in: %s\n",
1052 c->name, c->tech->type, c->uniqueid,
1053 S_OR(c->cid.cid_num, "(N/A)"),
1054 S_OR(c->cid.cid_name, "(N/A)"),
1055 S_OR(c->cid.cid_dnid, "(N/A)"), ast_state2str(c->_state), c->_state, c->rings,
1056 ast_getformatname_multiple(nf, sizeof(nf), c->nativeformats),
1057 ast_getformatname_multiple(wf, sizeof(wf), c->writeformat),
1058 ast_getformatname_multiple(rf, sizeof(rf), c->readformat),
1059 c->writetrans ? "Yes" : "No",
1060 c->readtrans ? "Yes" : "No",
1061 c->fds[0],
1062 c->fin & ~DEBUGCHAN_FLAG, (c->fin & DEBUGCHAN_FLAG) ? " (DEBUGGED)" : "",
1063 c->fout & ~DEBUGCHAN_FLAG, (c->fout & DEBUGCHAN_FLAG) ? " (DEBUGGED)" : "",
1064 (long)c->whentohangup,
1065 cdrtime, c->_bridge ? c->_bridge->name : "<none>", ast_bridged_channel(c) ? ast_bridged_channel(c)->name : "<none>",
1066 c->context, c->exten, c->priority, c->callgroup, c->pickupgroup, ( c->appl ? c->appl : "(N/A)" ),
1067 ( c-> data ? S_OR(c->data, "(Empty)") : "(None)"),
1068 (ast_test_flag(c, AST_FLAG_BLOCKING) ? c->blockproc : "(Not Blocking)"));
1070 if(pbx_builtin_serialize_variables(c,buf,sizeof(buf)))
1071 ast_cli(fd," Variables:\n%s\n",buf);
1072 if(c->cdr && ast_cdr_serialize_variables(c->cdr,buf, sizeof(buf), '=', '\n', 1))
1073 ast_cli(fd," CDR Variables:\n%s\n",buf);
1075 ast_channel_unlock(c);
1076 return RESULT_SUCCESS;
1079 static int handle_showchan(int fd, int argc, char *argv[])
1081 struct ast_channel *c=NULL;
1082 struct timeval now;
1083 char buf[2048];
1084 char cdrtime[256];
1085 char nf[256], wf[256], rf[256];
1086 long elapsed_seconds=0;
1087 int hour=0, min=0, sec=0;
1089 if (argc != 4)
1090 return RESULT_SHOWUSAGE;
1091 now = ast_tvnow();
1092 c = ast_get_channel_by_name_locked(argv[3]);
1093 if (!c) {
1094 ast_cli(fd, "%s is not a known channel\n", argv[3]);
1095 return RESULT_SUCCESS;
1097 if(c->cdr) {
1098 elapsed_seconds = now.tv_sec - c->cdr->start.tv_sec;
1099 hour = elapsed_seconds / 3600;
1100 min = (elapsed_seconds % 3600) / 60;
1101 sec = elapsed_seconds % 60;
1102 snprintf(cdrtime, sizeof(cdrtime), "%dh%dm%ds", hour, min, sec);
1103 } else
1104 strcpy(cdrtime, "N/A");
1105 ast_cli(fd,
1106 " -- General --\n"
1107 " Name: %s\n"
1108 " Type: %s\n"
1109 " UniqueID: %s\n"
1110 " Caller ID: %s\n"
1111 " Caller ID Name: %s\n"
1112 " DNID Digits: %s\n"
1113 " State: %s (%d)\n"
1114 " Rings: %d\n"
1115 " NativeFormats: %s\n"
1116 " WriteFormat: %s\n"
1117 " ReadFormat: %s\n"
1118 " WriteTranscode: %s\n"
1119 " ReadTranscode: %s\n"
1120 "1st File Descriptor: %d\n"
1121 " Frames in: %d%s\n"
1122 " Frames out: %d%s\n"
1123 " Time to Hangup: %ld\n"
1124 " Elapsed Time: %s\n"
1125 " Direct Bridge: %s\n"
1126 "Indirect Bridge: %s\n"
1127 " -- PBX --\n"
1128 " Context: %s\n"
1129 " Extension: %s\n"
1130 " Priority: %d\n"
1131 " Call Group: %llu\n"
1132 " Pickup Group: %llu\n"
1133 " Application: %s\n"
1134 " Data: %s\n"
1135 " Blocking in: %s\n",
1136 c->name, c->tech->type, c->uniqueid,
1137 S_OR(c->cid.cid_num, "(N/A)"),
1138 S_OR(c->cid.cid_name, "(N/A)"),
1139 S_OR(c->cid.cid_dnid, "(N/A)"), ast_state2str(c->_state), c->_state, c->rings,
1140 ast_getformatname_multiple(nf, sizeof(nf), c->nativeformats),
1141 ast_getformatname_multiple(wf, sizeof(wf), c->writeformat),
1142 ast_getformatname_multiple(rf, sizeof(rf), c->readformat),
1143 c->writetrans ? "Yes" : "No",
1144 c->readtrans ? "Yes" : "No",
1145 c->fds[0],
1146 c->fin & ~DEBUGCHAN_FLAG, (c->fin & DEBUGCHAN_FLAG) ? " (DEBUGGED)" : "",
1147 c->fout & ~DEBUGCHAN_FLAG, (c->fout & DEBUGCHAN_FLAG) ? " (DEBUGGED)" : "",
1148 (long)c->whentohangup,
1149 cdrtime, c->_bridge ? c->_bridge->name : "<none>", ast_bridged_channel(c) ? ast_bridged_channel(c)->name : "<none>",
1150 c->context, c->exten, c->priority, c->callgroup, c->pickupgroup, ( c->appl ? c->appl : "(N/A)" ),
1151 ( c-> data ? S_OR(c->data, "(Empty)") : "(None)"),
1152 (ast_test_flag(c, AST_FLAG_BLOCKING) ? c->blockproc : "(Not Blocking)"));
1154 if(pbx_builtin_serialize_variables(c,buf,sizeof(buf)))
1155 ast_cli(fd," Variables:\n%s\n",buf);
1156 if(c->cdr && ast_cdr_serialize_variables(c->cdr,buf, sizeof(buf), '=', '\n', 1))
1157 ast_cli(fd," CDR Variables:\n%s\n",buf);
1159 ast_channel_unlock(c);
1160 return RESULT_SUCCESS;
1164 * helper function to generate CLI matches from a fixed set of values.
1165 * A NULL word is acceptable.
1167 char *ast_cli_complete(const char *word, char *const choices[], int state)
1169 int i, which = 0, len;
1170 len = ast_strlen_zero(word) ? 0 : strlen(word);
1172 for (i = 0; choices[i]; i++) {
1173 if ((!len || !strncasecmp(word, choices[i], len)) && ++which > state)
1174 return ast_strdup(choices[i]);
1176 return NULL;
1179 static char *complete_show_channels_deprecated(const char *line, const char *word, int pos, int state)
1181 static char *choices[] = { "concise", "verbose", NULL };
1183 return (pos != 2) ? NULL : ast_cli_complete(word, choices, state);
1186 static char *complete_show_channels(const char *line, const char *word, int pos, int state)
1188 static char *choices[] = { "concise", "verbose", NULL };
1190 return (pos != 3) ? NULL : ast_cli_complete(word, choices, state);
1193 char *ast_complete_channels(const char *line, const char *word, int pos, int state, int rpos)
1195 struct ast_channel *c = NULL;
1196 int which = 0;
1197 int wordlen;
1198 char notfound = '\0';
1199 char *ret = &notfound; /* so NULL can break the loop */
1201 if (pos != rpos)
1202 return NULL;
1204 wordlen = strlen(word);
1206 while (ret == &notfound && (c = ast_channel_walk_locked(c))) {
1207 if (!strncasecmp(word, c->name, wordlen) && ++which > state)
1208 ret = ast_strdup(c->name);
1209 ast_channel_unlock(c);
1211 return ret == &notfound ? NULL : ret;
1214 static char *complete_ch_3(const char *line, const char *word, int pos, int state)
1216 return ast_complete_channels(line, word, pos, state, 2);
1219 static char *complete_ch_4(const char *line, const char *word, int pos, int state)
1221 return ast_complete_channels(line, word, pos, state, 3);
1224 static char *complete_ch_5(const char *line, const char *word, int pos, int state)
1226 return ast_complete_channels(line, word, pos, state, 4);
1229 static char *complete_mod_2(const char *line, const char *word, int pos, int state)
1231 return ast_module_helper(line, word, pos, state, 1, 1);
1234 static char *complete_mod_3_nr(const char *line, const char *word, int pos, int state)
1236 return ast_module_helper(line, word, pos, state, 2, 0);
1239 static char *complete_mod_3(const char *line, const char *word, int pos, int state)
1241 return ast_module_helper(line, word, pos, state, 2, 1);
1244 static char *complete_mod_4(const char *line, const char *word, int pos, int state)
1246 return ast_module_helper(line, word, pos, state, 3, 0);
1249 static char *complete_fn_2(const char *line, const char *word, int pos, int state)
1251 char *c, *d;
1252 char filename[256];
1254 if (pos != 1)
1255 return NULL;
1257 if (word[0] == '/')
1258 ast_copy_string(filename, word, sizeof(filename));
1259 else
1260 snprintf(filename, sizeof(filename), "%s/%s", ast_config_AST_MODULE_DIR, word);
1262 c = d = filename_completion_function(filename, state);
1264 if (c && word[0] != '/')
1265 c += (strlen(ast_config_AST_MODULE_DIR) + 1);
1266 if (c)
1267 c = strdup(c);
1268 free(d);
1270 return c;
1273 static char *complete_fn_3(const char *line, const char *word, int pos, int state)
1275 char *c, *d;
1276 char filename[256];
1278 if (pos != 2)
1279 return NULL;
1281 if (word[0] == '/')
1282 ast_copy_string(filename, word, sizeof(filename));
1283 else
1284 snprintf(filename, sizeof(filename), "%s/%s", ast_config_AST_MODULE_DIR, word);
1286 c = d = filename_completion_function(filename, state);
1288 if (c && word[0] != '/')
1289 c += (strlen(ast_config_AST_MODULE_DIR) + 1);
1290 if (c)
1291 c = strdup(c);
1292 free(d);
1294 return c;
1297 static int group_show_channels(int fd, int argc, char *argv[])
1299 #define FORMAT_STRING "%-25s %-20s %-20s\n"
1301 struct ast_group_info *gi = NULL;
1302 int numchans = 0;
1303 regex_t regexbuf;
1304 int havepattern = 0;
1306 if (argc < 3 || argc > 4)
1307 return RESULT_SHOWUSAGE;
1309 if (argc == 4) {
1310 if (regcomp(&regexbuf, argv[3], REG_EXTENDED | REG_NOSUB))
1311 return RESULT_SHOWUSAGE;
1312 havepattern = 1;
1315 ast_cli(fd, FORMAT_STRING, "Channel", "Group", "Category");
1317 ast_app_group_list_lock();
1319 gi = ast_app_group_list_head();
1320 while (gi) {
1321 if (!havepattern || !regexec(&regexbuf, gi->group, 0, NULL, 0)) {
1322 ast_cli(fd, FORMAT_STRING, gi->chan->name, gi->group, (ast_strlen_zero(gi->category) ? "(default)" : gi->category));
1323 numchans++;
1325 gi = AST_LIST_NEXT(gi, list);
1328 ast_app_group_list_unlock();
1330 if (havepattern)
1331 regfree(&regexbuf);
1333 ast_cli(fd, "%d active channel%s\n", numchans, (numchans != 1) ? "s" : "");
1334 return RESULT_SUCCESS;
1335 #undef FORMAT_STRING
1338 static int handle_help(int fd, int argc, char *argv[]);
1340 static char * complete_help(const char *text, const char *word, int pos, int state)
1342 /* skip first 4 or 5 chars, "help "*/
1343 int l = strlen(text);
1345 if (l > 5)
1346 l = 5;
1347 text += l;
1348 /* XXX watch out, should stop to the non-generator parts */
1349 return __ast_cli_generator(text, word, state, 0);
1352 /* XXX Nothing in this array can currently be deprecated...
1353 You have to change the way find_cli works in order to remove this array
1354 I recommend doing this eventually...
1356 static struct ast_cli_entry builtins[] = {
1357 /* Keep alphabetized, with longer matches first (example: abcd before abc) */
1358 { { "_command", "complete", NULL },
1359 handle_commandcomplete, "Command complete",
1360 commandcomplete_help },
1362 { { "_command", "nummatches", NULL },
1363 handle_commandnummatches, "Returns number of command matches",
1364 commandnummatches_help },
1366 { { "_command", "matchesarray", NULL },
1367 handle_commandmatchesarray, "Returns command matches array",
1368 commandmatchesarray_help },
1370 { { NULL }, NULL, NULL, NULL }
1373 static struct ast_cli_entry cli_debug_channel_deprecated = {
1374 { "debug", "channel", NULL },
1375 handle_debugchan_deprecated, NULL,
1376 NULL, complete_ch_3 };
1378 static struct ast_cli_entry cli_debug_level_deprecated = {
1379 { "debug", "level", NULL },
1380 handle_debuglevel_deprecated, NULL,
1381 NULL };
1383 static struct ast_cli_entry cli_set_debug_deprecated = {
1384 { "set", "debug", NULL },
1385 handle_set_debug_deprecated, NULL,
1386 NULL, NULL, &cli_debug_level_deprecated };
1388 static struct ast_cli_entry cli_set_verbose_deprecated = {
1389 { "set", "verbose", NULL },
1390 handle_set_verbose_deprecated, NULL,
1391 NULL };
1393 static struct ast_cli_entry cli_show_channel_deprecated = {
1394 { "show", "channel", NULL },
1395 handle_showchan_deprecated, NULL,
1396 NULL, complete_ch_3 };
1398 static struct ast_cli_entry cli_show_channels_deprecated = {
1399 { "show", "channels", NULL },
1400 handle_chanlist_deprecated, NULL,
1401 NULL, complete_show_channels_deprecated };
1403 static struct ast_cli_entry cli_show_modules_deprecated = {
1404 { "show", "modules", NULL },
1405 handle_modlist, NULL,
1406 NULL };
1408 static struct ast_cli_entry cli_show_modules_like_deprecated = {
1409 { "show", "modules", "like", NULL },
1410 handle_modlist, NULL,
1411 NULL, complete_mod_4 };
1413 static struct ast_cli_entry cli_module_load_deprecated = {
1414 { "load", NULL },
1415 handle_load_deprecated, NULL,
1416 NULL, complete_fn_2 };
1418 static struct ast_cli_entry cli_module_reload_deprecated = {
1419 { "reload", NULL },
1420 handle_reload_deprecated, NULL,
1421 NULL, complete_mod_2 };
1423 static struct ast_cli_entry cli_module_unload_deprecated = {
1424 { "unload", NULL },
1425 handle_unload_deprecated, NULL,
1426 NULL, complete_mod_2 };
1428 static struct ast_cli_entry cli_show_uptime_deprecated = {
1429 { "show", "uptime", NULL },
1430 handle_showuptime_deprecated, "Show uptime information",
1431 NULL };
1433 static struct ast_cli_entry cli_cli[] = {
1434 /* Deprecated, but preferred command is now consolidated (and already has a deprecated command for it). */
1435 { { "no", "debug", "channel", NULL },
1436 handle_nodebugchan_deprecated, NULL,
1437 NULL, complete_ch_4 },
1439 { { "core", "show", "channels", NULL },
1440 handle_chanlist, "Display information on channels",
1441 chanlist_help, complete_show_channels, &cli_show_channels_deprecated },
1443 { { "core", "show", "channel", NULL },
1444 handle_showchan, "Display information on a specific channel",
1445 showchan_help, complete_ch_4, &cli_show_channel_deprecated },
1447 { { "core", "set", "debug", "channel", NULL },
1448 handle_core_set_debug_channel, "Enable/disable debugging on a channel",
1449 debugchan_help, complete_ch_5, &cli_debug_channel_deprecated },
1451 { { "core", "set", "debug", NULL },
1452 handle_set_debug, "Set level of debug chattiness",
1453 debug_help, NULL, &cli_set_debug_deprecated },
1455 { { "core", "set", "debug", "off", NULL },
1456 handle_nodebug, "Turns off debug chattiness",
1457 nodebug_help },
1459 { { "core", "set", "verbose", NULL },
1460 handle_verbose, "Set level of verboseness",
1461 verbose_help, NULL, &cli_set_verbose_deprecated },
1463 { { "group", "show", "channels", NULL },
1464 group_show_channels, "Display active channels with group(s)",
1465 group_show_channels_help },
1467 { { "help", NULL },
1468 handle_help, "Display help list, or specific help on a command",
1469 help_help, complete_help },
1471 { { "logger", "mute", NULL },
1472 handle_logger_mute, "Toggle logging output to a console",
1473 logger_mute_help },
1475 { { "module", "show", NULL },
1476 handle_modlist, "List modules and info",
1477 modlist_help, NULL, &cli_show_modules_deprecated },
1479 { { "module", "show", "like", NULL },
1480 handle_modlist, "List modules and info",
1481 modlist_help, complete_mod_4, &cli_show_modules_like_deprecated },
1483 { { "module", "load", NULL },
1484 handle_load, "Load a module by name",
1485 load_help, complete_fn_3, &cli_module_load_deprecated },
1487 { { "module", "reload", NULL },
1488 handle_reload, "Reload configuration",
1489 reload_help, complete_mod_3, &cli_module_reload_deprecated },
1491 { { "module", "unload", NULL },
1492 handle_unload, "Unload a module by name",
1493 unload_help, complete_mod_3_nr, &cli_module_unload_deprecated },
1495 { { "core", "show", "uptime", NULL },
1496 handle_showuptime, "Show uptime information",
1497 uptime_help, NULL, &cli_show_uptime_deprecated },
1499 { { "soft", "hangup", NULL },
1500 handle_softhangup, "Request a hangup on a given channel",
1501 softhangup_help, complete_ch_3 },
1504 /*! \brief initialize the _full_cmd string in * each of the builtins. */
1505 void ast_builtins_init(void)
1507 struct ast_cli_entry *e;
1509 for (e = builtins; e->cmda[0] != NULL; e++) {
1510 char buf[80];
1511 ast_join(buf, sizeof(buf), e->cmda);
1512 e->_full_cmd = strdup(buf);
1513 if (!e->_full_cmd)
1514 ast_log(LOG_WARNING, "-- cannot allocate <%s>\n", buf);
1517 ast_cli_register_multiple(cli_cli, sizeof(cli_cli) / sizeof(struct ast_cli_entry));
1521 * We have two sets of commands: builtins are stored in a
1522 * NULL-terminated array of ast_cli_entry, whereas external
1523 * commands are in a list.
1524 * When navigating, we need to keep two pointers and get
1525 * the next one in lexicographic order. For the purpose,
1526 * we use a structure.
1529 struct cli_iterator {
1530 struct ast_cli_entry *builtins;
1531 struct ast_cli_entry *helpers;
1534 static struct ast_cli_entry *cli_next(struct cli_iterator *i)
1536 struct ast_cli_entry *e;
1538 if (i->builtins == NULL && i->helpers == NULL) {
1539 /* initialize */
1540 i->builtins = builtins;
1541 i->helpers = AST_LIST_FIRST(&helpers);
1543 e = i->builtins; /* temporary */
1544 if (!e->cmda[0] || (i->helpers &&
1545 strcmp(i->helpers->_full_cmd, e->_full_cmd) < 0)) {
1546 /* Use helpers */
1547 e = i->helpers;
1548 if (e)
1549 i->helpers = AST_LIST_NEXT(e, list);
1550 } else { /* use builtin. e is already set */
1551 (i->builtins)++; /* move to next */
1553 return e;
1557 * \brief locate a cli command in the 'helpers' list (which must be locked).
1558 * exact has 3 values:
1559 * 0 returns if the search key is equal or longer than the entry.
1560 * -1 true if the mismatch is on the last word XXX not true!
1561 * 1 true only on complete, exact match.
1563 static struct ast_cli_entry *find_cli(char *const cmds[], int match_type)
1565 int matchlen = -1; /* length of longest match so far */
1566 struct ast_cli_entry *cand = NULL, *e=NULL;
1567 struct cli_iterator i = { NULL, NULL};
1569 while( (e = cli_next(&i)) ) {
1570 int y;
1571 for (y = 0 ; cmds[y] && e->cmda[y]; y++) {
1572 if (strcasecmp(e->cmda[y], cmds[y]))
1573 break;
1575 if (e->cmda[y] == NULL) { /* no more words in candidate */
1576 if (cmds[y] == NULL) /* this is an exact match, cannot do better */
1577 break;
1578 /* here the search key is longer than the candidate */
1579 if (match_type != 0) /* but we look for almost exact match... */
1580 continue; /* so we skip this one. */
1581 /* otherwise we like it (case 0) */
1582 } else { /* still words in candidate */
1583 if (cmds[y] == NULL) /* search key is shorter, not good */
1584 continue;
1585 /* if we get here, both words exist but there is a mismatch */
1586 if (match_type == 0) /* not the one we look for */
1587 continue;
1588 if (match_type == 1) /* not the one we look for */
1589 continue;
1590 if (cmds[y+1] != NULL || e->cmda[y+1] != NULL) /* not the one we look for */
1591 continue;
1592 /* we are in case match_type == -1 and mismatch on last word */
1594 if (y > matchlen) { /* remember the candidate */
1595 matchlen = y;
1596 cand = e;
1599 return e ? e : cand;
1602 static char *find_best(char *argv[])
1604 static char cmdline[80];
1605 int x;
1606 /* See how close we get, then print the candidate */
1607 char *myargv[AST_MAX_CMD_LEN];
1608 for (x=0;x<AST_MAX_CMD_LEN;x++)
1609 myargv[x]=NULL;
1610 AST_LIST_LOCK(&helpers);
1611 for (x=0;argv[x];x++) {
1612 myargv[x] = argv[x];
1613 if (!find_cli(myargv, -1))
1614 break;
1616 AST_LIST_UNLOCK(&helpers);
1617 ast_join(cmdline, sizeof(cmdline), myargv);
1618 return cmdline;
1621 static int __ast_cli_unregister(struct ast_cli_entry *e, struct ast_cli_entry *ed)
1623 if (e->deprecate_cmd) {
1624 __ast_cli_unregister(e->deprecate_cmd, e);
1626 if (e->inuse) {
1627 ast_log(LOG_WARNING, "Can't remove command that is in use\n");
1628 } else {
1629 AST_LIST_LOCK(&helpers);
1630 AST_LIST_REMOVE(&helpers, e, list);
1631 AST_LIST_UNLOCK(&helpers);
1632 free(e->_full_cmd);
1634 return 0;
1637 static int __ast_cli_register(struct ast_cli_entry *e, struct ast_cli_entry *ed)
1639 struct ast_cli_entry *cur;
1640 char fulle[80] ="";
1641 int lf, ret = -1;
1643 ast_join(fulle, sizeof(fulle), e->cmda);
1644 AST_LIST_LOCK(&helpers);
1646 if (find_cli(e->cmda, 1)) {
1647 ast_log(LOG_WARNING, "Command '%s' already registered (or something close enough)\n", fulle);
1648 goto done;
1650 e->_full_cmd = ast_strdup(fulle);
1651 if (!e->_full_cmd)
1652 goto done;
1654 if (ed) {
1655 e->deprecated = 1;
1656 e->summary = ed->summary;
1657 e->usage = ed->usage;
1658 /* XXX If command A deprecates command B, and command B deprecates command C...
1659 Do we want to show command A or command B when telling the user to use new syntax?
1660 This currently would show command A.
1661 To show command B, you just need to always use ed->_full_cmd.
1663 e->_deprecated_by = S_OR(ed->_deprecated_by, ed->_full_cmd);
1664 } else {
1665 e->deprecated = 0;
1668 lf = strlen(fulle);
1669 AST_LIST_TRAVERSE_SAFE_BEGIN(&helpers, cur, list) {
1670 int len = strlen(cur->_full_cmd);
1671 if (lf < len)
1672 len = lf;
1673 if (strncasecmp(fulle, cur->_full_cmd, len) < 0) {
1674 AST_LIST_INSERT_BEFORE_CURRENT(&helpers, e, list);
1675 break;
1678 AST_LIST_TRAVERSE_SAFE_END;
1680 if (!cur)
1681 AST_LIST_INSERT_TAIL(&helpers, e, list);
1682 ret = 0; /* success */
1684 done:
1685 AST_LIST_UNLOCK(&helpers);
1687 if (e->deprecate_cmd) {
1688 /* This command deprecates another command. Register that one also. */
1689 __ast_cli_register(e->deprecate_cmd, e);
1692 return ret;
1695 /* wrapper function, so we can unregister deprecated commands recursively */
1696 int ast_cli_unregister(struct ast_cli_entry *e)
1698 return __ast_cli_unregister(e, NULL);
1701 /* wrapper function, so we can register deprecated commands recursively */
1702 int ast_cli_register(struct ast_cli_entry *e)
1704 return __ast_cli_register(e, NULL);
1708 * register/unregister an array of entries.
1710 void ast_cli_register_multiple(struct ast_cli_entry *e, int len)
1712 int i;
1714 for (i = 0; i < len; i++)
1715 ast_cli_register(e + i);
1718 void ast_cli_unregister_multiple(struct ast_cli_entry *e, int len)
1720 int i;
1722 for (i = 0; i < len; i++)
1723 ast_cli_unregister(e + i);
1727 /*! \brief helper for help_workhorse and final part of handle_help
1728 * if locked = 0 it's just help_workhorse, otherwise assume the
1729 * list is already locked.
1731 static int help1(int fd, char *match[], int locked)
1733 char matchstr[80] = "";
1734 struct ast_cli_entry *e;
1735 int len = 0;
1736 int found = 0;
1737 struct cli_iterator i = { NULL, NULL};
1739 if (match) {
1740 ast_join(matchstr, sizeof(matchstr), match);
1741 len = strlen(matchstr);
1743 if (!locked)
1744 AST_LIST_LOCK(&helpers);
1745 while ( (e = cli_next(&i)) ) {
1746 /* Hide commands that start with '_' */
1747 if (e->_full_cmd[0] == '_')
1748 continue;
1749 /* Hide commands that are marked as deprecated. */
1750 if (e->deprecated)
1751 continue;
1752 if (match && strncasecmp(matchstr, e->_full_cmd, len))
1753 continue;
1754 ast_cli(fd, "%25.25s %s\n", e->_full_cmd, S_OR(e->summary, ""));
1755 found++;
1757 if (!locked)
1758 AST_LIST_UNLOCK(&helpers);
1759 if (!found && matchstr[0])
1760 ast_cli(fd, "No such command '%s'.\n", matchstr);
1761 return RESULT_SUCCESS;
1764 static int help_workhorse(int fd, char *match[])
1766 return help1(fd, match, 0 /* do not print errors */);
1769 static int handle_help(int fd, int argc, char *argv[])
1771 char fullcmd[80];
1772 struct ast_cli_entry *e;
1773 int res = RESULT_SUCCESS;
1775 if (argc < 1)
1776 return RESULT_SHOWUSAGE;
1777 if (argc == 1)
1778 return help_workhorse(fd, NULL);
1780 AST_LIST_LOCK(&helpers);
1781 e = find_cli(argv + 1, 1); /* try exact match first */
1782 if (!e) {
1783 res = help1(fd, argv + 1, 1 /* locked */);
1784 AST_LIST_UNLOCK(&helpers);
1785 return res;
1787 if (e->usage)
1788 ast_cli(fd, "%s", e->usage);
1789 else {
1790 ast_join(fullcmd, sizeof(fullcmd), argv+1);
1791 ast_cli(fd, "No help text available for '%s'.\n", fullcmd);
1793 AST_LIST_UNLOCK(&helpers);
1794 return res;
1797 static char *parse_args(const char *s, int *argc, char *argv[], int max, int *trailingwhitespace)
1799 char *dup, *cur;
1800 int x = 0;
1801 int quoted = 0;
1802 int escaped = 0;
1803 int whitespace = 1;
1805 *trailingwhitespace = 0;
1806 if (s == NULL) /* invalid, though! */
1807 return NULL;
1808 /* make a copy to store the parsed string */
1809 if (!(dup = ast_strdup(s)))
1810 return NULL;
1812 cur = dup;
1813 /* scan the original string copying into cur when needed */
1814 for (; *s ; s++) {
1815 if (x >= max - 1) {
1816 ast_log(LOG_WARNING, "Too many arguments, truncating at %s\n", s);
1817 break;
1819 if (*s == '"' && !escaped) {
1820 quoted = !quoted;
1821 if (quoted && whitespace) {
1822 /* start a quoted string from previous whitespace: new argument */
1823 argv[x++] = cur;
1824 whitespace = 0;
1826 } else if ((*s == ' ' || *s == '\t') && !(quoted || escaped)) {
1827 /* If we are not already in whitespace, and not in a quoted string or
1828 processing an escape sequence, and just entered whitespace, then
1829 finalize the previous argument and remember that we are in whitespace
1831 if (!whitespace) {
1832 *cur++ = '\0';
1833 whitespace = 1;
1835 } else if (*s == '\\' && !escaped) {
1836 escaped = 1;
1837 } else {
1838 if (whitespace) {
1839 /* we leave whitespace, and are not quoted. So it's a new argument */
1840 argv[x++] = cur;
1841 whitespace = 0;
1843 *cur++ = *s;
1844 escaped = 0;
1847 /* Null terminate */
1848 *cur++ = '\0';
1849 /* XXX put a NULL in the last argument, because some functions that take
1850 * the array may want a null-terminated array.
1851 * argc still reflects the number of non-NULL entries.
1853 argv[x] = NULL;
1854 *argc = x;
1855 *trailingwhitespace = whitespace;
1856 return dup;
1859 /*! \brief Return the number of unique matches for the generator */
1860 int ast_cli_generatornummatches(const char *text, const char *word)
1862 int matches = 0, i = 0;
1863 char *buf = NULL, *oldbuf = NULL;
1865 while ((buf = ast_cli_generator(text, word, i++))) {
1866 if (!oldbuf || strcmp(buf,oldbuf))
1867 matches++;
1868 if (oldbuf)
1869 free(oldbuf);
1870 oldbuf = buf;
1872 if (oldbuf)
1873 free(oldbuf);
1874 return matches;
1877 char **ast_cli_completion_matches(const char *text, const char *word)
1879 char **match_list = NULL, *retstr, *prevstr;
1880 size_t match_list_len, max_equal, which, i;
1881 int matches = 0;
1883 /* leave entry 0 free for the longest common substring */
1884 match_list_len = 1;
1885 while ((retstr = ast_cli_generator(text, word, matches)) != NULL) {
1886 if (matches + 1 >= match_list_len) {
1887 match_list_len <<= 1;
1888 if (!(match_list = ast_realloc(match_list, match_list_len * sizeof(*match_list))))
1889 return NULL;
1891 match_list[++matches] = retstr;
1894 if (!match_list)
1895 return match_list; /* NULL */
1897 /* Find the longest substring that is common to all results
1898 * (it is a candidate for completion), and store a copy in entry 0.
1900 prevstr = match_list[1];
1901 max_equal = strlen(prevstr);
1902 for (which = 2; which <= matches; which++) {
1903 for (i = 0; i < max_equal && toupper(prevstr[i]) == toupper(match_list[which][i]); i++)
1904 continue;
1905 max_equal = i;
1908 if (!(retstr = ast_malloc(max_equal + 1)))
1909 return NULL;
1911 ast_copy_string(retstr, match_list[1], max_equal + 1);
1912 match_list[0] = retstr;
1914 /* ensure that the array is NULL terminated */
1915 if (matches + 1 >= match_list_len) {
1916 if (!(match_list = ast_realloc(match_list, (match_list_len + 1) * sizeof(*match_list))))
1917 return NULL;
1919 match_list[matches + 1] = NULL;
1921 return match_list;
1924 static char *__ast_cli_generator(const char *text, const char *word, int state, int lock)
1926 char *argv[AST_MAX_ARGS];
1927 struct ast_cli_entry *e;
1928 struct cli_iterator i = { NULL, NULL };
1929 int x = 0, argindex, matchlen;
1930 int matchnum=0;
1931 char *ret = NULL;
1932 char matchstr[80] = "";
1933 int tws = 0;
1934 char *dup = parse_args(text, &x, argv, sizeof(argv) / sizeof(argv[0]), &tws);
1936 if (!dup) /* error */
1937 return NULL;
1938 argindex = (!ast_strlen_zero(word) && x>0) ? x-1 : x;
1939 /* rebuild the command, ignore tws */
1940 ast_join(matchstr, sizeof(matchstr)-1, argv);
1941 matchlen = strlen(matchstr);
1942 if (tws) {
1943 strcat(matchstr, " "); /* XXX */
1944 if (matchlen)
1945 matchlen++;
1947 if (lock)
1948 AST_LIST_LOCK(&helpers);
1949 while( !ret && (e = cli_next(&i)) ) {
1950 int lc = strlen(e->_full_cmd);
1951 if (e->_full_cmd[0] != '_' && lc > 0 && matchlen <= lc &&
1952 !strncasecmp(matchstr, e->_full_cmd, matchlen)) {
1953 /* Found initial part, return a copy of the next word... */
1954 if (e->cmda[argindex] && ++matchnum > state)
1955 ret = strdup(e->cmda[argindex]); /* we need a malloced string */
1956 } else if (e->generator && !strncasecmp(matchstr, e->_full_cmd, lc) && matchstr[lc] < 33) {
1957 /* We have a command in its entirity within us -- theoretically only one
1958 command can have this occur */
1959 ret = e->generator(matchstr, word, argindex, state);
1962 if (lock)
1963 AST_LIST_UNLOCK(&helpers);
1964 free(dup);
1965 return ret;
1968 char *ast_cli_generator(const char *text, const char *word, int state)
1970 return __ast_cli_generator(text, word, state, 1);
1973 int ast_cli_command(int fd, const char *s)
1975 char *argv[AST_MAX_ARGS];
1976 struct ast_cli_entry *e;
1977 int x;
1978 char *dup;
1979 int tws;
1981 if (!(dup = parse_args(s, &x, argv, sizeof(argv) / sizeof(argv[0]), &tws)))
1982 return -1;
1984 /* We need at least one entry, or ignore */
1985 if (x > 0) {
1986 AST_LIST_LOCK(&helpers);
1987 e = find_cli(argv, 0);
1988 if (e)
1989 e->inuse++;
1990 AST_LIST_UNLOCK(&helpers);
1991 if (e) {
1992 switch(e->handler(fd, x, argv)) {
1993 case RESULT_SHOWUSAGE:
1994 if (e->usage)
1995 ast_cli(fd, "%s", e->usage);
1996 else
1997 ast_cli(fd, "Invalid usage, but no usage information available.\n");
1998 AST_LIST_LOCK(&helpers);
1999 if (e->deprecated)
2000 ast_cli(fd, "The '%s' command is deprecated and will be removed in a future release. Please use '%s' instead.\n", e->_full_cmd, e->_deprecated_by);
2001 AST_LIST_UNLOCK(&helpers);
2002 break;
2003 default:
2004 AST_LIST_LOCK(&helpers);
2005 if (e->deprecated == 1) {
2006 ast_cli(fd, "The '%s' command is deprecated and will be removed in a future release. Please use '%s' instead.\n", e->_full_cmd, e->_deprecated_by);
2007 e->deprecated = 2;
2009 AST_LIST_UNLOCK(&helpers);
2010 break;
2012 } else
2013 ast_cli(fd, "No such command '%s' (type 'help %s' for other possible commands)\n", s, find_best(argv));
2014 if (e)
2015 ast_atomic_fetchadd_int(&e->inuse, -1);
2017 free(dup);
2019 return 0;
2022 int ast_cli_command_multiple(int fd, size_t size, const char *s)
2024 char cmd[512];
2025 int x, y = 0, count = 0;
2027 for (x = 0; x < size; x++) {
2028 cmd[y] = s[x];
2029 y++;
2030 if (s[x] == '\0') {
2031 ast_cli_command(fd, cmd);
2032 y = 0;
2033 count++;
2036 return count;