e9f0d15c4bb4a22de0418a89ad2cd3652d09926c
[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 int ast_pjproject_get_buildopt(char *option, char *format_string, ...)
129 {
130         int res = 0;
131         char *format_temp;
132         int i;
133
134         format_temp = ast_alloca(strlen(option) + strlen(" : ") + strlen(format_string) + 1);
135         sprintf(format_temp, "%s : %s", option, format_string);
136
137         for (i = 0; i < AST_VECTOR_SIZE(&buildopts); i++) {
138                 va_list arg_ptr;
139                 va_start(arg_ptr, format_string);
140                 res = vsscanf(AST_VECTOR_GET(&buildopts, i), format_temp, arg_ptr);
141                 va_end(arg_ptr);
142                 if (res) {
143                         break;
144                 }
145         }
146
147         return res;
148 }
149
150 void ast_pjproject_log_intercept_begin(int fd)
151 {
152         /* Protect from other CLI instances trying to do this at the same time. */
153         ast_mutex_lock(&pjproject_log_intercept_lock);
154
155         pjproject_log_intercept.thread = pthread_self();
156         pjproject_log_intercept.fd = fd;
157 }
158
159 void ast_pjproject_log_intercept_end(void)
160 {
161         pjproject_log_intercept.fd = -1;
162         pjproject_log_intercept.thread = AST_PTHREADT_NULL;
163
164         ast_mutex_unlock(&pjproject_log_intercept_lock);
165 }
166
167 void ast_pjproject_ref(void)
168 {
169         ast_module_ref(ast_module_info->self);
170 }
171
172 void ast_pjproject_unref(void)
173 {
174         ast_module_unref(ast_module_info->self);
175 }
176
177 static char *handle_pjproject_show_buildopts(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
178 {
179         int i;
180
181         switch (cmd) {
182         case CLI_INIT:
183                 e->command = "pjproject show buildopts";
184                 e->usage =
185                         "Usage: pjproject show buildopts\n"
186                         "       Show the compile time config of the pjproject that Asterisk is\n"
187                         "       running against.\n";
188                 return NULL;
189         case CLI_GENERATE:
190                 return NULL;
191         }
192
193         ast_cli(a->fd, "PJPROJECT compile time config currently running against:\n");
194
195         for (i = 0; i < AST_VECTOR_SIZE(&buildopts); i++) {
196                 ast_cli(a->fd, "%s\n", AST_VECTOR_GET(&buildopts, i));
197         }
198
199         return CLI_SUCCESS;
200 }
201
202 static struct ast_cli_entry pjproject_cli[] = {
203         AST_CLI_DEFINE(handle_pjproject_show_buildopts, "Show the compiled config of the pjproject in use"),
204 };
205
206 static int load_module(void)
207 {
208         ast_debug(3, "Starting PJPROJECT logging to Asterisk logger\n");
209
210         pj_init();
211
212         decor_orig = pj_log_get_decor();
213         log_cb_orig = pj_log_get_log_func();
214
215         if (AST_VECTOR_INIT(&buildopts, 64)) {
216                 return AST_MODULE_LOAD_DECLINE;
217         }
218
219         /*
220          * On startup, we want to capture the dump once and store it.
221          */
222         pj_log_set_log_func(capture_buildopts_cb);
223         pj_log_set_decor(0);
224         pj_dump_config();
225         pj_log_set_decor(PJ_LOG_HAS_SENDER | PJ_LOG_HAS_INDENT);
226         pj_log_set_log_func(log_forwarder);
227
228         ast_cli_register_multiple(pjproject_cli, ARRAY_LEN(pjproject_cli));
229
230         return AST_MODULE_LOAD_SUCCESS;
231 }
232
233 #define NOT_EQUALS(a, b) (a != b)
234
235 static int unload_module(void)
236 {
237         ast_cli_unregister_multiple(pjproject_cli, ARRAY_LEN(pjproject_cli));
238         pj_log_set_log_func(log_cb_orig);
239         pj_log_set_decor(decor_orig);
240
241         AST_VECTOR_REMOVE_CMP_UNORDERED(&buildopts, NULL, NOT_EQUALS, ast_free);
242         AST_VECTOR_FREE(&buildopts);
243
244         ast_debug(3, "Stopped PJPROJECT logging to Asterisk logger\n");
245
246         pj_shutdown();
247
248         return 0;
249 }
250
251 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "PJPROJECT Log and Utility Support",
252         .support_level = AST_MODULE_SUPPORT_CORE,
253         .load = load_module,
254         .unload = unload_module,
255         .load_pri = AST_MODPRI_CHANNEL_DEPEND - 6,
256 );