Version 0.1.9 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         "      Caller ID: %s\n"
172         "    DNID Digits: %s\n"
173         "          State: %d\n"
174         "          Rings: %d\n"
175         "    WriteFormat: %d\n"
176         "     ReadFormat: %d\n"
177         "   NativeFormat: %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, 
188         (c->callerid ? c->callerid : "(N/A)"),
189         (c->dnid ? c->dnid : "(N/A)" ), c->state, c->rings, c->nativeformats, c->writeformat, c->readformat,
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         { { "help", NULL }, handle_help, "Display help list, or specific help on a command", help_help },
237         { { "load", NULL }, handle_load, "Load a dynamic module by name", load_help, complete_fn },
238         { { "show", "channel", NULL }, handle_showchan, "Display information on a specific channel", showchan_help, complete_ch },
239         { { "show", "channels", NULL }, handle_chanlist, "Display information on channels", chanlist_help },
240     { { "show", "modules", NULL }, handle_modlist, "List modules and info", modlist_help },
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 && cmds[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 void join2(char *s, int len, char *w[])
301 {
302         int x;
303         /* Join words into a string */
304         strcpy(s, "");
305         for (x=0;w[x];x++) {
306                 strncat(s, w[x], len - strlen(s));
307         }
308 }
309
310 static char *find_best(char *argv[])
311 {
312         static char cmdline[80];
313         int x;
314         /* See how close we get, then print the  */
315         char *myargv[AST_MAX_CMD_LEN];
316         for (x=0;x<AST_MAX_CMD_LEN;x++)
317                 myargv[x]=NULL;
318         for (x=0;argv[x];x++) {
319                 myargv[x] = argv[x];
320                 if (!find_cli(myargv, -1))
321                         break;
322         }
323         join(cmdline, sizeof(cmdline), myargv);
324         return cmdline;
325 }
326
327 int ast_cli_unregister(struct ast_cli_entry *e)
328 {
329         struct ast_cli_entry *cur, *l=NULL;
330         pthread_mutex_lock(&clilock);
331         cur = helpers;
332         while(cur) {
333                 if (e == cur) {
334                         /* Rewrite */
335                         if (l)
336                                 l->next = e->next;
337                         else
338                                 helpers = e->next;
339                         e->next = NULL;
340                         break;
341                 }
342                 l = cur;
343                 cur = cur->next;
344         }
345         pthread_mutex_unlock(&clilock);
346         return 0;
347 }
348
349 int ast_cli_register(struct ast_cli_entry *e)
350 {
351         struct ast_cli_entry *cur, *l=NULL;
352         char fulle[80], fulltst[80];
353         static int len;
354         pthread_mutex_lock(&clilock);
355         join2(fulle, sizeof(fulle), e->cmda);
356         if (find_cli(e->cmda, -1)) {
357                 ast_log(LOG_WARNING, "Command '%s' already registered (or something close enough)\n", fulle);
358                 pthread_mutex_unlock(&clilock);
359                 return -1;
360         }
361         cur = helpers;
362         while(cur) {
363                 join2(fulltst, sizeof(fulltst), cur->cmda);
364                 len = strlen(fulltst);
365                 if (strlen(fulle) < len)
366                         len = strlen(fulle);
367                 if (strncasecmp(fulle, fulltst, len) < 0) {
368                         if (l) {
369                                 e->next = l->next;
370                                 l->next = e;
371                         } else {
372                                 e->next = helpers;
373                                 helpers = e;
374                         }
375                         break;
376                 }
377                 l = cur;
378                 cur = cur->next;
379         }
380         if (!cur) {
381                 if (l)
382                         l->next = e;
383                 else
384                         helpers = e;
385                 e->next = NULL;
386         }
387         pthread_mutex_unlock(&clilock);
388         return 0;
389 }
390
391 static int help_workhorse(int fd, char *match[])
392 {
393         char fullcmd1[80];
394         char fullcmd2[80];
395         char matchstr[80];
396         char *fullcmd;
397         struct ast_cli_entry *e, *e1, *e2;
398         e1 = builtins;
399         e2 = helpers;
400         if (match)
401                 join(matchstr, sizeof(matchstr), match);
402         while(e1->cmda[0] || e2) {
403                 if (e2)
404                         join(fullcmd2, sizeof(fullcmd2), e2->cmda);
405                 if (e1->cmda[0])
406                         join(fullcmd1, sizeof(fullcmd1), e1->cmda);
407                 if (!e1->cmda[0] || 
408                                 (e2 && (strcmp(fullcmd2, fullcmd1) < 0))) {
409                         /* Use e2 */
410                         e = e2;
411                         fullcmd = fullcmd2;
412                         /* Increment by going to next */
413                         e2 = e2->next;
414                 } else {
415                         /* Use e1 */
416                         e = e1;
417                         fullcmd = fullcmd1;
418                         e1++;
419                 }
420                 if (match) {
421                         if (strncasecmp(matchstr, fullcmd, strlen(matchstr))) {
422                                 continue;
423                         }
424                 }
425                 ast_cli(fd, "%20.20s   %s\n", fullcmd, e->summary);
426         }
427         return 0;
428 }
429
430 static int handle_help(int fd, int argc, char *argv[]) {
431         struct ast_cli_entry *e;
432         char fullcmd[80];
433         if ((argc < 1))
434                 return RESULT_SHOWUSAGE;
435         if (argc > 1) {
436                 e = find_cli(argv + 1, 1);
437                 if (e) 
438                         ast_cli(fd, e->usage);
439                 else {
440                         if (find_cli(argv + 1, -1)) {
441                                 return help_workhorse(fd, argv + 1);
442                         } else {
443                                 join(fullcmd, sizeof(fullcmd), argv+1);
444                                 ast_cli(fd, "No such command '%s'.\n", fullcmd);
445                         }
446                 }
447         } else {
448                 return help_workhorse(fd, NULL);
449         }
450         return RESULT_SUCCESS;
451 }
452
453 static char *parse_args(char *s, int *max, char *argv[])
454 {
455         char *dup, *cur;
456         int x=0;
457         int quoted=0;
458         int escaped=0;
459         int whitespace=1;
460
461         dup = strdup(s);
462         if (dup) {
463                 cur = dup;
464                 while(*s) {
465                         switch(*s) {
466                         case '"':
467                                 /* If it's escaped, put a literal quote */
468                                 if (escaped) 
469                                         goto normal;
470                                 else 
471                                         quoted = !quoted;
472                                 escaped = 0;
473                                 break;
474                         case ' ':
475                         case '\t':
476                                 if (!quoted && !escaped) {
477                                         /* If we're not quoted, mark this as whitespace, and
478                                            end the previous argument */
479                                         whitespace = 1;
480                                         *(cur++) = '\0';
481                                 } else
482                                         /* Otherwise, just treat it as anything else */ 
483                                         goto normal;
484                                 break;
485                         case '\\':
486                                 /* If we're escaped, print a literal, otherwise enable escaping */
487                                 if (escaped) {
488                                         goto normal;
489                                 } else {
490                                         escaped=1;
491                                 }
492                                 break;
493                         default:
494 normal:
495                                 if (whitespace) {
496                                         if (x >= AST_MAX_ARGS -1) {
497                                                 ast_log(LOG_WARNING, "Too many arguments, truncating\n");
498                                                 break;
499                                         }
500                                         /* Coming off of whitespace, start the next argument */
501                                         argv[x++] = cur;
502                                         whitespace=0;
503                                 }
504                                 *(cur++) = *s;
505                                 escaped=0;
506                         }
507                         s++;
508                 }
509                 /* Null terminate */
510                 *(cur++) = '\0';
511                 argv[x] = NULL;
512                 *max = x;
513         }
514         return dup;
515 }
516
517 char *ast_cli_generator(char *text, char *word, int state)
518 {
519         char *argv[AST_MAX_ARGS];
520         struct ast_cli_entry *e, *e1, *e2;
521         int x;
522         int matchnum=0;
523         char *dup, *res;
524         char fullcmd1[80];
525         char fullcmd2[80];
526         char matchstr[80];
527         char *fullcmd;
528
529         if ((dup = parse_args(text, &x, argv))) {
530                 join(matchstr, sizeof(matchstr), argv);
531                 pthread_mutex_lock(&clilock);
532                 e1 = builtins;
533                 e2 = helpers;
534                 while(e1->cmda[0] || e2) {
535                         if (e2)
536                                 join(fullcmd2, sizeof(fullcmd2), e2->cmda);
537                         if (e1->cmda[0])
538                                 join(fullcmd1, sizeof(fullcmd1), e1->cmda);
539                         if (!e1->cmda || 
540                                         (e2 && (strcmp(fullcmd2, fullcmd1) < 0))) {
541                                 /* Use e2 */
542                                 e = e2;
543                                 fullcmd = fullcmd2;
544                                 /* Increment by going to next */
545                                 e2 = e2->next;
546                         } else {
547                                 /* Use e1 */
548                                 e = e1;
549                                 fullcmd = fullcmd1;
550                                 e1++;
551                         }
552                         if (!strncasecmp(matchstr, fullcmd, strlen(matchstr))) {
553                                 /* We contain the first part of one or more commands */
554                                 matchnum++;
555                                 if (matchnum > state) {
556                                         /* Now, what we're supposed to return is the next word... */
557                                         if (strlen(word)) {
558                                                 res = e->cmda[x-1];
559                                         } else {
560                                                 res = e->cmda[x];
561                                         }
562                                         if (res) {
563                                                 pthread_mutex_unlock(&clilock);
564                                                 return res ? strdup(res) : NULL;
565                                         }
566                                 }
567                         }
568                         if (e->generator && !strncasecmp(matchstr, fullcmd, strlen(fullcmd))) {
569                                 /* We have a command in its entirity within us -- theoretically only one
570                                    command can have this occur */
571                                 fullcmd = e->generator(text, word, (strlen(word) ? (x - 1) : (x)), state);
572                                 pthread_mutex_unlock(&clilock);
573                                 return fullcmd;
574                         }
575                         
576                 }
577                 pthread_mutex_unlock(&clilock);
578                 free(dup);
579         }
580         return NULL;
581 }
582
583 int ast_cli_command(int fd, char *s)
584 {
585         char *argv[AST_MAX_ARGS];
586         struct ast_cli_entry *e;
587         int x;
588         char *dup;
589         x = AST_MAX_ARGS;
590         if ((dup = parse_args(s, &x, argv))) {
591                 /* We need at least one entry, or ignore */
592                 if (x > 0) {
593                         pthread_mutex_lock(&clilock);
594                         e = find_cli(argv, 0);
595                         if (e) {
596                                 switch(e->handler(fd, x, argv)) {
597                                 case RESULT_SHOWUSAGE:
598                                         ast_cli(fd, e->usage);
599                                         break;
600                                 default:
601                                 }
602                         } else 
603                                 ast_cli(fd, "No such command '%s' (type 'help' for help)\n", find_best(argv));
604                         pthread_mutex_unlock(&clilock);
605                 }
606                 free(dup);
607         } else {
608                 ast_log(LOG_WARNING, "Out of memory\n");        
609                 return -1;
610         }
611         return 0;
612 }