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