325 lines
7.7 KiB
C
325 lines
7.7 KiB
C
|
/*
|
||
|
* Asterisk -- An open source telephony toolkit.
|
||
|
*
|
||
|
* Copyright (C) 2020, Sangoma Technologies Corporation
|
||
|
*
|
||
|
* Kevin Harwell <kharwell@sangoma.com>
|
||
|
*
|
||
|
* See http://www.asterisk.org for more information about
|
||
|
* the Asterisk project. Please do not directly contact
|
||
|
* any of the maintainers of this project for assistance;
|
||
|
* the project provides a web site, mailing lists and IRC
|
||
|
* channels for your use.
|
||
|
*
|
||
|
* This program is free software, distributed under the terms of
|
||
|
* the GNU General Public License Version 2. See the LICENSE file
|
||
|
* at the top of the source tree.
|
||
|
*/
|
||
|
|
||
|
#include "asterisk.h"
|
||
|
|
||
|
#include "asterisk/cli.h"
|
||
|
#include "asterisk/conversions.h"
|
||
|
#include "asterisk/logger_category.h"
|
||
|
#include "asterisk/vector.h"
|
||
|
|
||
|
struct category_t {
|
||
|
int sublevel;
|
||
|
uintmax_t id;
|
||
|
char name[0];
|
||
|
};
|
||
|
|
||
|
AST_VECTOR_RW(categories_t, struct category_t *);
|
||
|
|
||
|
struct categories_level_t {
|
||
|
int type;
|
||
|
int sublevel;
|
||
|
uintmax_t id_pool;
|
||
|
uintmax_t state;
|
||
|
struct categories_t categories;
|
||
|
};
|
||
|
|
||
|
/*! \brief Retrieve the next available id.
|
||
|
*
|
||
|
* Ids must be a power of 2. This allows for fast lookup, and "or'ing" of ids
|
||
|
* in order to permit multiple categories in a log statement.
|
||
|
*/
|
||
|
static uintmax_t get_next_id(struct categories_level_t *level)
|
||
|
{
|
||
|
if (level->id_pool == 0) {
|
||
|
level->id_pool = 1;
|
||
|
} else if (level->id_pool >= (UINTMAX_MAX / 2)) {
|
||
|
/* No more ids left*/
|
||
|
return 0;
|
||
|
} else {
|
||
|
level->id_pool <<= 1;
|
||
|
}
|
||
|
|
||
|
return level->id_pool;
|
||
|
}
|
||
|
|
||
|
static int cmp_by_name(const struct category_t *category, const char *name)
|
||
|
{
|
||
|
return !strcmp(category->name, name);
|
||
|
}
|
||
|
|
||
|
static uintmax_t category_register(struct categories_level_t *level, const char *name)
|
||
|
{
|
||
|
int i;
|
||
|
struct category_t *category;
|
||
|
|
||
|
AST_VECTOR_RW_WRLOCK(&level->categories);
|
||
|
|
||
|
i = AST_VECTOR_GET_INDEX(&level->categories, name, cmp_by_name);
|
||
|
if (i >= 0) {
|
||
|
AST_VECTOR_RW_UNLOCK(&level->categories);
|
||
|
ast_log(LOG_ERROR, "Cannot register logger category '%s'. "
|
||
|
"Name already used for type.\n", name);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
category = ast_calloc(1, sizeof(*category) + strlen(name) + 1);
|
||
|
if (!category) {
|
||
|
AST_VECTOR_RW_UNLOCK(&level->categories);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
category->id = get_next_id(level);
|
||
|
category->sublevel = AST_LOG_CATEGORY_DISABLED;
|
||
|
strcpy(category->name, name); /* Safe */
|
||
|
|
||
|
if (AST_VECTOR_APPEND(&level->categories, category)) {
|
||
|
AST_VECTOR_RW_UNLOCK(&level->categories);
|
||
|
ast_log(LOG_ERROR, "Cannot register logger category '%s'. "
|
||
|
"Unable to append.\n", name);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
AST_VECTOR_RW_UNLOCK(&level->categories);
|
||
|
return category->id;
|
||
|
}
|
||
|
|
||
|
static int category_unregister(struct categories_level_t *level, const char *name)
|
||
|
{
|
||
|
int res;
|
||
|
|
||
|
AST_VECTOR_RW_WRLOCK(&level->categories);
|
||
|
res = AST_VECTOR_REMOVE_CMP_UNORDERED(&level->categories, name, cmp_by_name, ast_free);
|
||
|
AST_VECTOR_RW_UNLOCK(&level->categories);
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
static int casecmp_by_name(const struct category_t *category, const char *name)
|
||
|
{
|
||
|
return !strcasecmp(category->name, name);
|
||
|
}
|
||
|
|
||
|
static int category_set_sublevel(struct category_t *category, struct categories_level_t *level,
|
||
|
const char *name, int sublevel)
|
||
|
{
|
||
|
int locked = 0;
|
||
|
|
||
|
if (!category) {
|
||
|
struct category_t **obj;
|
||
|
|
||
|
if (!name) {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
locked = !AST_VECTOR_RW_WRLOCK(&level->categories);
|
||
|
if (!locked) {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
obj = AST_VECTOR_GET_CMP(&level->categories, name, casecmp_by_name);
|
||
|
if (!obj) {
|
||
|
AST_VECTOR_RW_UNLOCK(&level->categories);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
category = *obj;
|
||
|
}
|
||
|
|
||
|
category->sublevel = sublevel;
|
||
|
|
||
|
if (category->sublevel == AST_LOG_CATEGORY_DISABLED) {
|
||
|
level->state &= ~category->id;
|
||
|
} else {
|
||
|
level->state |= category->id;
|
||
|
}
|
||
|
|
||
|
if (locked) {
|
||
|
AST_VECTOR_RW_UNLOCK(&level->categories);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int category_set_sublevels(struct categories_level_t *level,
|
||
|
const char * const *names, size_t size, int default_sublevel)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
if (!names || !size) {
|
||
|
level->state = default_sublevel;
|
||
|
AST_VECTOR_RW_WRLOCK(&level->categories);
|
||
|
AST_VECTOR_CALLBACK_VOID(&level->categories, category_set_sublevel,
|
||
|
level, NULL, default_sublevel);
|
||
|
AST_VECTOR_RW_UNLOCK(&level->categories);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < size; ++i) {
|
||
|
const char *sublevel;
|
||
|
int num = default_sublevel;
|
||
|
|
||
|
sublevel = strchr(names[i], ':');
|
||
|
if (sublevel) {
|
||
|
size_t len = ++sublevel - names[i];
|
||
|
char name[len];
|
||
|
|
||
|
if (*sublevel && ast_str_to_int(sublevel, &num)) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
ast_copy_string(name, names[i], len);
|
||
|
category_set_sublevel(NULL, level, name, num);
|
||
|
} else {
|
||
|
category_set_sublevel(NULL, level, names[i], num);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static char *category_complete(struct categories_level_t *level, const char * const *argv,
|
||
|
int argc, const char *word, int state)
|
||
|
{
|
||
|
int wordlen = strlen(word);
|
||
|
int which = 0;
|
||
|
int i, j;
|
||
|
|
||
|
AST_VECTOR_RW_RDLOCK(&level->categories);
|
||
|
|
||
|
if (argc == AST_VECTOR_SIZE(&level->categories)) {
|
||
|
AST_VECTOR_RW_UNLOCK(&level->categories);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < AST_VECTOR_SIZE(&level->categories); ++i) {
|
||
|
struct category_t *category = AST_VECTOR_GET(&level->categories, i);
|
||
|
|
||
|
if (!strncasecmp(word, category->name, wordlen) && (++which > state)) {
|
||
|
/* Check to see if one is already been included */
|
||
|
for (j = 0; j < argc; ++j) {
|
||
|
if (!strncasecmp(category->name, argv[j], strlen(category->name))) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (j != argc) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (state != -1) {
|
||
|
AST_VECTOR_RW_UNLOCK(&level->categories);
|
||
|
return ast_strdup(category->name);
|
||
|
}
|
||
|
|
||
|
if (ast_cli_completion_add(ast_strdup(category->name))) {
|
||
|
AST_VECTOR_RW_UNLOCK(&level->categories);
|
||
|
return NULL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
AST_VECTOR_RW_UNLOCK(&level->categories);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static int category_is_allowed(int sublevel, struct categories_level_t *level, uintmax_t ids)
|
||
|
{
|
||
|
/* Check level, and potentially allow but only if there is a matching state enabled */
|
||
|
if (ids & level->state) {
|
||
|
int i;
|
||
|
|
||
|
if (sublevel == AST_LOG_CATEGORY_ENABLED || sublevel == 0) {
|
||
|
/* If at least one id is enabled then always allow these sublevels */
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
AST_VECTOR_RW_RDLOCK(&level->categories);
|
||
|
for (i = 0; i < AST_VECTOR_SIZE(&level->categories); ++i) {
|
||
|
struct category_t *category = AST_VECTOR_GET(&level->categories, i);
|
||
|
|
||
|
/*
|
||
|
* If there is at least one matching category, and that category is enabled
|
||
|
* or its sub-level is at or above the given sub-level then allow.
|
||
|
*/
|
||
|
if ((category->id & ids) && category->sublevel != AST_LOG_CATEGORY_DISABLED &&
|
||
|
(category->sublevel == AST_LOG_CATEGORY_ENABLED || category->sublevel >= sublevel)) {
|
||
|
AST_VECTOR_RW_UNLOCK(&level->categories);
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
AST_VECTOR_RW_UNLOCK(&level->categories);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct categories_level_t debug_categories = {
|
||
|
.type = __LOG_DEBUG,
|
||
|
.sublevel = 0,
|
||
|
.id_pool = 0,
|
||
|
.state = 0,
|
||
|
};
|
||
|
|
||
|
uintmax_t ast_debug_category_register(const char *name)
|
||
|
{
|
||
|
return category_register(&debug_categories, name);
|
||
|
}
|
||
|
|
||
|
int ast_debug_category_unregister(const char *name)
|
||
|
{
|
||
|
return category_unregister(&debug_categories, name);
|
||
|
}
|
||
|
|
||
|
int ast_debug_category_set_sublevel(const char *name, int sublevel)
|
||
|
{
|
||
|
return category_set_sublevel(NULL, &debug_categories, name, sublevel);
|
||
|
}
|
||
|
|
||
|
int ast_debug_category_set_sublevels(const char * const *names,
|
||
|
size_t size, int default_sublevel)
|
||
|
{
|
||
|
return category_set_sublevels(&debug_categories, names, size, default_sublevel);
|
||
|
}
|
||
|
|
||
|
char *ast_debug_category_complete(const char * const *argv, int argc,
|
||
|
const char *word, int state)
|
||
|
{
|
||
|
return category_complete(&debug_categories, argv, argc, word, state);
|
||
|
}
|
||
|
|
||
|
int ast_debug_category_is_allowed(int sublevel, uintmax_t ids)
|
||
|
{
|
||
|
return category_is_allowed(sublevel, &debug_categories, ids);
|
||
|
}
|
||
|
|
||
|
int ast_logger_category_unload(void)
|
||
|
{
|
||
|
AST_VECTOR_RW_FREE(&debug_categories.categories);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int ast_logger_category_load(void)
|
||
|
{
|
||
|
if (AST_VECTOR_RW_INIT(&debug_categories.categories, 10)) {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|