/src/wireshark/wsutil/wslog.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2021, João Valverde <j@v6e.pt> |
3 | | * |
4 | | * Wireshark - Network traffic analyzer |
5 | | * By Gerald Combs <gerald@wireshark.org> |
6 | | * Copyright 1998 Gerald Combs |
7 | | * |
8 | | * SPDX-License-Identifier: GPL-2.0-or-later |
9 | | */ |
10 | | |
11 | | #include "config.h" |
12 | | |
13 | | #include "wslog.h" |
14 | | |
15 | | #include <stdlib.h> |
16 | | #include <string.h> |
17 | | #include <errno.h> |
18 | | #include <time.h> |
19 | | /* Because ws_assert() depends on ws_error() we do not use it |
20 | | * here and fall back on assert() instead. */ |
21 | | #include <assert.h> |
22 | | #ifdef HAVE_UNISTD_H |
23 | | #include <unistd.h> |
24 | | #endif |
25 | | #ifdef _WIN32 |
26 | | #include <process.h> |
27 | | #include <windows.h> |
28 | | #include <conio.h> |
29 | | #endif |
30 | | |
31 | | #include "clopts_common.h" |
32 | | #include "file_util.h" |
33 | | #include "time_util.h" |
34 | | #include "to_str.h" |
35 | | #include "strtoi.h" |
36 | | #ifdef _WIN32 |
37 | | #include "console_win32.h" |
38 | | #endif |
39 | | |
40 | | |
41 | 0 | #define ASSERT(expr) assert(expr) |
42 | | |
43 | | /* Runtime log level. */ |
44 | 15 | #define ENV_VAR_LEVEL "WIRESHARK_LOG_LEVEL" |
45 | | |
46 | | /* Log domains enabled/disabled. */ |
47 | 15 | #define ENV_VAR_DOMAIN "WIRESHARK_LOG_DOMAIN" |
48 | | |
49 | | /* Alias "domain" and "domains". */ |
50 | 15 | #define ENV_VAR_DOMAIN_S "WIRESHARK_LOG_DOMAINS" |
51 | | |
52 | | /* Log level that generates a trap and aborts. Can be "critical" |
53 | | * or "warning". */ |
54 | 15 | #define ENV_VAR_FATAL "WIRESHARK_LOG_FATAL" |
55 | | |
56 | | /* Fatal log count before program is trapped. */ |
57 | 15 | #define ENV_VAR_FATAL_COUNT "WIRESHARK_LOG_FATAL_COUNT" |
58 | | |
59 | | /* Log domains that are fatal. */ |
60 | 15 | #define ENV_VAR_FATAL_DOMAIN "WIRESHARK_LOG_FATAL_DOMAIN" |
61 | | |
62 | | /* Alias "domain" and "domains". */ |
63 | 15 | #define ENV_VAR_FATAL_DOMAIN_S "WIRESHARK_LOG_FATAL_DOMAINS" |
64 | | |
65 | | /* Domains that will produce debug output, regardless of log level or |
66 | | * domain filter. */ |
67 | 15 | #define ENV_VAR_DEBUG "WIRESHARK_LOG_DEBUG" |
68 | | |
69 | | /* Domains that will produce noisy output, regardless of log level or |
70 | | * domain filter. */ |
71 | 15 | #define ENV_VAR_NOISY "WIRESHARK_LOG_NOISY" |
72 | | |
73 | 15 | #define DEFAULT_LOG_LEVEL LOG_LEVEL_MESSAGE |
74 | | |
75 | | #define DEFAULT_PROGNAME "PID" |
76 | | |
77 | 25.8k | #define DOMAIN_UNDEFED(domain) ((domain) == NULL || *(domain) == '\0') |
78 | 37.3k | #define DOMAIN_DEFINED(domain) (!DOMAIN_UNDEFED(domain)) |
79 | | |
80 | | /* |
81 | | * Note: I didn't measure it but I assume using a string array is faster than |
82 | | * a GHashTable for small number N of domains. |
83 | | */ |
84 | | typedef struct { |
85 | | char **domainv; |
86 | | bool positive; /* positive or negative match */ |
87 | | enum ws_log_level min_level; /* for level filters */ |
88 | | } log_filter_t; |
89 | | |
90 | | |
91 | | /* If the module is not initialized by calling ws_log_init() all messages |
92 | | * will be printed regardless of log level. This is a feature, not a bug. */ |
93 | | static enum ws_log_level current_log_level = LOG_LEVEL_NONE; |
94 | | |
95 | | static bool stdout_color_enabled; |
96 | | |
97 | | static bool stderr_color_enabled; |
98 | | |
99 | | /* Use stdout for levels "info" and below, for backward compatibility |
100 | | * with GLib. */ |
101 | | static bool stdout_logging_enabled; |
102 | | |
103 | | static const char *registered_progname = DEFAULT_PROGNAME; |
104 | | |
105 | | /* List of domains to filter. */ |
106 | | static log_filter_t *domain_filter; |
107 | | |
108 | | /* List of domains to output debug level unconditionally. */ |
109 | | static log_filter_t *debug_filter; |
110 | | |
111 | | /* List of domains to output noisy level unconditionally. */ |
112 | | static log_filter_t *noisy_filter; |
113 | | |
114 | | /* List of domains that are fatal. */ |
115 | | static log_filter_t *fatal_filter; |
116 | | |
117 | | static ws_log_writer_cb *registered_log_writer; |
118 | | |
119 | | static void *registered_log_writer_data; |
120 | | |
121 | | static ws_log_writer_free_data_cb *registered_log_writer_data_free; |
122 | | |
123 | | static FILE *custom_log; |
124 | | |
125 | | static enum ws_log_level fatal_log_level = LOG_LEVEL_ERROR; |
126 | | |
127 | | static uint32_t fatal_log_count = 1; |
128 | | |
129 | | #ifdef WS_DEBUG |
130 | | static bool init_complete; |
131 | | #endif |
132 | | |
133 | | ws_log_console_open_pref ws_log_console_open = LOG_CONSOLE_OPEN_NEVER; |
134 | | |
135 | | |
136 | | static void print_err(void (*vcmdarg_err)(const char *, va_list ap), |
137 | | int exit_failure, |
138 | | const char *fmt, ...) G_GNUC_PRINTF(3,4); |
139 | | |
140 | | static void ws_log_cleanup(void); |
141 | | |
142 | | |
143 | | const char *ws_log_level_to_string(enum ws_log_level level) |
144 | 905 | { |
145 | 905 | switch (level) { |
146 | 0 | case LOG_LEVEL_NONE: |
147 | 0 | return "(zero)"; |
148 | 0 | case LOG_LEVEL_ECHO: |
149 | 0 | return "ECHO"; |
150 | 0 | case LOG_LEVEL_ERROR: |
151 | 0 | return "ERROR"; |
152 | 0 | case LOG_LEVEL_CRITICAL: |
153 | 0 | return "CRITICAL"; |
154 | 890 | case LOG_LEVEL_WARNING: |
155 | 890 | return "WARNING"; |
156 | 15 | case LOG_LEVEL_MESSAGE: |
157 | 15 | return "MESSAGE"; |
158 | 0 | case LOG_LEVEL_INFO: |
159 | 0 | return "INFO"; |
160 | 0 | case LOG_LEVEL_DEBUG: |
161 | 0 | return "DEBUG"; |
162 | 0 | case LOG_LEVEL_NOISY: |
163 | 0 | return "NOISY"; |
164 | 0 | default: |
165 | 0 | return "(BOGUS LOG LEVEL)"; |
166 | 905 | } |
167 | 905 | } |
168 | | |
169 | | |
170 | | static enum ws_log_level string_to_log_level(const char *str_level) |
171 | 0 | { |
172 | 0 | if (!str_level) |
173 | 0 | return LOG_LEVEL_NONE; |
174 | | |
175 | 0 | if (g_ascii_strcasecmp(str_level, "noisy") == 0) |
176 | 0 | return LOG_LEVEL_NOISY; |
177 | 0 | else if (g_ascii_strcasecmp(str_level, "debug") == 0) |
178 | 0 | return LOG_LEVEL_DEBUG; |
179 | 0 | else if (g_ascii_strcasecmp(str_level, "info") == 0) |
180 | 0 | return LOG_LEVEL_INFO; |
181 | 0 | else if (g_ascii_strcasecmp(str_level, "message") == 0) |
182 | 0 | return LOG_LEVEL_MESSAGE; |
183 | 0 | else if (g_ascii_strcasecmp(str_level, "warning") == 0) |
184 | 0 | return LOG_LEVEL_WARNING; |
185 | 0 | else if (g_ascii_strcasecmp(str_level, "critical") == 0) |
186 | 0 | return LOG_LEVEL_CRITICAL; |
187 | 0 | else if (g_ascii_strcasecmp(str_level, "error") == 0) |
188 | 0 | return LOG_LEVEL_ERROR; |
189 | 0 | else if (g_ascii_strcasecmp(str_level, "echo") == 0) |
190 | 0 | return LOG_LEVEL_ECHO; |
191 | 0 | else |
192 | 0 | return LOG_LEVEL_NONE; |
193 | 0 | } |
194 | | |
195 | | |
196 | | WS_RETNONNULL |
197 | | static inline const char *domain_to_string(const char *domain) |
198 | 905 | { |
199 | 905 | return DOMAIN_UNDEFED(domain) ? "(none)" : domain; |
200 | 905 | } |
201 | | |
202 | | |
203 | | static inline bool filter_contains(log_filter_t *filter, |
204 | | const char *domain) |
205 | 0 | { |
206 | 0 | if (filter == NULL || DOMAIN_UNDEFED(domain)) |
207 | 0 | return false; |
208 | | |
209 | 0 | for (char **domv = filter->domainv; *domv != NULL; domv++) { |
210 | 0 | if (g_ascii_strcasecmp(*domv, domain) == 0) { |
211 | 0 | return true; |
212 | 0 | } |
213 | 0 | } |
214 | 0 | return false; |
215 | 0 | } |
216 | | |
217 | | |
218 | | static inline bool level_filter_matches(log_filter_t *filter, |
219 | | const char *domain, |
220 | | enum ws_log_level level, |
221 | | bool *active_ptr) |
222 | 24.1k | { |
223 | 24.1k | if (filter == NULL || DOMAIN_UNDEFED(domain)) |
224 | 24.1k | return false; |
225 | | |
226 | 0 | if (!filter_contains(filter, domain)) |
227 | 0 | return false; |
228 | | |
229 | 0 | if (filter->positive) { |
230 | 0 | if (active_ptr) |
231 | 0 | *active_ptr = level >= filter->min_level; |
232 | 0 | return true; |
233 | 0 | } |
234 | | |
235 | | /* negative match */ |
236 | 0 | if (level <= filter->min_level) { |
237 | 0 | if (active_ptr) |
238 | 0 | *active_ptr = false; |
239 | 0 | return true; |
240 | 0 | } |
241 | | |
242 | 0 | return false; |
243 | 0 | } |
244 | | |
245 | | |
246 | | static inline void |
247 | | get_timestamp(struct timespec *ts) |
248 | 905 | { |
249 | 905 | bool ok = false; |
250 | | |
251 | 905 | #if defined(HAVE_CLOCK_GETTIME) |
252 | 905 | ok = (clock_gettime(CLOCK_REALTIME, ts) == 0); |
253 | | #elif defined(HAVE_TIMESPEC_GET) |
254 | | ok = (timespec_get(ts, TIME_UTC) == TIME_UTC); |
255 | | #endif |
256 | 905 | if (ok) |
257 | 905 | return; |
258 | | |
259 | | /* Fall back on time(). */ |
260 | 0 | ts->tv_sec = time(NULL); |
261 | 0 | ts->tv_nsec = -1; |
262 | 0 | } |
263 | | |
264 | | |
265 | | static inline void fill_manifest(ws_log_manifest_t *mft) |
266 | 905 | { |
267 | 905 | struct timespec ts; |
268 | 905 | get_timestamp(&ts); |
269 | 905 | ws_localtime_r(&ts.tv_sec, &mft->tstamp_secs); |
270 | 905 | mft->nanosecs = ts.tv_nsec; |
271 | 905 | mft->pid = getpid(); |
272 | 905 | } |
273 | | |
274 | | |
275 | | static inline bool msg_is_active(const char *domain, enum ws_log_level level, |
276 | | ws_log_manifest_t *mft) |
277 | 1.28k | { |
278 | 1.28k | bool is_active = ws_log_msg_is_active(domain, level); |
279 | 1.28k | if (is_active) |
280 | 905 | fill_manifest(mft); |
281 | 1.28k | return is_active; |
282 | 1.28k | } |
283 | | |
284 | | |
285 | | bool ws_log_msg_is_active(const char *domain, enum ws_log_level level) |
286 | 12.4k | { |
287 | | /* |
288 | | * Higher numerical levels have higher priority. Critical and above |
289 | | * are always enabled. |
290 | | */ |
291 | 12.4k | if (level >= LOG_LEVEL_CRITICAL) |
292 | 0 | return true; |
293 | | |
294 | | /* |
295 | | * Check if the level has been configured as fatal. |
296 | | */ |
297 | 12.4k | if (level >= fatal_log_level) |
298 | 0 | return true; |
299 | | |
300 | | /* |
301 | | * Check if the domain has been configured as fatal. |
302 | | */ |
303 | 12.4k | if (DOMAIN_DEFINED(domain) && fatal_filter != NULL) { |
304 | 0 | if (filter_contains(fatal_filter, domain) && fatal_filter->positive) { |
305 | 0 | return true; |
306 | 0 | } |
307 | 0 | } |
308 | | |
309 | | /* |
310 | | * The debug/noisy filter overrides the other parameters. |
311 | | */ |
312 | 12.4k | if (DOMAIN_DEFINED(domain)) { |
313 | 12.0k | bool active; |
314 | | |
315 | 12.0k | if (level_filter_matches(noisy_filter, domain, level, &active)) |
316 | 0 | return active; |
317 | 12.0k | if (level_filter_matches(debug_filter, domain, level, &active)) |
318 | 0 | return active; |
319 | 12.0k | } |
320 | | |
321 | | /* |
322 | | * If the priority is lower than the current minimum drop the |
323 | | * message. |
324 | | */ |
325 | 12.4k | if (level < current_log_level) |
326 | 11.5k | return false; |
327 | | |
328 | | /* |
329 | | * If we don't have domain filtering enabled we are done. |
330 | | */ |
331 | 905 | if (domain_filter == NULL) |
332 | 905 | return true; |
333 | | |
334 | | /* |
335 | | * We have a filter but we don't use it with the undefined domain, |
336 | | * pretty much every permanent call to ws_log should be using a |
337 | | * chosen domain. |
338 | | */ |
339 | 0 | if (DOMAIN_UNDEFED(domain)) |
340 | 0 | return true; |
341 | | |
342 | | /* Check if the domain filter matches. */ |
343 | 0 | if (filter_contains(domain_filter, domain)) |
344 | 0 | return domain_filter->positive; |
345 | | |
346 | | /* We have a domain filter but it didn't match. */ |
347 | 0 | return !domain_filter->positive; |
348 | 0 | } |
349 | | |
350 | | |
351 | | enum ws_log_level ws_log_get_level(void) |
352 | 0 | { |
353 | 0 | return current_log_level; |
354 | 0 | } |
355 | | |
356 | | |
357 | | enum ws_log_level ws_log_set_level(enum ws_log_level level) |
358 | 0 | { |
359 | 0 | if (level <= LOG_LEVEL_NONE || level >= _LOG_LEVEL_LAST) |
360 | 0 | return LOG_LEVEL_NONE; |
361 | 0 | if (level > LOG_LEVEL_CRITICAL) |
362 | 0 | level = LOG_LEVEL_CRITICAL; |
363 | |
|
364 | 0 | current_log_level = level; |
365 | 0 | return current_log_level; |
366 | 0 | } |
367 | | |
368 | | |
369 | | enum ws_log_level ws_log_set_level_str(const char *str_level) |
370 | 0 | { |
371 | 0 | enum ws_log_level level; |
372 | |
|
373 | 0 | level = string_to_log_level(str_level); |
374 | 0 | return ws_log_set_level(level); |
375 | 0 | } |
376 | | |
377 | | |
378 | | static void print_err(void (*vcmdarg_err)(const char *, va_list ap), |
379 | | int exit_failure, |
380 | | const char *fmt, ...) |
381 | 0 | { |
382 | 0 | va_list ap; |
383 | |
|
384 | 0 | va_start(ap, fmt); |
385 | 0 | if (vcmdarg_err) |
386 | 0 | vcmdarg_err(fmt, ap); |
387 | 0 | else |
388 | 0 | vfprintf(stderr, fmt, ap); |
389 | 0 | va_end(ap); |
390 | 0 | if (exit_failure != LOG_ARGS_NOEXIT) |
391 | 0 | exit(exit_failure); |
392 | 0 | } |
393 | | |
394 | | int ws_log_parse_args(int* argc, char* argv[], |
395 | | const char* optstring, const struct ws_option* long_options, |
396 | | void (*vcmdarg_err)(const char*, va_list ap), |
397 | | int exit_failure) |
398 | 15 | { |
399 | 15 | int opt; |
400 | | |
401 | | /* Save the global setting of erroring on unknown options, because this list will probably contain application options |
402 | | not handled here */ |
403 | 15 | int old_ws_opterr = ws_opterr; |
404 | | |
405 | | /* Clear erroring on unknown options */ |
406 | 15 | ws_opterr = 0; |
407 | | |
408 | 675 | while ((opt = ws_getopt_long_only(*argc, argv, optstring, long_options, NULL)) != -1) { |
409 | | |
410 | 660 | switch (opt) |
411 | 660 | { |
412 | 0 | case LONGOPT_WSLOG_LOG_LEVEL: |
413 | 0 | if (ws_log_set_level_str(ws_optarg) == LOG_LEVEL_NONE) { |
414 | 0 | print_err(vcmdarg_err, exit_failure, |
415 | 0 | "Invalid log level \"%s\".\n", ws_optarg); |
416 | 0 | } |
417 | 0 | break; |
418 | | |
419 | 0 | case LONGOPT_WSLOG_LOG_DOMAIN: |
420 | 0 | ws_log_set_domain_filter(ws_optarg); |
421 | 0 | break; |
422 | | |
423 | 0 | case LONGOPT_WSLOG_LOG_FILE: |
424 | 0 | { |
425 | 0 | FILE* fp = ws_fopen(ws_optarg, "w"); |
426 | 0 | if (fp == NULL) { |
427 | 0 | print_err(vcmdarg_err, exit_failure, |
428 | 0 | "Error opening file '%s' for writing: %s.\n", |
429 | 0 | ws_optarg, g_strerror(errno)); |
430 | 0 | } |
431 | 0 | else { |
432 | 0 | ws_log_add_custom_file(fp); |
433 | 0 | } |
434 | 0 | } |
435 | 0 | break; |
436 | 0 | case LONGOPT_WSLOG_LOG_FATAL: |
437 | 0 | if (ws_log_set_fatal_level_str(ws_optarg) == LOG_LEVEL_NONE) { |
438 | 0 | print_err(vcmdarg_err, exit_failure, |
439 | 0 | "Fatal log level must be \"critical\" or \"warning\", not \"%s\".\n", ws_optarg); |
440 | 0 | } |
441 | 0 | break; |
442 | 0 | case LONGOPT_WSLOG_LOG_FATAL_COUNT: |
443 | 0 | if (!ws_log_set_fatal_count_str(ws_optarg)) { |
444 | 0 | print_err(vcmdarg_err, exit_failure, |
445 | 0 | "Fatal log count must be decimal and larger than 0, not \"%s\".\n", ws_optarg); |
446 | 0 | } |
447 | 0 | break; |
448 | 0 | case LONGOPT_WSLOG_LOG_FATAL_DOMAIN: |
449 | 0 | ws_log_set_fatal_domain_filter(ws_optarg); |
450 | 0 | break; |
451 | 0 | case LONGOPT_WSLOG_LOG_DEBUG: |
452 | 0 | ws_log_set_debug_filter(ws_optarg); |
453 | 0 | break; |
454 | 0 | case LONGOPT_WSLOG_LOG_NOISY: |
455 | 0 | ws_log_set_noisy_filter(ws_optarg); |
456 | 0 | break; |
457 | 0 | case 'o': |
458 | 0 | if (g_str_has_prefix(ws_optarg, "console.log.level:")) { |
459 | 0 | uint32_t mask; |
460 | 0 | enum ws_log_level level; |
461 | 0 | const char* mask_str = ws_optarg + strlen("console.log.level:"); |
462 | 0 | if (*mask_str == '\0') { |
463 | 0 | print_err(vcmdarg_err, exit_failure, |
464 | 0 | "Missing value to 'console.log.level' option."); |
465 | 0 | break; |
466 | 0 | } |
467 | | |
468 | 0 | if (!ws_strtou32(mask_str, NULL, &mask)) { |
469 | 0 | print_err(vcmdarg_err, exit_failure, |
470 | 0 | "%s is not a valid decimal number.", mask_str); |
471 | 0 | break; |
472 | 0 | } |
473 | | |
474 | | /* |
475 | | * The lowest priority bit in the mask defines the level. |
476 | | */ |
477 | 0 | if (mask & G_LOG_LEVEL_DEBUG) |
478 | 0 | level = LOG_LEVEL_DEBUG; |
479 | 0 | else if (mask & G_LOG_LEVEL_INFO) |
480 | 0 | level = LOG_LEVEL_INFO; |
481 | 0 | else if (mask & G_LOG_LEVEL_MESSAGE) |
482 | 0 | level = LOG_LEVEL_MESSAGE; |
483 | 0 | else if (mask & G_LOG_LEVEL_WARNING) |
484 | 0 | level = LOG_LEVEL_WARNING; |
485 | 0 | else if (mask & G_LOG_LEVEL_CRITICAL) |
486 | 0 | level = LOG_LEVEL_CRITICAL; |
487 | 0 | else if (mask & G_LOG_LEVEL_ERROR) |
488 | 0 | level = LOG_LEVEL_ERROR; |
489 | 0 | else |
490 | 0 | level = LOG_LEVEL_NONE; |
491 | |
|
492 | 0 | if (level != LOG_LEVEL_NONE) { |
493 | 0 | ws_log_set_level(level); |
494 | 0 | } else { |
495 | | /* Some values (like zero) might not contain any meaningful bits. |
496 | | * Throwing an error in that case seems appropriate. */ |
497 | 0 | print_err(vcmdarg_err, exit_failure, |
498 | 0 | "Value %s is not a valid log mask.", mask_str); |
499 | 0 | } |
500 | 0 | } |
501 | 0 | break; |
502 | 660 | default: |
503 | | /* Ignore options not found because they are probably supported by the application */ |
504 | 660 | break; |
505 | 660 | } |
506 | 660 | } |
507 | | |
508 | | /* Restore the global setting of erroring on unknown options */ |
509 | 15 | ws_opterr = old_ws_opterr; |
510 | 15 | ws_optreset = 1; |
511 | | |
512 | 15 | return 0; |
513 | 15 | } |
514 | | |
515 | | bool ws_log_is_wslog_arg(int arg) |
516 | 0 | { |
517 | 0 | static const struct ws_option test_options[] = { |
518 | 0 | LONGOPT_WSLOG |
519 | 0 | {0, 0, 0, 0 } |
520 | 0 | }; |
521 | |
|
522 | 0 | const struct ws_option* option = test_options; |
523 | 0 | while (option->val != 0) { |
524 | 0 | if (option->val == arg) |
525 | 0 | return true; |
526 | | |
527 | 0 | option++; |
528 | 0 | } |
529 | | |
530 | 0 | return false; |
531 | 0 | } |
532 | | |
533 | | |
534 | | static void free_log_filter(log_filter_t **filter_ptr) |
535 | 60 | { |
536 | 60 | if (filter_ptr == NULL || *filter_ptr == NULL) |
537 | 60 | return; |
538 | 0 | g_strfreev((*filter_ptr)->domainv); |
539 | 0 | g_free(*filter_ptr); |
540 | 0 | *filter_ptr = NULL; |
541 | 0 | } |
542 | | |
543 | | |
544 | | static void tokenize_filter_str(log_filter_t **filter_ptr, |
545 | | const char *str_filter, |
546 | | enum ws_log_level min_level) |
547 | 0 | { |
548 | 0 | const char *sep = ",;"; |
549 | 0 | bool negated = false; |
550 | 0 | log_filter_t *filter; |
551 | |
|
552 | 0 | ASSERT(filter_ptr); |
553 | 0 | ASSERT(*filter_ptr == NULL); |
554 | |
|
555 | 0 | if (str_filter == NULL) |
556 | 0 | return; |
557 | | |
558 | 0 | if (str_filter[0] == '!') { |
559 | 0 | negated = true; |
560 | 0 | str_filter += 1; |
561 | 0 | } |
562 | 0 | if (*str_filter == '\0') |
563 | 0 | return; |
564 | | |
565 | 0 | filter = g_new(log_filter_t, 1); |
566 | 0 | filter->domainv = g_strsplit_set(str_filter, sep, -1); |
567 | 0 | filter->positive = !negated; |
568 | 0 | filter->min_level = min_level; |
569 | 0 | *filter_ptr = filter; |
570 | 0 | } |
571 | | |
572 | | |
573 | | void ws_log_set_domain_filter(const char *str_filter) |
574 | 0 | { |
575 | 0 | free_log_filter(&domain_filter); |
576 | 0 | tokenize_filter_str(&domain_filter, str_filter, LOG_LEVEL_NONE); |
577 | 0 | } |
578 | | |
579 | | |
580 | | void ws_log_set_fatal_domain_filter(const char *str_filter) |
581 | 0 | { |
582 | 0 | free_log_filter(&fatal_filter); |
583 | 0 | tokenize_filter_str(&fatal_filter, str_filter, LOG_LEVEL_NONE); |
584 | 0 | } |
585 | | |
586 | | |
587 | | void ws_log_set_debug_filter(const char *str_filter) |
588 | 0 | { |
589 | 0 | free_log_filter(&debug_filter); |
590 | 0 | tokenize_filter_str(&debug_filter, str_filter, LOG_LEVEL_DEBUG); |
591 | 0 | } |
592 | | |
593 | | |
594 | | void ws_log_set_noisy_filter(const char *str_filter) |
595 | 0 | { |
596 | 0 | free_log_filter(&noisy_filter); |
597 | 0 | tokenize_filter_str(&noisy_filter, str_filter, LOG_LEVEL_NOISY); |
598 | 0 | } |
599 | | |
600 | | |
601 | | enum ws_log_level ws_log_set_fatal_level(enum ws_log_level level) |
602 | 0 | { |
603 | 0 | if (level <= LOG_LEVEL_NONE || level >= _LOG_LEVEL_LAST) |
604 | 0 | return LOG_LEVEL_NONE; |
605 | 0 | if (level > LOG_LEVEL_ERROR) |
606 | 0 | level = LOG_LEVEL_ERROR; |
607 | 0 | if (level < LOG_LEVEL_WARNING) |
608 | 0 | level = LOG_LEVEL_WARNING; |
609 | |
|
610 | 0 | fatal_log_level = level; |
611 | 0 | return fatal_log_level; |
612 | 0 | } |
613 | | |
614 | | |
615 | | enum ws_log_level ws_log_set_fatal_level_str(const char *str_level) |
616 | 0 | { |
617 | 0 | enum ws_log_level level; |
618 | |
|
619 | 0 | level = string_to_log_level(str_level); |
620 | 0 | return ws_log_set_fatal_level(level); |
621 | 0 | } |
622 | | |
623 | | void ws_log_set_fatal_count(uint32_t count) |
624 | 0 | { |
625 | 0 | fatal_log_count = MIN(1, count); |
626 | 0 | } |
627 | | |
628 | | bool ws_log_set_fatal_count_str(const char *str_count) |
629 | 0 | { |
630 | 0 | const char *endptr; |
631 | 0 | uint32_t count; |
632 | 0 | bool result = ws_strtou32(str_count, &endptr, &count); |
633 | |
|
634 | 0 | if (result && count < 1) |
635 | 0 | result = false; |
636 | |
|
637 | 0 | if (result) |
638 | 0 | ws_log_set_fatal_count(count); |
639 | |
|
640 | 0 | return result; |
641 | 0 | } |
642 | | |
643 | | void ws_log_set_writer(ws_log_writer_cb *writer) |
644 | 0 | { |
645 | 0 | if (registered_log_writer_data_free) |
646 | 0 | registered_log_writer_data_free(registered_log_writer_data); |
647 | |
|
648 | 0 | registered_log_writer = writer; |
649 | 0 | registered_log_writer_data = NULL; |
650 | 0 | registered_log_writer_data_free = NULL; |
651 | 0 | } |
652 | | |
653 | | |
654 | | void ws_log_set_writer_with_data(ws_log_writer_cb *writer, |
655 | | void *user_data, |
656 | | ws_log_writer_free_data_cb *free_user_data) |
657 | 0 | { |
658 | 0 | if (registered_log_writer_data_free) |
659 | 0 | registered_log_writer_data_free(registered_log_writer_data); |
660 | |
|
661 | 0 | registered_log_writer = writer; |
662 | 0 | registered_log_writer_data = user_data; |
663 | 0 | registered_log_writer_data_free = free_user_data; |
664 | 0 | } |
665 | | |
666 | | |
667 | | static void glib_log_handler(const char *domain, GLogLevelFlags flags, |
668 | | const char *message, void * user_data _U_) |
669 | 0 | { |
670 | 0 | enum ws_log_level level; |
671 | | |
672 | | /* |
673 | | * The highest priority bit in the mask defines the level. We |
674 | | * ignore the GLib fatal log level mask and use our own fatal |
675 | | * log level setting instead. |
676 | | */ |
677 | |
|
678 | 0 | if (flags & G_LOG_LEVEL_ERROR) |
679 | 0 | level = LOG_LEVEL_ERROR; |
680 | 0 | else if (flags & G_LOG_LEVEL_CRITICAL) |
681 | 0 | level = LOG_LEVEL_CRITICAL; |
682 | 0 | else if (flags & G_LOG_LEVEL_WARNING) |
683 | 0 | level = LOG_LEVEL_WARNING; |
684 | 0 | else if (flags & G_LOG_LEVEL_MESSAGE) |
685 | 0 | level = LOG_LEVEL_MESSAGE; |
686 | 0 | else if (flags & G_LOG_LEVEL_INFO) |
687 | 0 | level = LOG_LEVEL_INFO; |
688 | 0 | else if (flags & G_LOG_LEVEL_DEBUG) |
689 | 0 | level = LOG_LEVEL_DEBUG; |
690 | 0 | else |
691 | 0 | level = LOG_LEVEL_NONE; /* Should not happen. */ |
692 | |
|
693 | 0 | ws_log(domain, level, "%s", message); |
694 | 0 | } |
695 | | |
696 | | |
697 | | #ifdef _WIN32 |
698 | | static void load_registry(void) |
699 | | { |
700 | | LONG lResult; |
701 | | DWORD ptype; |
702 | | DWORD data; |
703 | | DWORD data_size = sizeof(DWORD); |
704 | | |
705 | | lResult = RegGetValueA(HKEY_CURRENT_USER, |
706 | | "Software\\Wireshark", |
707 | | LOG_HKCU_CONSOLE_OPEN, |
708 | | RRF_RT_REG_DWORD, |
709 | | &ptype, |
710 | | &data, |
711 | | &data_size); |
712 | | if (lResult != ERROR_SUCCESS || ptype != REG_DWORD) { |
713 | | return; |
714 | | } |
715 | | |
716 | | ws_log_console_open = (ws_log_console_open_pref)data; |
717 | | } |
718 | | |
719 | | static const char* log_console_title = "Debug Console"; |
720 | | #endif |
721 | | |
722 | | |
723 | | /* |
724 | | * We can't write to stderr in ws_log_init() because dumpcap uses stderr |
725 | | * to communicate with the parent and it will block. We have to use |
726 | | * vcmdarg_err to report errors. |
727 | | */ |
728 | | void ws_log_init(void (*vcmdarg_err)(const char *, va_list ap), const char* console_title _U_) |
729 | 15 | { |
730 | 15 | const char *env; |
731 | 15 | int fd; |
732 | | |
733 | 15 | registered_progname = g_get_prgname(); |
734 | | |
735 | 15 | ws_tzset(); |
736 | | |
737 | 15 | current_log_level = DEFAULT_LOG_LEVEL; |
738 | | |
739 | 15 | if ((fd = fileno(stdout)) >= 0) |
740 | 15 | stdout_color_enabled = g_log_writer_supports_color(fd); |
741 | 15 | if ((fd = fileno(stderr)) >= 0) |
742 | 15 | stderr_color_enabled = g_log_writer_supports_color(fd); |
743 | | |
744 | | /* Set ourselves as the default log handler for all GLib domains. */ |
745 | 15 | g_log_set_default_handler(glib_log_handler, NULL); |
746 | | |
747 | | #ifdef _WIN32 |
748 | | log_console_title = console_title; |
749 | | |
750 | | load_registry(); |
751 | | |
752 | | /* if the user wants a console to be always there, well, we should open one for him */ |
753 | | if (ws_log_console_open == LOG_CONSOLE_OPEN_ALWAYS) |
754 | | create_console(log_console_title); |
755 | | #endif |
756 | | |
757 | 15 | atexit(ws_log_cleanup); |
758 | | |
759 | | /* Configure from environment. */ |
760 | | |
761 | 15 | env = g_getenv(ENV_VAR_LEVEL); |
762 | 15 | if (env != NULL) { |
763 | 0 | if (ws_log_set_level_str(env) == LOG_LEVEL_NONE) { |
764 | 0 | print_err(vcmdarg_err, LOG_ARGS_NOEXIT, |
765 | 0 | "Ignoring invalid environment value %s=\"%s\"", |
766 | 0 | ENV_VAR_LEVEL, env); |
767 | 0 | } |
768 | 0 | } |
769 | | |
770 | 15 | env = g_getenv(ENV_VAR_FATAL); |
771 | 15 | if (env != NULL) { |
772 | 0 | if (ws_log_set_fatal_level_str(env) == LOG_LEVEL_NONE) { |
773 | 0 | print_err(vcmdarg_err, LOG_ARGS_NOEXIT, |
774 | 0 | "Ignoring invalid environment value %s=\"%s\"", |
775 | 0 | ENV_VAR_FATAL, env); |
776 | 0 | } |
777 | 0 | } |
778 | | |
779 | 15 | env = g_getenv(ENV_VAR_FATAL_COUNT); |
780 | 15 | if (env != NULL) { |
781 | 0 | if (!ws_log_set_fatal_count_str(env)) { |
782 | 0 | print_err(vcmdarg_err, LOG_ARGS_NOEXIT, |
783 | 0 | "Ignoring invalid environment value %s=\"%s\"", |
784 | 0 | ENV_VAR_FATAL_COUNT, env); |
785 | 0 | } |
786 | 0 | } |
787 | | |
788 | | /* Alias "domain" and "domains". The plural form wins. */ |
789 | 15 | if ((env = g_getenv(ENV_VAR_DOMAIN_S)) != NULL) |
790 | 0 | ws_log_set_domain_filter(env); |
791 | 15 | else if ((env = g_getenv(ENV_VAR_DOMAIN)) != NULL) |
792 | 0 | ws_log_set_domain_filter(env); |
793 | | |
794 | | /* Alias "domain" and "domains". The plural form wins. */ |
795 | 15 | if ((env = g_getenv(ENV_VAR_FATAL_DOMAIN_S)) != NULL) |
796 | 0 | ws_log_set_fatal_domain_filter(env); |
797 | 15 | else if ((env = g_getenv(ENV_VAR_FATAL_DOMAIN)) != NULL) |
798 | 0 | ws_log_set_fatal_domain_filter(env); |
799 | | |
800 | 15 | env = g_getenv(ENV_VAR_DEBUG); |
801 | 15 | if (env != NULL) |
802 | 0 | ws_log_set_debug_filter(env); |
803 | | |
804 | 15 | env = g_getenv(ENV_VAR_NOISY); |
805 | 15 | if (env != NULL) |
806 | 0 | ws_log_set_noisy_filter(env); |
807 | | |
808 | | #ifdef WS_DEBUG |
809 | | init_complete = true; |
810 | | #endif |
811 | 15 | } |
812 | | |
813 | | |
814 | | void ws_log_init_with_writer(ws_log_writer_cb *writer, |
815 | | void (*vcmdarg_err)(const char *, va_list ap), const char* console_title) |
816 | 0 | { |
817 | 0 | registered_log_writer = writer; |
818 | 0 | ws_log_init(vcmdarg_err, console_title); |
819 | 0 | } |
820 | | |
821 | | |
822 | | void ws_log_init_with_writer_and_data(ws_log_writer_cb *writer, |
823 | | void *user_data, |
824 | | ws_log_writer_free_data_cb *free_user_data, |
825 | | void (*vcmdarg_err)(const char *, va_list ap), const char* console_title) |
826 | 0 | { |
827 | 0 | registered_log_writer_data = user_data; |
828 | 0 | registered_log_writer_data_free = free_user_data; |
829 | 0 | ws_log_init_with_writer(writer, vcmdarg_err, console_title); |
830 | 0 | } |
831 | | |
832 | | |
833 | 0 | #define MAGENTA "\033[35m" |
834 | | #define BLUE "\033[34m" |
835 | 0 | #define CYAN "\033[36m" |
836 | 0 | #define GREEN "\033[32m" |
837 | 0 | #define YELLOW "\033[33m" |
838 | 0 | #define RED "\033[31m" |
839 | 0 | #define RESET "\033[0m" |
840 | | |
841 | | static inline const char *level_color_on(bool enable, enum ws_log_level level) |
842 | 905 | { |
843 | 905 | if (!enable) |
844 | 905 | return ""; |
845 | | |
846 | 0 | switch (level) { |
847 | 0 | case LOG_LEVEL_NOISY: |
848 | 0 | case LOG_LEVEL_DEBUG: |
849 | 0 | return GREEN; |
850 | 0 | case LOG_LEVEL_INFO: |
851 | 0 | case LOG_LEVEL_MESSAGE: |
852 | 0 | return CYAN; |
853 | 0 | case LOG_LEVEL_WARNING: |
854 | 0 | return YELLOW; |
855 | 0 | case LOG_LEVEL_CRITICAL: |
856 | 0 | return MAGENTA; |
857 | 0 | case LOG_LEVEL_ERROR: |
858 | 0 | return RED; |
859 | 0 | case LOG_LEVEL_ECHO: |
860 | 0 | return YELLOW; |
861 | 0 | default: |
862 | 0 | break; |
863 | 0 | } |
864 | 0 | return ""; |
865 | 0 | } |
866 | | |
867 | | static inline const char *color_off(bool enable) |
868 | 905 | { |
869 | 905 | return enable ? RESET : ""; |
870 | 905 | } |
871 | | |
872 | | #define NANOSECS_IN_MICROSEC 1000 |
873 | | |
874 | | /* |
875 | | * We must not call anything that might log a message |
876 | | * in the log handler context (GLib might log a message if we register |
877 | | * our own handler for the GLib domain). |
878 | | */ |
879 | | static void log_write_do_work(FILE *fp, bool use_color, |
880 | | struct tm *when, long nanosecs, intmax_t pid, |
881 | | const char *domain, enum ws_log_level level, |
882 | | const char *file, long line, const char *func, |
883 | | const char *user_format, va_list user_ap) |
884 | 905 | { |
885 | 905 | fputs(" **", fp); |
886 | | |
887 | | #ifdef WS_DEBUG |
888 | | if (!init_complete) |
889 | | fputs(" no init!", fp); |
890 | | #endif |
891 | | |
892 | | /* Process */ |
893 | 905 | fprintf(fp, " (%s:%"PRIdMAX")", registered_progname, pid); |
894 | | |
895 | | /* Timestamp */ |
896 | 905 | if (when != NULL) { |
897 | 905 | fprintf(fp, " %02d:%02d:%02d", |
898 | 905 | when->tm_hour, when->tm_min, when->tm_sec); |
899 | 905 | if (nanosecs >= 0) { |
900 | 905 | fprintf(fp, ".%06ld", nanosecs / NANOSECS_IN_MICROSEC); |
901 | 905 | } |
902 | 905 | } |
903 | | |
904 | | /* Domain/level */ |
905 | 905 | fprintf(fp, " [%s %s%s%s]", domain_to_string(domain), |
906 | 905 | level_color_on(use_color, level), |
907 | 905 | ws_log_level_to_string(level), |
908 | 905 | color_off(use_color)); |
909 | | |
910 | | /* File/line */ |
911 | 905 | if (file != NULL) { |
912 | 374 | fprintf(fp, " %s", file); |
913 | 374 | if (line >= 0) { |
914 | 374 | fprintf(fp, ":%ld", line); |
915 | 374 | } |
916 | 374 | } |
917 | | |
918 | | /* Any formatting changes here need to be synced with ui/capture.c:capture_input_closed. */ |
919 | 905 | fputs(" --", fp); |
920 | | |
921 | | /* Function name */ |
922 | 905 | if (func != NULL) |
923 | 905 | fprintf(fp, " %s():", func); |
924 | | |
925 | | /* User message */ |
926 | 905 | fputc(' ', fp); |
927 | 905 | vfprintf(fp, user_format, user_ap); |
928 | 905 | fputc('\n', fp); |
929 | 905 | fflush(fp); |
930 | 905 | } |
931 | | |
932 | | |
933 | | static inline FILE *console_file(enum ws_log_level level) |
934 | 905 | { |
935 | 905 | if (level <= LOG_LEVEL_INFO && stdout_logging_enabled) |
936 | 0 | return stdout; |
937 | 905 | return stderr; |
938 | 905 | } |
939 | | |
940 | | |
941 | | static inline bool console_color_enabled(enum ws_log_level level) |
942 | 905 | { |
943 | 905 | if (level <= LOG_LEVEL_INFO && stdout_logging_enabled) |
944 | 0 | return stdout_color_enabled; |
945 | 905 | return stderr_color_enabled; |
946 | 905 | } |
947 | | |
948 | | |
949 | | static void log_write_fatal_msg(FILE *fp, intmax_t pid, const char *msg) |
950 | 0 | { |
951 | | /* Process */ |
952 | 0 | fprintf(fp, " ** (%s:%"PRIdMAX") %s", registered_progname, pid, msg); |
953 | 0 | } |
954 | | |
955 | | |
956 | | /* |
957 | | * We must not call anything that might log a message |
958 | | * in the log handler context (GLib might log a message if we register |
959 | | * our own handler for the GLib domain). |
960 | | */ |
961 | | static void log_write_dispatch(const char *domain, enum ws_log_level level, |
962 | | const char *file, long line, const char *func, |
963 | | ws_log_manifest_t *mft, |
964 | | const char *user_format, va_list user_ap) |
965 | 905 | { |
966 | 905 | bool fatal_event = false; |
967 | 905 | const char *fatal_msg = NULL; |
968 | 905 | va_list user_ap_copy; |
969 | | |
970 | 905 | if (level >= fatal_log_level && level != LOG_LEVEL_ECHO) { |
971 | 0 | fatal_event = true; |
972 | 0 | fatal_msg = "Aborting on fatal log level exception\n"; |
973 | 0 | } |
974 | 905 | else if (fatal_filter != NULL) { |
975 | 0 | if (filter_contains(fatal_filter, domain) && fatal_filter->positive) { |
976 | 0 | fatal_event = true; |
977 | 0 | fatal_msg = "Aborting on fatal log domain exception\n"; |
978 | 0 | } |
979 | 0 | } |
980 | | |
981 | | #ifdef _WIN32 |
982 | | if (ws_log_console_open != LOG_CONSOLE_OPEN_NEVER) { |
983 | | create_console(log_console_title); |
984 | | } |
985 | | #endif /* _WIN32 */ |
986 | | |
987 | 905 | if (custom_log) { |
988 | 0 | va_copy(user_ap_copy, user_ap); |
989 | 0 | log_write_do_work(custom_log, false, |
990 | 0 | &mft->tstamp_secs, mft->nanosecs, mft->pid, |
991 | 0 | domain, level, file, line, func, |
992 | 0 | user_format, user_ap_copy); |
993 | 0 | va_end(user_ap_copy); |
994 | 0 | if (fatal_msg) { |
995 | 0 | log_write_fatal_msg(custom_log, mft->pid, fatal_msg); |
996 | 0 | } |
997 | 0 | } |
998 | | |
999 | 905 | if (registered_log_writer) { |
1000 | 0 | registered_log_writer(domain, level, file, line, func, fatal_msg, mft, |
1001 | 0 | user_format, user_ap, registered_log_writer_data); |
1002 | 0 | } |
1003 | 905 | else { |
1004 | 905 | log_write_do_work(console_file(level), console_color_enabled(level), |
1005 | 905 | &mft->tstamp_secs, mft->nanosecs, mft->pid, |
1006 | 905 | domain, level, file, line, func, |
1007 | 905 | user_format, user_ap); |
1008 | 905 | if (fatal_msg) { |
1009 | 0 | log_write_fatal_msg(console_file(level), mft->pid, fatal_msg); |
1010 | 0 | } |
1011 | 905 | } |
1012 | | |
1013 | | #ifdef _WIN32 |
1014 | | if (fatal_event && ws_log_console_open != LOG_CONSOLE_OPEN_NEVER) { |
1015 | | /* wait for a key press before the following error handler will terminate the program |
1016 | | this way the user at least can read the error message */ |
1017 | | printf("\n\nPress any key to exit\n"); |
1018 | | _getch(); |
1019 | | } |
1020 | | #endif /* _WIN32 */ |
1021 | | |
1022 | 905 | if (fatal_event) { |
1023 | 0 | if (--fatal_log_count == 0) |
1024 | 0 | abort(); |
1025 | 0 | } |
1026 | 905 | } |
1027 | | |
1028 | | |
1029 | | void ws_logv(const char *domain, enum ws_log_level level, |
1030 | | const char *format, va_list ap) |
1031 | 0 | { |
1032 | 0 | ws_log_manifest_t mft; |
1033 | 0 | if (!msg_is_active(domain, level, &mft)) |
1034 | 0 | return; |
1035 | | |
1036 | 0 | log_write_dispatch(domain, level, NULL, -1, NULL, &mft, format, ap); |
1037 | 0 | } |
1038 | | |
1039 | | |
1040 | | void ws_logv_full(const char *domain, enum ws_log_level level, |
1041 | | const char *file, long line, const char *func, |
1042 | | const char *format, va_list ap) |
1043 | 0 | { |
1044 | 0 | ws_log_manifest_t mft; |
1045 | 0 | if (!msg_is_active(domain, level, &mft)) |
1046 | 0 | return; |
1047 | | |
1048 | 0 | log_write_dispatch(domain, level, file, line, func, &mft, format, ap); |
1049 | 0 | } |
1050 | | |
1051 | | |
1052 | | void ws_log(const char *domain, enum ws_log_level level, |
1053 | | const char *format, ...) |
1054 | 516 | { |
1055 | 516 | ws_log_manifest_t mft; |
1056 | 516 | if (!msg_is_active(domain, level, &mft)) |
1057 | 0 | return; |
1058 | | |
1059 | 516 | va_list ap; |
1060 | | |
1061 | 516 | va_start(ap, format); |
1062 | 516 | log_write_dispatch(domain, level, NULL, -1, NULL, &mft, format, ap); |
1063 | 516 | va_end(ap); |
1064 | 516 | } |
1065 | | |
1066 | | |
1067 | | void ws_log_full(const char *domain, enum ws_log_level level, |
1068 | | const char *file, long line, const char *func, |
1069 | | const char *format, ...) |
1070 | 765 | { |
1071 | 765 | ws_log_manifest_t mft; |
1072 | 765 | if (!msg_is_active(domain, level, &mft)) |
1073 | 376 | return; |
1074 | | |
1075 | 389 | va_list ap; |
1076 | | |
1077 | 389 | va_start(ap, format); |
1078 | 389 | log_write_dispatch(domain, level, file, line, func, &mft, format, ap); |
1079 | 389 | va_end(ap); |
1080 | 389 | } |
1081 | | |
1082 | | |
1083 | | void ws_log_fatal_full(const char *domain, enum ws_log_level level, |
1084 | | const char *file, long line, const char *func, |
1085 | | const char *format, ...) |
1086 | 0 | { |
1087 | 0 | ws_log_manifest_t mft; |
1088 | 0 | va_list ap; |
1089 | |
|
1090 | 0 | fill_manifest(&mft); |
1091 | 0 | va_start(ap, format); |
1092 | 0 | log_write_dispatch(domain, level, file, line, func, &mft, format, ap); |
1093 | 0 | va_end(ap); |
1094 | 0 | abort(); |
1095 | 0 | } |
1096 | | |
1097 | | |
1098 | | void ws_log_write_always_full(const char *domain, enum ws_log_level level, |
1099 | | const char *file, long line, const char *func, |
1100 | | const char *format, ...) |
1101 | 0 | { |
1102 | 0 | ws_log_manifest_t mft; |
1103 | 0 | va_list ap; |
1104 | |
|
1105 | 0 | fill_manifest(&mft); |
1106 | 0 | va_start(ap, format); |
1107 | 0 | log_write_dispatch(domain, level, file, line, func, &mft, format, ap); |
1108 | 0 | va_end(ap); |
1109 | 0 | } |
1110 | | |
1111 | | |
1112 | | static void |
1113 | | append_trailer(const char *src, size_t src_length, wmem_strbuf_t *display, wmem_strbuf_t *underline) |
1114 | 0 | { |
1115 | 0 | gunichar ch; |
1116 | 0 | size_t hex_len; |
1117 | |
|
1118 | 0 | while (src_length > 0) { |
1119 | 0 | ch = g_utf8_get_char_validated(src, src_length); |
1120 | 0 | if (ch == (gunichar)-1 || ch == (gunichar)-2) { |
1121 | 0 | wmem_strbuf_append_hex(display, *src); |
1122 | 0 | wmem_strbuf_append_c_count(underline, '^', 4); |
1123 | 0 | src += 1; |
1124 | 0 | src_length -= 1; |
1125 | 0 | } |
1126 | 0 | else { |
1127 | 0 | if (g_unichar_isprint(ch)) { |
1128 | 0 | wmem_strbuf_append_unichar(display, ch); |
1129 | 0 | wmem_strbuf_append_c_count(underline, ' ', 1); |
1130 | 0 | } |
1131 | 0 | else { |
1132 | 0 | hex_len = wmem_strbuf_append_hex_unichar(display, ch); |
1133 | 0 | wmem_strbuf_append_c_count(underline, ' ', hex_len); |
1134 | 0 | } |
1135 | 0 | const char *tmp = g_utf8_next_char(src); |
1136 | 0 | src_length -= tmp - src; |
1137 | 0 | src = tmp; |
1138 | 0 | } |
1139 | 0 | } |
1140 | 0 | } |
1141 | | |
1142 | | |
1143 | | static char * |
1144 | | make_utf8_display(const char *src, size_t src_length, size_t good_length) |
1145 | 0 | { |
1146 | 0 | wmem_strbuf_t *display; |
1147 | 0 | wmem_strbuf_t *underline; |
1148 | 0 | gunichar ch; |
1149 | 0 | size_t hex_len; |
1150 | |
|
1151 | 0 | display = wmem_strbuf_create(NULL); |
1152 | 0 | underline = wmem_strbuf_create(NULL); |
1153 | |
|
1154 | 0 | for (const char *s = src; s < src + good_length; s = g_utf8_next_char(s)) { |
1155 | 0 | ch = g_utf8_get_char(s); |
1156 | |
|
1157 | 0 | if (g_unichar_isprint(ch)) { |
1158 | 0 | wmem_strbuf_append_unichar(display, ch); |
1159 | 0 | wmem_strbuf_append_c(underline, ' '); |
1160 | 0 | } |
1161 | 0 | else { |
1162 | 0 | hex_len = wmem_strbuf_append_hex_unichar(display, ch); |
1163 | 0 | wmem_strbuf_append_c_count(underline, ' ', hex_len); |
1164 | 0 | } |
1165 | 0 | } |
1166 | |
|
1167 | 0 | append_trailer(&src[good_length], src_length - good_length, display, underline); |
1168 | |
|
1169 | 0 | wmem_strbuf_append_c(display, '\n'); |
1170 | 0 | wmem_strbuf_append(display, underline->str); |
1171 | 0 | wmem_strbuf_destroy(underline); |
1172 | |
|
1173 | 0 | return wmem_strbuf_finalize(display); |
1174 | 0 | } |
1175 | | |
1176 | | |
1177 | | void ws_log_utf8_full(const char *domain, enum ws_log_level level, |
1178 | | const char *file, long line, const char *func, |
1179 | | const char *string, ssize_t _length, const char *endptr) |
1180 | 0 | { |
1181 | 0 | if (!ws_log_msg_is_active(domain, level)) |
1182 | 0 | return; |
1183 | | |
1184 | 0 | char *display; |
1185 | 0 | size_t length; |
1186 | 0 | size_t good_length; |
1187 | |
|
1188 | 0 | if (_length < 0) |
1189 | 0 | length = strlen(string); |
1190 | 0 | else |
1191 | 0 | length = _length; |
1192 | |
|
1193 | 0 | if (endptr == NULL || endptr < string) { |
1194 | | /* Find the pointer to the first invalid byte. */ |
1195 | 0 | if (g_utf8_validate(string, length, &endptr)) { |
1196 | | /* Valid string - should not happen. */ |
1197 | 0 | return; |
1198 | 0 | } |
1199 | 0 | } |
1200 | 0 | good_length = endptr - string; |
1201 | |
|
1202 | 0 | display = make_utf8_display(string, length, good_length); |
1203 | |
|
1204 | 0 | ws_log_write_always_full(domain, level, file, line, func, |
1205 | 0 | "Invalid UTF-8 at address %p offset %zu (length = %zu):\n%s", |
1206 | 0 | string, good_length, length, display); |
1207 | |
|
1208 | 0 | g_free(display); |
1209 | 0 | } |
1210 | | |
1211 | | |
1212 | | void ws_log_buffer_full(const char *domain, enum ws_log_level level, |
1213 | | const char *file, long line, const char *func, |
1214 | | const uint8_t *ptr, size_t size, size_t max_bytes_len, |
1215 | | const char *msg) |
1216 | 0 | { |
1217 | 0 | if (!ws_log_msg_is_active(domain, level)) |
1218 | 0 | return; |
1219 | | |
1220 | 0 | char *bufstr = bytes_to_str_punct_maxlen(NULL, ptr, size, ' ', max_bytes_len); |
1221 | |
|
1222 | 0 | if (G_UNLIKELY(msg == NULL)) |
1223 | 0 | ws_log_write_always_full(domain, level, file, line, func, |
1224 | 0 | "<buffer:%p>: %s (%zu bytes)", |
1225 | 0 | ptr, bufstr, size); |
1226 | 0 | else |
1227 | 0 | ws_log_write_always_full(domain, level, file, line, func, |
1228 | 0 | "%s: %s (%zu bytes)", |
1229 | 0 | msg, bufstr, size); |
1230 | 0 | wmem_free(NULL, bufstr); |
1231 | 0 | } |
1232 | | |
1233 | | |
1234 | | void ws_log_file_writer(FILE *fp, const char *domain, enum ws_log_level level, |
1235 | | const char *file, long line, const char *func, |
1236 | | ws_log_manifest_t *mft, |
1237 | | const char *user_format, va_list user_ap) |
1238 | 0 | { |
1239 | 0 | log_write_do_work(fp, false, |
1240 | 0 | &mft->tstamp_secs, mft->nanosecs, mft->pid, |
1241 | 0 | domain, level, file, line, func, |
1242 | 0 | user_format, user_ap); |
1243 | 0 | } |
1244 | | |
1245 | | |
1246 | | void ws_log_console_writer(const char *domain, enum ws_log_level level, |
1247 | | const char *file, long line, const char *func, |
1248 | | ws_log_manifest_t *mft, |
1249 | | const char *user_format, va_list user_ap) |
1250 | 0 | { |
1251 | 0 | log_write_do_work(console_file(level), console_color_enabled(level), |
1252 | 0 | &mft->tstamp_secs, mft->nanosecs, mft->pid, |
1253 | 0 | domain, level, file, line, func, |
1254 | 0 | user_format, user_ap); |
1255 | 0 | } |
1256 | | |
1257 | | |
1258 | | WS_DLL_PUBLIC |
1259 | | void ws_log_console_writer_set_use_stdout(bool use_stdout) |
1260 | 0 | { |
1261 | 0 | stdout_logging_enabled = use_stdout; |
1262 | 0 | } |
1263 | | |
1264 | | |
1265 | | static void ws_log_cleanup(void) |
1266 | 15 | { |
1267 | 15 | if (registered_log_writer_data_free) { |
1268 | 0 | registered_log_writer_data_free(registered_log_writer_data); |
1269 | 0 | registered_log_writer_data = NULL; |
1270 | 0 | } |
1271 | 15 | if (custom_log) { |
1272 | 0 | fclose(custom_log); |
1273 | 0 | custom_log = NULL; |
1274 | 0 | } |
1275 | 15 | free_log_filter(&domain_filter); |
1276 | 15 | free_log_filter(&debug_filter); |
1277 | 15 | free_log_filter(&noisy_filter); |
1278 | 15 | free_log_filter(&fatal_filter); |
1279 | 15 | } |
1280 | | |
1281 | | |
1282 | | void ws_log_add_custom_file(FILE *fp) |
1283 | 0 | { |
1284 | 0 | if (custom_log != NULL) { |
1285 | 0 | fclose(custom_log); |
1286 | 0 | } |
1287 | 0 | custom_log = fp; |
1288 | 0 | } |
1289 | | |
1290 | | |
1291 | | #define USAGE_LEVEL \ |
1292 | | "sets the active log level (\"critical\", \"warning\", etc.)" |
1293 | | |
1294 | | #define USAGE_FATAL \ |
1295 | | "sets level to abort the program (\"critical\" or \"warning\")" |
1296 | | |
1297 | | #define USAGE_FATAL_COUNT \ |
1298 | | "sets the number of fatal errors before aborting the program" |
1299 | | |
1300 | | #define USAGE_DOMAINS \ |
1301 | | "comma-separated list of the active log domains" |
1302 | | |
1303 | | #define USAGE_FATAL_DOMAINS \ |
1304 | | "list of domains that cause the program to abort" |
1305 | | |
1306 | | #define USAGE_DEBUG \ |
1307 | | "list of domains with \"debug\" level" |
1308 | | |
1309 | | #define USAGE_NOISY \ |
1310 | | "list of domains with \"noisy\" level" |
1311 | | |
1312 | | #define USAGE_FILE \ |
1313 | | "file to output messages to (in addition to stderr)" |
1314 | | |
1315 | | void ws_log_print_usage(FILE *fp) |
1316 | 0 | { |
1317 | 0 | fprintf(fp, "Diagnostic output:\n"); |
1318 | 0 | fprintf(fp, " --log-level <level> " USAGE_LEVEL "\n"); |
1319 | 0 | fprintf(fp, " --log-fatal <level> " USAGE_FATAL "\n"); |
1320 | 0 | fprintf(fp, " --log-fatal-count <count> " USAGE_FATAL_COUNT "\n"); |
1321 | 0 | fprintf(fp, " --log-domains <[!]list> " USAGE_DOMAINS "\n"); |
1322 | 0 | fprintf(fp, " --log-fatal-domains <list>\n"); |
1323 | 0 | fprintf(fp, " " USAGE_FATAL_DOMAINS "\n"); |
1324 | 0 | fprintf(fp, " --log-debug <[!]list> " USAGE_DEBUG "\n"); |
1325 | 0 | fprintf(fp, " --log-noisy <[!]list> " USAGE_NOISY "\n"); |
1326 | | fprintf(fp, " --log-file <path> " USAGE_FILE "\n"); |
1327 | 0 | } |