Display elapsed time in hours/mins/seconds (bug #2365 kinda sorta)
[asterisk/asterisk.git] / cli.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Standard Command Line Interface
5  * 
6  * Copyright (C) 1999-2004, Digium, Inc.
7  *
8  * Mark Spencer <markster@digium.com>
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         int hour=0, min=0, sec=0;
534         if (argc != 3)
535                 return RESULT_SHOWUSAGE;
536         gettimeofday(&now, NULL);
537         c = ast_channel_walk_locked(NULL);
538         while(c) {
539                 if (!strcasecmp(c->name, argv[2])) {
540                         if(c->cdr) {
541                                 elapsed_seconds = now.tv_sec - c->cdr->start.tv_sec;
542                                 hour = elapsed_seconds / 3600;
543                                 min = (elapsed_seconds % 3600) / 60;
544                                 sec = elapsed_seconds % 60;
545                         }
546                         ast_cli(fd, 
547         " -- General --\n"
548         "           Name: %s\n"
549         "           Type: %s\n"
550         "       UniqueID: %s\n"
551         "      Caller ID: %s\n"
552         "    DNID Digits: %s\n"
553         "          State: %s (%d)\n"
554         "          Rings: %d\n"
555         "   NativeFormat: %d\n"
556         "    WriteFormat: %d\n"
557         "     ReadFormat: %d\n"
558         "1st File Descriptor: %d\n"
559         "      Frames in: %d%s\n"
560         "     Frames out: %d%s\n"
561         " Time to Hangup: %ld\n"
562         "   Elapsed Time: %dh%dm%ds\n"
563         " --   PBX   --\n"
564         "        Context: %s\n"
565         "      Extension: %s\n"
566         "       Priority: %d\n"
567         "     Call Group: %d\n"
568         "   Pickup Group: %d\n"
569         "    Application: %s\n"
570         "           Data: %s\n"
571         "          Stack: %d\n"
572         "    Blocking in: %s\n",
573         c->name, c->type, c->uniqueid,
574         (c->callerid ? c->callerid : "(N/A)"),
575         (c->dnid ? c->dnid : "(N/A)" ), ast_state2str(c->_state), c->_state, c->rings, c->nativeformats, c->writeformat, c->readformat,
576         c->fds[0], c->fin & 0x7fffffff, (c->fin & 0x80000000) ? " (DEBUGGED)" : "",
577         c->fout & 0x7fffffff, (c->fout & 0x80000000) ? " (DEBUGGED)" : "", (long)c->whentohangup,
578         hour, min, sec, 
579         c->context, c->exten, c->priority, c->callgroup, c->pickupgroup, ( c->appl ? c->appl : "(N/A)" ),
580         ( c-> data ? (!ast_strlen_zero(c->data) ? c->data : "(Empty)") : "(None)"),
581         c->stack, (c->blocking ? c->blockproc : "(Not Blocking)"));
582                 ast_mutex_unlock(&c->lock);
583                 break;
584                 }
585                 ast_mutex_unlock(&c->lock);
586                 c = ast_channel_walk_locked(c);
587         }
588         if (!c) 
589                 ast_cli(fd, "%s is not a known channel\n", argv[2]);
590         return RESULT_SUCCESS;
591 }
592
593 static char *complete_ch_helper(char *line, char *word, int pos, int state, int rpos)
594 {
595         struct ast_channel *c;
596         int which=0;
597         char *ret;
598         if (pos != rpos)
599                 return NULL;
600         c = ast_channel_walk_locked(NULL);
601         while(c) {
602                 if (!strncasecmp(word, c->name, strlen(word))) {
603                         if (++which > state)
604                                 break;
605                 }
606                 ast_mutex_unlock(&c->lock);
607                 c = ast_channel_walk_locked(c);
608         }
609         if (c) {
610                 ret = strdup(c->name);
611                 ast_mutex_unlock(&c->lock);
612         } else
613                 ret = NULL;
614         return ret;
615 }
616
617 static char *complete_ch_3(char *line, char *word, int pos, int state)
618 {
619         return complete_ch_helper(line, word, pos, state, 2);
620 }
621
622 static char *complete_ch_4(char *line, char *word, int pos, int state)
623 {
624         return complete_ch_helper(line, word, pos, state, 3);
625 }
626
627 static char *complete_fn(char *line, char *word, int pos, int state)
628 {
629         char *c;
630         char filename[256];
631         if (pos != 1)
632                 return NULL;
633         if (word[0] == '/')
634                 strncpy(filename, word, sizeof(filename)-1);
635         else
636                 snprintf(filename, sizeof(filename), "%s/%s", (char *)ast_config_AST_MODULE_DIR, word);
637         c = (char*)filename_completion_function(filename, state);
638         if (c && word[0] != '/')
639                 c += (strlen((char*)ast_config_AST_MODULE_DIR) + 1);
640         return c ? strdup(c) : c;
641 }
642
643 static int handle_help(int fd, int argc, char *argv[]);
644
645 static struct ast_cli_entry builtins[] = {
646         /* Keep alphabetized, with longer matches first (example: abcd before abc) */
647         { { "_command", "complete", NULL }, handle_commandcomplete, "Command complete", commandcomplete_help },
648         { { "_command", "nummatches", NULL }, handle_commandnummatches, "Returns number of command matches", commandnummatches_help },
649         { { "_command", "matchesarray", NULL }, handle_commandmatchesarray, "Returns command matches array", commandmatchesarray_help },
650         { { "debug", "channel", NULL }, handle_debugchan, "Enable debugging on a channel", debugchan_help, complete_ch_3 },
651         { { "help", NULL }, handle_help, "Display help list, or specific help on a command", help_help },
652         { { "load", NULL }, handle_load, "Load a dynamic module by name", load_help, complete_fn },
653         { { "no", "debug", "channel", NULL }, handle_nodebugchan, "Disable debugging on a channel", nodebugchan_help, complete_ch_4 },
654         { { "reload", NULL }, handle_reload, "Reload configuration", reload_help },
655         { { "set", "verbose", NULL }, handle_set_verbose, "Set level of verboseness", set_verbose_help },
656         { { "show", "channels", NULL }, handle_chanlist, "Display information on channels", chanlist_help },
657         { { "show", "channel", NULL }, handle_showchan, "Display information on a specific channel", showchan_help, complete_ch_3 },
658         { { "show", "modules", NULL }, handle_modlist, "List modules and info", modlist_help },
659         { { "show", "uptime", NULL }, handle_showuptime, "Show uptime information", modlist_help },
660         { { "show", "version", NULL }, handle_version, "Display version info", version_help },
661         { { "soft", "hangup", NULL }, handle_softhangup, "Request a hangup on a given channel", softhangup_help, complete_ch_3 },
662         { { "unload", NULL }, handle_unload, "Unload a dynamic module by name", unload_help, complete_fn },
663         { { NULL }, NULL, NULL, NULL }
664 };
665
666 static struct ast_cli_entry *find_cli(char *cmds[], int exact)
667 {
668         int x;
669         int y;
670         int match;
671         struct ast_cli_entry *e=NULL;
672         for (x=0;builtins[x].cmda[0];x++) {
673                 /* start optimistic */
674                 match = 1;
675                 for (y=0;match && cmds[y]; y++) {
676                         /* If there are no more words in the candidate command, then we're
677                            there.  */
678                         if (!builtins[x].cmda[y] && !exact)
679                                 break;
680                         /* If there are no more words in the command (and we're looking for
681                            an exact match) or there is a difference between the two words,
682                            then this is not a match */
683                         if (!builtins[x].cmda[y] || strcasecmp(builtins[x].cmda[y], cmds[y]))
684                                 match = 0;
685                 }
686                 /* If more words are needed to complete the command then this is not
687                    a candidate (unless we're looking for a really inexact answer  */
688                 if ((exact > -1) && builtins[x].cmda[y])
689                         match = 0;
690                 if (match)
691                         return &builtins[x];
692         }
693         for (e=helpers;e;e=e->next) {
694                 match = 1;
695                 for (y=0;match && cmds[y]; y++) {
696                         if (!e->cmda[y] && !exact)
697                                 break;
698                         if (!e->cmda[y] || strcasecmp(e->cmda[y], cmds[y]))
699                                 match = 0;
700                 }
701                 if ((exact > -1) && e->cmda[y])
702                         match = 0;
703                 if (match)
704                         break;
705         }
706         return e;
707 }
708
709 static void join(char *dest, size_t destsize, char *w[])
710 {
711         int x;
712         /* Join words into a string */
713         if (!dest || destsize < 1) {
714                 return;
715         }
716         dest[0] = '\0';
717         for (x=0;w[x];x++) {
718                 if (x)
719                         strncat(dest, " ", destsize - strlen(dest) - 1);
720                 strncat(dest, w[x], destsize - strlen(dest) - 1);
721         }
722 }
723
724 static void join2(char *dest, size_t destsize, char *w[])
725 {
726         int x;
727         /* Join words into a string */
728         if (!dest || destsize < 1) {
729                 return;
730         }
731         dest[0] = '\0';
732         for (x=0;w[x];x++) {
733                 strncat(dest, w[x], destsize - strlen(dest) - 1);
734         }
735 }
736
737 static char *find_best(char *argv[])
738 {
739         static char cmdline[80];
740         int x;
741         /* See how close we get, then print the  */
742         char *myargv[AST_MAX_CMD_LEN];
743         for (x=0;x<AST_MAX_CMD_LEN;x++)
744                 myargv[x]=NULL;
745         for (x=0;argv[x];x++) {
746                 myargv[x] = argv[x];
747                 if (!find_cli(myargv, -1))
748                         break;
749         }
750         join(cmdline, sizeof(cmdline), myargv);
751         return cmdline;
752 }
753
754 int ast_cli_unregister(struct ast_cli_entry *e)
755 {
756         struct ast_cli_entry *cur, *l=NULL;
757         ast_mutex_lock(&clilock);
758         cur = helpers;
759         while(cur) {
760                 if (e == cur) {
761                         if (e->inuse) {
762                                 ast_log(LOG_WARNING, "Can't remove command that is in use\n");
763                         } else {
764                                 /* Rewrite */
765                                 if (l)
766                                         l->next = e->next;
767                                 else
768                                         helpers = e->next;
769                                 e->next = NULL;
770                                 break;
771                         }
772                 }
773                 l = cur;
774                 cur = cur->next;
775         }
776         ast_mutex_unlock(&clilock);
777         return 0;
778 }
779
780 int ast_cli_register(struct ast_cli_entry *e)
781 {
782         struct ast_cli_entry *cur, *l=NULL;
783         char fulle[80] ="", fulltst[80] ="";
784         static int len;
785         ast_mutex_lock(&clilock);
786         join2(fulle, sizeof(fulle), e->cmda);
787         if (find_cli(e->cmda, -1)) {
788                 ast_mutex_unlock(&clilock);
789                 ast_log(LOG_WARNING, "Command '%s' already registered (or something close enough)\n", fulle);
790                 return -1;
791         }
792         cur = helpers;
793         while(cur) {
794                 join2(fulltst, sizeof(fulltst), cur->cmda);
795                 len = strlen(fulltst);
796                 if (strlen(fulle) < len)
797                         len = strlen(fulle);
798                 if (strncasecmp(fulle, fulltst, len) < 0) {
799                         if (l) {
800                                 e->next = l->next;
801                                 l->next = e;
802                         } else {
803                                 e->next = helpers;
804                                 helpers = e;
805                         }
806                         break;
807                 }
808                 l = cur;
809                 cur = cur->next;
810         }
811         if (!cur) {
812                 if (l)
813                         l->next = e;
814                 else
815                         helpers = e;
816                 e->next = NULL;
817         }
818         ast_mutex_unlock(&clilock);
819         return 0;
820 }
821
822 static int help_workhorse(int fd, char *match[])
823 {
824         char fullcmd1[80];
825         char fullcmd2[80];
826         char matchstr[80];
827         char *fullcmd;
828         struct ast_cli_entry *e, *e1, *e2;
829         e1 = builtins;
830         e2 = helpers;
831         if (match)
832                 join(matchstr, sizeof(matchstr), match);
833         while(e1->cmda[0] || e2) {
834                 if (e2)
835                         join(fullcmd2, sizeof(fullcmd2), e2->cmda);
836                 if (e1->cmda[0])
837                         join(fullcmd1, sizeof(fullcmd1), e1->cmda);
838                 if (!e1->cmda[0] || 
839                                 (e2 && (strcmp(fullcmd2, fullcmd1) < 0))) {
840                         /* Use e2 */
841                         e = e2;
842                         fullcmd = fullcmd2;
843                         /* Increment by going to next */
844                         e2 = e2->next;
845                 } else {
846                         /* Use e1 */
847                         e = e1;
848                         fullcmd = fullcmd1;
849                         e1++;
850                 }
851                 /* Hide commands that start with '_' */
852                 if (fullcmd[0] == '_')
853                         continue;
854                 if (match) {
855                         if (strncasecmp(matchstr, fullcmd, strlen(matchstr))) {
856                                 continue;
857                         }
858                 }
859                 ast_cli(fd, "%25.25s  %s\n", fullcmd, e->summary);
860         }
861         return 0;
862 }
863
864 static int handle_help(int fd, int argc, char *argv[]) {
865         struct ast_cli_entry *e;
866         char fullcmd[80];
867         if ((argc < 1))
868                 return RESULT_SHOWUSAGE;
869         if (argc > 1) {
870                 e = find_cli(argv + 1, 1);
871                 if (e) 
872                         ast_cli(fd, e->usage);
873                 else {
874                         if (find_cli(argv + 1, -1)) {
875                                 return help_workhorse(fd, argv + 1);
876                         } else {
877                                 join(fullcmd, sizeof(fullcmd), argv+1);
878                                 ast_cli(fd, "No such command '%s'.\n", fullcmd);
879                         }
880                 }
881         } else {
882                 return help_workhorse(fd, NULL);
883         }
884         return RESULT_SUCCESS;
885 }
886
887 static char *parse_args(char *s, int *max, char *argv[])
888 {
889         char *dup, *cur;
890         int x=0;
891         int quoted=0;
892         int escaped=0;
893         int whitespace=1;
894
895         dup = strdup(s);
896         if (dup) {
897                 cur = dup;
898                 while(*s) {
899                         switch(*s) {
900                         case '"':
901                                 /* If it's escaped, put a literal quote */
902                                 if (escaped) 
903                                         goto normal;
904                                 else 
905                                         quoted = !quoted;
906                                 if (quoted && whitespace) {
907                                         /* If we're starting a quote, coming off white space start a new word, too */
908                                         argv[x++] = cur;
909                                         whitespace=0;
910                                 }
911                                 escaped = 0;
912                                 break;
913                         case ' ':
914                         case '\t':
915                                 if (!quoted && !escaped) {
916                                         /* If we're not quoted, mark this as whitespace, and
917                                            end the previous argument */
918                                         whitespace = 1;
919                                         *(cur++) = '\0';
920                                 } else
921                                         /* Otherwise, just treat it as anything else */ 
922                                         goto normal;
923                                 break;
924                         case '\\':
925                                 /* If we're escaped, print a literal, otherwise enable escaping */
926                                 if (escaped) {
927                                         goto normal;
928                                 } else {
929                                         escaped=1;
930                                 }
931                                 break;
932                         default:
933 normal:
934                                 if (whitespace) {
935                                         if (x >= AST_MAX_ARGS -1) {
936                                                 ast_log(LOG_WARNING, "Too many arguments, truncating\n");
937                                                 break;
938                                         }
939                                         /* Coming off of whitespace, start the next argument */
940                                         argv[x++] = cur;
941                                         whitespace=0;
942                                 }
943                                 *(cur++) = *s;
944                                 escaped=0;
945                         }
946                         s++;
947                 }
948                 /* Null terminate */
949                 *(cur++) = '\0';
950                 argv[x] = NULL;
951                 *max = x;
952         }
953         return dup;
954 }
955
956 /* This returns the number of unique matches for the generator */
957 int ast_cli_generatornummatches(char *text, char *word)
958 {
959         int matches = 0, i = 0;
960         char *buf, *oldbuf = NULL;
961
962
963         while ( (buf = ast_cli_generator(text, word, i)) ) {
964                 if (++i > 1 && strcmp(buf,oldbuf) == 0)  {
965                                 continue;
966                 }
967                 oldbuf = buf;
968                 matches++;
969         }
970
971         return matches;
972 }
973
974 char **ast_cli_completion_matches(char *text, char *word)
975 {
976         char **match_list = NULL, *retstr, *prevstr;
977         size_t match_list_len, max_equal, which, i;
978         int matches = 0;
979
980         match_list_len = 1;
981         while ((retstr = ast_cli_generator(text, word, matches)) != NULL) {
982                 if (matches + 1 >= match_list_len) {
983                         match_list_len <<= 1;
984                         match_list = realloc(match_list, match_list_len * sizeof(char *));
985                 }
986                 match_list[++matches] = retstr;
987         }
988
989         if (!match_list)
990                 return (char **) NULL;
991
992         which = 2;
993         prevstr = match_list[1];
994         max_equal = strlen(prevstr);
995         for (; which <= matches; which++) {
996                 for (i = 0; i < max_equal && prevstr[i] == match_list[which][i]; i++)
997                         continue;
998                 max_equal = i;
999         }
1000
1001         retstr = malloc(max_equal + 1);
1002         (void) strncpy(retstr, match_list[1], max_equal);
1003         retstr[max_equal] = '\0';
1004         match_list[0] = retstr;
1005
1006         if (matches + 1 >= match_list_len)
1007                 match_list = realloc(match_list, (match_list_len + 1) * sizeof(char *));
1008         match_list[matches + 1] = (char *) NULL;
1009
1010         return (match_list);
1011 }
1012
1013 static char *__ast_cli_generator(char *text, char *word, int state, int lock)
1014 {
1015         char *argv[AST_MAX_ARGS];
1016         struct ast_cli_entry *e, *e1, *e2;
1017         int x;
1018         int matchnum=0;
1019         char *dup, *res;
1020         char fullcmd1[80];
1021         char fullcmd2[80];
1022         char matchstr[80];
1023         char *fullcmd;
1024
1025         if ((dup = parse_args(text, &x, argv))) {
1026                 join(matchstr, sizeof(matchstr), argv);
1027                 if (lock)
1028                         ast_mutex_lock(&clilock);
1029                 e1 = builtins;
1030                 e2 = helpers;
1031                 while(e1->cmda[0] || e2) {
1032                         if (e2)
1033                                 join(fullcmd2, sizeof(fullcmd2), e2->cmda);
1034                         if (e1->cmda[0])
1035                                 join(fullcmd1, sizeof(fullcmd1), e1->cmda);
1036                         if (!e1->cmda[0] || 
1037                                         (e2 && (strcmp(fullcmd2, fullcmd1) < 0))) {
1038                                 /* Use e2 */
1039                                 e = e2;
1040                                 fullcmd = fullcmd2;
1041                                 /* Increment by going to next */
1042                                 e2 = e2->next;
1043                         } else {
1044                                 /* Use e1 */
1045                                 e = e1;
1046                                 fullcmd = fullcmd1;
1047                                 e1++;
1048                         }
1049                         if ((fullcmd[0] != '_') && !strncasecmp(matchstr, fullcmd, strlen(matchstr))) {
1050                                 /* We contain the first part of one or more commands */
1051                                 matchnum++;
1052                                 if (matchnum > state) {
1053                                         /* Now, what we're supposed to return is the next word... */
1054                                         if (!ast_strlen_zero(word) && x>0) {
1055                                                 res = e->cmda[x-1];
1056                                         } else {
1057                                                 res = e->cmda[x];
1058                                         }
1059                                         if (res) {
1060                                                 if (lock)
1061                                                         ast_mutex_unlock(&clilock);
1062                                                 free(dup);
1063                                                 return res ? strdup(res) : NULL;
1064                                         }
1065                                 }
1066                         }
1067                         if (e->generator && !strncasecmp(matchstr, fullcmd, strlen(fullcmd))) {
1068                                 /* We have a command in its entirity within us -- theoretically only one
1069                                    command can have this occur */
1070                                 fullcmd = e->generator(matchstr, word, (!ast_strlen_zero(word) ? (x - 1) : (x)), state);
1071                                 if (lock)
1072                                         ast_mutex_unlock(&clilock);
1073                                 free(dup);
1074                                 return fullcmd;
1075                         }
1076                         
1077                 }
1078                 if (lock)
1079                         ast_mutex_unlock(&clilock);
1080                 free(dup);
1081         }
1082         return NULL;
1083 }
1084
1085 char *ast_cli_generator(char *text, char *word, int state)
1086 {
1087         return __ast_cli_generator(text, word, state, 1);
1088 }
1089
1090 int ast_cli_command(int fd, char *s)
1091 {
1092         char *argv[AST_MAX_ARGS];
1093         struct ast_cli_entry *e;
1094         int x;
1095         char *dup;
1096         x = AST_MAX_ARGS;
1097         if ((dup = parse_args(s, &x, argv))) {
1098                 /* We need at least one entry, or ignore */
1099                 if (x > 0) {
1100                         ast_mutex_lock(&clilock);
1101                         e = find_cli(argv, 0);
1102                         if (e)
1103                                 e->inuse++;
1104                         ast_mutex_unlock(&clilock);
1105                         if (e) {
1106                                 switch(e->handler(fd, x, argv)) {
1107                                 case RESULT_SHOWUSAGE:
1108                                         ast_cli(fd, e->usage);
1109                                         break;
1110                                 }
1111                         } else 
1112                                 ast_cli(fd, "No such command '%s' (type 'help' for help)\n", find_best(argv));
1113                         if (e) {
1114                                 ast_mutex_lock(&clilock);
1115                                 e->inuse--;
1116                                 ast_mutex_unlock(&clilock);
1117                         }
1118                 }
1119                 free(dup);
1120         } else {
1121                 ast_log(LOG_WARNING, "Out of memory\n");        
1122                 return -1;
1123         }
1124         return 0;
1125 }