Show uptime
[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 pthread_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 pthread_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_pthread_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_pthread_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         "      Caller ID: %s\n"
480         "    DNID Digits: %s\n"
481         "          State: %s (%d)\n"
482         "          Rings: %d\n"
483         "   NativeFormat: %d\n"
484         "    WriteFormat: %d\n"
485         "     ReadFormat: %d\n"
486         "1st File Descriptor: %d\n"
487         "      Frames in: %d%s\n"
488         "     Frames out: %d%s\n"
489         " Time to Hangup: %d\n"
490         " --   PBX   --\n"
491         "        Context: %s\n"
492         "      Extension: %s\n"
493         "       Priority: %d\n"
494         "     Call Group: %d\n"
495         "   Pickup Group: %d\n"
496         "    Application: %s\n"
497         "           Data: %s\n"
498         "          Stack: %d\n"
499         "    Blocking in: %s\n",
500         c->name, c->type, 
501         (c->callerid ? c->callerid : "(N/A)"),
502         (c->dnid ? c->dnid : "(N/A)" ), ast_state2str(c->_state), c->_state, c->rings, c->nativeformats, c->writeformat, c->readformat,
503         c->fds[0], c->fin & 0x7fffffff, (c->fin & 0x80000000) ? " (DEBUGGED)" : "",
504         c->fout & 0x7fffffff, (c->fout & 0x80000000) ? " (DEBUGGED)" : "", c->whentohangup,
505         c->context, c->exten, c->priority, c->callgroup, c->pickupgroup, ( c->appl ? c->appl : "(N/A)" ),
506         ( c-> data ? (strlen(c->data) ? c->data : "(Empty)") : "(None)"),
507         c->stack, (c->blocking ? c->blockproc : "(Not Blocking)"));
508         
509                 break;
510                 }
511                 c = ast_channel_walk(c);
512         }
513         if (!c) 
514                 ast_cli(fd, "%s is not a known channel\n", argv[2]);
515         return RESULT_SUCCESS;
516 }
517
518 static char *complete_ch(char *line, char *word, int pos, int state)
519 {
520         struct ast_channel *c;
521         int which=0;
522         c = ast_channel_walk(NULL);
523         while(c) {
524                 if (!strncasecmp(word, c->name, strlen(word))) {
525                         if (++which > state)
526                                 break;
527                 }
528                 c = ast_channel_walk(c);
529         }
530         return c ? strdup(c->name) : NULL;
531 }
532
533 static char *complete_fn(char *line, char *word, int pos, int state)
534 {
535         char *c;
536         char filename[256];
537         if (pos != 1)
538                 return NULL;
539         if (word[0] == '/')
540                 strncpy(filename, word, sizeof(filename)-1);
541         else
542                 snprintf(filename, sizeof(filename), "%s/%s", (char *)ast_config_AST_MODULE_DIR, word);
543         c = (char*)filename_completion_function(filename, state);
544         if (c && word[0] != '/')
545                 c += (strlen((char*)ast_config_AST_MODULE_DIR) + 1);
546         return c ? strdup(c) : c;
547 }
548
549 static int handle_help(int fd, int argc, char *argv[]);
550
551 static struct ast_cli_entry builtins[] = {
552         /* Keep alphabetized */
553         { { "_command", "complete", NULL }, handle_commandcomplete, "Command complete", commandcomplete_help },
554         { { "_command", "nummatches", NULL }, handle_commandnummatches, "Returns number of command matches", commandnummatches_help },
555         { { "_command", "matchesarray", NULL }, handle_commandmatchesarray, "Returns command matches array", commandmatchesarray_help },
556         { { "debug", "channel", NULL }, handle_debugchan, "Enable debugging on a channel", debugchan_help, complete_ch },
557         { { "help", NULL }, handle_help, "Display help list, or specific help on a command", help_help },
558         { { "load", NULL }, handle_load, "Load a dynamic module by name", load_help, complete_fn },
559         { { "no", "debug", "channel", NULL }, handle_nodebugchan, "Disable debugging on a channel", nodebugchan_help, complete_ch },
560         { { "reload", NULL }, handle_reload, "Reload configuration", reload_help },
561         { { "set", "verbose", NULL }, handle_set_verbose, "Set level of verboseness", set_verbose_help },
562         { { "show", "channel", NULL }, handle_showchan, "Display information on a specific channel", showchan_help, complete_ch },
563         { { "show", "channels", NULL }, handle_chanlist, "Display information on channels", chanlist_help },
564         { { "show", "modules", NULL }, handle_modlist, "List modules and info", modlist_help },
565         { { "show", "uptime", NULL }, handle_showuptime, "Show uptime information", modlist_help },
566         { { "show", "version", NULL }, handle_version, "Display version info", version_help },
567         { { "soft", "hangup", NULL }, handle_softhangup, "Request a hangup on a given channel", softhangup_help, complete_ch },
568         { { "unload", NULL }, handle_unload, "Unload a dynamic module by name", unload_help, complete_fn },
569         { { NULL }, NULL, NULL, NULL }
570 };
571
572 static struct ast_cli_entry *find_cli(char *cmds[], int exact)
573 {
574         int x;
575         int y;
576         int match;
577         struct ast_cli_entry *e=NULL;
578         for (x=0;builtins[x].cmda[0];x++) {
579                 /* start optimistic */
580                 match = 1;
581                 for (y=0;match && cmds[y]; y++) {
582                         /* If there are no more words in the candidate command, then we're
583                            there.  */
584                         if (!builtins[x].cmda[y] && !exact)
585                                 break;
586                         /* If there are no more words in the command (and we're looking for
587                            an exact match) or there is a difference between the two words,
588                            then this is not a match */
589                         if (!builtins[x].cmda[y] || strcasecmp(builtins[x].cmda[y], cmds[y]))
590                                 match = 0;
591                 }
592                 /* If more words are needed to complete the command then this is not
593                    a candidate (unless we're looking for a really inexact answer  */
594                 if ((exact > -1) && builtins[x].cmda[y])
595                         match = 0;
596                 if (match)
597                         return &builtins[x];
598         }
599         for (e=helpers;e;e=e->next) {
600                 match = 1;
601                 for (y=0;match && cmds[y]; y++) {
602                         if (!e->cmda[y] && !exact)
603                                 break;
604                         if (!e->cmda[y] || strcasecmp(e->cmda[y], cmds[y]))
605                                 match = 0;
606                 }
607                 if ((exact > -1) && e->cmda[y])
608                         match = 0;
609                 if (match)
610                         break;
611         }
612         return e;
613 }
614
615 static void join(char *s, int len, char *w[])
616 {
617         int x;
618         /* Join words into a string */
619         strcpy(s, "");
620         for (x=0;w[x];x++) {
621                 if (x)
622                         strncat(s, " ", len - strlen(s));
623                 strncat(s, w[x], len - strlen(s));
624         }
625 }
626
627 static void join2(char *s, int len, char *w[])
628 {
629         int x;
630         /* Join words into a string */
631         strcpy(s, "");
632         for (x=0;w[x];x++) {
633                 strncat(s, w[x], len - strlen(s));
634         }
635 }
636
637 static char *find_best(char *argv[])
638 {
639         static char cmdline[80];
640         int x;
641         /* See how close we get, then print the  */
642         char *myargv[AST_MAX_CMD_LEN];
643         for (x=0;x<AST_MAX_CMD_LEN;x++)
644                 myargv[x]=NULL;
645         for (x=0;argv[x];x++) {
646                 myargv[x] = argv[x];
647                 if (!find_cli(myargv, -1))
648                         break;
649         }
650         join(cmdline, sizeof(cmdline), myargv);
651         return cmdline;
652 }
653
654 int ast_cli_unregister(struct ast_cli_entry *e)
655 {
656         struct ast_cli_entry *cur, *l=NULL;
657         ast_pthread_mutex_lock(&clilock);
658         cur = helpers;
659         while(cur) {
660                 if (e == cur) {
661                         if (e->inuse) {
662                                 ast_log(LOG_WARNING, "Can't remove command that is in use\n");
663                         } else {
664                                 /* Rewrite */
665                                 if (l)
666                                         l->next = e->next;
667                                 else
668                                         helpers = e->next;
669                                 e->next = NULL;
670                                 break;
671                         }
672                 }
673                 l = cur;
674                 cur = cur->next;
675         }
676         ast_pthread_mutex_unlock(&clilock);
677         return 0;
678 }
679
680 int ast_cli_register(struct ast_cli_entry *e)
681 {
682         struct ast_cli_entry *cur, *l=NULL;
683         char fulle[80] ="", fulltst[80] ="";
684         static int len;
685         ast_pthread_mutex_lock(&clilock);
686         join2(fulle, sizeof(fulle), e->cmda);
687         if (find_cli(e->cmda, -1)) {
688                 ast_pthread_mutex_unlock(&clilock);
689                 ast_log(LOG_WARNING, "Command '%s' already registered (or something close enough)\n", fulle);
690                 return -1;
691         }
692         cur = helpers;
693         while(cur) {
694                 join2(fulltst, sizeof(fulltst), cur->cmda);
695                 len = strlen(fulltst);
696                 if (strlen(fulle) < len)
697                         len = strlen(fulle);
698                 if (strncasecmp(fulle, fulltst, len) < 0) {
699                         if (l) {
700                                 e->next = l->next;
701                                 l->next = e;
702                         } else {
703                                 e->next = helpers;
704                                 helpers = e;
705                         }
706                         break;
707                 }
708                 l = cur;
709                 cur = cur->next;
710         }
711         if (!cur) {
712                 if (l)
713                         l->next = e;
714                 else
715                         helpers = e;
716                 e->next = NULL;
717         }
718         ast_pthread_mutex_unlock(&clilock);
719         return 0;
720 }
721
722 static int help_workhorse(int fd, char *match[])
723 {
724         char fullcmd1[80];
725         char fullcmd2[80];
726         char matchstr[80];
727         char *fullcmd;
728         struct ast_cli_entry *e, *e1, *e2;
729         e1 = builtins;
730         e2 = helpers;
731         if (match)
732                 join(matchstr, sizeof(matchstr), match);
733         while(e1->cmda[0] || e2) {
734                 if (e2)
735                         join(fullcmd2, sizeof(fullcmd2), e2->cmda);
736                 if (e1->cmda[0])
737                         join(fullcmd1, sizeof(fullcmd1), e1->cmda);
738                 if (!e1->cmda[0] || 
739                                 (e2 && (strcmp(fullcmd2, fullcmd1) < 0))) {
740                         /* Use e2 */
741                         e = e2;
742                         fullcmd = fullcmd2;
743                         /* Increment by going to next */
744                         e2 = e2->next;
745                 } else {
746                         /* Use e1 */
747                         e = e1;
748                         fullcmd = fullcmd1;
749                         e1++;
750                 }
751                 /* Hide commands that start with '_' */
752                 if (fullcmd[0] == '_')
753                         continue;
754                 if (match) {
755                         if (strncasecmp(matchstr, fullcmd, strlen(matchstr))) {
756                                 continue;
757                         }
758                 }
759                 ast_cli(fd, "%20.20s   %s\n", fullcmd, e->summary);
760         }
761         return 0;
762 }
763
764 static int handle_help(int fd, int argc, char *argv[]) {
765         struct ast_cli_entry *e;
766         char fullcmd[80];
767         if ((argc < 1))
768                 return RESULT_SHOWUSAGE;
769         if (argc > 1) {
770                 e = find_cli(argv + 1, 1);
771                 if (e) 
772                         ast_cli(fd, e->usage);
773                 else {
774                         if (find_cli(argv + 1, -1)) {
775                                 return help_workhorse(fd, argv + 1);
776                         } else {
777                                 join(fullcmd, sizeof(fullcmd), argv+1);
778                                 ast_cli(fd, "No such command '%s'.\n", fullcmd);
779                         }
780                 }
781         } else {
782                 return help_workhorse(fd, NULL);
783         }
784         return RESULT_SUCCESS;
785 }
786
787 static char *parse_args(char *s, int *max, char *argv[])
788 {
789         char *dup, *cur;
790         int x=0;
791         int quoted=0;
792         int escaped=0;
793         int whitespace=1;
794
795         dup = strdup(s);
796         if (dup) {
797                 cur = dup;
798                 while(*s) {
799                         switch(*s) {
800                         case '"':
801                                 /* If it's escaped, put a literal quote */
802                                 if (escaped) 
803                                         goto normal;
804                                 else 
805                                         quoted = !quoted;
806                                 if (quoted && whitespace) {
807                                         /* If we're starting a quote, coming off white space start a new word, too */
808                                         argv[x++] = cur;
809                                         whitespace=0;
810                                 }
811                                 escaped = 0;
812                                 break;
813                         case ' ':
814                         case '\t':
815                                 if (!quoted && !escaped) {
816                                         /* If we're not quoted, mark this as whitespace, and
817                                            end the previous argument */
818                                         whitespace = 1;
819                                         *(cur++) = '\0';
820                                 } else
821                                         /* Otherwise, just treat it as anything else */ 
822                                         goto normal;
823                                 break;
824                         case '\\':
825                                 /* If we're escaped, print a literal, otherwise enable escaping */
826                                 if (escaped) {
827                                         goto normal;
828                                 } else {
829                                         escaped=1;
830                                 }
831                                 break;
832                         default:
833 normal:
834                                 if (whitespace) {
835                                         if (x >= AST_MAX_ARGS -1) {
836                                                 ast_log(LOG_WARNING, "Too many arguments, truncating\n");
837                                                 break;
838                                         }
839                                         /* Coming off of whitespace, start the next argument */
840                                         argv[x++] = cur;
841                                         whitespace=0;
842                                 }
843                                 *(cur++) = *s;
844                                 escaped=0;
845                         }
846                         s++;
847                 }
848                 /* Null terminate */
849                 *(cur++) = '\0';
850                 argv[x] = NULL;
851                 *max = x;
852         }
853         return dup;
854 }
855
856 /* This returns the number of unique matches for the generator */
857 int ast_cli_generatornummatches(char *text, char *word)
858 {
859         int matches = 0, i = 0;
860         char *buf, *oldbuf = NULL;
861
862
863         while ( (buf = ast_cli_generator(text, word, i)) ) {
864                 if (++i > 1 && strcmp(buf,oldbuf) == 0)  {
865                                 continue;
866                 }
867                 oldbuf = buf;
868                 matches++;
869         }
870
871         return matches;
872 }
873
874 char **ast_cli_completion_matches(char *text, char *word)
875 {
876         char **match_list = NULL, *retstr, *prevstr;
877         size_t match_list_len, max_equal, which, i;
878         int matches = 0;
879
880         match_list_len = 1;
881         while ((retstr = ast_cli_generator(text, word, matches)) != NULL) {
882                 if (matches + 1 >= match_list_len) {
883                         match_list_len <<= 1;
884                         match_list = realloc(match_list, match_list_len * sizeof(char *));
885                 }
886                 match_list[++matches] = retstr;
887         }
888
889         if (!match_list)
890                 return (char **) NULL;
891
892         which = 2;
893         prevstr = match_list[1];
894         max_equal = strlen(prevstr);
895         for (; which <= matches; which++) {
896                 for (i = 0; i < max_equal && prevstr[i] == match_list[which][i]; i++)
897                         continue;
898                 max_equal = i;
899         }
900
901         retstr = malloc(max_equal + 1);
902         (void) strncpy(retstr, match_list[1], max_equal);
903         retstr[max_equal] = '\0';
904         match_list[0] = retstr;
905
906         if (matches + 1 >= match_list_len)
907                 match_list = realloc(match_list, (match_list_len + 1) * sizeof(char *));
908         match_list[matches + 1] = (char *) NULL;
909
910         return (match_list);
911 }
912
913 static char *__ast_cli_generator(char *text, char *word, int state, int lock)
914 {
915         char *argv[AST_MAX_ARGS];
916         struct ast_cli_entry *e, *e1, *e2;
917         int x;
918         int matchnum=0;
919         char *dup, *res;
920         char fullcmd1[80];
921         char fullcmd2[80];
922         char matchstr[80];
923         char *fullcmd;
924
925         if ((dup = parse_args(text, &x, argv))) {
926                 join(matchstr, sizeof(matchstr), argv);
927                 if (lock)
928                         ast_pthread_mutex_lock(&clilock);
929                 e1 = builtins;
930                 e2 = helpers;
931                 while(e1->cmda[0] || e2) {
932                         if (e2)
933                                 join(fullcmd2, sizeof(fullcmd2), e2->cmda);
934                         if (e1->cmda[0])
935                                 join(fullcmd1, sizeof(fullcmd1), e1->cmda);
936                         if (!e1->cmda[0] || 
937                                         (e2 && (strcmp(fullcmd2, fullcmd1) < 0))) {
938                                 /* Use e2 */
939                                 e = e2;
940                                 fullcmd = fullcmd2;
941                                 /* Increment by going to next */
942                                 e2 = e2->next;
943                         } else {
944                                 /* Use e1 */
945                                 e = e1;
946                                 fullcmd = fullcmd1;
947                                 e1++;
948                         }
949                         if ((fullcmd[0] != '_') && !strncasecmp(matchstr, fullcmd, strlen(matchstr))) {
950                                 /* We contain the first part of one or more commands */
951                                 matchnum++;
952                                 if (matchnum > state) {
953                                         /* Now, what we're supposed to return is the next word... */
954                                         if (strlen(word) && x>0) {
955                                                 res = e->cmda[x-1];
956                                         } else {
957                                                 res = e->cmda[x];
958                                         }
959                                         if (res) {
960                                                 if (lock)
961                                                         ast_pthread_mutex_unlock(&clilock);
962                                                 free(dup);
963                                                 return res ? strdup(res) : NULL;
964                                         }
965                                 }
966                         }
967                         if (e->generator && !strncasecmp(matchstr, fullcmd, strlen(fullcmd))) {
968                                 /* We have a command in its entirity within us -- theoretically only one
969                                    command can have this occur */
970                                 fullcmd = e->generator(text, word, (strlen(word) ? (x - 1) : (x)), state);
971                                 if (lock)
972                                         ast_pthread_mutex_unlock(&clilock);
973                                 return fullcmd;
974                         }
975                         
976                 }
977                 if (lock)
978                         ast_pthread_mutex_unlock(&clilock);
979                 free(dup);
980         }
981         return NULL;
982 }
983
984 char *ast_cli_generator(char *text, char *word, int state)
985 {
986         return __ast_cli_generator(text, word, state, 1);
987 }
988
989 int ast_cli_command(int fd, char *s)
990 {
991         char *argv[AST_MAX_ARGS];
992         struct ast_cli_entry *e;
993         int x;
994         char *dup;
995         x = AST_MAX_ARGS;
996         if ((dup = parse_args(s, &x, argv))) {
997                 /* We need at least one entry, or ignore */
998                 if (x > 0) {
999                         ast_pthread_mutex_lock(&clilock);
1000                         e = find_cli(argv, 0);
1001                         if (e)
1002                                 e->inuse++;
1003                         ast_pthread_mutex_unlock(&clilock);
1004                         if (e) {
1005                                 switch(e->handler(fd, x, argv)) {
1006                                 case RESULT_SHOWUSAGE:
1007                                         ast_cli(fd, e->usage);
1008                                         break;
1009                                 default:
1010                                 }
1011                         } else 
1012                                 ast_cli(fd, "No such command '%s' (type 'help' for help)\n", find_best(argv));
1013                         if (e) {
1014                                 ast_pthread_mutex_lock(&clilock);
1015                                 e->inuse--;
1016                                 ast_pthread_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 }