1 /*
2  * Copyright (C) 2011 Red Hat, Inc.
3  *
4  * All rights reserved.
5  *
6  * Author: Angus Salkeld <asalkeld@redhat.com>
7  *
8  * libqb is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation, either version 2.1 of the License, or
11  * (at your option) any later version.
12  *
13  * libqb is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with libqb.  If not, see <http://www.gnu.org/licenses/>.
20  */
21 #include "os_base.h"
22 #include <ctype.h>
23 #ifdef HAVE_LINK_H
24 #include <link.h>
25 #endif /* HAVE_LINK_H */
26 #include <stdarg.h>
27 #include <pthread.h>
28 #include <stdarg.h>
29 #include <string.h>
30 
31 #include <qb/qbdefs.h>
32 #include <qb/qblist.h>
33 #include <qb/qblog.h>
34 #include <qb/qbutil.h>
35 #include <qb/qbarray.h>
36 #include <qb/qbatomic.h>
37 #include "log_int.h"
38 #include "util_int.h"
39 #include <regex.h>
40 
41 static struct qb_log_target conf[QB_LOG_TARGET_MAX];
42 static uint32_t conf_active_max = 0;
43 static int32_t in_logger = QB_FALSE;
44 static int32_t logger_inited = QB_FALSE;
45 static pthread_rwlock_t _listlock;
46 static qb_log_filter_fn _custom_filter_fn = NULL;
47 
48 static QB_LIST_DECLARE(tags_head);
49 static QB_LIST_DECLARE(callsite_sections);
50 
51 struct callsite_section {
52 	struct qb_log_callsite *start;
53 	struct qb_log_callsite *stop;
54 	struct qb_list_head list;
55 };
56 
57 static int32_t _log_target_enable(struct qb_log_target *t);
58 static void _log_target_disable(struct qb_log_target *t);
59 static void _log_filter_apply(struct callsite_section *sect,
60 			      uint32_t t, enum qb_log_filter_conf c,
61 			      enum qb_log_filter_type type,
62 			      const char *text,
63 			      regex_t *regex,
64 			      uint8_t high_priority, uint8_t low_priority);
65 static void _log_filter_apply_to_cs(struct qb_log_callsite *cs,
66 				    uint32_t t, enum qb_log_filter_conf c,
67 				    enum qb_log_filter_type type,
68 				    const char *text,
69 				    regex_t *regex,
70 				    uint8_t high_priority, uint8_t low_priority);
71 
72 /* deprecated method of getting internal log messages */
73 static qb_util_log_fn_t old_internal_log_fn = NULL;
74 void
qb_util_set_log_function(qb_util_log_fn_t fn)75 qb_util_set_log_function(qb_util_log_fn_t fn)
76 {
77 	old_internal_log_fn = fn;
78 }
79 
80 static void
_log_free_filter(struct qb_log_filter * flt)81 _log_free_filter(struct qb_log_filter *flt)
82 {
83 	if (flt->regex) {
84 		regfree(flt->regex);
85 	}
86 	free(flt->regex);
87 	free(flt->text);
88 	free(flt);
89 }
90 
91 static int32_t
_cs_matches_filter_(struct qb_log_callsite * cs,enum qb_log_filter_type type,const char * text,regex_t * regex,uint8_t high_priority,uint8_t low_priority)92 _cs_matches_filter_(struct qb_log_callsite *cs,
93 		    enum qb_log_filter_type type,
94 		    const char *text,
95 		    regex_t *regex,
96 		    uint8_t high_priority,
97 		    uint8_t low_priority)
98 {
99 	int32_t match = QB_FALSE;
100 	const char *offset = NULL;
101 	const char *next = NULL;
102 
103 	if (cs->priority > low_priority ||
104 	    cs->priority < high_priority) {
105 		return QB_FALSE;
106 	}
107 	if (strcmp(text, "*") == 0) {
108 		return QB_TRUE;
109 	}
110 
111 	switch (type) {
112 	case QB_LOG_FILTER_FILE:
113 	case QB_LOG_FILTER_FUNCTION:
114 		next = text;
115 		do {
116 			char token[500];
117 			offset = next;
118 			next = strchrnul(offset, ',');
119 			snprintf(token, 499, "%.*s", (int)(next - offset), offset);
120 
121 			if (type == QB_LOG_FILTER_FILE) {
122 				match = (strcmp(cs->filename, token) == 0) ? 1 : 0;
123 			} else {
124 				match = (strcmp(cs->function, token) == 0) ? 1 : 0;
125 			}
126 			if (!match && next[0] != 0) {
127 				next++;
128 			}
129 
130 		} while (match == QB_FALSE && next != NULL && next[0] != 0);
131 		break;
132 	case QB_LOG_FILTER_FILE_REGEX:
133 		next = next ? next : cs->filename;
134 		/* fallthrough */
135 	case QB_LOG_FILTER_FUNCTION_REGEX:
136 		next = next ? next : cs->function;
137 		/* fallthrough */
138 	case QB_LOG_FILTER_FORMAT_REGEX:
139 		next = next ? next : cs->format;
140 
141 		if (regex == NULL) {
142 			return QB_FALSE;
143 		}
144 		match = regexec(regex, next, 0, NULL, 0);
145 		if (match == 0) {
146 			match = QB_TRUE;
147 		} else {
148 			match = QB_FALSE;
149 		}
150 		break;
151 	case QB_LOG_FILTER_FORMAT:
152 		if (strstr(cs->format, text)) {
153 			match = QB_TRUE;
154 		}
155 		break;
156 	}
157 
158 	return match;
159 }
160 
161 /**
162  * @internal
163  * @brief Format a log message into a string buffer
164  *
165  * @param[out] str  Destination buffer to contain formatted string
166  * @param[in]  cs   Callsite containing format to use
167  * @param[in]  ap   Variable arguments for format
168  */
169 /* suppress suggestion that we currently can do nothing better about
170    as the format specification is hidden in cs argument */
171 #ifdef HAVE_GCC_FORMAT_COMPLAINTS
172 #pragma GCC diagnostic push
173 #ifdef HAVE_GCC_MISSING_FORMAT_ATTRIBUTE
174 #pragma GCC diagnostic ignored "-Wmissing-format-attribute"
175 #endif
176 #ifdef HAVE_GCC_SUGGEST_ATTRIBUTE_FORMAT
177 #pragma GCC diagnostic ignored "-Wsuggest-attribute=format"
178 #endif
179 #endif
180 static inline void
cs_format(char * str,size_t maxlen,struct qb_log_callsite * cs,va_list ap)181 cs_format(char *str, size_t maxlen, struct qb_log_callsite *cs, va_list ap)
182 {
183 	va_list ap_copy;
184 	int len;
185 
186 	va_copy(ap_copy, ap);
187 	len = vsnprintf(str, maxlen, cs->format, ap_copy);
188 	va_end(ap_copy);
189 
190 	if (len > maxlen) {
191 		len = maxlen;
192 	}
193 	if (str[len - 1] == '\n') {
194 		str[len - 1] = '\0';
195 	}
196 }
197 #ifdef HAVE_GCC_FORMAT_COMPLAINTS
198 #pragma GCC diagnostic pop
199 #endif
200 
201 void
qb_log_real_va_(struct qb_log_callsite * cs,va_list ap)202 qb_log_real_va_(struct qb_log_callsite *cs, va_list ap)
203 {
204 	int32_t found_threaded = QB_FALSE;
205 	struct qb_log_target *t;
206 	struct timespec tv;
207 	enum qb_log_target_slot pos;
208 	size_t max_line_length = 0;
209 	int32_t formatted = QB_FALSE;
210 	char buf[QB_LOG_MAX_LEN];
211 	char *str = buf;
212 	va_list ap_copy;
213 
214 	if (qb_atomic_int_compare_and_exchange(&in_logger, QB_FALSE, QB_TRUE) == QB_FALSE || cs == NULL) {
215 		return;
216 	}
217 
218 	/* 0 Work out the longest line length available */
219 	for (pos = QB_LOG_TARGET_START; pos <= conf_active_max; pos++) {
220 		t = &conf[pos];
221 		if ((t->state == QB_LOG_STATE_ENABLED)
222 		       && qb_bit_is_set(cs->targets, pos)) {
223 			if (t->max_line_length > max_line_length) {
224 				max_line_length = t->max_line_length;
225 			}
226 		}
227 	}
228 
229 	if (max_line_length > QB_LOG_MAX_LEN) {
230 		str = malloc(max_line_length);
231 		if (!str) {
232 			return;
233 		}
234 	}
235 
236 	if (old_internal_log_fn &&
237 	    qb_bit_is_set(cs->tags, QB_LOG_TAG_LIBQB_MSG_BIT)) {
238 		if (formatted == QB_FALSE) {
239 			cs_format(str, max_line_length, cs, ap);
240 			formatted = QB_TRUE;
241 		}
242 		qb_do_extended(str, QB_TRUE,
243 			old_internal_log_fn(cs->filename, cs->lineno,
244 					    cs->priority, str));
245 	}
246 
247 	qb_util_timespec_from_epoch_get(&tv);
248 
249 	/*
250 	 * 1 if we can find a threaded target that needs this log then post it
251 	 * 2 foreach non-threaded target call it's logger function
252 	 */
253 	for (pos = QB_LOG_TARGET_START; pos <= conf_active_max; pos++) {
254 		t = &conf[pos];
255 		if ((t->state == QB_LOG_STATE_ENABLED)
256 		    && qb_bit_is_set(cs->targets, pos)) {
257 			if (t->threaded) {
258 				if (!found_threaded) {
259 					found_threaded = QB_TRUE;
260 					if (formatted == QB_FALSE) {
261 						cs_format(str, max_line_length, cs, ap);
262 						formatted = QB_TRUE;
263 					}
264 				}
265 
266 			} else if (t->vlogger) {
267 				va_copy(ap_copy, ap);
268 				t->vlogger(t->pos, cs, &tv, ap_copy);
269 				va_end(ap_copy);
270 
271 			} else if (t->logger) {
272 				if (formatted == QB_FALSE) {
273 					cs_format(str, max_line_length, cs, ap);
274 					formatted = QB_TRUE;
275 				}
276 				qb_do_extended(str, t->extended,
277 					       t->logger(t->pos, cs, &tv, str));
278 			}
279 		}
280 	}
281 
282 	if (found_threaded) {
283 		qb_log_thread_log_post(cs, &tv, str);
284 	}
285 	qb_atomic_int_set(&in_logger, QB_FALSE);
286 
287 	if (max_line_length > QB_LOG_MAX_LEN) {
288 		free(str);
289 	}
290 }
291 
292 void
qb_log_real_(struct qb_log_callsite * cs,...)293 qb_log_real_(struct qb_log_callsite *cs, ...)
294 {
295 	va_list ap;
296 
297 	va_start(ap, cs);
298 	qb_log_real_va_(cs, ap);
299 	va_end(ap);
300 }
301 
302 void
qb_log_thread_log_write(struct qb_log_callsite * cs,struct timespec * timestamp,const char * buffer)303 qb_log_thread_log_write(struct qb_log_callsite *cs,
304 			struct timespec *timestamp, const char *buffer)
305 {
306 	struct qb_log_target *t;
307 	enum qb_log_target_slot pos;
308 
309 	for (pos = QB_LOG_TARGET_START; pos <= conf_active_max; pos++) {
310 		t = &conf[pos];
311 		if ((t->state == QB_LOG_STATE_ENABLED) && t->threaded
312 		    && qb_bit_is_set(cs->targets, t->pos)) {
313 			qb_do_extended(buffer, t->extended,
314 				t->logger(t->pos, cs, timestamp, buffer));
315 		}
316 	}
317 }
318 
319 struct qb_log_callsite*
qb_log_callsite_get2(const char * message_id,const char * function,const char * filename,const char * format,uint8_t priority,uint32_t lineno,uint32_t tags)320 qb_log_callsite_get2(const char *message_id,
321 		    const char *function,
322 		    const char *filename,
323 		    const char *format,
324 		    uint8_t priority,
325 		    uint32_t lineno,
326 		    uint32_t tags)
327 {
328 	struct qb_log_target *t;
329 	struct qb_log_filter *flt;
330 	struct qb_log_callsite *cs;
331 	int32_t new_dcs = QB_FALSE;
332 	struct qb_list_head *f_item;
333 	enum qb_log_target_slot pos;
334 
335 	if (!logger_inited) {
336 		return NULL;
337 	}
338 
339 	cs = qb_log_dcs_get(&new_dcs, message_id, function, filename,
340  			    format, priority, lineno, tags);
341 
342 	if (cs == NULL) {
343 		return NULL;
344 	}
345 
346 	if (new_dcs) {
347 		pthread_rwlock_rdlock(&_listlock);
348 		for (pos = QB_LOG_TARGET_START; pos <= conf_active_max; pos++) {
349 			t = &conf[pos];
350 			if (t->state != QB_LOG_STATE_ENABLED) {
351 				continue;
352 			}
353 			qb_list_for_each(f_item, &t->filter_head) {
354 				flt = qb_list_entry(f_item, struct qb_log_filter, list);
355 				_log_filter_apply_to_cs(cs, t->pos, flt->conf, flt->type,
356 							flt->text, flt->regex, flt->high_priority,
357 							flt->low_priority);
358 			}
359 		}
360 		if (tags == 0) {
361 			qb_list_for_each(f_item, &tags_head) {
362 				flt = qb_list_entry(f_item, struct qb_log_filter, list);
363 				_log_filter_apply_to_cs(cs, flt->new_value, flt->conf, flt->type,
364 							flt->text, flt->regex, flt->high_priority,
365 							flt->low_priority);
366 			}
367 		} else {
368 			cs->tags = tags;
369 		}
370 		if (_custom_filter_fn) {
371 			_custom_filter_fn(cs);
372 		}
373 		pthread_rwlock_unlock(&_listlock);
374 	} else {
375 	        if (tags && cs->tags != tags) {
376 		        cs->tags = tags;
377 		}
378 		if (_custom_filter_fn) {
379 			_custom_filter_fn(cs);
380 		}
381 	}
382 	return cs;
383 }
384 
385 struct qb_log_callsite*
qb_log_callsite_get(const char * function,const char * filename,const char * format,uint8_t priority,uint32_t lineno,uint32_t tags)386 qb_log_callsite_get(const char *function,
387 		    const char *filename,
388 		    const char *format,
389 		    uint8_t priority,
390 		    uint32_t lineno,
391 		    uint32_t tags)
392 {
393 	return qb_log_callsite_get2(NULL, function, filename, format,
394 				    priority, lineno, tags);
395 }
396 
397 void
qb_log_from_external_source_va2(const char * message_id,const char * function,const char * filename,const char * format,uint8_t priority,uint32_t lineno,uint32_t tags,va_list ap)398 qb_log_from_external_source_va2(const char *message_id,
399 			       const char *function,
400 			       const char *filename,
401 			       const char *format,
402 			       uint8_t priority,
403 			       uint32_t lineno, uint32_t tags, va_list ap)
404 {
405 	struct qb_log_callsite *cs;
406 
407 	if (!logger_inited) {
408 		return;
409 	}
410 
411 	cs = qb_log_callsite_get2(message_id, function, filename,
412 				 format, priority, lineno, tags);
413 	qb_log_real_va_(cs, ap);
414 }
415 
416 void
qb_log_from_external_source_va(const char * function,const char * filename,const char * format,uint8_t priority,uint32_t lineno,uint32_t tags,va_list ap)417 qb_log_from_external_source_va(const char *function,
418 			       const char *filename,
419 			       const char *format,
420 			       uint8_t priority,
421 			       uint32_t lineno, uint32_t tags, va_list ap)
422 {
423 	qb_log_from_external_source_va2(NULL, function, filename,
424 				   format, priority, lineno, tags, ap);
425 }
426 
427 void
qb_log_from_external_source(const char * function,const char * filename,const char * format,uint8_t priority,uint32_t lineno,uint32_t tags,...)428 qb_log_from_external_source(const char *function,
429 			    const char *filename,
430 			    const char *format,
431 			    uint8_t priority,
432 			    uint32_t lineno, uint32_t tags, ...)
433 {
434 	struct qb_log_callsite *cs;
435 	va_list ap;
436 
437 	if (!logger_inited) {
438 		return;
439 	}
440 
441 	cs = qb_log_callsite_get(function, filename,
442 				 format, priority, lineno, tags);
443 	va_start(ap, tags);
444 	qb_log_real_va_(cs, ap);
445 	va_end(ap);
446 }
447 
448 static void
qb_log_callsites_dump_sect(struct callsite_section * sect)449 qb_log_callsites_dump_sect(struct callsite_section *sect)
450 {
451 	struct qb_log_callsite *cs;
452 	printf(" start %p - stop %p\n", sect->start, sect->stop);
453 	printf("filename    lineno targets         tags\n");
454 	for (cs = sect->start; cs < sect->stop; cs++) {
455 		if (cs->lineno > 0) {
456 #ifndef S_SPLINT_S
457 			printf("%12s %6" PRIu32 " %16" PRIu32 " %16u\n",
458 			       cs->filename, cs->lineno, cs->targets,
459 			       cs->tags);
460 #endif /* S_SPLINT_S */
461 		}
462 	}
463 }
464 
465 
466 int32_t
qb_log_callsites_register(struct qb_log_callsite * _start,struct qb_log_callsite * _stop)467 qb_log_callsites_register(struct qb_log_callsite *_start,
468 			  struct qb_log_callsite *_stop)
469 {
470 	struct callsite_section *sect;
471 	struct qb_log_callsite *cs;
472 	struct qb_log_target *t;
473 	struct qb_log_filter *flt;
474 	enum qb_log_target_slot pos;
475 
476 	if (_start == NULL || _stop == NULL) {
477 		return -EINVAL;
478 	}
479 
480 	pthread_rwlock_rdlock(&_listlock);
481 	qb_list_for_each_entry(sect, &callsite_sections, list) {
482 		if (sect->start == _start || sect->stop == _stop) {
483 			pthread_rwlock_unlock(&_listlock);
484 			return -EEXIST;
485 		}
486 	}
487 	pthread_rwlock_unlock(&_listlock);
488 
489 	sect = calloc(1, sizeof(struct callsite_section));
490 	if (sect == NULL) {
491 		return -ENOMEM;
492 	}
493 	sect->start = _start;
494 	sect->stop = _stop;
495 	qb_list_init(&sect->list);
496 
497 	pthread_rwlock_wrlock(&_listlock);
498 	qb_list_add(&sect->list, &callsite_sections);
499 
500 	/*
501 	 * Now apply the filters on these new callsites
502 	 */
503 	for (pos = QB_LOG_TARGET_START; pos <= conf_active_max; pos++) {
504 		t = &conf[pos];
505 		if (t->state != QB_LOG_STATE_ENABLED) {
506 			continue;
507 		}
508 		qb_list_for_each_entry(flt, &t->filter_head, list) {
509 			_log_filter_apply(sect, t->pos, flt->conf,
510 					  flt->type, flt->text, flt->regex,
511 					  flt->high_priority, flt->low_priority);
512 		}
513 	}
514 	qb_list_for_each_entry(flt, &tags_head, list) {
515 		_log_filter_apply(sect, flt->new_value, flt->conf,
516 				  flt->type, flt->text, flt->regex,
517 				  flt->high_priority, flt->low_priority);
518 	}
519 	pthread_rwlock_unlock(&_listlock);
520 	if (_custom_filter_fn) {
521 		for (cs = sect->start; cs < sect->stop; cs++) {
522 			if (cs->lineno > 0) {
523 				_custom_filter_fn(cs);
524 			}
525 		}
526 	}
527 	/* qb_log_callsites_dump_sect(sect); */
528 
529 	return 0;
530 }
531 
532 void
qb_log_callsites_dump(void)533 qb_log_callsites_dump(void)
534 {
535 	struct callsite_section *sect;
536 	int32_t l;
537 
538 	pthread_rwlock_rdlock(&_listlock);
539 	l = qb_list_length(&callsite_sections);
540 	printf("Callsite Database [%d]\n", l);
541 	printf("---------------------\n");
542 	qb_list_for_each_entry(sect, &callsite_sections, list) {
543 		qb_log_callsites_dump_sect(sect);
544 	}
545 	pthread_rwlock_unlock(&_listlock);
546 }
547 
548 static int32_t
_log_filter_exists(struct qb_list_head * list_head,enum qb_log_filter_type type,const char * text,uint8_t high_priority,uint8_t low_priority,uint32_t new_value)549 _log_filter_exists(struct qb_list_head *list_head,
550 		   enum qb_log_filter_type type,
551 		   const char *text,
552 		   uint8_t high_priority,
553 		   uint8_t low_priority,
554 		   uint32_t new_value)
555 {
556 	struct qb_log_filter *flt;
557 
558 	qb_list_for_each_entry(flt, list_head, list) {
559 		if (flt->type == type &&
560 		    flt->high_priority == high_priority &&
561 		    flt->low_priority == low_priority &&
562 		    flt->new_value == new_value &&
563 		    strcmp(flt->text, text) == 0) {
564 			return QB_TRUE;
565 		}
566 	}
567 	return QB_FALSE;
568 }
569 
570 static int32_t
_log_filter_store(uint32_t t,enum qb_log_filter_conf c,enum qb_log_filter_type type,const char * text,uint8_t high_priority,uint8_t low_priority,struct qb_log_filter ** new)571 _log_filter_store(uint32_t t, enum qb_log_filter_conf c,
572 		  enum qb_log_filter_type type,
573 		  const char *text,
574 		  uint8_t high_priority,
575 		  uint8_t low_priority,
576 		  struct qb_log_filter **new)
577 {
578 	struct qb_log_filter *flt;
579 	struct qb_list_head *iter;
580 	struct qb_list_head *next;
581 	struct qb_list_head *list_head;
582 
583 	switch (c) {
584 	case QB_LOG_FILTER_ADD:
585 	case QB_LOG_FILTER_REMOVE:
586 	case QB_LOG_FILTER_CLEAR_ALL:
587 		list_head = &conf[t].filter_head;
588 		break;
589 
590 	case QB_LOG_TAG_SET:
591 	case QB_LOG_TAG_CLEAR:
592 	case QB_LOG_TAG_CLEAR_ALL:
593 		list_head = &tags_head;
594 		break;
595 	default:
596 		return -ENOSYS;
597 	}
598 
599 	if (c == QB_LOG_FILTER_ADD || c == QB_LOG_TAG_SET) {
600 		if (text == NULL) {
601 			return -EINVAL;
602 		}
603 		if (_log_filter_exists(list_head, type, text,
604 				       high_priority, low_priority, t)) {
605 			return -EEXIST;
606 		}
607 		flt = calloc(1, sizeof(struct qb_log_filter));
608 		if (flt == NULL) {
609 			return -ENOMEM;
610 		}
611 		qb_list_init(&flt->list);
612 		flt->conf = c;
613 		flt->type = type;
614 		flt->text = strdup(text);
615 		if (flt->text == NULL) {
616 			_log_free_filter(flt);
617 			return -ENOMEM;
618 		}
619 
620 		if (type == QB_LOG_FILTER_FUNCTION_REGEX ||
621 			type == QB_LOG_FILTER_FILE_REGEX ||
622 			type == QB_LOG_FILTER_FORMAT_REGEX) {
623 			int res;
624 
625 			flt->regex = calloc(1, sizeof(regex_t));
626 			if (flt->regex == NULL) {
627 				_log_free_filter(flt);
628 				return -ENOMEM;
629 			}
630 			res = regcomp(flt->regex, flt->text, 0);
631 			if (res) {
632 				_log_free_filter(flt);
633 				return -EINVAL;
634 			}
635 		}
636 		flt->high_priority = high_priority;
637 		flt->low_priority = low_priority;
638 		flt->new_value = t;
639 		qb_list_add_tail(&flt->list, list_head);
640 		if (new) {
641 			*new = flt;
642 		}
643 	} else if (c == QB_LOG_FILTER_REMOVE || c == QB_LOG_TAG_CLEAR) {
644 		qb_list_for_each_safe(iter, next, list_head) {
645 			flt = qb_list_entry(iter, struct qb_log_filter, list);
646 			if (flt->type == type &&
647 			    flt->low_priority <= low_priority &&
648 			    flt->high_priority >= high_priority &&
649 			    (strcmp(flt->text, text) == 0 ||
650 			     strcmp("*", text) == 0)) {
651 				qb_list_del(iter);
652 				_log_free_filter(flt);
653 				return 0;
654 			}
655 		}
656 
657 	} else if (c == QB_LOG_FILTER_CLEAR_ALL || c == QB_LOG_TAG_CLEAR_ALL) {
658 		qb_list_for_each_safe(iter, next, list_head) {
659 			flt = qb_list_entry(iter, struct qb_log_filter, list);
660 			qb_list_del(iter);
661 			_log_free_filter(flt);
662 		}
663 	}
664 	return 0;
665 }
666 
667 static void
_log_filter_apply(struct callsite_section * sect,uint32_t t,enum qb_log_filter_conf c,enum qb_log_filter_type type,const char * text,regex_t * regex,uint8_t high_priority,uint8_t low_priority)668 _log_filter_apply(struct callsite_section *sect,
669 		  uint32_t t, enum qb_log_filter_conf c,
670 		  enum qb_log_filter_type type,
671 		  const char *text,
672 		  regex_t *regex,
673 		  uint8_t high_priority, uint8_t low_priority)
674 {
675 	struct qb_log_callsite *cs;
676 
677 	for (cs = sect->start; cs < sect->stop; cs++) {
678 		if (cs->lineno > 0) {
679 			_log_filter_apply_to_cs(cs, t, c, type, text, regex,
680 					    high_priority, low_priority);
681 		}
682 	}
683 }
684 
685 /* #define _QB_FILTER_DEBUGGING_ 1 */
686 static void
_log_filter_apply_to_cs(struct qb_log_callsite * cs,uint32_t t,enum qb_log_filter_conf c,enum qb_log_filter_type type,const char * text,regex_t * regex,uint8_t high_priority,uint8_t low_priority)687 _log_filter_apply_to_cs(struct qb_log_callsite *cs,
688 			uint32_t t, enum qb_log_filter_conf c,
689 			enum qb_log_filter_type type,
690 			const char *text,
691 			regex_t *regex,
692 			uint8_t high_priority, uint8_t low_priority)
693 {
694 
695 	if (c == QB_LOG_FILTER_CLEAR_ALL) {
696 		qb_bit_clear(cs->targets, t);
697 		return;
698 	} else if (c == QB_LOG_TAG_CLEAR_ALL) {
699 		cs->tags = 0;
700 		return;
701 	}
702 
703 	if (_cs_matches_filter_(cs, type, text, regex, high_priority, low_priority)) {
704 #ifdef _QB_FILTER_DEBUGGING_
705 		uint32_t old_targets = cs->targets;
706 		uint32_t old_tags = cs->tags;
707 #endif /* _QB_FILTER_DEBUGGING_ */
708 		if (c == QB_LOG_FILTER_ADD) {
709 			qb_bit_set(cs->targets, t);
710 		} else if (c == QB_LOG_FILTER_REMOVE) {
711 			qb_bit_clear(cs->targets, t);
712 		} else if (c == QB_LOG_TAG_SET) {
713 			cs->tags = t;
714 		} else if (c == QB_LOG_TAG_CLEAR) {
715 			cs->tags = 0;
716 		}
717 #ifdef _QB_FILTER_DEBUGGING_
718 		if (old_targets != cs->targets) {
719 			printf("targets: %s:%u value(%d) %d -> %d\n",
720 			       cs->filename, cs->lineno, t,
721 			       old_targets, cs->targets);
722 		}
723 		if (old_tags != cs->tags) {
724 			printf("tags: %s:%u value(%d) %d -> %d\n",
725 			       cs->filename, cs->lineno, t, old_tags, cs->tags);
726 		}
727 #endif /* _QB_FILTER_DEBUGGING_ */
728 	}
729 }
730 
731 int32_t
qb_log_filter_ctl2(int32_t t,enum qb_log_filter_conf c,enum qb_log_filter_type type,const char * text,uint8_t high_priority,uint8_t low_priority)732 qb_log_filter_ctl2(int32_t t, enum qb_log_filter_conf c,
733 		   enum qb_log_filter_type type, const char * text,
734 		   uint8_t high_priority, uint8_t low_priority)
735 {
736 	struct qb_log_filter *new_flt = NULL;
737 	regex_t *regex = NULL;
738 	struct callsite_section *sect;
739 	int32_t rc;
740 
741 	if (!logger_inited) {
742 		return -EINVAL;
743 	}
744 
745 	if (c == QB_LOG_FILTER_ADD ||
746 	    c == QB_LOG_FILTER_CLEAR_ALL ||
747 	    c == QB_LOG_FILTER_REMOVE) {
748 		if (t < 0 || t >= QB_LOG_TARGET_MAX ||
749 		    conf[t].state == QB_LOG_STATE_UNUSED) {
750 			return -EBADF;
751 		}
752 	}
753 
754 	if (text == NULL ||
755 	    low_priority < high_priority ||
756 	    type > QB_LOG_FILTER_FORMAT_REGEX ||
757 	    c > QB_LOG_TAG_CLEAR_ALL) {
758 		return -EINVAL;
759 	}
760 	pthread_rwlock_rdlock(&_listlock);
761 	rc = _log_filter_store(t, c, type, text, high_priority, low_priority, &new_flt);
762 	if (rc < 0) {
763 		pthread_rwlock_unlock(&_listlock);
764 		return rc;
765 	}
766 
767 	if (new_flt && new_flt->regex) {
768 		regex = new_flt->regex;
769 	}
770 	qb_list_for_each_entry(sect, &callsite_sections, list) {
771 		_log_filter_apply(sect, t, c, type, text, regex, high_priority, low_priority);
772 	}
773 	pthread_rwlock_unlock(&_listlock);
774 	return 0;
775 }
776 
777 int32_t
qb_log_filter_fn_set(qb_log_filter_fn fn)778 qb_log_filter_fn_set(qb_log_filter_fn fn)
779 {
780 	struct callsite_section *sect;
781 	struct qb_log_callsite *cs;
782 
783 	if (!logger_inited) {
784 		return -EINVAL;
785 	}
786 	_custom_filter_fn = fn;
787 	if (_custom_filter_fn == NULL) {
788 		return 0;
789 	}
790 
791 	qb_list_for_each_entry(sect, &callsite_sections, list) {
792 		for (cs = sect->start; cs < sect->stop; cs++) {
793 			if (cs->lineno > 0) {
794 				_custom_filter_fn(cs);
795 			}
796 		}
797 	}
798 	return 0;
799 }
800 
801 int32_t
qb_log_filter_ctl(int32_t t,enum qb_log_filter_conf c,enum qb_log_filter_type type,const char * text,uint8_t priority)802 qb_log_filter_ctl(int32_t t, enum qb_log_filter_conf c,
803 		  enum qb_log_filter_type type,
804 		  const char *text, uint8_t priority)
805 {
806 	return qb_log_filter_ctl2(t, c, type, text, LOG_EMERG, priority);
807 }
808 
809 static void
_log_target_state_set(struct qb_log_target * t,enum qb_log_target_state s)810 _log_target_state_set(struct qb_log_target *t, enum qb_log_target_state s)
811 {
812 	enum qb_log_target_slot i;
813 	int32_t a_set = QB_FALSE;
814 	int32_t u_set = QB_FALSE;
815 
816 	t->state = s;
817 
818 	for (i = QB_LOG_TARGET_MAX; i > QB_LOG_TARGET_START; i--) {
819 		if (!a_set && conf[i-1].state == QB_LOG_STATE_ENABLED) {
820 			a_set = QB_TRUE;
821 			conf_active_max = i-1;
822 		}
823 		if (!u_set && conf[i-1].state != QB_LOG_STATE_UNUSED) {
824 			u_set = QB_TRUE;
825 		}
826 	}
827 }
828 
829 void
qb_log_init(const char * name,int32_t facility,uint8_t priority)830 qb_log_init(const char *name, int32_t facility, uint8_t priority)
831 {
832 	int32_t l;
833 	enum qb_log_target_slot i;
834 
835 	l = pthread_rwlock_init(&_listlock, NULL);
836 	assert(l == 0);
837 	qb_log_format_init();
838 
839 	for (i = QB_LOG_TARGET_START; i < QB_LOG_TARGET_MAX; i++) {
840 		conf[i].pos = i;
841 		conf[i].debug = QB_FALSE;
842 		conf[i].file_sync = QB_FALSE;
843 		conf[i].extended = QB_TRUE;
844 		conf[i].state = QB_LOG_STATE_UNUSED;
845 		(void)strlcpy(conf[i].name, name, PATH_MAX);
846 		conf[i].facility = facility;
847 		conf[i].max_line_length = QB_LOG_MAX_LEN;
848 		qb_list_init(&conf[i].filter_head);
849 	}
850 
851 	qb_log_dcs_init();
852 
853 	for (i = QB_LOG_TARGET_STATIC_START; i < QB_LOG_TARGET_STATIC_MAX; i++)
854 		conf[i].state = QB_LOG_STATE_DISABLED;
855 
856 	logger_inited = QB_TRUE;
857 	(void)qb_log_syslog_open(&conf[QB_LOG_SYSLOG]);
858 	_log_target_state_set(&conf[QB_LOG_SYSLOG], QB_LOG_STATE_ENABLED);
859 	(void)qb_log_filter_ctl(QB_LOG_SYSLOG, QB_LOG_FILTER_ADD,
860 				QB_LOG_FILTER_FILE, "*", priority);
861 }
862 
863 void
qb_log_fini(void)864 qb_log_fini(void)
865 {
866 	struct qb_log_target *t;
867 	struct qb_log_filter *flt;
868 	struct callsite_section *s;
869 	struct qb_list_head *iter;
870 	struct qb_list_head *iter2;
871 	struct qb_list_head *next;
872 	struct qb_list_head *next2;
873 	enum qb_log_target_slot pos;
874 
875 	if (!logger_inited) {
876 		return;
877 	}
878 	logger_inited = QB_FALSE;
879 	qb_log_thread_stop();
880 	pthread_rwlock_destroy(&_listlock);
881 
882 	for (pos = QB_LOG_TARGET_START; pos <= conf_active_max; pos++) {
883 		t = &conf[pos];
884 		_log_target_disable(t);
885 		qb_list_for_each_safe(iter2, next2, &t->filter_head) {
886 			flt = qb_list_entry(iter2, struct qb_log_filter, list);
887 			qb_list_del(iter2);
888 			_log_free_filter(flt);
889 		}
890 	}
891 	qb_log_format_fini();
892 	qb_log_dcs_fini();
893 	qb_list_for_each_safe(iter, next, &callsite_sections) {
894 		s = qb_list_entry(iter, struct callsite_section, list);
895 		qb_list_del(iter);
896 		free(s);
897 	}
898 	qb_list_for_each_safe(iter, next, &tags_head) {
899 		flt = qb_list_entry(iter, struct qb_log_filter, list);
900 		qb_list_del(iter);
901 		_log_free_filter(flt);
902 	}
903 }
904 
905 struct qb_log_target *
qb_log_target_alloc(void)906 qb_log_target_alloc(void)
907 {
908 	enum qb_log_target_slot i;
909 	for (i = QB_LOG_TARGET_START; i < QB_LOG_TARGET_MAX; i++) {
910 		if (conf[i].state == QB_LOG_STATE_UNUSED) {
911 			_log_target_state_set(&conf[i], QB_LOG_STATE_DISABLED);
912 			return &conf[i];
913 		}
914 	}
915 	errno = EMFILE;
916 	return NULL;
917 }
918 
919 void
qb_log_target_free(struct qb_log_target * t)920 qb_log_target_free(struct qb_log_target *t)
921 {
922 	(void)qb_log_filter_ctl(t->pos, QB_LOG_FILTER_CLEAR_ALL,
923 				QB_LOG_FILTER_FILE, NULL, 0);
924 	t->debug = QB_FALSE;
925 	t->filename[0] = '\0';
926 	qb_log_format_set(t->pos, NULL);
927 	_log_target_state_set(t, QB_LOG_STATE_UNUSED);
928 }
929 
930 struct qb_log_target *
qb_log_target_get(int32_t pos)931 qb_log_target_get(int32_t pos)
932 {
933 	return &conf[pos];
934 }
935 
936 void *
qb_log_target_user_data_get(int32_t t)937 qb_log_target_user_data_get(int32_t t)
938 {
939 	if (t < 0 || t >= QB_LOG_TARGET_MAX ||
940 	    conf[t].state == QB_LOG_STATE_UNUSED) {
941 		errno = EBADF;
942 		return NULL;
943 	}
944 
945 	return conf[t].instance;
946 }
947 
948 int32_t
qb_log_target_user_data_set(int32_t t,void * user_data)949 qb_log_target_user_data_set(int32_t t, void *user_data)
950 {
951 	if (!logger_inited) {
952 		return -EINVAL;
953 	}
954 	if (t < 0 || t >= QB_LOG_TARGET_MAX ||
955 	    conf[t].state == QB_LOG_STATE_UNUSED) {
956 		return -EBADF;
957 	}
958 
959 	conf[t].instance = user_data;
960 	return 0;
961 }
962 
963 int32_t
qb_log_custom_open(qb_log_logger_fn log_fn,qb_log_close_fn close_fn,qb_log_reload_fn reload_fn,void * user_data)964 qb_log_custom_open(qb_log_logger_fn log_fn,
965 		   qb_log_close_fn close_fn,
966 		   qb_log_reload_fn reload_fn, void *user_data)
967 {
968 	struct qb_log_target *t;
969 
970 	t = qb_log_target_alloc();
971 	if (t == NULL) {
972 		return -errno;
973 	}
974 
975 	t->instance = user_data;
976 #ifndef S_SPLINT_S
977 	snprintf(t->filename, PATH_MAX, "custom-%" PRIu32, t->pos);
978 #endif /* S_SPLINT_S */
979 
980 	t->logger = log_fn;
981 	t->vlogger = NULL;
982 	t->reload = reload_fn;
983 	t->close = close_fn;
984 
985 	return t->pos;
986 }
987 
988 void
qb_log_custom_close(int32_t t)989 qb_log_custom_close(int32_t t)
990 {
991 	struct qb_log_target *target;
992 
993 	if (!logger_inited) {
994 		return;
995 	}
996 	if (t < 0 || t >= QB_LOG_TARGET_MAX ||
997 	    conf[t].state == QB_LOG_STATE_UNUSED) {
998 		return;
999 	}
1000 
1001 	target = qb_log_target_get(t);
1002 
1003 	if (target->close) {
1004 		qb_atomic_int_set(&in_logger, QB_TRUE);
1005 		target->close(t);
1006 		qb_atomic_int_set(&in_logger, QB_FALSE);
1007 	}
1008 	qb_log_target_free(target);
1009 }
1010 
1011 static int32_t
_log_target_enable(struct qb_log_target * t)1012 _log_target_enable(struct qb_log_target *t)
1013 {
1014 	int32_t rc = 0;
1015 
1016 	if (t->state == QB_LOG_STATE_ENABLED) {
1017 		return 0;
1018 	}
1019 	if (t->pos == QB_LOG_STDERR ||
1020 	    t->pos == QB_LOG_STDOUT) {
1021 		rc = qb_log_stderr_open(t);
1022 	} else if (t->pos == QB_LOG_SYSLOG) {
1023 		rc = qb_log_syslog_open(t);
1024 	} else if (t->pos == QB_LOG_BLACKBOX) {
1025 		rc = qb_log_blackbox_open(t);
1026 	}
1027 	if (rc == 0) {
1028 		_log_target_state_set(t, QB_LOG_STATE_ENABLED);
1029 	}
1030 	return rc;
1031 }
1032 
1033 static void
_log_target_disable(struct qb_log_target * t)1034 _log_target_disable(struct qb_log_target *t)
1035 {
1036 	if (t->state != QB_LOG_STATE_ENABLED) {
1037 		return;
1038 	}
1039 	_log_target_state_set(t, QB_LOG_STATE_DISABLED);
1040 	if (t->close) {
1041 		qb_atomic_int_set(&in_logger, QB_TRUE);
1042 		t->close(t->pos);
1043 		qb_atomic_int_set(&in_logger, QB_FALSE);
1044 	}
1045 }
1046 
1047 int32_t
qb_log_ctl2(int32_t t,enum qb_log_conf c,qb_log_ctl2_arg_t arg_not4directuse)1048 qb_log_ctl2(int32_t t, enum qb_log_conf c, qb_log_ctl2_arg_t arg_not4directuse)
1049 {
1050 	int32_t rc = 0;
1051 	int32_t need_reload = QB_FALSE;
1052 
1053 	/* extract the constants and do not touch the origin anymore */
1054 	const int32_t arg_i32 = arg_not4directuse.i32;
1055 	const char * const arg_s = arg_not4directuse.s;
1056 
1057 	if (!logger_inited) {
1058 		return -EINVAL;
1059 	}
1060 	if (t < 0 || t >= QB_LOG_TARGET_MAX ||
1061 	    conf[t].state == QB_LOG_STATE_UNUSED) {
1062 		return -EBADF;
1063 	}
1064 
1065 	/* Starting/stopping the thread has its own locking that can interfere with this */
1066 	if (c != QB_LOG_CONF_THREADED) {
1067 		qb_log_thread_pause(&conf[t]);
1068 	}
1069 
1070 	switch (c) {
1071 	case QB_LOG_CONF_ENABLED:
1072 		if (arg_i32) {
1073 			rc = _log_target_enable(&conf[t]);
1074 		} else {
1075 			_log_target_disable(&conf[t]);
1076 		}
1077 		break;
1078 	case QB_LOG_CONF_STATE_GET:
1079 		rc = conf[t].state;
1080 		break;
1081 	case QB_LOG_CONF_FACILITY:
1082 		conf[t].facility = arg_i32;
1083 		if (t == QB_LOG_SYSLOG) {
1084 			need_reload = QB_TRUE;
1085 		}
1086 		break;
1087 	case QB_LOG_CONF_IDENT:
1088 		(void)strlcpy(conf[t].name, arg_s, PATH_MAX);
1089 		if (t == QB_LOG_SYSLOG) {
1090 			need_reload = QB_TRUE;
1091 		}
1092 		break;
1093 	case QB_LOG_CONF_FILE_SYNC:
1094 		conf[t].file_sync = arg_i32;
1095 		break;
1096 	case QB_LOG_CONF_PRIORITY_BUMP:
1097 		conf[t].priority_bump = arg_i32;
1098 		break;
1099 	case QB_LOG_CONF_SIZE:
1100 		if (t == QB_LOG_BLACKBOX) {
1101 			if (arg_i32 <= 0) {
1102 				rc = -EINVAL;
1103 				goto unlock_fini;
1104 			}
1105 			conf[t].size = arg_i32;
1106 			need_reload = QB_TRUE;
1107 		} else {
1108 			rc = -ENOSYS;
1109 		}
1110 		break;
1111 	case QB_LOG_CONF_THREADED:
1112 		conf[t].threaded = arg_i32;
1113 		break;
1114 	case QB_LOG_CONF_EXTENDED:
1115 		conf[t].extended = arg_i32;
1116 		break;
1117 	case QB_LOG_CONF_MAX_LINE_LEN:
1118 		/* arbitrary limit, but you'd be insane to go further */
1119 		if (arg_i32 > QB_LOG_ABSOLUTE_MAX_LEN) {
1120 			rc = -EINVAL;
1121 		} else {
1122 			conf[t].max_line_length = arg_i32;
1123 		}
1124 		break;
1125 	case QB_LOG_CONF_ELLIPSIS:
1126 		conf[t].ellipsis = arg_i32;
1127 		break;
1128 	case QB_LOG_CONF_USE_JOURNAL:
1129 #ifdef USE_JOURNAL
1130 		if (t == QB_LOG_SYSLOG) {
1131 			conf[t].use_journal = arg_i32;
1132 			need_reload = QB_TRUE;
1133 		} else {
1134 			rc = -EINVAL;
1135 		}
1136 #else
1137 		rc = -EOPNOTSUPP;
1138 #endif
1139 		break;
1140 
1141 	default:
1142 		rc = -EINVAL;
1143 	}
1144 	if (rc == 0 && need_reload && conf[t].reload) {
1145 		qb_atomic_int_set(&in_logger, QB_TRUE);
1146 		conf[t].reload(t);
1147 		qb_atomic_int_set(&in_logger, QB_FALSE);
1148 	}
1149 
1150 unlock_fini:
1151 	if (c != QB_LOG_CONF_THREADED) {
1152 		qb_log_thread_resume(&conf[t]);
1153 	}
1154 	return rc;
1155 }
1156 
1157 int32_t
qb_log_ctl(int32_t t,enum qb_log_conf c,int32_t arg)1158 qb_log_ctl(int32_t t, enum qb_log_conf c, int32_t arg)
1159 {
1160 	return qb_log_ctl2(t, c, QB_LOG_CTL2_I32(arg));
1161 }
1162