Merge "core/frame: Fix ast_frdup() and ast_frisolate() for empty text frames"
[asterisk/asterisk.git] / main / pbx_app.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2016, CFWare, LLC
5  *
6  * Corey Farrell <git@cfware.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief Custom function management routines.
22  *
23  * \author Corey Farrell <git@cfware.com>
24  */
25
26 /*** MODULEINFO
27         <support_level>core</support_level>
28  ***/
29
30 #include "asterisk.h"
31
32 #include "asterisk/_private.h"
33 #include "asterisk/cli.h"
34 #include "asterisk/linkedlists.h"
35 #include "asterisk/module.h"
36 #include "asterisk/pbx.h"
37 #include "asterisk/stasis_channels.h"
38 #include "asterisk/strings.h"
39 #include "asterisk/term.h"
40 #include "asterisk/utils.h"
41 #include "asterisk/xmldoc.h"
42 #include "pbx_private.h"
43
44 /*! \brief ast_app: A registered application */
45 struct ast_app {
46         int (*execute)(struct ast_channel *chan, const char *data);
47         AST_DECLARE_STRING_FIELDS(
48                 AST_STRING_FIELD(synopsis);     /*!< Synopsis text for 'show applications' */
49                 AST_STRING_FIELD(description);  /*!< Description (help text) for 'show application &lt;name&gt;' */
50                 AST_STRING_FIELD(syntax);       /*!< Syntax text for 'core show applications' */
51                 AST_STRING_FIELD(arguments);    /*!< Arguments description */
52                 AST_STRING_FIELD(seealso);      /*!< See also */
53         );
54 #ifdef AST_XML_DOCS
55         enum ast_doc_src docsrc;                /*!< Where the documentation come from. */
56 #endif
57         AST_RWLIST_ENTRY(ast_app) list;         /*!< Next app in list */
58         struct ast_module *module;              /*!< Module this app belongs to */
59         char name[0];                           /*!< Name of the application */
60 };
61
62 /*!
63  * \brief Registered applications container.
64  *
65  * It is sorted by application name.
66  */
67 static AST_RWLIST_HEAD_STATIC(apps, ast_app);
68
69 static struct ast_app *pbx_findapp_nolock(const char *name)
70 {
71         struct ast_app *cur;
72         int cmp;
73
74         AST_RWLIST_TRAVERSE(&apps, cur, list) {
75                 cmp = strcasecmp(name, cur->name);
76                 if (cmp > 0) {
77                         continue;
78                 }
79                 if (!cmp) {
80                         /* Found it. */
81                         break;
82                 }
83                 /* Not in container. */
84                 cur = NULL;
85                 break;
86         }
87
88         return cur;
89 }
90
91 struct ast_app *pbx_findapp(const char *app)
92 {
93         struct ast_app *ret;
94
95         AST_RWLIST_RDLOCK(&apps);
96         ret = pbx_findapp_nolock(app);
97         AST_RWLIST_UNLOCK(&apps);
98
99         return ret;
100 }
101
102 /*! \brief Dynamically register a new dial plan application */
103 int ast_register_application2(const char *app, int (*execute)(struct ast_channel *, const char *), const char *synopsis, const char *description, void *mod)
104 {
105         struct ast_app *tmp;
106         struct ast_app *cur;
107         int length;
108 #ifdef AST_XML_DOCS
109         char *tmpxml;
110 #endif
111
112         AST_RWLIST_WRLOCK(&apps);
113         cur = pbx_findapp_nolock(app);
114         if (cur) {
115                 ast_log(LOG_WARNING, "Already have an application '%s'\n", app);
116                 AST_RWLIST_UNLOCK(&apps);
117                 return -1;
118         }
119
120         length = sizeof(*tmp) + strlen(app) + 1;
121
122         if (!(tmp = ast_calloc(1, length))) {
123                 AST_RWLIST_UNLOCK(&apps);
124                 return -1;
125         }
126
127         if (ast_string_field_init(tmp, 128)) {
128                 AST_RWLIST_UNLOCK(&apps);
129                 ast_free(tmp);
130                 return -1;
131         }
132
133         strcpy(tmp->name, app);
134         tmp->execute = execute;
135         tmp->module = mod;
136
137 #ifdef AST_XML_DOCS
138         /* Try to lookup the docs in our XML documentation database */
139         if (ast_strlen_zero(synopsis) && ast_strlen_zero(description)) {
140                 /* load synopsis */
141                 tmpxml = ast_xmldoc_build_synopsis("application", app, ast_module_name(tmp->module));
142                 ast_string_field_set(tmp, synopsis, tmpxml);
143                 ast_free(tmpxml);
144
145                 /* load description */
146                 tmpxml = ast_xmldoc_build_description("application", app, ast_module_name(tmp->module));
147                 ast_string_field_set(tmp, description, tmpxml);
148                 ast_free(tmpxml);
149
150                 /* load syntax */
151                 tmpxml = ast_xmldoc_build_syntax("application", app, ast_module_name(tmp->module));
152                 ast_string_field_set(tmp, syntax, tmpxml);
153                 ast_free(tmpxml);
154
155                 /* load arguments */
156                 tmpxml = ast_xmldoc_build_arguments("application", app, ast_module_name(tmp->module));
157                 ast_string_field_set(tmp, arguments, tmpxml);
158                 ast_free(tmpxml);
159
160                 /* load seealso */
161                 tmpxml = ast_xmldoc_build_seealso("application", app, ast_module_name(tmp->module));
162                 ast_string_field_set(tmp, seealso, tmpxml);
163                 ast_free(tmpxml);
164                 tmp->docsrc = AST_XML_DOC;
165         } else {
166 #endif
167                 ast_string_field_set(tmp, synopsis, synopsis);
168                 ast_string_field_set(tmp, description, description);
169 #ifdef AST_XML_DOCS
170                 tmp->docsrc = AST_STATIC_DOC;
171         }
172 #endif
173
174         /* Store in alphabetical order */
175         AST_RWLIST_TRAVERSE_SAFE_BEGIN(&apps, cur, list) {
176                 if (strcasecmp(tmp->name, cur->name) < 0) {
177                         AST_RWLIST_INSERT_BEFORE_CURRENT(tmp, list);
178                         break;
179                 }
180         }
181         AST_RWLIST_TRAVERSE_SAFE_END;
182         if (!cur)
183                 AST_RWLIST_INSERT_TAIL(&apps, tmp, list);
184
185         ast_verb(2, "Registered application '" COLORIZE_FMT "'\n", COLORIZE(COLOR_BRCYAN, 0, tmp->name));
186
187         AST_RWLIST_UNLOCK(&apps);
188
189         return 0;
190 }
191
192 static void print_app_docs(struct ast_app *aa, int fd)
193 {
194 #ifdef AST_XML_DOCS
195         char *synopsis = NULL, *description = NULL, *arguments = NULL, *seealso = NULL;
196         if (aa->docsrc == AST_XML_DOC) {
197                 synopsis = ast_xmldoc_printable(S_OR(aa->synopsis, "Not available"), 1);
198                 description = ast_xmldoc_printable(S_OR(aa->description, "Not available"), 1);
199                 arguments = ast_xmldoc_printable(S_OR(aa->arguments, "Not available"), 1);
200                 seealso = ast_xmldoc_printable(S_OR(aa->seealso, "Not available"), 1);
201                 if (!synopsis || !description || !arguments || !seealso) {
202                         goto free_docs;
203                 }
204                 ast_cli(fd, "\n"
205                         "%s  -= Info about application '%s' =- %s\n\n"
206                         COLORIZE_FMT "\n"
207                         "%s\n\n"
208                         COLORIZE_FMT "\n"
209                         "%s\n\n"
210                         COLORIZE_FMT "\n"
211                         "%s%s%s\n\n"
212                         COLORIZE_FMT "\n"
213                         "%s\n\n"
214                         COLORIZE_FMT "\n"
215                         "%s\n",
216                         ast_term_color(COLOR_MAGENTA, 0), aa->name, ast_term_reset(),
217                         COLORIZE(COLOR_MAGENTA, 0, "[Synopsis]"), synopsis,
218                         COLORIZE(COLOR_MAGENTA, 0, "[Description]"), description,
219                         COLORIZE(COLOR_MAGENTA, 0, "[Syntax]"),
220                                 ast_term_color(COLOR_CYAN, 0), S_OR(aa->syntax, "Not available"), ast_term_reset(),
221                         COLORIZE(COLOR_MAGENTA, 0, "[Arguments]"), arguments,
222                         COLORIZE(COLOR_MAGENTA, 0, "[See Also]"), seealso);
223 free_docs:
224                 ast_free(synopsis);
225                 ast_free(description);
226                 ast_free(arguments);
227                 ast_free(seealso);
228         } else
229 #endif
230         {
231                 ast_cli(fd, "\n"
232                         "%s  -= Info about application '%s' =- %s\n\n"
233                         COLORIZE_FMT "\n"
234                         COLORIZE_FMT "\n\n"
235                         COLORIZE_FMT "\n"
236                         COLORIZE_FMT "\n\n"
237                         COLORIZE_FMT "\n"
238                         COLORIZE_FMT "\n\n"
239                         COLORIZE_FMT "\n"
240                         COLORIZE_FMT "\n\n"
241                         COLORIZE_FMT "\n"
242                         COLORIZE_FMT "\n",
243                         ast_term_color(COLOR_MAGENTA, 0), aa->name, ast_term_reset(),
244                         COLORIZE(COLOR_MAGENTA, 0, "[Synopsis]"),
245                         COLORIZE(COLOR_CYAN, 0, S_OR(aa->synopsis, "Not available")),
246                         COLORIZE(COLOR_MAGENTA, 0, "[Description]"),
247                         COLORIZE(COLOR_CYAN, 0, S_OR(aa->description, "Not available")),
248                         COLORIZE(COLOR_MAGENTA, 0, "[Syntax]"),
249                         COLORIZE(COLOR_CYAN, 0, S_OR(aa->syntax, "Not available")),
250                         COLORIZE(COLOR_MAGENTA, 0, "[Arguments]"),
251                         COLORIZE(COLOR_CYAN, 0, S_OR(aa->arguments, "Not available")),
252                         COLORIZE(COLOR_MAGENTA, 0, "[See Also]"),
253                         COLORIZE(COLOR_CYAN, 0, S_OR(aa->seealso, "Not available")));
254         }
255 }
256
257 /*
258  * \brief 'show application' CLI command implementation function...
259  */
260 static char *handle_show_application(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
261 {
262         struct ast_app *aa;
263         int app, no_registered_app = 1;
264
265         switch (cmd) {
266         case CLI_INIT:
267                 e->command = "core show application";
268                 e->usage =
269                         "Usage: core show application <application> [<application> [<application> [...]]]\n"
270                         "       Describes a particular application.\n";
271                 return NULL;
272         case CLI_GENERATE:
273                 /*
274                  * There is a possibility to show informations about more than one
275                  * application at one time. You can type 'show application Dial Echo' and
276                  * you will see informations about these two applications ...
277                  */
278                 return ast_complete_applications(a->line, a->word, -1);
279         }
280
281         if (a->argc < 4) {
282                 return CLI_SHOWUSAGE;
283         }
284
285         AST_RWLIST_RDLOCK(&apps);
286         AST_RWLIST_TRAVERSE(&apps, aa, list) {
287                 /* Check for each app that was supplied as an argument */
288                 for (app = 3; app < a->argc; app++) {
289                         if (strcasecmp(aa->name, a->argv[app])) {
290                                 continue;
291                         }
292
293                         /* We found it! */
294                         no_registered_app = 0;
295
296                         print_app_docs(aa, a->fd);
297                 }
298         }
299         AST_RWLIST_UNLOCK(&apps);
300
301         /* we found at least one app? no? */
302         if (no_registered_app) {
303                 ast_cli(a->fd, "Your application(s) is (are) not registered\n");
304                 return CLI_FAILURE;
305         }
306
307         return CLI_SUCCESS;
308 }
309
310 static char *handle_show_applications(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
311 {
312         struct ast_app *aa;
313         int like = 0, describing = 0;
314         int total_match = 0;    /* Number of matches in like clause */
315         int total_apps = 0;     /* Number of apps registered */
316
317         switch (cmd) {
318         case CLI_INIT:
319                 e->command = "core show applications [like|describing]";
320                 e->usage =
321                         "Usage: core show applications [{like|describing} <text>]\n"
322                         "       List applications which are currently available.\n"
323                         "       If 'like', <text> will be a substring of the app name\n"
324                         "       If 'describing', <text> will be a substring of the description\n";
325                 return NULL;
326         case CLI_GENERATE:
327                 return NULL;
328         }
329
330         AST_RWLIST_RDLOCK(&apps);
331
332         if (AST_RWLIST_EMPTY(&apps)) {
333                 ast_cli(a->fd, "There are no registered applications\n");
334                 AST_RWLIST_UNLOCK(&apps);
335                 return CLI_SUCCESS;
336         }
337
338         /* core list applications like <keyword> */
339         if ((a->argc == 5) && (!strcmp(a->argv[3], "like"))) {
340                 like = 1;
341         } else if ((a->argc > 4) && (!strcmp(a->argv[3], "describing"))) {
342                 describing = 1;
343         }
344
345         /* core list applications describing <keyword1> [<keyword2>] [...] */
346         if ((!like) && (!describing)) {
347                 ast_cli(a->fd, "    -= Registered Asterisk Applications =-\n");
348         } else {
349                 ast_cli(a->fd, "    -= Matching Asterisk Applications =-\n");
350         }
351
352         AST_RWLIST_TRAVERSE(&apps, aa, list) {
353                 int printapp = 0;
354                 total_apps++;
355                 if (like) {
356                         if (strcasestr(aa->name, a->argv[4])) {
357                                 printapp = 1;
358                                 total_match++;
359                         }
360                 } else if (describing) {
361                         if (aa->description) {
362                                 /* Match all words on command line */
363                                 int i;
364                                 printapp = 1;
365                                 for (i = 4; i < a->argc; i++) {
366                                         if (!strcasestr(aa->description, a->argv[i])) {
367                                                 printapp = 0;
368                                         } else {
369                                                 total_match++;
370                                         }
371                                 }
372                         }
373                 } else {
374                         printapp = 1;
375                 }
376
377                 if (printapp) {
378                         ast_cli(a->fd,"  %20s: %s\n", aa->name, aa->synopsis ? aa->synopsis : "<Synopsis not available>");
379                 }
380         }
381         if ((!like) && (!describing)) {
382                 ast_cli(a->fd, "    -= %d Applications Registered =-\n",total_apps);
383         } else {
384                 ast_cli(a->fd, "    -= %d Applications Matching =-\n",total_match);
385         }
386
387         AST_RWLIST_UNLOCK(&apps);
388
389         return CLI_SUCCESS;
390 }
391
392 int ast_unregister_application(const char *app)
393 {
394         struct ast_app *cur;
395         int cmp;
396
397         /* Anticipate need for conlock in unreference_cached_app(), in order to avoid
398          * possible deadlock with pbx_extension_helper()/pbx_findapp()
399          */
400         ast_rdlock_contexts();
401
402         AST_RWLIST_WRLOCK(&apps);
403         AST_RWLIST_TRAVERSE_SAFE_BEGIN(&apps, cur, list) {
404                 cmp = strcasecmp(app, cur->name);
405                 if (cmp > 0) {
406                         continue;
407                 }
408                 if (!cmp) {
409                         /* Found it. */
410                         unreference_cached_app(cur);
411                         AST_RWLIST_REMOVE_CURRENT(list);
412                         ast_verb(2, "Unregistered application '%s'\n", cur->name);
413                         ast_string_field_free_memory(cur);
414                         ast_free(cur);
415                         break;
416                 }
417                 /* Not in container. */
418                 cur = NULL;
419                 break;
420         }
421         AST_RWLIST_TRAVERSE_SAFE_END;
422         AST_RWLIST_UNLOCK(&apps);
423
424         ast_unlock_contexts();
425
426         return cur ? 0 : -1;
427 }
428
429 char *ast_complete_applications(const char *line, const char *word, int state)
430 {
431         struct ast_app *app;
432         int which = 0;
433         int cmp;
434         char *ret = NULL;
435         size_t wordlen = strlen(word);
436
437         AST_RWLIST_RDLOCK(&apps);
438         AST_RWLIST_TRAVERSE(&apps, app, list) {
439                 cmp = strncasecmp(word, app->name, wordlen);
440                 if (cmp < 0) {
441                         /* No more matches. */
442                         break;
443                 } else if (!cmp) {
444                         /* Found match. */
445                         if (state != -1) {
446                                 if (++which <= state) {
447                                         /* Not enough matches. */
448                                         continue;
449                                 }
450                                 ret = ast_strdup(app->name);
451                                 break;
452                         }
453                         if (ast_cli_completion_add(ast_strdup(app->name))) {
454                                 break;
455                         }
456                 }
457         }
458         AST_RWLIST_UNLOCK(&apps);
459
460         return ret;
461 }
462
463 const char *app_name(struct ast_app *app)
464 {
465         return app->name;
466 }
467
468 /*
469    \note This function is special. It saves the stack so that no matter
470    how many times it is called, it returns to the same place */
471 int pbx_exec(struct ast_channel *c,     /*!< Channel */
472              struct ast_app *app,       /*!< Application */
473              const char *data)          /*!< Data for execution */
474 {
475         int res;
476         struct ast_module_user *u = NULL;
477         const char *saved_c_appl;
478         const char *saved_c_data;
479
480         /* save channel values */
481         saved_c_appl= ast_channel_appl(c);
482         saved_c_data= ast_channel_data(c);
483
484         ast_channel_lock(c);
485         ast_channel_appl_set(c, app->name);
486         ast_channel_data_set(c, data);
487         ast_channel_publish_snapshot(c);
488         ast_channel_unlock(c);
489
490         if (app->module)
491                 u = __ast_module_user_add(app->module, c);
492         res = app->execute(c, S_OR(data, ""));
493         if (app->module && u)
494                 __ast_module_user_remove(app->module, u);
495         /* restore channel values */
496         ast_channel_appl_set(c, saved_c_appl);
497         ast_channel_data_set(c, saved_c_data);
498         return res;
499 }
500
501 static struct ast_cli_entry app_cli[] = {
502         AST_CLI_DEFINE(handle_show_applications, "Shows registered dialplan applications"),
503         AST_CLI_DEFINE(handle_show_application, "Describe a specific dialplan application"),
504 };
505
506 static void unload_pbx_app(void)
507 {
508         ast_cli_unregister_multiple(app_cli, ARRAY_LEN(app_cli));
509 }
510
511 int load_pbx_app(void)
512 {
513         ast_cli_register_multiple(app_cli, ARRAY_LEN(app_cli));
514         ast_register_cleanup(unload_pbx_app);
515
516         return 0;
517 }