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