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