<commit_before>/*
 * Copyright (C) 2011 Red Hat, Inc.
 *
 * All rights reserved.
 *
 * Author: <NAME> <<EMAIL>>
 *
 * libqb is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * libqb is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with libqb.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "os_base.h"
#include <ctype.h>
#ifdef HAVE_LINK_H
#include <link.h>
#endif /* HAVE_LINK_H */
#include <stdarg.h>
#include <pthread.h>
#ifdef HAVE_DLFCN_H
#include <dlfcn.h>
#endif /* HAVE_DLFCN_H */
#include <stdarg.h>
#include <string.h>

#include <qb/qbdefs.h>
#include <qb/qblist.h>
#include <qb/qblog.h>
#include <qb/qbutil.h>
#include <qb/qbarray.h>
#include "log_int.h"
#include "util_int.h"
#include <regex.h>

static struct qb_log_target conf[QB_LOG_TARGET_MAX];
static uint32_t conf_active_max = 0;
static int32_t in_logger = QB_FALSE;
static int32_t logger_inited = QB_FALSE;
static pthread_rwlock_t _listlock;
static qb_log_filter_fn _custom_filter_fn = NULL;

static QB_LIST_DECLARE(dlnames);
static QB_LIST_DECLARE(tags_head);
static QB_LIST_DECLARE(callsite_sections);

struct dlname {
	char *dln_name;
	struct qb_list_head list;
};

struct callsite_section {
	struct qb_log_callsite *start;
	struct qb_log_callsite *stop;
	struct qb_list_head list;
};

static int32_t _log_target_enable(struct qb_log_target *t);
static void _log_target_disable(struct qb_log_target *t);
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);
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);

/* deprecated method of getting internal log messages */
static qb_util_log_fn_t old_internal_log_fn = NULL;
void
qb_util_set_log_function(qb_util_log_fn_t fn)
{
	old_internal_log_fn = fn;
}

static void
_log_free_filter(struct qb_log_filter *flt)
{
	if (flt->regex) {
		regfree(flt->regex);
	}
	free(flt->regex);
	free(flt->text);
	free(flt);
}

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)
{
	int32_t match = QB_FALSE;
	const char *offset = NULL;
	const char *next = NULL;

	if (cs->priority > low_priority ||
	    cs->priority < high_priority) {
		return QB_FALSE;
	}
	if (strcmp(text, "*") == 0) {
		return QB_TRUE;
	}

	switch (type) {
	case QB_LOG_FILTER_FILE:
	case QB_LOG_FILTER_FUNCTION:
		next = text;
		do {
			char token[500];
			offset = next;
			next = strchrnul(offset, ',');
			snprintf(token, 499, "%.*s", (int)(next - offset), offset);

			if (type == QB_LOG_FILTER_FILE) {
				match = (strcmp(cs->filename, token) == 0) ? 1 : 0;
			} else {
				match = (strcmp(cs->function, token) == 0) ? 1 : 0;
			}
			if (!match && next[0] != 0) {
				next++;
			}

		} while (match == QB_FALSE && next != NULL && next[0] != 0);
		break;
	case QB_LOG_FILTER_FILE_REGEX:
		next = next ? next : cs->filename;
	case QB_LOG_FILTER_FUNCTION_REGEX:
		next = next ? next : cs->function;
	case QB_LOG_FILTER_FORMAT_REGEX:
		next = next ? next : cs->format;

		if (regex == NULL) {
			return QB_FALSE;
		}
		match = regexec(regex, next, 0, NULL, 0);
		if (match == 0) {
			match = QB_TRUE;
		} else {
			match = QB_FALSE;
		}
		break;
	case QB_LOG_FILTER_FORMAT:
		if (strstr(cs->format, text)) {
			match = QB_TRUE;
		}
		break;
	}

	return match;
}

/**
 * @internal
 * @brief Format a log message into a string buffer
 *
 * @param[out] str  Destination buffer to contain formatted string
 * @param[in]  cs   Callsite containing format to use
 * @param[in]  ap   Variable arguments for format
 */
#ifdef HAVE_GCC_SUGGEST_ATTRIBUTE_FORMAT
/* suppress suggestion that we currently can do nothing better about
   as the format specification is hidden in cs argument */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsuggest-attribute=format"
#endif
static inline void
cs_format(char *str, struct qb_log_callsite *cs, va_list ap)
{
	va_list ap_copy;
	int len;

	va_copy(ap_copy, ap);
	len = vsnprintf(str, QB_LOG_MAX_LEN, cs->format, ap_copy);
	va_end(ap_copy);
	if (len > QB_LOG_MAX_LEN) {
		len = QB_LOG_MAX_LEN;
	}
	if (str[len - 1] == '\n') {
		str[len - 1] = '\0';
	}
}
#ifdef HAVE_GCC_SUGGEST_ATTRIBUTE_FORMAT
#pragma GCC diagnostic pop
#endif

void
qb_log_real_va_(struct qb_log_callsite *cs, va_list ap)
{
	int32_t found_threaded = QB_FALSE;
	struct qb_log_target *t;
	struct timespec tv;
	int32_t pos;
	int32_t formatted = QB_FALSE;
	char buf[QB_LOG_MAX_LEN];
	char *str = buf;
	va_list ap_copy;

	if (in_logger || cs == NULL) {
		return;
	}
	in_logger = QB_TRUE;

	if (old_internal_log_fn &&
	    qb_bit_is_set(cs->tags, QB_LOG_TAG_LIBQB_MSG_BIT)) {
		if (formatted == QB_FALSE) {
			cs_format(str, cs, ap);
			formatted = QB_TRUE;
		}
		qb_do_extended(str, QB_TRUE,
			old_internal_log_fn(cs->filename, cs->lineno,
					    cs->priority, str));
	}

	qb_util_timespec_from_epoch_get(&tv);

	/*
	 * 1 if we can find a threaded target that needs this log then post it
	 * 2 foreach non-threaded target call it's logger function
	 */
	for (pos = 0; pos <= conf_active_max; pos++) {
		t = &conf[pos];
		if ((t->state == QB_LOG_STATE_ENABLED)
		    && qb_bit_is_set(cs->targets, pos)) {
			if (t->threaded) {
				if (!found_threaded) {
					found_threaded = QB_TRUE;
					if (formatted == QB_FALSE) {
						cs_format(str, cs, ap);
						formatted = QB_TRUE;
					}
				}

			} else if (t->vlogger) {
				va_copy(ap_copy, ap);
				t->vlogger(t->pos, cs, tv.tv_sec, ap_copy);
				va_end(ap_copy);

			} else if (t->logger) {
				if (formatted == QB_FALSE) {
					cs_format(str, cs, ap);
					formatted = QB_TRUE;
				}
				qb_do_extended(str, t->extended,
					       t->logger(t->pos, cs, tv.tv_sec, str));
			}
		}
	}

	if (found_threaded) {
		qb_log_thread_log_post(cs, tv.tv_sec, str);
	}
	in_logger = QB_FALSE;
}

void
qb_log_real_(struct qb_log_callsite *cs, ...)
{
	va_list ap;

	va_start(ap, cs);
	qb_log_real_va_(cs, ap);
	va_end(ap);
}

void
qb_log_thread_log_write(struct qb_log_callsite *cs,
			time_t timestamp, const char *buffer)
{
	struct qb_log_target *t;
	int32_t pos;

	for (pos = 0; pos <= conf_active_max; pos++) {
		t = &conf[pos];
		if ((t->state == QB_LOG_STATE_ENABLED) && t->threaded
		    && qb_bit_is_set(cs->targets, t->pos)) {
			qb_do_extended(buffer, t->extended,
				t->logger(t->pos, cs, timestamp, buffer));
		}
	}
}

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)
{
	struct qb_log_target *t;
	struct qb_log_filter *flt;
	struct qb_log_callsite *cs;
	int32_t new_dcs = QB_FALSE;
	struct qb_list_head *f_item;
	int32_t pos;

	if (!logger_inited) {
		return NULL;
	}

	cs = qb_log_dcs_get(&new_dcs, function, filename,
			    format, priority, lineno, tags);
	if (cs == NULL) {
		return NULL;
	}

	if (new_dcs) {
		pthread_rwlock_rdlock(&_listlock);
		for (pos = 0; pos <= conf_active_max; pos++) {
			t = &conf[pos];
			if (t->state != QB_LOG_STATE_ENABLED) {
				continue;
			}
			qb_list_for_each(f_item, &t->filter_head) {
				flt = qb_list_entry(f_item, struct qb_log_filter, list);
				_log_filter_apply_to_cs(cs, t->pos, flt->conf, flt->type,
							flt->text, flt->regex, flt->high_priority,
							flt->low_priority);
			}
		}
		if (tags == 0) {
			qb_list_for_each(f_item, &tags_head) {
				flt = qb_list_entry(f_item, struct qb_log_filter, list);
				_log_filter_apply_to_cs(cs, flt->new_value, flt->conf, flt->type,
							flt->text, flt->regex, flt->high_priority,
							flt->low_priority);
			}
		} else {
			cs->tags = tags;
		}
		if (_custom_filter_fn) {
			_custom_filter_fn(cs);
		}
		pthread_rwlock_unlock(&_listlock);
	} else if (cs->tags != tags) {
		cs->tags = tags;
		if (_custom_filter_fn) {
			_custom_filter_fn(cs);
		}
	}
	return cs;
}

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)
{
	struct qb_log_callsite *cs;

	if (!logger_inited) {
		return;
	}

	cs = qb_log_callsite_get(function, filename,
				 format, priority, lineno, tags);
	qb_log_real_va_(cs, ap);
}

void
qb_log_from_external_source(const char *function,
			    const char *filename,
			    const char *format,
			    uint8_t priority,
			    uint32_t lineno, uint32_t tags, ...)
{
	struct qb_log_callsite *cs;
	va_list ap;

	if (!logger_inited) {
		return;
	}

	cs = qb_log_callsite_get(function, filename,
				 format, priority, lineno, tags);
	va_start(ap, tags);
	qb_log_real_va_(cs, ap);
	va_end(ap);
}

int32_t
qb_log_callsites_register(struct qb_log_callsite *_start,
			  struct qb_log_callsite *_stop)
{
	struct callsite_section *sect;
	struct qb_log_callsite *cs;
	struct qb_log_target *t;
	struct qb_log_filter *flt;
	int32_t pos;

	if (_start == NULL || _stop == NULL) {
		return -EINVAL;
	}

	pthread_rwlock_rdlock(&_listlock);
	qb_list_for_each_entry(sect, &callsite_sections, list) {
		if (sect->start == _start || sect->stop == _stop) {
			pthread_rwlock_unlock(&_listlock);
			return -EEXIST;
		}
	}
	pthread_rwlock_unlock(&_listlock);

	sect = calloc(1, sizeof(struct callsite_section));
	if (sect == NULL) {
		return -ENOMEM;
	}
	sect->start = _start;
	sect->stop = _stop;
	qb_list_init(&sect->list);

	pthread_rwlock_wrlock(&_listlock);
	qb_list_add(&sect->list, &callsite_sections);

	/*
	 * Now apply the filters on these new callsites
	 */
	for (pos = 0; pos <= conf_active_max; pos++) {
		t = &conf[pos];
		if (t->state != QB_LOG_STATE_ENABLED) {
			continue;
		}
		qb_list_for_each_entry(flt, &t->filter_head, list) {
			_log_filter_apply(sect, t->pos, flt->conf,
					  flt->type, flt->text, flt->regex,
					  flt->high_priority, flt->low_priority);
		}
	}
	qb_list_for_each_entry(flt, &tags_head, list) {
		_log_filter_apply(sect, flt->new_value, flt->conf,
				  flt->type, flt->text, flt->regex,
				  flt->high_priority, flt->low_priority);
	}
	pthread_rwlock_unlock(&_listlock);
	if (_custom_filter_fn) {
		for (cs = sect->start; cs < sect->stop; cs++) {
			if (cs->lineno > 0) {
				_custom_filter_fn(cs);
			}
		}
	}
	/* qb_log_callsites_dump_sect(sect); */

	return 0;
}

static void
qb_log_callsites_dump_sect(struct callsite_section *sect)
{
	struct qb_log_callsite *cs;

	printf(" start %p - stop %p\n", sect->start, sect->stop);
	printf("filename    lineno targets         tags\n");
	for (cs = sect->start; cs < sect->stop; cs++) {
		if (cs->lineno > 0) {
			printf("%12s %6d %16d %16d\n", cs->filename, cs->lineno,
			       cs->targets, cs->tags);
		}
	}
}

void
qb_log_callsites_dump(void)
{
	struct callsite_section *sect;
	int32_t l;

	pthread_rwlock_rdlock(&_listlock);
	l = qb_list_length(&callsite_sections);
	printf("Callsite Database [%d]\n", l);
	printf("---------------------\n");
	qb_list_for_each_entry(sect, &callsite_sections, list) {
		qb_log_callsites_dump_sect(sect);
	}
	pthread_rwlock_unlock(&_listlock);
}

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)
{
	struct qb_log_filter *flt;

	qb_list_for_each_entry(flt, list_head, list) {
		if (flt->type == type &&
		    flt->high_priority == high_priority &&
		    flt->low_priority == low_priority &&
		    flt->new_value == new_value &&
		    strcmp(flt->text, text) == 0) {
			return QB_TRUE;
		}
	}
	return QB_FALSE;
}

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)
{
	struct qb_log_filter *flt;
	struct qb_list_head *iter;
	struct qb_list_head *next;
	struct qb_list_head *list_head;

	switch (c) {
	case QB_LOG_FILTER_ADD:
	case QB_LOG_FILTER_REMOVE:
	case QB_LOG_FILTER_CLEAR_ALL:
		list_head = &conf[t].filter_head;
		break;

	case QB_LOG_TAG_SET:
	case QB_LOG_TAG_CLEAR:
	case QB_LOG_TAG_CLEAR_ALL:
		list_head = &tags_head;
		break;
	default:
		return -ENOSYS;
	}

	if (c == QB_LOG_FILTER_ADD || c == QB_LOG_TAG_SET) {
		if (text == NULL) {
			return -EINVAL;
		}
		if (_log_filter_exists(list_head, type, text,
				       high_priority, low_priority, t)) {
			return -EEXIST;
		}
		flt = calloc(1, sizeof(struct qb_log_filter));
		if (flt == NULL) {
			return -ENOMEM;
		}
		qb_list_init(&flt->list);
		flt->conf = c;
		flt->type = type;
		flt->text = strdup(text);
		if (flt->text == NULL) {
			_log_free_filter(flt);
			return -ENOMEM;
		}

		if (type == QB_LOG_FILTER_FUNCTION_REGEX ||
			type == QB_LOG_FILTER_FILE_REGEX ||
			type == QB_LOG_FILTER_FORMAT_REGEX) {
			int res;

			flt->regex = calloc(1, sizeof(regex_t));
			if (flt->regex == NULL) {
				_log_free_filter(flt);
				return -ENOMEM;
			}
			res = regcomp(flt->regex, flt->text, 0);
			if (res) {
				_log_free_filter(flt);
				return -EINVAL;
			}
		}
		flt->high_priority = high_priority;
		flt->low_priority = low_priority;
		flt->new_value = t;
		qb_list_add_tail(&flt->list, list_head);
		if (new) {
			*new = flt;
		}
	} else if (c == QB_LOG_FILTER_REMOVE || c == QB_LOG_TAG_CLEAR) {
		qb_list_for_each_safe(iter, next, list_head) {
			flt = qb_list_entry(iter, struct qb_log_filter, list);
			if (flt->type == type &&
			    flt->low_priority <= low_priority &&
			    flt->high_priority >= high_priority &&
			    (strcmp(flt->text, text) == 0 ||
			     strcmp("*", text) == 0)) {
				qb_list_del(iter);
				_log_free_filter(flt);
				return 0;
			}
		}

	} else if (c == QB_LOG_FILTER_CLEAR_ALL || c == QB_LOG_TAG_CLEAR_ALL) {
		qb_list_for_each_safe(iter, next, list_head) {
			flt = qb_list_entry(iter, struct qb_log_filter, list);
			qb_list_del(iter);
			_log_free_filter(flt);
		}
	}
	return 0;
}

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)
{
	struct qb_log_callsite *cs;

	for (cs = sect->start; cs < sect->stop; cs++) {
		if (cs->lineno > 0) {
			_log_filter_apply_to_cs(cs, t, c, type, text, regex,
					    high_priority, low_priority);
		}
	}
}

/* #define _QB_FILTER_DEBUGGING_ 1 */
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)
{

	if (c == QB_LOG_FILTER_CLEAR_ALL) {
		qb_bit_clear(cs->targets, t);
		return;
	} else if (c == QB_LOG_TAG_CLEAR_ALL) {
		cs->tags = 0;
		return;
	}

	if (_cs_matches_filter_(cs, type, text, regex, high_priority, low_priority)) {
#ifdef _QB_FILTER_DEBUGGING_
		uint32_t old_targets = cs->targets;
		uint32_t old_tags = cs->tags;
#endif /* _QB_FILTER_DEBUGGING_ */
		if (c == QB_LOG_FILTER_ADD) {
			qb_bit_set(cs->targets, t);
		} else if (c == QB_LOG_FILTER_REMOVE) {
			qb_bit_clear(cs->targets, t);
		} else if (c == QB_LOG_TAG_SET) {
			cs->tags = t;
		} else if (c == QB_LOG_TAG_CLEAR) {
			cs->tags = 0;
		}
#ifdef _QB_FILTER_DEBUGGING_
		if (old_targets != cs->targets) {
			printf("targets: %s:%u value(%d) %d -> %d\n",
			       cs->filename, cs->lineno, t,
			       old_targets, cs->targets);
		}
		if (old_tags != cs->tags) {
			printf("tags: %s:%u value(%d) %d -> %d\n",
			       cs->filename, cs->lineno, t, old_tags, cs->tags);
		}
#endif /* _QB_FILTER_DEBUGGING_ */
	}
}

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)
{
	struct qb_log_filter *new_flt = NULL;
	regex_t *regex = NULL;
	struct callsite_section *sect;
	int32_t rc;

	if (!logger_inited) {
		return -EINVAL;
	}

	if (c == QB_LOG_FILTER_ADD ||
	    c == QB_LOG_FILTER_CLEAR_ALL ||
	    c == QB_LOG_FILTER_REMOVE) {
		if (t < 0 || t >= QB_LOG_TARGET_MAX ||
		    conf[t].state == QB_LOG_STATE_UNUSED) {
			return -EBADF;
		}
	}

	if (text == NULL ||
	    low_priority < high_priority ||
	    type > QB_LOG_FILTER_FORMAT_REGEX ||
	    c > QB_LOG_TAG_CLEAR_ALL) {
		return -EINVAL;
	}
	pthread_rwlock_rdlock(&_listlock);
	rc = _log_filter_store(t, c, type, text, high_priority, low_priority, &new_flt);
	if (rc < 0) {
		pthread_rwlock_unlock(&_listlock);
		return rc;
	}

	if (new_flt && new_flt->regex) {
		regex = new_flt->regex;
	}
	qb_list_for_each_entry(sect, &callsite_sections, list) {
		_log_filter_apply(sect, t, c, type, text, regex, high_priority, low_priority);
	}
	pthread_rwlock_unlock(&_listlock);
	return 0;
}

int32_t
qb_log_filter_fn_set(qb_log_filter_fn fn)
{
	struct callsite_section *sect;
	struct qb_log_callsite *cs;

	if (!logger_inited) {
		return -EINVAL;
	}
	_custom_filter_fn = fn;
	if (_custom_filter_fn == NULL) {
		return 0;
	}

	qb_list_for_each_entry(sect, &callsite_sections, list) {
		for (cs = sect->start; cs < sect->stop; cs++) {
			if (cs->lineno > 0) {
				_custom_filter_fn(cs);
			}
		}
	}
	return 0;
}

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)
{
	return qb_log_filter_ctl2(t, c, type, text, LOG_EMERG, priority);
}

#ifdef QB_HAVE_ATTRIBUTE_SECTION
/* Some platforms (eg. FreeBSD 10+) don't support calling dlopen() from
 * within a dl_iterate_phdr() callback; so save all of the dlpi_names to
 * a list and iterate over them afterwards. */
static int32_t
_log_so_walk_callback(struct dl_phdr_info *info, size_t size, void *data)
{
	struct dlname *dlname;

	if (strlen(info->dlpi_name) > 0) {
		dlname = calloc(1, sizeof(struct dlname));
		if (!dlname)
			return 0;
		dlname->dln_name = strdup(info->dlpi_name);
		qb_list_add_tail(&dlname->list, &dlnames);
	}

	return 0;
}

static void
_log_so_walk_dlnames(void)
{
	struct dlname *dlname;
	struct qb_list_head *iter;
	struct qb_list_head *next;

	void *handle;
	void *start;
	void *stop;
	const char *error;

	qb_list_for_each_safe(iter, next, &dlnames) {
		dlname = qb_list_entry(iter, struct dlname, list);

		handle = dlopen(dlname->dln_name, RTLD_LAZY);
		error = dlerror();
		if (!handle || error) {
			qb_log(LOG_ERR, "%s", error);
			goto done;
		}

		start = dlsym(handle, "__start___verbose");
		error = dlerror();
		if (error) {
			goto done;
		}

		stop = dlsym(handle, "__stop___verbose");
		error = dlerror();
		if (error) {
			goto done;

		} else {
			qb_log_callsites_register(start, stop);
		}
done:
		if (handle)
			dlclose(handle);
		qb_list_del(iter);
		if (dlname->dln_name)
			free(dlname->dln_name);
		free(dlname);
	}
}
#endif /* QB_HAVE_ATTRIBUTE_SECTION */

static void
_log_target_state_set(struct qb_log_target *t, enum qb_log_target_state s)
{
	int32_t i;
	int32_t a_set = QB_FALSE;
	int32_t u_set = QB_FALSE;

	t->state = s;

	for (i = 31; i >= 0; i--) {
		if (!a_set && conf[i].state == QB_LOG_STATE_ENABLED) {
			a_set = QB_TRUE;
			conf_active_max = i;
		}
		if (!u_set && conf[i].state != QB_LOG_STATE_UNUSED) {
			u_set = QB_TRUE;
		}
	}
}

void
qb_log_init(const char *name, int32_t facility, uint8_t priority)
{
	int32_t l;
	enum qb_log_target_slot i;

	l = pthread_rwlock_init(&_listlock, NULL);
	assert(l == 0);
	qb_log_format_init();

	for (i = QB_LOG_TARGET_START; i < QB_LOG_TARGET_MAX; i++) {
		conf[i].pos = i;
		conf[i].debug = QB_FALSE;
		conf[i].file_sync = QB_FALSE;
		conf[i].extended = QB_TRUE;
		conf[i].state = QB_LOG_STATE_UNUSED;
		(void)strlcpy(conf[i].name, name, PATH_MAX);
		conf[i].facility = facility;
		qb_list_init(&conf[i].filter_head);
	}

	qb_log_dcs_init();
#ifdef QB_HAVE_ATTRIBUTE_SECTION
	qb_log_callsites_register(__start___verbose, __stop___verbose);
	dl_iterate_phdr(_log_so_walk_callback, NULL);
	_log_so_walk_dlnames();
#endif /* QB_HAVE_ATTRIBUTE_SECTION */

	conf[QB_LOG_STDERR].state = QB_LOG_STATE_DISABLED;
	conf[QB_LOG_BLACKBOX].state = QB_LOG_STATE_DISABLED;
	conf[QB_LOG_STDOUT].state = QB_LOG_STATE_DISABLED;

	logger_inited = QB_TRUE;
	(void)qb_log_syslog_open(&conf[QB_LOG_SYSLOG]);
	_log_target_state_set(&conf[QB_LOG_SYSLOG], QB_LOG_STATE_ENABLED);
	(void)qb_log_filter_ctl(QB_LOG_SYSLOG, QB_LOG_FILTER_ADD,
				QB_LOG_FILTER_FILE, "*", priority);
}

void<commit_msg>log: refactor static target slots state initialization<commit_after>/*
 * Copyright (C) 2011 Red Hat, Inc.
 *
 * All rights reserved.
 *
 * Author: <NAME> <<EMAIL>>
 *
 * libqb is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * libqb is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with libqb.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "os_base.h"
#include <ctype.h>
#ifdef HAVE_LINK_H
#include <link.h>
#endif /* HAVE_LINK_H */
#include <stdarg.h>
#include <pthread.h>
#ifdef HAVE_DLFCN_H
#include <dlfcn.h>
#endif /* HAVE_DLFCN_H */
#include <stdarg.h>
#include <string.h>

#include <qb/qbdefs.h>
#include <qb/qblist.h>
#include <qb/qblog.h>
#include <qb/qbutil.h>
#include <qb/qbarray.h>
#include "log_int.h"
#include "util_int.h"
#include <regex.h>

static struct qb_log_target conf[QB_LOG_TARGET_MAX];
static uint32_t conf_active_max = 0;
static int32_t in_logger = QB_FALSE;
static int32_t logger_inited = QB_FALSE;
static pthread_rwlock_t _listlock;
static qb_log_filter_fn _custom_filter_fn = NULL;

static QB_LIST_DECLARE(dlnames);
static QB_LIST_DECLARE(tags_head);
static QB_LIST_DECLARE(callsite_sections);

struct dlname {
	char *dln_name;
	struct qb_list_head list;
};

struct callsite_section {
	struct qb_log_callsite *start;
	struct qb_log_callsite *stop;
	struct qb_list_head list;
};

static int32_t _log_target_enable(struct qb_log_target *t);
static void _log_target_disable(struct qb_log_target *t);
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);
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);

/* deprecated method of getting internal log messages */
static qb_util_log_fn_t old_internal_log_fn = NULL;
void
qb_util_set_log_function(qb_util_log_fn_t fn)
{
	old_internal_log_fn = fn;
}

static void
_log_free_filter(struct qb_log_filter *flt)
{
	if (flt->regex) {
		regfree(flt->regex);
	}
	free(flt->regex);
	free(flt->text);
	free(flt);
}

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)
{
	int32_t match = QB_FALSE;
	const char *offset = NULL;
	const char *next = NULL;

	if (cs->priority > low_priority ||
	    cs->priority < high_priority) {
		return QB_FALSE;
	}
	if (strcmp(text, "*") == 0) {
		return QB_TRUE;
	}

	switch (type) {
	case QB_LOG_FILTER_FILE:
	case QB_LOG_FILTER_FUNCTION:
		next = text;
		do {
			char token[500];
			offset = next;
			next = strchrnul(offset, ',');
			snprintf(token, 499, "%.*s", (int)(next - offset), offset);

			if (type == QB_LOG_FILTER_FILE) {
				match = (strcmp(cs->filename, token) == 0) ? 1 : 0;
			} else {
				match = (strcmp(cs->function, token) == 0) ? 1 : 0;
			}
			if (!match && next[0] != 0) {
				next++;
			}

		} while (match == QB_FALSE && next != NULL && next[0] != 0);
		break;
	case QB_LOG_FILTER_FILE_REGEX:
		next = next ? next : cs->filename;
	case QB_LOG_FILTER_FUNCTION_REGEX:
		next = next ? next : cs->function;
	case QB_LOG_FILTER_FORMAT_REGEX:
		next = next ? next : cs->format;

		if (regex == NULL) {
			return QB_FALSE;
		}
		match = regexec(regex, next, 0, NULL, 0);
		if (match == 0) {
			match = QB_TRUE;
		} else {
			match = QB_FALSE;
		}
		break;
	case QB_LOG_FILTER_FORMAT:
		if (strstr(cs->format, text)) {
			match = QB_TRUE;
		}
		break;
	}

	return match;
}

/**
 * @internal
 * @brief Format a log message into a string buffer
 *
 * @param[out] str  Destination buffer to contain formatted string
 * @param[in]  cs   Callsite containing format to use
 * @param[in]  ap   Variable arguments for format
 */
#ifdef HAVE_GCC_SUGGEST_ATTRIBUTE_FORMAT
/* suppress suggestion that we currently can do nothing better about
   as the format specification is hidden in cs argument */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsuggest-attribute=format"
#endif
static inline void
cs_format(char *str, struct qb_log_callsite *cs, va_list ap)
{
	va_list ap_copy;
	int len;

	va_copy(ap_copy, ap);
	len = vsnprintf(str, QB_LOG_MAX_LEN, cs->format, ap_copy);
	va_end(ap_copy);
	if (len > QB_LOG_MAX_LEN) {
		len = QB_LOG_MAX_LEN;
	}
	if (str[len - 1] == '\n') {
		str[len - 1] = '\0';
	}
}
#ifdef HAVE_GCC_SUGGEST_ATTRIBUTE_FORMAT
#pragma GCC diagnostic pop
#endif

void
qb_log_real_va_(struct qb_log_callsite *cs, va_list ap)
{
	int32_t found_threaded = QB_FALSE;
	struct qb_log_target *t;
	struct timespec tv;
	int32_t pos;
	int32_t formatted = QB_FALSE;
	char buf[QB_LOG_MAX_LEN];
	char *str = buf;
	va_list ap_copy;

	if (in_logger || cs == NULL) {
		return;
	}
	in_logger = QB_TRUE;

	if (old_internal_log_fn &&
	    qb_bit_is_set(cs->tags, QB_LOG_TAG_LIBQB_MSG_BIT)) {
		if (formatted == QB_FALSE) {
			cs_format(str, cs, ap);
			formatted = QB_TRUE;
		}
		qb_do_extended(str, QB_TRUE,
			old_internal_log_fn(cs->filename, cs->lineno,
					    cs->priority, str));
	}

	qb_util_timespec_from_epoch_get(&tv);

	/*
	 * 1 if we can find a threaded target that needs this log then post it
	 * 2 foreach non-threaded target call it's logger function
	 */
	for (pos = 0; pos <= conf_active_max; pos++) {
		t = &conf[pos];
		if ((t->state == QB_LOG_STATE_ENABLED)
		    && qb_bit_is_set(cs->targets, pos)) {
			if (t->threaded) {
				if (!found_threaded) {
					found_threaded = QB_TRUE;
					if (formatted == QB_FALSE) {
						cs_format(str, cs, ap);
						formatted = QB_TRUE;
					}
				}

			} else if (t->vlogger) {
				va_copy(ap_copy, ap);
				t->vlogger(t->pos, cs, tv.tv_sec, ap_copy);
				va_end(ap_copy);

			} else if (t->logger) {
				if (formatted == QB_FALSE) {
					cs_format(str, cs, ap);
					formatted = QB_TRUE;
				}
				qb_do_extended(str, t->extended,
					       t->logger(t->pos, cs, tv.tv_sec, str));
			}
		}
	}

	if (found_threaded) {
		qb_log_thread_log_post(cs, tv.tv_sec, str);
	}
	in_logger = QB_FALSE;
}

void
qb_log_real_(struct qb_log_callsite *cs, ...)
{
	va_list ap;

	va_start(ap, cs);
	qb_log_real_va_(cs, ap);
	va_end(ap);
}

void
qb_log_thread_log_write(struct qb_log_callsite *cs,
			time_t timestamp, const char *buffer)
{
	struct qb_log_target *t;
	int32_t pos;

	for (pos = 0; pos <= conf_active_max; pos++) {
		t = &conf[pos];
		if ((t->state == QB_LOG_STATE_ENABLED) && t->threaded
		    && qb_bit_is_set(cs->targets, t->pos)) {
			qb_do_extended(buffer, t->extended,
				t->logger(t->pos, cs, timestamp, buffer));
		}
	}
}

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)
{
	struct qb_log_target *t;
	struct qb_log_filter *flt;
	struct qb_log_callsite *cs;
	int32_t new_dcs = QB_FALSE;
	struct qb_list_head *f_item;
	int32_t pos;

	if (!logger_inited) {
		return NULL;
	}

	cs = qb_log_dcs_get(&new_dcs, function, filename,
			    format, priority, lineno, tags);
	if (cs == NULL) {
		return NULL;
	}

	if (new_dcs) {
		pthread_rwlock_rdlock(&_listlock);
		for (pos = 0; pos <= conf_active_max; pos++) {
			t = &conf[pos];
			if (t->state != QB_LOG_STATE_ENABLED) {
				continue;
			}
			qb_list_for_each(f_item, &t->filter_head) {
				flt = qb_list_entry(f_item, struct qb_log_filter, list);
				_log_filter_apply_to_cs(cs, t->pos, flt->conf, flt->type,
							flt->text, flt->regex, flt->high_priority,
							flt->low_priority);
			}
		}
		if (tags == 0) {
			qb_list_for_each(f_item, &tags_head) {
				flt = qb_list_entry(f_item, struct qb_log_filter, list);
				_log_filter_apply_to_cs(cs, flt->new_value, flt->conf, flt->type,
							flt->text, flt->regex, flt->high_priority,
							flt->low_priority);
			}
		} else {
			cs->tags = tags;
		}
		if (_custom_filter_fn) {
			_custom_filter_fn(cs);
		}
		pthread_rwlock_unlock(&_listlock);
	} else if (cs->tags != tags) {
		cs->tags = tags;
		if (_custom_filter_fn) {
			_custom_filter_fn(cs);
		}
	}
	return cs;
}

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)
{
	struct qb_log_callsite *cs;

	if (!logger_inited) {
		return;
	}

	cs = qb_log_callsite_get(function, filename,
				 format, priority, lineno, tags);
	qb_log_real_va_(cs, ap);
}

void
qb_log_from_external_source(const char *function,
			    const char *filename,
			    const char *format,
			    uint8_t priority,
			    uint32_t lineno, uint32_t tags, ...)
{
	struct qb_log_callsite *cs;
	va_list ap;

	if (!logger_inited) {
		return;
	}

	cs = qb_log_callsite_get(function, filename,
				 format, priority, lineno, tags);
	va_start(ap, tags);
	qb_log_real_va_(cs, ap);
	va_end(ap);
}

int32_t
qb_log_callsites_register(struct qb_log_callsite *_start,
			  struct qb_log_callsite *_stop)
{
	struct callsite_section *sect;
	struct qb_log_callsite *cs;
	struct qb_log_target *t;
	struct qb_log_filter *flt;
	int32_t pos;

	if (_start == NULL || _stop == NULL) {
		return -EINVAL;
	}

	pthread_rwlock_rdlock(&_listlock);
	qb_list_for_each_entry(sect, &callsite_sections, list) {
		if (sect->start == _start || sect->stop == _stop) {
			pthread_rwlock_unlock(&_listlock);
			return -EEXIST;
		}
	}
	pthread_rwlock_unlock(&_listlock);

	sect = calloc(1, sizeof(struct callsite_section));
	if (sect == NULL) {
		return -ENOMEM;
	}
	sect->start = _start;
	sect->stop = _stop;
	qb_list_init(&sect->list);

	pthread_rwlock_wrlock(&_listlock);
	qb_list_add(&sect->list, &callsite_sections);

	/*
	 * Now apply the filters on these new callsites
	 */
	for (pos = 0; pos <= conf_active_max; pos++) {
		t = &conf[pos];
		if (t->state != QB_LOG_STATE_ENABLED) {
			continue;
		}
		qb_list_for_each_entry(flt, &t->filter_head, list) {
			_log_filter_apply(sect, t->pos, flt->conf,
					  flt->type, flt->text, flt->regex,
					  flt->high_priority, flt->low_priority);
		}
	}
	qb_list_for_each_entry(flt, &tags_head, list) {
		_log_filter_apply(sect, flt->new_value, flt->conf,
				  flt->type, flt->text, flt->regex,
				  flt->high_priority, flt->low_priority);
	}
	pthread_rwlock_unlock(&_listlock);
	if (_custom_filter_fn) {
		for (cs = sect->start; cs < sect->stop; cs++) {
			if (cs->lineno > 0) {
				_custom_filter_fn(cs);
			}
		}
	}
	/* qb_log_callsites_dump_sect(sect); */

	return 0;
}

static void
qb_log_callsites_dump_sect(struct callsite_section *sect)
{
	struct qb_log_callsite *cs;

	printf(" start %p - stop %p\n", sect->start, sect->stop);
	printf("filename    lineno targets         tags\n");
	for (cs = sect->start; cs < sect->stop; cs++) {
		if (cs->lineno > 0) {
			printf("%12s %6d %16d %16d\n", cs->filename, cs->lineno,
			       cs->targets, cs->tags);
		}
	}
}

void
qb_log_callsites_dump(void)
{
	struct callsite_section *sect;
	int32_t l;

	pthread_rwlock_rdlock(&_listlock);
	l = qb_list_length(&callsite_sections);
	printf("Callsite Database [%d]\n", l);
	printf("---------------------\n");
	qb_list_for_each_entry(sect, &callsite_sections, list) {
		qb_log_callsites_dump_sect(sect);
	}
	pthread_rwlock_unlock(&_listlock);
}

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)
{
	struct qb_log_filter *flt;

	qb_list_for_each_entry(flt, list_head, list) {
		if (flt->type == type &&
		    flt->high_priority == high_priority &&
		    flt->low_priority == low_priority &&
		    flt->new_value == new_value &&
		    strcmp(flt->text, text) == 0) {
			return QB_TRUE;
		}
	}
	return QB_FALSE;
}

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)
{
	struct qb_log_filter *flt;
	struct qb_list_head *iter;
	struct qb_list_head *next;
	struct qb_list_head *list_head;

	switch (c) {
	case QB_LOG_FILTER_ADD:
	case QB_LOG_FILTER_REMOVE:
	case QB_LOG_FILTER_CLEAR_ALL:
		list_head = &conf[t].filter_head;
		break;

	case QB_LOG_TAG_SET:
	case QB_LOG_TAG_CLEAR:
	case QB_LOG_TAG_CLEAR_ALL:
		list_head = &tags_head;
		break;
	default:
		return -ENOSYS;
	}

	if (c == QB_LOG_FILTER_ADD || c == QB_LOG_TAG_SET) {
		if (text == NULL) {
			return -EINVAL;
		}
		if (_log_filter_exists(list_head, type, text,
				       high_priority, low_priority, t)) {
			return -EEXIST;
		}
		flt = calloc(1, sizeof(struct qb_log_filter));
		if (flt == NULL) {
			return -ENOMEM;
		}
		qb_list_init(&flt->list);
		flt->conf = c;
		flt->type = type;
		flt->text = strdup(text);
		if (flt->text == NULL) {
			_log_free_filter(flt);
			return -ENOMEM;
		}

		if (type == QB_LOG_FILTER_FUNCTION_REGEX ||
			type == QB_LOG_FILTER_FILE_REGEX ||
			type == QB_LOG_FILTER_FORMAT_REGEX) {
			int res;

			flt->regex = calloc(1, sizeof(regex_t));
			if (flt->regex == NULL) {
				_log_free_filter(flt);
				return -ENOMEM;
			}
			res = regcomp(flt->regex, flt->text, 0);
			if (res) {
				_log_free_filter(flt);
				return -EINVAL;
			}
		}
		flt->high_priority = high_priority;
		flt->low_priority = low_priority;
		flt->new_value = t;
		qb_list_add_tail(&flt->list, list_head);
		if (new) {
			*new = flt;
		}
	} else if (c == QB_LOG_FILTER_REMOVE || c == QB_LOG_TAG_CLEAR) {
		qb_list_for_each_safe(iter, next, list_head) {
			flt = qb_list_entry(iter, struct qb_log_filter, list);
			if (flt->type == type &&
			    flt->low_priority <= low_priority &&
			    flt->high_priority >= high_priority &&
			    (strcmp(flt->text, text) == 0 ||
			     strcmp("*", text) == 0)) {
				qb_list_del(iter);
				_log_free_filter(flt);
				return 0;
			}
		}

	} else if (c == QB_LOG_FILTER_CLEAR_ALL || c == QB_LOG_TAG_CLEAR_ALL) {
		qb_list_for_each_safe(iter, next, list_head) {
			flt = qb_list_entry(iter, struct qb_log_filter, list);
			qb_list_del(iter);
			_log_free_filter(flt);
		}
	}
	return 0;
}

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)
{
	struct qb_log_callsite *cs;

	for (cs = sect->start; cs < sect->stop; cs++) {
		if (cs->lineno > 0) {
			_log_filter_apply_to_cs(cs, t, c, type, text, regex,
					    high_priority, low_priority);
		}
	}
}

/* #define _QB_FILTER_DEBUGGING_ 1 */
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)
{

	if (c == QB_LOG_FILTER_CLEAR_ALL) {
		qb_bit_clear(cs->targets, t);
		return;
	} else if (c == QB_LOG_TAG_CLEAR_ALL) {
		cs->tags = 0;
		return;
	}

	if (_cs_matches_filter_(cs, type, text, regex, high_priority, low_priority)) {
#ifdef _QB_FILTER_DEBUGGING_
		uint32_t old_targets = cs->targets;
		uint32_t old_tags = cs->tags;
#endif /* _QB_FILTER_DEBUGGING_ */
		if (c == QB_LOG_FILTER_ADD) {
			qb_bit_set(cs->targets, t);
		} else if (c == QB_LOG_FILTER_REMOVE) {
			qb_bit_clear(cs->targets, t);
		} else if (c == QB_LOG_TAG_SET) {
			cs->tags = t;
		} else if (c == QB_LOG_TAG_CLEAR) {
			cs->tags = 0;
		}
#ifdef _QB_FILTER_DEBUGGING_
		if (old_targets != cs->targets) {
			printf("targets: %s:%u value(%d) %d -> %d\n",
			       cs->filename, cs->lineno, t,
			       old_targets, cs->targets);
		}
		if (old_tags != cs->tags) {
			printf("tags: %s:%u value(%d) %d -> %d\n",
			       cs->filename, cs->lineno, t, old_tags, cs->tags);
		}
#endif /* _QB_FILTER_DEBUGGING_ */
	}
}

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)
{
	struct qb_log_filter *new_flt = NULL;
	regex_t *regex = NULL;
	struct callsite_section *sect;
	int32_t rc;

	if (!logger_inited) {
		return -EINVAL;
	}

	if (c == QB_LOG_FILTER_ADD ||
	    c == QB_LOG_FILTER_CLEAR_ALL ||
	    c == QB_LOG_FILTER_REMOVE) {
		if (t < 0 || t >= QB_LOG_TARGET_MAX ||
		    conf[t].state == QB_LOG_STATE_UNUSED) {
			return -EBADF;
		}
	}

	if (text == NULL ||
	    low_priority < high_priority ||
	    type > QB_LOG_FILTER_FORMAT_REGEX ||
	    c > QB_LOG_TAG_CLEAR_ALL) {
		return -EINVAL;
	}
	pthread_rwlock_rdlock(&_listlock);
	rc = _log_filter_store(t, c, type, text, high_priority, low_priority, &new_flt);
	if (rc < 0) {
		pthread_rwlock_unlock(&_listlock);
		return rc;
	}

	if (new_flt && new_flt->regex) {
		regex = new_flt->regex;
	}
	qb_list_for_each_entry(sect, &callsite_sections, list) {
		_log_filter_apply(sect, t, c, type, text, regex, high_priority, low_priority);
	}
	pthread_rwlock_unlock(&_listlock);
	return 0;
}

int32_t
qb_log_filter_fn_set(qb_log_filter_fn fn)
{
	struct callsite_section *sect;
	struct qb_log_callsite *cs;

	if (!logger_inited) {
		return -EINVAL;
	}
	_custom_filter_fn = fn;
	if (_custom_filter_fn == NULL) {
		return 0;
	}

	qb_list_for_each_entry(sect, &callsite_sections, list) {
		for (cs = sect->start; cs < sect->stop; cs++) {
			if (cs->lineno > 0) {
				_custom_filter_fn(cs);
			}
		}
	}
	return 0;
}

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)
{
	return qb_log_filter_ctl2(t, c, type, text, LOG_EMERG, priority);
}

#ifdef QB_HAVE_ATTRIBUTE_SECTION
/* Some platforms (eg. FreeBSD 10+) don't support calling dlopen() from
 * within a dl_iterate_phdr() callback; so save all of the dlpi_names to
 * a list and iterate over them afterwards. */
static int32_t
_log_so_walk_callback(struct dl_phdr_info *info, size_t size, void *data)
{
	struct dlname *dlname;

	if (strlen(info->dlpi_name) > 0) {
		dlname = calloc(1, sizeof(struct dlname));
		if (!dlname)
			return 0;
		dlname->dln_name = strdup(info->dlpi_name);
		qb_list_add_tail(&dlname->list, &dlnames);
	}

	return 0;
}

static void
_log_so_walk_dlnames(void)
{
	struct dlname *dlname;
	struct qb_list_head *iter;
	struct qb_list_head *next;

	void *handle;
	void *start;
	void *stop;
	const char *error;

	qb_list_for_each_safe(iter, next, &dlnames) {
		dlname = qb_list_entry(iter, struct dlname, list);

		handle = dlopen(dlname->dln_name, RTLD_LAZY);
		error = dlerror();
		if (!handle || error) {
			qb_log(LOG_ERR, "%s", error);
			goto done;
		}

		start = dlsym(handle, "__start___verbose");
		error = dlerror();
		if (error) {
			goto done;
		}

		stop = dlsym(handle, "__stop___verbose");
		error = dlerror();
		if (error) {
			goto done;

		} else {
			qb_log_callsites_register(start, stop);
		}
done:
		if (handle)
			dlclose(handle);
		qb_list_del(iter);
		if (dlname->dln_name)
			free(dlname->dln_name);
		free(dlname);
	}
}
#endif /* QB_HAVE_ATTRIBUTE_SECTION */

static void
_log_target_state_set(struct qb_log_target *t, enum qb_log_target_state s)
{
	int32_t i;
	int32_t a_set = QB_FALSE;
	int32_t u_set = QB_FALSE;

	t->state = s;

	for (i = 31; i >= 0; i--) {
		if (!a_set && conf[i].state == QB_LOG_STATE_ENABLED) {
			a_set = QB_TRUE;
			conf_active_max = i;
		}
		if (!u_set && conf[i].state != QB_LOG_STATE_UNUSED) {
			u_set = QB_TRUE;
		}
	}
}

void
qb_log_init(const char *name, int32_t facility, uint8_t priority)
{
	int32_t l;
	enum qb_log_target_slot i;

	l = pthread_rwlock_init(&_listlock, NULL);
	assert(l == 0);
	qb_log_format_init();

	for (i = QB_LOG_TARGET_START; i < QB_LOG_TARGET_MAX; i++) {
		conf[i].pos = i;
		conf[i].debug = QB_FALSE;
		conf[i].file_sync = QB_FALSE;
		conf[i].extended = QB_TRUE;
		conf[i].state = QB_LOG_STATE_UNUSED;
		(void)strlcpy(conf[i].name, name, PATH_MAX);
		conf[i].facility = facility;
		qb_list_init(&conf[i].filter_head);
	}

	qb_log_dcs_init();
#ifdef QB_HAVE_ATTRIBUTE_SECTION
	qb_log_callsites_register(__start___verbose, __stop___verbose);
	dl_iterate_phdr(_log_so_walk_callback, NULL);
	_log_so_walk_dlnames();
#endif /* QB_HAVE_ATTRIBUTE_SECTION */

	for (i = QB_LOG_TARGET_STATIC_START; i < QB_LOG_TARGET_STATIC_MAX; i++)
		conf[i].state = QB_LOG_STATE_DISABLED;

	logger_inited = QB_TRUE;
	(void)qb_log_syslog_open(&conf[QB_LOG_SYSLOG]);
	_log_target_state_set(&conf[QB_LOG_SYSLOG], QB_LOG_STATE_ENABLED);
	(void)qb_log_filter_ctl(QB_LOG_SYSLOG, QB_LOG_FILTER_ADD,
				QB_LOG_FILTER_FILE, "*", priority);
}

void
{'fraction_non_alphanumeric': 0.12786543966312602, 'fraction_numerical': 0.00875304582337773, 'mean_word_length': 3.8460392066949445, 'pattern_counts': {'":': 0, '<': 66, '<?xml version=': 0, '>': 347, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6141833', 'n_tokens_mistral': 19087, 'n_tokens_neox': 17145, 'n_words': 3649}