res_resolver_unbound: Fix config documentation.
[asterisk/asterisk.git] / res / res_pjproject.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * David M. Lee, II <dlee@digium.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 Bridge PJPROJECT logging to Asterisk logging.
22  * \author David M. Lee, II <dlee@digium.com>
23  *
24  * PJPROJECT logging doesn't exactly match Asterisk logging, but mapping the two is
25  * not too bad. PJPROJECT log levels are identified by a single int. Limits are
26  * not specified by PJPROJECT, but their implementation used 1 through 6.
27  *
28  * The mapping is as follows:
29  *  - 0: LOG_ERROR
30  *  - 1: LOG_ERROR
31  *  - 2: LOG_WARNING
32  *  - 3 and above: equivalent to ast_debug(level, ...) for res_pjproject.so
33  */
34
35 /*** MODULEINFO
36         <depend>pjproject</depend>
37         <support_level>core</support_level>
38  ***/
39
40 /*** DOCUMENTATION
41         <configInfo name="res_pjproject" language="en_US">
42                 <synopsis>pjproject common configuration</synopsis>
43                 <configFile name="pjproject.conf">
44                         <configObject name="log_mappings">
45                                 <synopsis>PJPROJECT to Asterisk Log Level Mapping</synopsis>
46                                 <description><para>Warnings and errors in the pjproject libraries are generally handled
47                                         by Asterisk.  In many cases, Asterisk wouldn't even consider them to
48                                         be warnings or errors so the messages emitted by pjproject directly
49                                         are either superfluous or misleading.  The 'log_mappings'
50                                         object allows mapping the pjproject levels to Asterisk levels, or nothing.
51                                         </para>
52                                         <note><para>The id of this object, as well as its type, must be
53                                         'log_mappings' or it won't be found.</para></note>
54                                 </description>
55                                 <configOption name="type">
56                                         <synopsis>Must be of type 'log_mappings'.</synopsis>
57                                 </configOption>
58                                 <configOption name="asterisk_error" default="0,1">
59                                         <synopsis>A comma separated list of pjproject log levels to map to Asterisk LOG_ERROR.</synopsis>
60                                 </configOption>
61                                 <configOption name="asterisk_warning" default="2">
62                                         <synopsis>A comma separated list of pjproject log levels to map to Asterisk LOG_WARNING.</synopsis>
63                                 </configOption>
64                                 <configOption name="asterisk_notice" default="">
65                                         <synopsis>A comma separated list of pjproject log levels to map to Asterisk LOG_NOTICE.</synopsis>
66                                 </configOption>
67                                 <configOption name="asterisk_debug" default="3,4,5">
68                                         <synopsis>A comma separated list of pjproject log levels to map to Asterisk LOG_DEBUG.</synopsis>
69                                 </configOption>
70                                 <configOption name="asterisk_verbose" default="">
71                                         <synopsis>A comma separated list of pjproject log levels to map to Asterisk LOG_VERBOSE.</synopsis>
72                                 </configOption>
73                         </configObject>
74                 </configFile>
75         </configInfo>
76  ***/
77
78 #include "asterisk.h"
79
80 ASTERISK_REGISTER_FILE()
81
82 #include <stdarg.h>
83 #include <pjlib.h>
84 #include <pjsip.h>
85 #include <pj/log.h>
86
87 #include "asterisk/logger.h"
88 #include "asterisk/module.h"
89 #include "asterisk/cli.h"
90 #include "asterisk/res_pjproject.h"
91 #include "asterisk/vector.h"
92 #include "asterisk/sorcery.h"
93
94 static struct ast_sorcery *pjproject_sorcery;
95 static pj_log_func *log_cb_orig;
96 static unsigned decor_orig;
97
98 static AST_VECTOR(buildopts, char *) buildopts;
99
100 /*! Protection from other log intercept instances.  There can be only one at a time. */
101 AST_MUTEX_DEFINE_STATIC(pjproject_log_intercept_lock);
102
103 struct pjproject_log_intercept_data {
104         pthread_t thread;
105         int fd;
106 };
107
108 static struct pjproject_log_intercept_data pjproject_log_intercept = {
109         .thread = AST_PTHREADT_NULL,
110         .fd = -1,
111 };
112
113 struct log_mappings {
114         /*! Sorcery object details */
115         SORCERY_OBJECT(details);
116         /*! These are all comma-separated lists of pjproject log levels */
117         AST_DECLARE_STRING_FIELDS(
118                 /*! pjproject log levels mapped to Asterisk ERROR */
119                 AST_STRING_FIELD(asterisk_error);
120                 /*! pjproject log levels mapped to Asterisk WARNING */
121                 AST_STRING_FIELD(asterisk_warning);
122                 /*! pjproject log levels mapped to Asterisk NOTICE */
123                 AST_STRING_FIELD(asterisk_notice);
124                 /*! pjproject log levels mapped to Asterisk VERBOSE */
125                 AST_STRING_FIELD(asterisk_verbose);
126                 /*! pjproject log levels mapped to Asterisk DEBUG */
127                 AST_STRING_FIELD(asterisk_debug);
128         );
129 };
130
131 static struct log_mappings *default_log_mappings;
132
133 static struct log_mappings *get_log_mappings(void)
134 {
135         struct log_mappings *mappings;
136
137         mappings = ast_sorcery_retrieve_by_id(pjproject_sorcery, "log_mappings", "log_mappings");
138         if (!mappings) {
139                 return ao2_bump(default_log_mappings);
140         }
141
142         return mappings;
143 }
144
145 #define __LOG_SUPPRESS -1
146
147 static int get_log_level(int pj_level)
148 {
149         RAII_VAR(struct log_mappings *, mappings, get_log_mappings(), ao2_cleanup);
150         unsigned char l;
151
152         if (!mappings) {
153                 return __LOG_ERROR;
154         }
155
156         l = '0' + fmin(pj_level, 9);
157
158         if (strchr(mappings->asterisk_error, l)) {
159                 return __LOG_ERROR;
160         } else if (strchr(mappings->asterisk_warning, l)) {
161                 return __LOG_WARNING;
162         } else if (strchr(mappings->asterisk_notice, l)) {
163                 return __LOG_NOTICE;
164         } else if (strchr(mappings->asterisk_verbose, l)) {
165                 return __LOG_VERBOSE;
166         } else if (strchr(mappings->asterisk_debug, l)) {
167                 return __LOG_DEBUG;
168         }
169
170         return __LOG_SUPPRESS;
171 }
172
173 static void log_forwarder(int level, const char *data, int len)
174 {
175         int ast_level;
176         /* PJPROJECT doesn't provide much in the way of source info */
177         const char * log_source = "pjproject";
178         int log_line = 0;
179         const char *log_func = "<?>";
180
181         if (pjproject_log_intercept.fd != -1
182                 && pjproject_log_intercept.thread == pthread_self()) {
183                 /*
184                  * We are handling a CLI command intercepting PJPROJECT
185                  * log output.
186                  */
187                 ast_cli(pjproject_log_intercept.fd, "%s\n", data);
188                 return;
189         }
190
191         ast_level = get_log_level(level);
192
193         if (ast_level == __LOG_SUPPRESS) {
194                 return;
195         }
196
197         if (ast_level == __LOG_DEBUG) {
198                 /* Obey the debug level for res_pjproject */
199                 if (!DEBUG_ATLEAST(level)) {
200                         return;
201                 }
202         }
203
204         /* PJPROJECT uses indention to indicate function call depth. We'll prepend
205          * log statements with a tab so they'll have a better shot at lining
206          * up */
207         ast_log(ast_level, log_source, log_line, log_func, "\t%s\n", data);
208 }
209
210 static void capture_buildopts_cb(int level, const char *data, int len)
211 {
212         if (strstr(data, "Teluu") || strstr(data, "Dumping")) {
213                 return;
214         }
215
216         AST_VECTOR_ADD_SORTED(&buildopts, ast_strdup(ast_skip_blanks(data)), strcmp);
217 }
218
219 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
220 int ast_pjproject_get_buildopt(char *option, char *format_string, ...)
221 {
222         int res = 0;
223         char *format_temp;
224         int i;
225
226         format_temp = ast_alloca(strlen(option) + strlen(" : ") + strlen(format_string) + 1);
227         sprintf(format_temp, "%s : %s", option, format_string);
228
229         for (i = 0; i < AST_VECTOR_SIZE(&buildopts); i++) {
230                 va_list arg_ptr;
231                 va_start(arg_ptr, format_string);
232                 res = vsscanf(AST_VECTOR_GET(&buildopts, i), format_temp, arg_ptr);
233                 va_end(arg_ptr);
234                 if (res) {
235                         break;
236                 }
237         }
238
239         return res;
240 }
241 #pragma GCC diagnostic warning "-Wformat-nonliteral"
242
243 void ast_pjproject_log_intercept_begin(int fd)
244 {
245         /* Protect from other CLI instances trying to do this at the same time. */
246         ast_mutex_lock(&pjproject_log_intercept_lock);
247
248         pjproject_log_intercept.thread = pthread_self();
249         pjproject_log_intercept.fd = fd;
250 }
251
252 void ast_pjproject_log_intercept_end(void)
253 {
254         pjproject_log_intercept.fd = -1;
255         pjproject_log_intercept.thread = AST_PTHREADT_NULL;
256
257         ast_mutex_unlock(&pjproject_log_intercept_lock);
258 }
259
260 void ast_pjproject_ref(void)
261 {
262         ast_module_ref(ast_module_info->self);
263 }
264
265 void ast_pjproject_unref(void)
266 {
267         ast_module_unref(ast_module_info->self);
268 }
269
270 static char *handle_pjproject_show_buildopts(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
271 {
272         int i;
273
274         switch (cmd) {
275         case CLI_INIT:
276                 e->command = "pjproject show buildopts";
277                 e->usage =
278                         "Usage: pjproject show buildopts\n"
279                         "       Show the compile time config of the pjproject that Asterisk is\n"
280                         "       running against.\n";
281                 return NULL;
282         case CLI_GENERATE:
283                 return NULL;
284         }
285
286         ast_cli(a->fd, "PJPROJECT compile time config currently running against:\n");
287
288         for (i = 0; i < AST_VECTOR_SIZE(&buildopts); i++) {
289                 ast_cli(a->fd, "%s\n", AST_VECTOR_GET(&buildopts, i));
290         }
291
292         return CLI_SUCCESS;
293 }
294
295 static void mapping_destroy(void *object)
296 {
297         struct log_mappings *mappings = object;
298
299         ast_string_field_free_memory(mappings);
300 }
301
302 static void *mapping_alloc(const char *name)
303 {
304         struct log_mappings *mappings = ast_sorcery_generic_alloc(sizeof(*mappings), mapping_destroy);
305         if (!mappings) {
306                 return NULL;
307         }
308         ast_string_field_init(mappings, 128);
309
310         return mappings;
311 }
312
313 static char *handle_pjproject_show_log_mappings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
314 {
315         struct ast_variable *objset;
316         struct ast_variable *i;
317         struct log_mappings *mappings;
318
319         switch (cmd) {
320         case CLI_INIT:
321                 e->command = "pjproject show log mappings";
322                 e->usage =
323                         "Usage: pjproject show log mappings\n"
324                         "       Show pjproject to Asterisk log mappings\n";
325                 return NULL;
326         case CLI_GENERATE:
327                 return NULL;
328         }
329
330         ast_cli(a->fd, "PJPROJECT to Asterisk log mappings:\n");
331         ast_cli(a->fd, "Asterisk Level   : PJPROJECT log levels\n");
332
333         mappings = get_log_mappings();
334         if (!mappings) {
335                 ast_log(LOG_ERROR, "Unable to retrieve pjproject log_mappings\n");
336                 return CLI_SUCCESS;
337         }
338
339         objset = ast_sorcery_objectset_create(pjproject_sorcery, mappings);
340         if (!objset) {
341                 ao2_ref(mappings, -1);
342                 return CLI_SUCCESS;
343         }
344
345         for (i = objset; i; i = i->next) {
346                 ast_cli(a->fd, "%-16s : %s\n", i->name, i->value);
347         }
348         ast_variables_destroy(objset);
349
350         ao2_ref(mappings, -1);
351         return CLI_SUCCESS;
352 }
353
354 static struct ast_cli_entry pjproject_cli[] = {
355         AST_CLI_DEFINE(handle_pjproject_show_buildopts, "Show the compiled config of the pjproject in use"),
356         AST_CLI_DEFINE(handle_pjproject_show_log_mappings, "Show pjproject to Asterisk log mappings"),
357 };
358
359 static int load_module(void)
360 {
361         ast_debug(3, "Starting PJPROJECT logging to Asterisk logger\n");
362
363         if (!(pjproject_sorcery = ast_sorcery_open())) {
364                 ast_log(LOG_ERROR, "Failed to open SIP sorcery failed to open\n");
365                 return AST_MODULE_LOAD_DECLINE;
366         }
367
368         ast_sorcery_apply_default(pjproject_sorcery, "log_mappings", "config", "pjproject.conf,criteria=type=log_mappings");
369         if (ast_sorcery_object_register(pjproject_sorcery, "log_mappings", mapping_alloc, NULL, NULL)) {
370                 ast_log(LOG_WARNING, "Failed to register pjproject log_mappings object with sorcery\n");
371                 ast_sorcery_unref(pjproject_sorcery);
372                 pjproject_sorcery = NULL;
373                 return AST_MODULE_LOAD_DECLINE;
374         }
375
376         ast_sorcery_object_field_register(pjproject_sorcery, "log_mappings", "type", "", OPT_NOOP_T, 0, 0);
377         ast_sorcery_object_field_register(pjproject_sorcery, "log_mappings", "asterisk_debug", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct log_mappings, asterisk_debug));
378         ast_sorcery_object_field_register(pjproject_sorcery, "log_mappings", "asterisk_error", "",  OPT_STRINGFIELD_T, 0, STRFLDSET(struct log_mappings, asterisk_error));
379         ast_sorcery_object_field_register(pjproject_sorcery, "log_mappings", "asterisk_warning", "",  OPT_STRINGFIELD_T, 0, STRFLDSET(struct log_mappings, asterisk_warning));
380         ast_sorcery_object_field_register(pjproject_sorcery, "log_mappings", "asterisk_notice", "",  OPT_STRINGFIELD_T, 0, STRFLDSET(struct log_mappings, asterisk_notice));
381         ast_sorcery_object_field_register(pjproject_sorcery, "log_mappings", "asterisk_verbose", "",  OPT_STRINGFIELD_T, 0, STRFLDSET(struct log_mappings, asterisk_verbose));
382
383         default_log_mappings = ast_sorcery_alloc(pjproject_sorcery, "log_mappings", "log_mappings");
384         if (!default_log_mappings) {
385                 ast_log(LOG_ERROR, "Unable to allocate memory for pjproject log_mappings\n");
386                 return AST_MODULE_LOAD_DECLINE;
387         }
388         ast_string_field_set(default_log_mappings, asterisk_error, "0,1");
389         ast_string_field_set(default_log_mappings, asterisk_warning, "2");
390         ast_string_field_set(default_log_mappings, asterisk_debug, "3,4,5");
391
392         ast_sorcery_load(pjproject_sorcery);
393
394         pj_init();
395
396         decor_orig = pj_log_get_decor();
397         log_cb_orig = pj_log_get_log_func();
398
399         if (AST_VECTOR_INIT(&buildopts, 64)) {
400                 return AST_MODULE_LOAD_DECLINE;
401         }
402
403         /*
404          * On startup, we want to capture the dump once and store it.
405          */
406         pj_log_set_log_func(capture_buildopts_cb);
407         pj_log_set_decor(0);
408         pj_dump_config();
409         pj_log_set_decor(PJ_LOG_HAS_SENDER | PJ_LOG_HAS_INDENT);
410         pj_log_set_log_func(log_forwarder);
411
412         ast_cli_register_multiple(pjproject_cli, ARRAY_LEN(pjproject_cli));
413
414         return AST_MODULE_LOAD_SUCCESS;
415 }
416
417 #define NOT_EQUALS(a, b) (a != b)
418
419 static int unload_module(void)
420 {
421         ast_cli_unregister_multiple(pjproject_cli, ARRAY_LEN(pjproject_cli));
422         pj_log_set_log_func(log_cb_orig);
423         pj_log_set_decor(decor_orig);
424
425         AST_VECTOR_REMOVE_CMP_UNORDERED(&buildopts, NULL, NOT_EQUALS, ast_free);
426         AST_VECTOR_FREE(&buildopts);
427
428         ast_debug(3, "Stopped PJPROJECT logging to Asterisk logger\n");
429
430         pj_shutdown();
431
432         ao2_cleanup(default_log_mappings);
433         default_log_mappings = NULL;
434
435         ast_sorcery_unref(pjproject_sorcery);
436
437         return 0;
438 }
439
440 static int reload_module(void)
441 {
442         if (pjproject_sorcery) {
443                 ast_sorcery_reload(pjproject_sorcery);
444         }
445
446         return AST_MODULE_LOAD_SUCCESS;
447 }
448
449 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "PJPROJECT Log and Utility Support",
450         .support_level = AST_MODULE_SUPPORT_CORE,
451         .load = load_module,
452         .unload = unload_module,
453         .reload = reload_module,
454         .load_pri = AST_MODPRI_CHANNEL_DEPEND - 6,
455 );