Version 0.1.1 from FTP
[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 <sys/signal.h>
22 #include <stdio.h>
23 #include <signal.h>
24 #include <string.h>
25 #include <pthread.h>
26 /* For rl_filename_completion */
27 #include <readline/readline.h>
28 /* For module directory */
29 #include "asterisk.h"
30
31 void ast_cli(int fd, char *fmt, ...)
32 {
33         char stuff[4096];
34         va_list ap;
35         va_start(ap, fmt);
36         vsnprintf(stuff, sizeof(stuff), fmt, ap);
37         va_end(ap);
38         write(fd, stuff, strlen(stuff));
39 }
40
41 pthread_mutex_t clilock = PTHREAD_MUTEX_INITIALIZER;
42
43
44 struct ast_cli_entry *helpers = NULL;
45
46 static char load_help[] = 
47 "Usage: load <module name>\n"
48 "       Loads the specified module into Asterisk.\n";
49
50 static char unload_help[] = 
51 "Usage: unload [-f|-h] <module name>\n"
52 "       Unloads the specified module from Asterisk.  The -f\n"
53 "       option causes the module to be unloaded even if it is\n"
54 "       in use (may cause a crash) and the -h module causes the\n"
55 "       module to be unloaded even if the module says it cannot, \n"
56 "       which almost always will cause a crash.\n";
57
58 static char help_help[] =
59 "Usage: help [topic]\n"
60 "       When called with a topic as an argument, displays usage\n"
61 "       information on the given command.  If called without a\n"
62 "       topic, it provides a list of commands.\n";
63
64 static char chanlist_help[] = 
65 "Usage: show channels\n"
66 "       Lists currently defined channels and some information about\n"
67 "       them.\n";
68
69 static int handle_load(int fd, int argc, char *argv[])
70 {
71         if (argc != 2)
72                 return RESULT_SHOWUSAGE;
73         if (ast_load_resource(argv[1])) {
74                 ast_cli(fd, "Unable to load module %s\n", argv[1]);
75                 return RESULT_FAILURE;
76         }
77         return RESULT_SUCCESS;
78 }
79
80 static int handle_unload(int fd, int argc, char *argv[])
81 {
82         int x;
83         int force=AST_FORCE_SOFT;
84         if (argc < 2)
85                 return RESULT_SHOWUSAGE;
86         for (x=1;x<argc;x++) {
87                 if (argv[x][0] == '-') {
88                         switch(argv[x][1]) {
89                         case 'f':
90                                 force = AST_FORCE_FIRM;
91                                 break;
92                         case 'h':
93                                 force = AST_FORCE_HARD;
94                                 break;
95                         default:
96                                 return RESULT_SHOWUSAGE;
97                         }
98                 } else if (x !=  argc - 1) 
99                         return RESULT_SHOWUSAGE;
100                 else if (ast_unload_resource(argv[x], force)) {
101                         ast_cli(fd, "Unable to unload resource %s\n", argv[x]);
102                         return RESULT_FAILURE;
103                 }
104         }
105         return RESULT_SUCCESS;
106 }
107
108 #define MODLIST_FORMAT  "%-20s %-40.40s %-10d\n"
109 #define MODLIST_FORMAT2 "%-20s %-40.40s %-10s\n"
110
111 static pthread_mutex_t climodentrylock = PTHREAD_MUTEX_INITIALIZER;
112 static int climodentryfd = -1;
113
114 static int modlist_modentry(char *module, char *description, int usecnt)
115 {
116         ast_cli(climodentryfd, MODLIST_FORMAT, module, description, usecnt);
117         return 0;
118 }
119
120 static char modlist_help[] =
121 "Usage: show modules\n"
122 "       Shows Asterisk modules currently in use, and usage "
123 "statistics.\n";
124
125 static int handle_modlist(int fd, int argc, char *argv[])
126 {
127         if (argc != 2)
128                 return RESULT_SHOWUSAGE;
129         pthread_mutex_lock(&climodentrylock);
130         climodentryfd = fd;
131         ast_cli(fd, MODLIST_FORMAT2, "Module", "Description", "Use Count");
132         ast_update_module_list(modlist_modentry);
133         climodentryfd = -1;
134         pthread_mutex_unlock(&climodentrylock);
135         return RESULT_SUCCESS;
136 }
137
138 static int handle_chanlist(int fd, int argc, char *argv[])
139 {
140 #define FORMAT_STRING  "%15s  (%-10s %-12s %-4d)  %-12s  %-15s\n"
141 #define FORMAT_STRING2 "%15s  (%-10s %-12s %-4s)  %-12s  %-15s\n"
142         struct ast_channel *c=NULL;
143         if (argc != 2)
144                 return RESULT_SHOWUSAGE;
145         c = ast_channel_walk(NULL);
146         ast_cli(fd, FORMAT_STRING2, "Channel", "Context", "Extension", "Pri", "Appl.", "Data");
147         while(c) {
148                 ast_cli(fd, FORMAT_STRING, c->name, c->context, c->exten, c->priority, 
149                 c->appl ? c->appl : "(None)", c->data ? ( strlen(c->data) ? c->data : "(Empty)" ): "(None)");
150                 c = ast_channel_walk(c);
151         }
152         return RESULT_SUCCESS;
153 }
154
155 static char showchan_help[] = 
156 "Usage: show channel <channel>\n"
157 "       Shows lots of information about the specified channel.\n";
158
159 static int handle_showchan(int fd, int argc, char *argv[])
160 {
161         struct ast_channel *c=NULL;
162         if (argc != 3)
163                 return RESULT_SHOWUSAGE;
164         c = ast_channel_walk(NULL);
165         while(c) {
166                 if (!strcasecmp(c->name, argv[2])) {
167                         ast_cli(fd, 
168         " -- General --\n"
169         "           Name: %s\n"
170         "           Type: %s\n"
171         "     Translator: %s\n"
172         "         Master: %s\n"
173         "      Caller ID: %s\n"
174         "    DNID Digits: %s\n"
175         "          State: %d\n"
176         "          Rings: %d\n"
177         "         Format: %d\n"
178         "File Descriptor: %d\n"
179         " --   PBX   --\n"
180         "        Context: %s\n"
181         "      Extension: %s\n"
182         "       Priority: %d\n"
183         "    Application: %s\n"
184         "           Data: %s\n"
185         "          Stack: %d\n"
186         "    Blocking in: %s\n",
187         c->name, c->type, (c->trans ? c->trans->name : "(N/A)"),
188         (c->master ? c->master->name : "(N/A)"), (c->callerid ? c->callerid : "(N/A)"),
189         (c->dnid ? c->dnid : "(N/A)" ), c->state, c->rings, c->format,
190         c->fd, c->context, c->exten, c->priority, ( c->appl ? c->appl : "(N/A)" ),
191         ( c-> data ? (strlen(c->data) ? c->data : "(Empty)") : "(None)"),
192         c->stack, (c->blocking ? c->blockproc : "(Not Blocking)"));
193         
194                 break;
195                 }
196                 c = ast_channel_walk(c);
197         }
198         if (!c) 
199                 ast_cli(fd, "%s is not a known channel\n", argv[2]);
200         return RESULT_SUCCESS;
201 }
202
203 static char *complete_ch(char *line, char *word, int pos, int state)
204 {
205         struct ast_channel *c;
206         int which=0;
207         c = ast_channel_walk(NULL);
208         while(c) {
209                 if (++which > state)
210                         break;
211                 c = ast_channel_walk(c);
212         }
213         return c ? strdup(c->name) : NULL;
214 }
215
216 static char *complete_fn(char *line, char *word, int pos, int state)
217 {
218         char *c;
219         char filename[256];
220         if (pos != 1)
221                 return NULL;
222         if (word[0] == '/')
223                 strncpy(filename, word, sizeof(filename));
224         else
225                 snprintf(filename, sizeof(filename), "%s/%s", AST_MODULE_DIR, word);
226         c = filename_completion_function(filename, state);
227         if (c && word[0] != '/')
228                 c += (strlen(AST_MODULE_DIR) + 1);
229         return c ? strdup(c) : c;
230 }
231
232 static int handle_help(int fd, int argc, char *argv[]);
233
234 static struct ast_cli_entry builtins[] = {
235         /* Keep alphabetized */
236         { { "show" , "channels", NULL }, handle_chanlist, "Display information on channels", chanlist_help },
237         { { "help", NULL }, handle_help, "Display help list, or specific help on a command", help_help },
238         { { "load", NULL }, handle_load, "Load a dynamic module by name", load_help, complete_fn },
239     { { "show", "modules", NULL }, handle_modlist, "List modules and info", modlist_help },
240         { { "show", "channel", NULL }, handle_showchan, "Display information on a specific channel", showchan_help, complete_ch },
241         { { "unload", NULL }, handle_unload, "Unload a dynamic module by name", unload_help, complete_fn },
242         { { NULL }, NULL, NULL, NULL }
243 };
244
245 static struct ast_cli_entry *find_cli(char *cmds[], int exact)
246 {
247         int x;
248         int y;
249         int match;
250         struct ast_cli_entry *e=NULL;
251         for (x=0;builtins[x].cmda[0];x++) {
252                 /* start optimistic */
253                 match = 1;
254                 for (y=0;match && cmds[y]; y++) {
255                         /* If there are no more words in the candidate command, then we're
256                            there.  */
257                         if (!builtins[x].cmda[y] && !exact)
258                                 break;
259                         /* If there are no more words in the command (and we're looking for
260                            an exact match) or there is a difference between the two words,
261                            then this is not a match */
262                         if (!builtins[x].cmda[y] || strcasecmp(builtins[x].cmda[y], cmds[y]))
263                                 match = 0;
264                 }
265                 /* If more words are needed to complete the command then this is not
266                    a candidate (unless we're looking for a really inexact answer  */
267                 if ((exact > -1) && builtins[x].cmda[y])
268                         match = 0;
269                 if (match)
270                         return &builtins[x];
271         }
272         for (e=helpers;e;e=e->next) {
273                 match = 1;
274                 for (y=0;match && e->cmda[y]; y++) {
275                         if (!e->cmda[y] && !exact)
276                                 break;
277                         if (!e->cmda[y] || strcasecmp(e->cmda[y], cmds[y]))
278                                 match = 0;
279                 }
280                 if ((exact > -1) && e->cmda[y])
281                         match = 0;
282                 if (match)
283                         break;
284         }
285         return e;
286 }
287
288 static void join(char *s, int len, char *w[])
289 {
290         int x;
291         /* Join words into a string */
292         strcpy(s, "");
293         for (x=0;w[x];x++) {
294                 if (x)
295                         strncat(s, " ", len - strlen(s));
296                 strncat(s, w[x], len - strlen(s));
297         }
298 }
299
300 static char *find_best(char *argv[])
301 {
302         static char cmdline[80];
303         int x;
304         /* See how close we get, then print the  */
305         char *myargv[AST_MAX_CMD_LEN];
306         for (x=0;x<AST_MAX_CMD_LEN;x++)
307                 myargv[x]=NULL;
308         for (x=0;argv[x];x++) {
309                 myargv[x] = argv[x];
310                 if (!find_cli(myargv, -1))
311                         break;
312         }
313         join(cmdline, sizeof(cmdline), myargv);
314         return cmdline;
315 }
316
317 int ast_cli_register(struct ast_cli_entry *e)
318 {
319         struct ast_cli_entry *cur, *l=NULL;
320         char fulle[80], fulltst[80];
321         pthread_mutex_lock(&clilock);
322         join(fulle, sizeof(fulle), e->cmda);
323         if (find_cli(e->cmda, -1)) {
324                 ast_log(LOG_WARNING, "Command '%s' already registered (or something close enough)\n", fulle);
325                 pthread_mutex_unlock(&clilock);
326                 return -1;
327         }
328         cur = helpers;
329         while(cur) {
330                 join(fulltst, sizeof(fulltst), cur->cmda);
331                 if (strcmp(fulle, fulltst) > 0) {
332                         /* Put it here */
333                         e->next = cur->next;
334                         cur->next = e;
335                         break;
336                 }
337                 l = cur;
338                 cur = cur->next;
339         }
340         if (!cur) {
341                 if (l)
342                         l->next = e;
343                 else
344                         helpers = e;
345                 e->next = NULL;
346         }
347         pthread_mutex_unlock(&clilock);
348         return 0;
349 }
350
351 static int help_workhorse(int fd, char *match[])
352 {
353         char fullcmd1[80];
354         char fullcmd2[80];
355         char matchstr[80];
356         char *fullcmd;
357         struct ast_cli_entry *e, *e1, *e2;
358         e1 = builtins;
359         e2 = helpers;
360         if (match)
361                 join(matchstr, sizeof(matchstr), match);
362         while(e1->cmda[0] || e2) {
363                 if (e2)
364                         join(fullcmd2, sizeof(fullcmd2), e2->cmda);
365                 if (e1->cmda[0])
366                         join(fullcmd1, sizeof(fullcmd1), e1->cmda);
367                 if (!e1->cmda || 
368                                 (e2 && (strcmp(fullcmd2, fullcmd1) < 0))) {
369                         /* Use e2 */
370                         e = e2;
371                         fullcmd = fullcmd2;
372                         /* Increment by going to next */
373                         e2 = e2->next;
374                 } else {
375                         /* Use e1 */
376                         e = e1;
377                         fullcmd = fullcmd1;
378                         e1++;
379                 }
380                 if (match) {
381                         if (strncasecmp(matchstr, fullcmd, strlen(matchstr))) {
382                                 continue;
383                         }
384                 }
385                 ast_cli(fd, "%15s   %s\n", fullcmd, e->summary);
386         }
387         return 0;
388 }
389
390 static int handle_help(int fd, int argc, char *argv[]) {
391         struct ast_cli_entry *e;
392         char fullcmd[80];
393         if ((argc < 1))
394                 return RESULT_SHOWUSAGE;
395         if (argc > 1) {
396                 e = find_cli(argv + 1, 1);
397                 if (e) 
398                         ast_cli(fd, e->usage);
399                 else {
400                         if (find_cli(argv + 1, -1)) {
401                                 return help_workhorse(fd, argv + 1);
402                         } else {
403                                 join(fullcmd, sizeof(fullcmd), argv+1);
404                                 ast_cli(fd, "No such command '%s'.\n", fullcmd);
405                         }
406                 }
407         } else {
408                 return help_workhorse(fd, NULL);
409         }
410         return RESULT_SUCCESS;
411 }
412
413 static char *parse_args(char *s, int *max, char *argv[])
414 {
415         char *dup, *cur;
416         int x=0;
417         int quoted=0;
418         int escaped=0;
419         int whitespace=1;
420
421         dup = strdup(s);
422         if (dup) {
423                 cur = dup;
424                 while(*s) {
425                         switch(*s) {
426                         case '"':
427                                 /* If it's escaped, put a literal quote */
428                                 if (escaped) 
429                                         goto normal;
430                                 else 
431                                         quoted = !quoted;
432                                 escaped = 0;
433                                 break;
434                         case ' ':
435                         case '\t':
436                                 if (!quoted && !escaped) {
437                                         /* If we're not quoted, mark this as whitespace, and
438                                            end the previous argument */
439                                         whitespace = 1;
440                                         *(cur++) = '\0';
441                                 } else
442                                         /* Otherwise, just treat it as anything else */ 
443                                         goto normal;
444                                 break;
445                         case '\\':
446                                 /* If we're escaped, print a literal, otherwise enable escaping */
447                                 if (escaped) {
448                                         goto normal;
449                                 } else {
450                                         escaped=1;
451                                 }
452                                 break;
453                         default:
454 normal:
455                                 if (whitespace) {
456                                         if (x >= AST_MAX_ARGS -1) {
457                                                 ast_log(LOG_WARNING, "Too many arguments, truncating\n");
458                                                 break;
459                                         }
460                                         /* Coming off of whitespace, start the next argument */
461                                         argv[x++] = cur;
462                                         whitespace=0;
463                                 }
464                                 *(cur++) = *s;
465                                 escaped=0;
466                         }
467                         s++;
468                 }
469                 /* Null terminate */
470                 *(cur++) = '\0';
471                 argv[x] = NULL;
472                 *max = x;
473         }
474         return dup;
475 }
476
477 char *ast_cli_generator(char *text, char *word, int state)
478 {
479         char *argv[AST_MAX_ARGS];
480         struct ast_cli_entry *e, *e1, *e2;
481         int x;
482         int matchnum=0;
483         char *dup, *res;
484         char fullcmd1[80];
485         char fullcmd2[80];
486         char matchstr[80];
487         char *fullcmd;
488
489         if ((dup = parse_args(text, &x, argv))) {
490                 join(matchstr, sizeof(matchstr), argv);
491                 pthread_mutex_lock(&clilock);
492                 e1 = builtins;
493                 e2 = helpers;
494                 while(e1->cmda[0] || e2) {
495                         if (e2)
496                                 join(fullcmd2, sizeof(fullcmd2), e2->cmda);
497                         if (e1->cmda[0])
498                                 join(fullcmd1, sizeof(fullcmd1), e1->cmda);
499                         if (!e1->cmda || 
500                                         (e2 && (strcmp(fullcmd2, fullcmd1) < 0))) {
501                                 /* Use e2 */
502                                 e = e2;
503                                 fullcmd = fullcmd2;
504                                 /* Increment by going to next */
505                                 e2 = e2->next;
506                         } else {
507                                 /* Use e1 */
508                                 e = e1;
509                                 fullcmd = fullcmd1;
510                                 e1++;
511                         }
512                         if (!strncasecmp(matchstr, fullcmd, strlen(matchstr))) {
513                                 /* We contain the first part of one or more commands */
514                                 matchnum++;
515                                 if (matchnum > state) {
516                                         /* Now, what we're supposed to return is the next word... */
517                                         if (strlen(word)) {
518                                                 res = e->cmda[x-1];
519                                         } else {
520                                                 res = e->cmda[x];
521                                         }
522                                         if (res) {
523                                                 pthread_mutex_unlock(&clilock);
524                                                 return res ? strdup(res) : NULL;
525                                         }
526                                 }
527                         }
528                         if (e->generator && !strncasecmp(matchstr, fullcmd, strlen(fullcmd))) {
529                                 /* We have a command in its entirity within us -- theoretically only one
530                                    command can have this occur */
531                                 fullcmd = e->generator(text, word, (strlen(word) ? (x - 1) : (x)), state);
532                                 pthread_mutex_unlock(&clilock);
533                                 return fullcmd;
534                         }
535                         
536                 }
537                 pthread_mutex_unlock(&clilock);
538                 free(dup);
539         }
540         return NULL;
541 }
542
543 int ast_cli_command(int fd, char *s)
544 {
545         char *argv[AST_MAX_ARGS];
546         struct ast_cli_entry *e;
547         int x;
548         char *dup;
549         x = AST_MAX_ARGS;
550         if ((dup = parse_args(s, &x, argv))) {
551                 /* We need at least one entry, or ignore */
552                 if (x > 0) {
553                         pthread_mutex_lock(&clilock);
554                         e = find_cli(argv, 0);
555                         if (e) {
556                                 switch(e->handler(fd, x, argv)) {
557                                 case RESULT_SHOWUSAGE:
558                                         ast_cli(fd, e->usage);
559                                         break;
560                                 default:
561                                 }
562                         } else 
563                                 ast_cli(fd, "No such command '%s'\n", find_best(argv));
564                         pthread_mutex_unlock(&clilock);
565                 }
566                 free(dup);
567         } else {
568                 ast_log(LOG_WARNING, "Out of memory\n");        
569                 return -1;
570         }
571         return 0;
572 }