Don't try to look offhook with channel banks & Loopstart (bug #2362), also make indiv...
[asterisk/asterisk.git] / cli.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Standard Command Line Interface
5  * 
6  * Copyright (C) 1999, Mark Spencer
7  *
8  * Mark Spencer <markster@linux-support.net>
9  *
10  * This program is free software, distributed under the terms of
11  * the GNU General Public License
12  */
13
14 #include <unistd.h>
15 #include <stdlib.h>
16 #include <asterisk/logger.h>
17 #include <asterisk/options.h>
18 #include <asterisk/cli.h>
19 #include <asterisk/module.h>
20 #include <asterisk/channel.h>
21 #include <asterisk/channel_pvt.h>
22 #include <asterisk/manager.h>
23 #include <asterisk/utils.h>
24 #include <asterisk/lock.h>
25 #include <sys/signal.h>
26 #include <stdio.h>
27 #include <signal.h>
28 #include <string.h>
29 /* For rl_filename_completion */
30 #include "editline/readline/readline.h"
31 /* For module directory */
32 #include "asterisk.h"
33 #include "build.h"
34 #include "astconf.h"
35
36 #define VERSION_INFO "Asterisk " ASTERISK_VERSION " built by " BUILD_USER "@" BUILD_HOSTNAME \
37         " on a " BUILD_MACHINE " running " BUILD_OS
38         
39 void ast_cli(int fd, char *fmt, ...)
40 {
41         char *stuff;
42         int res = 0;
43
44         va_list ap;
45         va_start(ap, fmt);
46         res = vasprintf(&stuff, fmt, ap);
47         va_end(ap);
48         if (res == -1) {
49                 ast_log(LOG_ERROR, "Out of memory\n");
50         } else {
51                 ast_carefulwrite(fd, stuff, strlen(stuff), 100);
52                 free(stuff);
53         }
54 }
55
56 AST_MUTEX_DEFINE_STATIC(clilock);
57
58 struct ast_cli_entry *helpers = NULL;
59
60 static char load_help[] = 
61 "Usage: load <module name>\n"
62 "       Loads the specified module into Asterisk.\n";
63
64 static char unload_help[] = 
65 "Usage: unload [-f|-h] <module name>\n"
66 "       Unloads the specified module from Asterisk.  The -f\n"
67 "       option causes the module to be unloaded even if it is\n"
68 "       in use (may cause a crash) and the -h module causes the\n"
69 "       module to be unloaded even if the module says it cannot, \n"
70 "       which almost always will cause a crash.\n";
71
72 static char help_help[] =
73 "Usage: help [topic]\n"
74 "       When called with a topic as an argument, displays usage\n"
75 "       information on the given command.  If called without a\n"
76 "       topic, it provides a list of commands.\n";
77
78 static char chanlist_help[] = 
79 "Usage: show channels [concise]\n"
80 "       Lists currently defined channels and some information about\n"
81 "       them.  If 'concise' is specified, format is abridged and in\n"
82 "       a more easily machine parsable format\n";
83
84 static char reload_help[] = 
85 "Usage: reload\n"
86 "       Reloads configuration files for all modules which support\n"
87 "       reloading.\n";
88
89 static char set_verbose_help[] = 
90 "Usage: set verbose <level>\n"
91 "       Sets level of verbose messages to be displayed.  0 means\n"
92 "       no messages should be displayed.\n";
93
94 static char softhangup_help[] =
95 "Usage: soft hangup <channel>\n"
96 "       Request that a channel be hung up.  The hangup takes effect\n"
97 "       the next time the driver reads or writes from the channel\n";
98
99 static int handle_load(int fd, int argc, char *argv[])
100 {
101         if (argc != 2)
102                 return RESULT_SHOWUSAGE;
103         if (ast_load_resource(argv[1])) {
104                 ast_cli(fd, "Unable to load module %s\n", argv[1]);
105                 return RESULT_FAILURE;
106         }
107         return RESULT_SUCCESS;
108 }
109
110 static int handle_reload(int fd, int argc, char *argv[])
111 {
112         int x;
113         if (argc < 1)
114                 return RESULT_SHOWUSAGE;
115         if (argc > 1) { 
116                 for (x=1;x<argc;x++) 
117                         ast_module_reload(argv[x]);
118         } else
119                 ast_module_reload(NULL);
120         return RESULT_SUCCESS;
121 }
122
123 static int handle_set_verbose(int fd, int argc, char *argv[])
124 {
125         int val;
126         /* Has a hidden 'at least' argument */
127         if ((argc != 3) && (argc != 4))
128                 return RESULT_SHOWUSAGE;
129         if ((argc == 4) && strcasecmp(argv[2], "atleast"))
130                 return RESULT_SHOWUSAGE;
131         if (argc == 3)
132                 option_verbose = atoi(argv[2]);
133         else {
134                 val = atoi(argv[3]);
135                 if (val > option_verbose)
136                         option_verbose = val;
137         }
138         return RESULT_SUCCESS;
139 }
140
141 static int handle_unload(int fd, int argc, char *argv[])
142 {
143         int x;
144         int force=AST_FORCE_SOFT;
145         if (argc < 2)
146                 return RESULT_SHOWUSAGE;
147         for (x=1;x<argc;x++) {
148                 if (argv[x][0] == '-') {
149                         switch(argv[x][1]) {
150                         case 'f':
151                                 force = AST_FORCE_FIRM;
152                                 break;
153                         case 'h':
154                                 force = AST_FORCE_HARD;
155                                 break;
156                         default:
157                                 return RESULT_SHOWUSAGE;
158                         }
159                 } else if (x !=  argc - 1) 
160                         return RESULT_SHOWUSAGE;
161                 else if (ast_unload_resource(argv[x], force)) {
162                         ast_cli(fd, "Unable to unload resource %s\n", argv[x]);
163                         return RESULT_FAILURE;
164                 }
165         }
166         return RESULT_SUCCESS;
167 }
168
169 #define MODLIST_FORMAT  "%-25s %-40.40s %-10d\n"
170 #define MODLIST_FORMAT2 "%-25s %-40.40s %-10s\n"
171
172 AST_MUTEX_DEFINE_STATIC(climodentrylock);
173 static int climodentryfd = -1;
174
175 static int modlist_modentry(char *module, char *description, int usecnt)
176 {
177         ast_cli(climodentryfd, MODLIST_FORMAT, module, description, usecnt);
178         return 0;
179 }
180
181 static char modlist_help[] =
182 "Usage: show modules\n"
183 "       Shows Asterisk modules currently in use, and usage "
184 "statistics.\n";
185
186 static char version_help[] =
187 "Usage: show version\n"
188 "       Shows Asterisk version information.\n ";
189
190 static char *format_uptimestr(time_t timeval)
191 {
192         int years = 0, weeks = 0, days = 0, hours = 0, mins = 0, secs = 0;
193         char timestr[256]="";
194         int bytes = 0;
195         int maxbytes = 0;
196         int offset = 0;
197 #define SECOND (1)
198 #define MINUTE (SECOND*60)
199 #define HOUR (MINUTE*60)
200 #define DAY (HOUR*24)
201 #define WEEK (DAY*7)
202 #define YEAR (DAY*365)
203 #define ESS(x) ((x == 1) ? "" : "s")
204
205         maxbytes = sizeof(timestr);
206         if (timeval < 0)
207                 return NULL;
208         if (timeval > YEAR) {
209                 years = (timeval / YEAR);
210                 timeval -= (years * YEAR);
211                 if (years > 0) {
212                         snprintf(timestr + offset, maxbytes, "%d year%s, ", years, ESS(years));
213                         bytes = strlen(timestr + offset);
214                         offset += bytes;
215                         maxbytes -= bytes;
216                 }
217         }
218         if (timeval > WEEK) {
219                 weeks = (timeval / WEEK);
220                 timeval -= (weeks * WEEK);
221                 if (weeks > 0) {
222                         snprintf(timestr + offset, maxbytes, "%d week%s, ", weeks, ESS(weeks));
223                         bytes = strlen(timestr + offset);
224                         offset += bytes;
225                         maxbytes -= bytes;
226                 }
227         }
228         if (timeval > DAY) {
229                 days = (timeval / DAY);
230                 timeval -= (days * DAY);
231                 if (days > 0) {
232                         snprintf(timestr + offset, maxbytes, "%d day%s, ", days, ESS(days));
233                         bytes = strlen(timestr + offset);
234                         offset += bytes;
235                         maxbytes -= bytes;
236                 }
237         }
238         if (timeval > HOUR) {
239                 hours = (timeval / HOUR);
240                 timeval -= (hours * HOUR);
241                 if (hours > 0) {
242                         snprintf(timestr + offset, maxbytes, "%d hour%s, ", hours, ESS(hours));
243                         bytes = strlen(timestr + offset);
244                         offset += bytes;
245                         maxbytes -= bytes;
246                 }
247         }
248         if (timeval > MINUTE) {
249                 mins = (timeval / MINUTE);
250                 timeval -= (mins * MINUTE);
251                 if (mins > 0) {
252                         snprintf(timestr + offset, maxbytes, "%d minute%s, ", mins, ESS(mins));
253                         bytes = strlen(timestr + offset);
254                         offset += bytes;
255                         maxbytes -= bytes;
256                 }
257         }
258         secs = timeval;
259
260         if (secs > 0) {
261                 snprintf(timestr + offset, maxbytes, "%d second%s", secs, ESS(secs));
262         }
263
264         return timestr ? strdup(timestr) : NULL;
265 }
266
267 static int handle_showuptime(int fd, int argc, char *argv[])
268 {
269         time_t curtime, tmptime;
270         char *timestr;
271
272         time(&curtime);
273         if (ast_startuptime) {
274                 tmptime = curtime - ast_startuptime;
275                 timestr = format_uptimestr(tmptime);
276                 if (timestr) {
277                         ast_cli(fd, "System uptime: %s\n", timestr);
278                         free(timestr);
279                 }
280         }               
281         if (ast_lastreloadtime) {
282                 tmptime = curtime - ast_lastreloadtime;
283                 timestr = format_uptimestr(tmptime);
284                 if (timestr) {
285                         ast_cli(fd, "Last reload: %s\n", timestr);
286                         free(timestr);
287                 }
288         }
289         return RESULT_SUCCESS;
290 }
291
292 static int handle_modlist(int fd, int argc, char *argv[])
293 {
294         if (argc != 2)
295                 return RESULT_SHOWUSAGE;
296         ast_mutex_lock(&climodentrylock);
297         climodentryfd = fd;
298         ast_cli(fd, MODLIST_FORMAT2, "Module", "Description", "Use Count");
299         ast_update_module_list(modlist_modentry);
300         climodentryfd = -1;
301         ast_mutex_unlock(&climodentrylock);
302         return RESULT_SUCCESS;
303 }
304
305 static int handle_version(int fd, int argc, char *argv[])
306 {
307         if (argc != 2)
308                 return RESULT_SHOWUSAGE;
309         ast_cli(fd, "%s\n", VERSION_INFO);
310         return RESULT_SUCCESS;
311 }
312 static int handle_chanlist(int fd, int argc, char *argv[])
313 {
314 #define FORMAT_STRING  "%15s  (%-10s %-12s %-4d) %7s %-12s  %-15s\n"
315 #define FORMAT_STRING2 "%15s  (%-10s %-12s %-4s) %7s %-12s  %-15s\n"
316 #define CONCISE_FORMAT_STRING  "%s:%s:%s:%d:%s:%s:%s:%s:%s:%d\n"
317
318         struct ast_channel *c=NULL;
319         int numchans = 0;
320         int concise = 0;
321         if (argc < 2 || argc > 3)
322                 return RESULT_SHOWUSAGE;
323         
324         concise = (argc == 3 && (!strcasecmp(argv[2],"concise")));
325         c = ast_channel_walk_locked(NULL);
326         if(!concise)
327                 ast_cli(fd, FORMAT_STRING2, "Channel", "Context", "Extension", "Pri", "State", "Appl.", "Data");
328         while(c) {
329                 if(concise)
330                         ast_cli(fd, CONCISE_FORMAT_STRING, c->name, c->context, c->exten, c->priority, ast_state2str(c->_state),
331                                         c->appl ? c->appl : "(None)", c->data ? ( !ast_strlen_zero(c->data) ? c->data : "" ): "",
332                                         (c->callerid && !ast_strlen_zero(c->callerid)) ? c->callerid : "",
333                                         (c->accountcode && !ast_strlen_zero(c->accountcode)) ? c->accountcode : "",c->amaflags);
334                 else
335                         ast_cli(fd, FORMAT_STRING, c->name, c->context, c->exten, c->priority, ast_state2str(c->_state),
336                                         c->appl ? c->appl : "(None)", c->data ? ( !ast_strlen_zero(c->data) ? c->data : "(Empty)" ): "(None)");
337
338                 numchans++;
339                 ast_mutex_unlock(&c->lock);
340                 c = ast_channel_walk_locked(c);
341         }
342         if(!concise)
343                 ast_cli(fd, "%d active channel(s)\n", numchans);
344         return RESULT_SUCCESS;
345 }
346
347 static char showchan_help[] = 
348 "Usage: show channel <channel>\n"
349 "       Shows lots of information about the specified channel.\n";
350
351 static char debugchan_help[] = 
352 "Usage: debug channel <channel>\n"
353 "       Enables debugging on a specific channel.\n";
354
355 static char nodebugchan_help[] = 
356 "Usage: no debug channel <channel>\n"
357 "       Disables debugging on a specific channel.\n";
358
359 static char commandcomplete_help[] = 
360 "Usage: _command complete \"<line>\" text state\n"
361 "       This function is used internally to help with command completion and should.\n"
362 "       never be called by the user directly.\n";
363
364 static char commandnummatches_help[] = 
365 "Usage: _command nummatches \"<line>\" text \n"
366 "       This function is used internally to help with command completion and should.\n"
367 "       never be called by the user directly.\n";
368
369 static char commandmatchesarray_help[] = 
370 "Usage: _command matchesarray \"<line>\" text \n"
371 "       This function is used internally to help with command completion and should.\n"
372 "       never be called by the user directly.\n";
373
374 static int handle_softhangup(int fd, int argc, char *argv[])
375 {
376         struct ast_channel *c=NULL;
377         if (argc != 3)
378                 return RESULT_SHOWUSAGE;
379         c = ast_channel_walk_locked(NULL);
380         while(c) {
381                 if (!strcasecmp(c->name, argv[2])) {
382                         ast_cli(fd, "Requested Hangup on channel '%s'\n", c->name);
383                         ast_softhangup(c, AST_SOFTHANGUP_EXPLICIT);
384                         ast_mutex_unlock(&c->lock);
385                         break;
386                 }
387                 ast_mutex_unlock(&c->lock);
388                 c = ast_channel_walk_locked(c);
389         }
390         if (!c) 
391                 ast_cli(fd, "%s is not a known channel\n", argv[2]);
392         return RESULT_SUCCESS;
393 }
394
395 static char *__ast_cli_generator(char *text, char *word, int state, int lock);
396
397 static int handle_commandmatchesarray(int fd, int argc, char *argv[])
398 {
399         char *buf;
400         int buflen = 2048;
401         int len = 0;
402         char **matches;
403         int x;
404
405         if (argc != 4)
406                 return RESULT_SHOWUSAGE;
407         buf = malloc(buflen);
408         if (!buf)
409                 return RESULT_FAILURE;
410         buf[len] = '\0';
411         matches = ast_cli_completion_matches(argv[2], argv[3]);
412         if (matches) {
413                 for (x=0; matches[x]; x++) {
414 #if 0
415                         printf("command matchesarray for '%s' %s got '%s'\n", argv[2], argv[3], matches[x]);
416 #endif
417                         if (len + strlen(matches[x]) >= buflen) {
418                                 buflen += strlen(matches[x]) * 3;
419                                 buf = realloc(buf, buflen);
420                         }
421                         len += sprintf( buf + len, "%s ", matches[x]);
422                         free(matches[x]);
423                         matches[x] = NULL;
424                 }
425                 free(matches);
426         }
427 #if 0
428         printf("array for '%s' %s got '%s'\n", argv[2], argv[3], buf);
429 #endif
430         
431         if (buf) {
432                 ast_cli(fd, "%s%s",buf, AST_CLI_COMPLETE_EOF);
433                 free(buf);
434         } else
435                 ast_cli(fd, "NULL\n");
436
437         return RESULT_SUCCESS;
438 }
439
440
441
442 static int handle_commandnummatches(int fd, int argc, char *argv[])
443 {
444         int matches = 0;
445
446         if (argc != 4)
447                 return RESULT_SHOWUSAGE;
448
449         matches = ast_cli_generatornummatches(argv[2], argv[3]);
450
451 #if 0
452         printf("Search for '%s' %s got '%d'\n", argv[2], argv[3], matches);
453 #endif
454         ast_cli(fd, "%d", matches);
455
456         return RESULT_SUCCESS;
457 }
458
459 static int handle_commandcomplete(int fd, int argc, char *argv[])
460 {
461         char *buf;
462 #if 0
463         printf("Search for %d args: '%s', '%s', '%s', '%s'\n", argc, argv[0], argv[1], argv[2], argv[3]);
464 #endif  
465         if (argc != 5)
466                 return RESULT_SHOWUSAGE;
467         buf = __ast_cli_generator(argv[2], argv[3], atoi(argv[4]), 0);
468 #if 0
469         printf("Search for '%s' %s %d got '%s'\n", argv[2], argv[3], atoi(argv[4]), buf);
470 #endif  
471         if (buf) {
472                 ast_cli(fd, buf);
473                 free(buf);
474         } else
475                 ast_cli(fd, "NULL\n");
476         return RESULT_SUCCESS;
477 }
478
479 static int handle_debugchan(int fd, int argc, char *argv[])
480 {
481         struct ast_channel *c=NULL;
482         if (argc != 3)
483                 return RESULT_SHOWUSAGE;
484         c = ast_channel_walk_locked(NULL);
485         while(c) {
486                 if (!strcasecmp(c->name, argv[2])) {
487                         c->fin |= 0x80000000;
488                         c->fout |= 0x80000000;
489                         break;
490                 }
491                 ast_mutex_unlock(&c->lock);
492                 c = ast_channel_walk_locked(c);
493         }
494         if (c) {
495                 ast_cli(fd, "Debugging enabled on channel %s\n", c->name);
496                 ast_mutex_unlock(&c->lock);
497         }
498         else
499                 ast_cli(fd, "No such channel %s\n", argv[2]);
500         return RESULT_SUCCESS;
501 }
502
503 static int handle_nodebugchan(int fd, int argc, char *argv[])
504 {
505         struct ast_channel *c=NULL;
506         if (argc != 4)
507                 return RESULT_SHOWUSAGE;
508         c = ast_channel_walk_locked(NULL);
509         while(c) {
510                 if (!strcasecmp(c->name, argv[3])) {
511                         c->fin &= 0x7fffffff;
512                         c->fout &= 0x7fffffff;
513                         break;
514                 }
515                 ast_mutex_unlock(&c->lock);
516                 c = ast_channel_walk_locked(c);
517         }
518         if (c) {
519                 ast_cli(fd, "Debugging disabled on channel %s\n", c->name);
520                 ast_mutex_unlock(&c->lock);
521         } else
522                 ast_cli(fd, "No such channel %s\n", argv[2]);
523         return RESULT_SUCCESS;
524 }
525                 
526         
527
528 static int handle_showchan(int fd, int argc, char *argv[])
529 {
530         struct ast_channel *c=NULL;
531         struct timeval now;
532         long elapsed_seconds=0;
533         if (argc != 3)
534                 return RESULT_SHOWUSAGE;
535         gettimeofday(&now, NULL);
536         c = ast_channel_walk_locked(NULL);
537         while(c) {
538                 if (!strcasecmp(c->name, argv[2])) {
539                         if(c->cdr) {
540                                 elapsed_seconds = now.tv_sec - c->cdr->start.tv_sec;
541                         }
542                         ast_cli(fd, 
543         " -- General --\n"
544         "           Name: %s\n"
545         "           Type: %s\n"
546         "       UniqueID: %s\n"
547         "      Caller ID: %s\n"
548         "    DNID Digits: %s\n"
549         "          State: %s (%d)\n"
550         "          Rings: %d\n"
551         "   NativeFormat: %d\n"
552         "    WriteFormat: %d\n"
553         "     ReadFormat: %d\n"
554         "1st File Descriptor: %d\n"
555         "      Frames in: %d%s\n"
556         "     Frames out: %d%s\n"
557         " Time to Hangup: %ld\n"
558         "Elapsed Seconds: %ld\n"
559         " --   PBX   --\n"
560         "        Context: %s\n"
561         "      Extension: %s\n"
562         "       Priority: %d\n"
563         "     Call Group: %d\n"
564         "   Pickup Group: %d\n"
565         "    Application: %s\n"
566         "           Data: %s\n"
567         "          Stack: %d\n"
568         "    Blocking in: %s\n",
569         c->name, c->type, c->uniqueid,
570         (c->callerid ? c->callerid : "(N/A)"),
571         (c->dnid ? c->dnid : "(N/A)" ), ast_state2str(c->_state), c->_state, c->rings, c->nativeformats, c->writeformat, c->readformat,
572         c->fds[0], c->fin & 0x7fffffff, (c->fin & 0x80000000) ? " (DEBUGGED)" : "",
573         c->fout & 0x7fffffff, (c->fout & 0x80000000) ? " (DEBUGGED)" : "", (long)c->whentohangup,
574         (long)elapsed_seconds, 
575         c->context, c->exten, c->priority, c->callgroup, c->pickupgroup, ( c->appl ? c->appl : "(N/A)" ),
576         ( c-> data ? (!ast_strlen_zero(c->data) ? c->data : "(Empty)") : "(None)"),
577         c->stack, (c->blocking ? c->blockproc : "(Not Blocking)"));
578                 ast_mutex_unlock(&c->lock);
579                 break;
580                 }
581                 ast_mutex_unlock(&c->lock);
582                 c = ast_channel_walk_locked(c);
583         }
584         if (!c) 
585                 ast_cli(fd, "%s is not a known channel\n", argv[2]);
586         return RESULT_SUCCESS;
587 }
588
589 static char *complete_ch_helper(char *line, char *word, int pos, int state, int rpos)
590 {
591         struct ast_channel *c;
592         int which=0;
593         char *ret;
594         if (pos != rpos)
595                 return NULL;
596         c = ast_channel_walk_locked(NULL);
597         while(c) {
598                 if (!strncasecmp(word, c->name, strlen(word))) {
599                         if (++which > state)
600                                 break;
601                 }
602                 ast_mutex_unlock(&c->lock);
603                 c = ast_channel_walk_locked(c);
604         }
605         if (c) {
606                 ret = strdup(c->name);
607                 ast_mutex_unlock(&c->lock);
608         } else
609                 ret = NULL;
610         return ret;
611 }
612
613 static char *complete_ch_3(char *line, char *word, int pos, int state)
614 {
615         return complete_ch_helper(line, word, pos, state, 2);
616 }
617
618 static char *complete_ch_4(char *line, char *word, int pos, int state)
619 {
620         return complete_ch_helper(line, word, pos, state, 3);
621 }
622
623 static char *complete_fn(char *line, char *word, int pos, int state)
624 {
625         char *c;
626         char filename[256];
627         if (pos != 1)
628                 return NULL;
629         if (word[0] == '/')
630                 strncpy(filename, word, sizeof(filename)-1);
631         else
632                 snprintf(filename, sizeof(filename), "%s/%s", (char *)ast_config_AST_MODULE_DIR, word);
633         c = (char*)filename_completion_function(filename, state);
634         if (c && word[0] != '/')
635                 c += (strlen((char*)ast_config_AST_MODULE_DIR) + 1);
636         return c ? strdup(c) : c;
637 }
638
639 static int handle_help(int fd, int argc, char *argv[]);
640
641 static struct ast_cli_entry builtins[] = {
642         /* Keep alphabetized, with longer matches first (example: abcd before abc) */
643         { { "_command", "complete", NULL }, handle_commandcomplete, "Command complete", commandcomplete_help },
644         { { "_command", "nummatches", NULL }, handle_commandnummatches, "Returns number of command matches", commandnummatches_help },
645         { { "_command", "matchesarray", NULL }, handle_commandmatchesarray, "Returns command matches array", commandmatchesarray_help },
646         { { "debug", "channel", NULL }, handle_debugchan, "Enable debugging on a channel", debugchan_help, complete_ch_3 },
647         { { "help", NULL }, handle_help, "Display help list, or specific help on a command", help_help },
648         { { "load", NULL }, handle_load, "Load a dynamic module by name", load_help, complete_fn },
649         { { "no", "debug", "channel", NULL }, handle_nodebugchan, "Disable debugging on a channel", nodebugchan_help, complete_ch_4 },
650         { { "reload", NULL }, handle_reload, "Reload configuration", reload_help },
651         { { "set", "verbose", NULL }, handle_set_verbose, "Set level of verboseness", set_verbose_help },
652         { { "show", "channels", NULL }, handle_chanlist, "Display information on channels", chanlist_help },
653         { { "show", "channel", NULL }, handle_showchan, "Display information on a specific channel", showchan_help, complete_ch_3 },
654         { { "show", "modules", NULL }, handle_modlist, "List modules and info", modlist_help },
655         { { "show", "uptime", NULL }, handle_showuptime, "Show uptime information", modlist_help },
656         { { "show", "version", NULL }, handle_version, "Display version info", version_help },
657         { { "soft", "hangup", NULL }, handle_softhangup, "Request a hangup on a given channel", softhangup_help, complete_ch_3 },
658         { { "unload", NULL }, handle_unload, "Unload a dynamic module by name", unload_help, complete_fn },
659         { { NULL }, NULL, NULL, NULL }
660 };
661
662 static struct ast_cli_entry *find_cli(char *cmds[], int exact)
663 {
664         int x;
665         int y;
666         int match;
667         struct ast_cli_entry *e=NULL;
668         for (x=0;builtins[x].cmda[0];x++) {
669                 /* start optimistic */
670                 match = 1;
671                 for (y=0;match && cmds[y]; y++) {
672                         /* If there are no more words in the candidate command, then we're
673                            there.  */
674                         if (!builtins[x].cmda[y] && !exact)
675                                 break;
676                         /* If there are no more words in the command (and we're looking for
677                            an exact match) or there is a difference between the two words,
678                            then this is not a match */
679                         if (!builtins[x].cmda[y] || strcasecmp(builtins[x].cmda[y], cmds[y]))
680                                 match = 0;
681                 }
682                 /* If more words are needed to complete the command then this is not
683                    a candidate (unless we're looking for a really inexact answer  */
684                 if ((exact > -1) && builtins[x].cmda[y])
685                         match = 0;
686                 if (match)
687                         return &builtins[x];
688         }
689         for (e=helpers;e;e=e->next) {
690                 match = 1;
691                 for (y=0;match && cmds[y]; y++) {
692                         if (!e->cmda[y] && !exact)
693                                 break;
694                         if (!e->cmda[y] || strcasecmp(e->cmda[y], cmds[y]))
695                                 match = 0;
696                 }
697                 if ((exact > -1) && e->cmda[y])
698                         match = 0;
699                 if (match)
700                         break;
701         }
702         return e;
703 }
704
705 static void join(char *dest, size_t destsize, char *w[])
706 {
707         int x;
708         /* Join words into a string */
709         if (!dest || destsize < 1) {
710                 return;
711         }
712         dest[0] = '\0';
713         for (x=0;w[x];x++) {
714                 if (x)
715                         strncat(dest, " ", destsize - strlen(dest) - 1);
716                 strncat(dest, w[x], destsize - strlen(dest) - 1);
717         }
718 }
719
720 static void join2(char *dest, size_t destsize, char *w[])
721 {
722         int x;
723         /* Join words into a string */
724         if (!dest || destsize < 1) {
725                 return;
726         }
727         dest[0] = '\0';
728         for (x=0;w[x];x++) {
729                 strncat(dest, w[x], destsize - strlen(dest) - 1);
730         }
731 }
732
733 static char *find_best(char *argv[])
734 {
735         static char cmdline[80];
736         int x;
737         /* See how close we get, then print the  */
738         char *myargv[AST_MAX_CMD_LEN];
739         for (x=0;x<AST_MAX_CMD_LEN;x++)
740                 myargv[x]=NULL;
741         for (x=0;argv[x];x++) {
742                 myargv[x] = argv[x];
743                 if (!find_cli(myargv, -1))
744                         break;
745         }
746         join(cmdline, sizeof(cmdline), myargv);
747         return cmdline;
748 }
749
750 int ast_cli_unregister(struct ast_cli_entry *e)
751 {
752         struct ast_cli_entry *cur, *l=NULL;
753         ast_mutex_lock(&clilock);
754         cur = helpers;
755         while(cur) {
756                 if (e == cur) {
757                         if (e->inuse) {
758                                 ast_log(LOG_WARNING, "Can't remove command that is in use\n");
759                         } else {
760                                 /* Rewrite */
761                                 if (l)
762                                         l->next = e->next;
763                                 else
764                                         helpers = e->next;
765                                 e->next = NULL;
766                                 break;
767                         }
768                 }
769                 l = cur;
770                 cur = cur->next;
771         }
772         ast_mutex_unlock(&clilock);
773         return 0;
774 }
775
776 int ast_cli_register(struct ast_cli_entry *e)
777 {
778         struct ast_cli_entry *cur, *l=NULL;
779         char fulle[80] ="", fulltst[80] ="";
780         static int len;
781         ast_mutex_lock(&clilock);
782         join2(fulle, sizeof(fulle), e->cmda);
783         if (find_cli(e->cmda, -1)) {
784                 ast_mutex_unlock(&clilock);
785                 ast_log(LOG_WARNING, "Command '%s' already registered (or something close enough)\n", fulle);
786                 return -1;
787         }
788         cur = helpers;
789         while(cur) {
790                 join2(fulltst, sizeof(fulltst), cur->cmda);
791                 len = strlen(fulltst);
792                 if (strlen(fulle) < len)
793                         len = strlen(fulle);
794                 if (strncasecmp(fulle, fulltst, len) < 0) {
795                         if (l) {
796                                 e->next = l->next;
797                                 l->next = e;
798                         } else {
799                                 e->next = helpers;
800                                 helpers = e;
801                         }
802                         break;
803                 }
804                 l = cur;
805                 cur = cur->next;
806         }
807         if (!cur) {
808                 if (l)
809                         l->next = e;
810                 else
811                         helpers = e;
812                 e->next = NULL;
813         }
814         ast_mutex_unlock(&clilock);
815         return 0;
816 }
817
818 static int help_workhorse(int fd, char *match[])
819 {
820         char fullcmd1[80];
821         char fullcmd2[80];
822         char matchstr[80];
823         char *fullcmd;
824         struct ast_cli_entry *e, *e1, *e2;
825         e1 = builtins;
826         e2 = helpers;
827         if (match)
828                 join(matchstr, sizeof(matchstr), match);
829         while(e1->cmda[0] || e2) {
830                 if (e2)
831                         join(fullcmd2, sizeof(fullcmd2), e2->cmda);
832                 if (e1->cmda[0])
833                         join(fullcmd1, sizeof(fullcmd1), e1->cmda);
834                 if (!e1->cmda[0] || 
835                                 (e2 && (strcmp(fullcmd2, fullcmd1) < 0))) {
836                         /* Use e2 */
837                         e = e2;
838                         fullcmd = fullcmd2;
839                         /* Increment by going to next */
840                         e2 = e2->next;
841                 } else {
842                         /* Use e1 */
843                         e = e1;
844                         fullcmd = fullcmd1;
845                         e1++;
846                 }
847                 /* Hide commands that start with '_' */
848                 if (fullcmd[0] == '_')
849                         continue;
850                 if (match) {
851                         if (strncasecmp(matchstr, fullcmd, strlen(matchstr))) {
852                                 continue;
853                         }
854                 }
855                 ast_cli(fd, "%25.25s  %s\n", fullcmd, e->summary);
856         }
857         return 0;
858 }
859
860 static int handle_help(int fd, int argc, char *argv[]) {
861         struct ast_cli_entry *e;
862         char fullcmd[80];
863         if ((argc < 1))
864                 return RESULT_SHOWUSAGE;
865         if (argc > 1) {
866                 e = find_cli(argv + 1, 1);
867                 if (e) 
868                         ast_cli(fd, e->usage);
869                 else {
870                         if (find_cli(argv + 1, -1)) {
871                                 return help_workhorse(fd, argv + 1);
872                         } else {
873                                 join(fullcmd, sizeof(fullcmd), argv+1);
874                                 ast_cli(fd, "No such command '%s'.\n", fullcmd);
875                         }
876                 }
877         } else {
878                 return help_workhorse(fd, NULL);
879         }
880         return RESULT_SUCCESS;
881 }
882
883 static char *parse_args(char *s, int *max, char *argv[])
884 {
885         char *dup, *cur;
886         int x=0;
887         int quoted=0;
888         int escaped=0;
889         int whitespace=1;
890
891         dup = strdup(s);
892         if (dup) {
893                 cur = dup;
894                 while(*s) {
895                         switch(*s) {
896                         case '"':
897                                 /* If it's escaped, put a literal quote */
898                                 if (escaped) 
899                                         goto normal;
900                                 else 
901                                         quoted = !quoted;
902                                 if (quoted && whitespace) {
903                                         /* If we're starting a quote, coming off white space start a new word, too */
904                                         argv[x++] = cur;
905                                         whitespace=0;
906                                 }
907                                 escaped = 0;
908                                 break;
909                         case ' ':
910                         case '\t':
911                                 if (!quoted && !escaped) {
912                                         /* If we're not quoted, mark this as whitespace, and
913                                            end the previous argument */
914                                         whitespace = 1;
915                                         *(cur++) = '\0';
916                                 } else
917                                         /* Otherwise, just treat it as anything else */ 
918                                         goto normal;
919                                 break;
920                         case '\\':
921                                 /* If we're escaped, print a literal, otherwise enable escaping */
922                                 if (escaped) {
923                                         goto normal;
924                                 } else {
925                                         escaped=1;
926                                 }
927                                 break;
928                         default:
929 normal:
930                                 if (whitespace) {
931                                         if (x >= AST_MAX_ARGS -1) {
932                                                 ast_log(LOG_WARNING, "Too many arguments, truncating\n");
933                                                 break;
934                                         }
935                                         /* Coming off of whitespace, start the next argument */
936                                         argv[x++] = cur;
937                                         whitespace=0;
938                                 }
939                                 *(cur++) = *s;
940                                 escaped=0;
941                         }
942                         s++;
943                 }
944                 /* Null terminate */
945                 *(cur++) = '\0';
946                 argv[x] = NULL;
947                 *max = x;
948         }
949         return dup;
950 }
951
952 /* This returns the number of unique matches for the generator */
953 int ast_cli_generatornummatches(char *text, char *word)
954 {
955         int matches = 0, i = 0;
956         char *buf, *oldbuf = NULL;
957
958
959         while ( (buf = ast_cli_generator(text, word, i)) ) {
960                 if (++i > 1 && strcmp(buf,oldbuf) == 0)  {
961                                 continue;
962                 }
963                 oldbuf = buf;
964                 matches++;
965         }
966
967         return matches;
968 }
969
970 char **ast_cli_completion_matches(char *text, char *word)
971 {
972         char **match_list = NULL, *retstr, *prevstr;
973         size_t match_list_len, max_equal, which, i;
974         int matches = 0;
975
976         match_list_len = 1;
977         while ((retstr = ast_cli_generator(text, word, matches)) != NULL) {
978                 if (matches + 1 >= match_list_len) {
979                         match_list_len <<= 1;
980                         match_list = realloc(match_list, match_list_len * sizeof(char *));
981                 }
982                 match_list[++matches] = retstr;
983         }
984
985         if (!match_list)
986                 return (char **) NULL;
987
988         which = 2;
989         prevstr = match_list[1];
990         max_equal = strlen(prevstr);
991         for (; which <= matches; which++) {
992                 for (i = 0; i < max_equal && prevstr[i] == match_list[which][i]; i++)
993                         continue;
994                 max_equal = i;
995         }
996
997         retstr = malloc(max_equal + 1);
998         (void) strncpy(retstr, match_list[1], max_equal);
999         retstr[max_equal] = '\0';
1000         match_list[0] = retstr;
1001
1002         if (matches + 1 >= match_list_len)
1003                 match_list = realloc(match_list, (match_list_len + 1) * sizeof(char *));
1004         match_list[matches + 1] = (char *) NULL;
1005
1006         return (match_list);
1007 }
1008
1009 static char *__ast_cli_generator(char *text, char *word, int state, int lock)
1010 {
1011         char *argv[AST_MAX_ARGS];
1012         struct ast_cli_entry *e, *e1, *e2;
1013         int x;
1014         int matchnum=0;
1015         char *dup, *res;
1016         char fullcmd1[80];
1017         char fullcmd2[80];
1018         char matchstr[80];
1019         char *fullcmd;
1020
1021         if ((dup = parse_args(text, &x, argv))) {
1022                 join(matchstr, sizeof(matchstr), argv);
1023                 if (lock)
1024                         ast_mutex_lock(&clilock);
1025                 e1 = builtins;
1026                 e2 = helpers;
1027                 while(e1->cmda[0] || e2) {
1028                         if (e2)
1029                                 join(fullcmd2, sizeof(fullcmd2), e2->cmda);
1030                         if (e1->cmda[0])
1031                                 join(fullcmd1, sizeof(fullcmd1), e1->cmda);
1032                         if (!e1->cmda[0] || 
1033                                         (e2 && (strcmp(fullcmd2, fullcmd1) < 0))) {
1034                                 /* Use e2 */
1035                                 e = e2;
1036                                 fullcmd = fullcmd2;
1037                                 /* Increment by going to next */
1038                                 e2 = e2->next;
1039                         } else {
1040                                 /* Use e1 */
1041                                 e = e1;
1042                                 fullcmd = fullcmd1;
1043                                 e1++;
1044                         }
1045                         if ((fullcmd[0] != '_') && !strncasecmp(matchstr, fullcmd, strlen(matchstr))) {
1046                                 /* We contain the first part of one or more commands */
1047                                 matchnum++;
1048                                 if (matchnum > state) {
1049                                         /* Now, what we're supposed to return is the next word... */
1050                                         if (!ast_strlen_zero(word) && x>0) {
1051                                                 res = e->cmda[x-1];
1052                                         } else {
1053                                                 res = e->cmda[x];
1054                                         }
1055                                         if (res) {
1056                                                 if (lock)
1057                                                         ast_mutex_unlock(&clilock);
1058                                                 free(dup);
1059                                                 return res ? strdup(res) : NULL;
1060                                         }
1061                                 }
1062                         }
1063                         if (e->generator && !strncasecmp(matchstr, fullcmd, strlen(fullcmd))) {
1064                                 /* We have a command in its entirity within us -- theoretically only one
1065                                    command can have this occur */
1066                                 fullcmd = e->generator(matchstr, word, (!ast_strlen_zero(word) ? (x - 1) : (x)), state);
1067                                 if (lock)
1068                                         ast_mutex_unlock(&clilock);
1069                                 free(dup);
1070                                 return fullcmd;
1071                         }
1072                         
1073                 }
1074                 if (lock)
1075                         ast_mutex_unlock(&clilock);
1076                 free(dup);
1077         }
1078         return NULL;
1079 }
1080
1081 char *ast_cli_generator(char *text, char *word, int state)
1082 {
1083         return __ast_cli_generator(text, word, state, 1);
1084 }
1085
1086 int ast_cli_command(int fd, char *s)
1087 {
1088         char *argv[AST_MAX_ARGS];
1089         struct ast_cli_entry *e;
1090         int x;
1091         char *dup;
1092         x = AST_MAX_ARGS;
1093         if ((dup = parse_args(s, &x, argv))) {
1094                 /* We need at least one entry, or ignore */
1095                 if (x > 0) {
1096                         ast_mutex_lock(&clilock);
1097                         e = find_cli(argv, 0);
1098                         if (e)
1099                                 e->inuse++;
1100                         ast_mutex_unlock(&clilock);
1101                         if (e) {
1102                                 switch(e->handler(fd, x, argv)) {
1103                                 case RESULT_SHOWUSAGE:
1104                                         ast_cli(fd, e->usage);
1105                                         break;
1106                                 }
1107                         } else 
1108                                 ast_cli(fd, "No such command '%s' (type 'help' for help)\n", find_best(argv));
1109                         if (e) {
1110                                 ast_mutex_lock(&clilock);
1111                                 e->inuse--;
1112                                 ast_mutex_unlock(&clilock);
1113                         }
1114                 }
1115                 free(dup);
1116         } else {
1117                 ast_log(LOG_WARNING, "Out of memory\n");        
1118                 return -1;
1119         }
1120         return 0;
1121 }