9e08bf3e0e698a270d658c33de5f84bf23ef7857
[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 #include "asterisk.h"
41
42 ASTERISK_REGISTER_FILE()
43
44 #include <stdarg.h>
45 #include <pjlib.h>
46 #include <pjsip.h>
47 #include <pj/log.h>
48
49 #include "asterisk/logger.h"
50 #include "asterisk/module.h"
51 #include "asterisk/cli.h"
52 #include "asterisk/res_pjproject.h"
53 #include "asterisk/vector.h"
54
55 static pj_log_func *log_cb_orig;
56 static unsigned decor_orig;
57
58 static AST_VECTOR(buildopts, char *) buildopts;
59
60 /*! Protection from other log intercept instances.  There can be only one at a time. */
61 AST_MUTEX_DEFINE_STATIC(pjproject_log_intercept_lock);
62
63 struct pjproject_log_intercept_data {
64         pthread_t thread;
65         int fd;
66 };
67
68 static struct pjproject_log_intercept_data pjproject_log_intercept = {
69         .thread = AST_PTHREADT_NULL,
70         .fd = -1,
71 };
72
73 static void log_forwarder(int level, const char *data, int len)
74 {
75         int ast_level;
76         /* PJPROJECT doesn't provide much in the way of source info */
77         const char * log_source = "pjproject";
78         int log_line = 0;
79         const char *log_func = "<?>";
80         int mod_level;
81
82         if (pjproject_log_intercept.fd != -1
83                 && pjproject_log_intercept.thread == pthread_self()) {
84                 /*
85                  * We are handling a CLI command intercepting PJPROJECT
86                  * log output.
87                  */
88                 ast_cli(pjproject_log_intercept.fd, "%s\n", data);
89                 return;
90         }
91
92         /* Lower number indicates higher importance */
93         switch (level) {
94         case 0: /* level zero indicates fatal error, according to docs */
95         case 1: /* 1 seems to be used for errors */
96                 ast_level = __LOG_ERROR;
97                 break;
98         case 2: /* 2 seems to be used for warnings and errors */
99                 ast_level = __LOG_WARNING;
100                 break;
101         default:
102                 ast_level = __LOG_DEBUG;
103
104                 /* For levels 3 and up, obey the debug level for res_pjproject */
105                 mod_level = ast_opt_dbg_module ?
106                         ast_debug_get_by_module("res_pjproject") : 0;
107                 if (option_debug < level && mod_level < level) {
108                         return;
109                 }
110                 break;
111         }
112
113         /* PJPROJECT uses indention to indicate function call depth. We'll prepend
114          * log statements with a tab so they'll have a better shot at lining
115          * up */
116         ast_log(ast_level, log_source, log_line, log_func, "\t%s\n", data);
117 }
118
119 static void capture_buildopts_cb(int level, const char *data, int len)
120 {
121         if (strstr(data, "Teluu") || strstr(data, "Dumping")) {
122                 return;
123         }
124
125         AST_VECTOR_ADD_SORTED(&buildopts, ast_strdup(ast_skip_blanks(data)), strcmp);
126 }
127
128 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
129 int ast_pjproject_get_buildopt(char *option, char *format_string, ...)
130 {
131         int res = 0;
132         char *format_temp;
133         int i;
134
135         format_temp = ast_alloca(strlen(option) + strlen(" : ") + strlen(format_string) + 1);
136         sprintf(format_temp, "%s : %s", option, format_string);
137
138         for (i = 0; i < AST_VECTOR_SIZE(&buildopts); i++) {
139                 va_list arg_ptr;
140                 va_start(arg_ptr, format_string);
141                 res = vsscanf(AST_VECTOR_GET(&buildopts, i), format_temp, arg_ptr);
142                 va_end(arg_ptr);
143                 if (res) {
144                         break;
145                 }
146         }
147
148         return res;
149 }
150 #pragma GCC diagnostic warning "-Wformat-nonliteral"
151
152 void ast_pjproject_log_intercept_begin(int fd)
153 {
154         /* Protect from other CLI instances trying to do this at the same time. */
155         ast_mutex_lock(&pjproject_log_intercept_lock);
156
157         pjproject_log_intercept.thread = pthread_self();
158         pjproject_log_intercept.fd = fd;
159 }
160
161 void ast_pjproject_log_intercept_end(void)
162 {
163         pjproject_log_intercept.fd = -1;
164         pjproject_log_intercept.thread = AST_PTHREADT_NULL;
165
166         ast_mutex_unlock(&pjproject_log_intercept_lock);
167 }
168
169 void ast_pjproject_ref(void)
170 {
171         ast_module_ref(ast_module_info->self);
172 }
173
174 void ast_pjproject_unref(void)
175 {
176         ast_module_unref(ast_module_info->self);
177 }
178
179 static char *handle_pjproject_show_buildopts(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
180 {
181         int i;
182
183         switch (cmd) {
184         case CLI_INIT:
185                 e->command = "pjproject show buildopts";
186                 e->usage =
187                         "Usage: pjproject show buildopts\n"
188                         "       Show the compile time config of the pjproject that Asterisk is\n"
189                         "       running against.\n";
190                 return NULL;
191         case CLI_GENERATE:
192                 return NULL;
193         }
194
195         ast_cli(a->fd, "PJPROJECT compile time config currently running against:\n");
196
197         for (i = 0; i < AST_VECTOR_SIZE(&buildopts); i++) {
198                 ast_cli(a->fd, "%s\n", AST_VECTOR_GET(&buildopts, i));
199         }
200
201         return CLI_SUCCESS;
202 }
203
204 static struct ast_cli_entry pjproject_cli[] = {
205         AST_CLI_DEFINE(handle_pjproject_show_buildopts, "Show the compiled config of the pjproject in use"),
206 };
207
208 static int load_module(void)
209 {
210         ast_debug(3, "Starting PJPROJECT logging to Asterisk logger\n");
211
212         pj_init();
213
214         decor_orig = pj_log_get_decor();
215         log_cb_orig = pj_log_get_log_func();
216
217         if (AST_VECTOR_INIT(&buildopts, 64)) {
218                 return AST_MODULE_LOAD_DECLINE;
219         }
220
221         /*
222          * On startup, we want to capture the dump once and store it.
223          */
224         pj_log_set_log_func(capture_buildopts_cb);
225         pj_log_set_decor(0);
226         pj_dump_config();
227         pj_log_set_decor(PJ_LOG_HAS_SENDER | PJ_LOG_HAS_INDENT);
228         pj_log_set_log_func(log_forwarder);
229
230         ast_cli_register_multiple(pjproject_cli, ARRAY_LEN(pjproject_cli));
231
232         return AST_MODULE_LOAD_SUCCESS;
233 }
234
235 #define NOT_EQUALS(a, b) (a != b)
236
237 static int unload_module(void)
238 {
239         ast_cli_unregister_multiple(pjproject_cli, ARRAY_LEN(pjproject_cli));
240         pj_log_set_log_func(log_cb_orig);
241         pj_log_set_decor(decor_orig);
242
243         AST_VECTOR_REMOVE_CMP_UNORDERED(&buildopts, NULL, NOT_EQUALS, ast_free);
244         AST_VECTOR_FREE(&buildopts);
245
246         ast_debug(3, "Stopped PJPROJECT logging to Asterisk logger\n");
247
248         pj_shutdown();
249
250         return 0;
251 }
252
253 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "PJPROJECT Log and Utility Support",
254         .support_level = AST_MODULE_SUPPORT_CORE,
255         .load = load_module,
256         .unload = unload_module,
257         .load_pri = AST_MODPRI_CHANNEL_DEPEND - 6,
258 );