Import the new logging architecture

This is the new logging architecture, including

* support for multiuple logging targets like stderr and vty
* log levels in addition to categories/subsystems
* filtering based on imsi, i.e. only see events for one subscriber
* dynamically change log level for each category for each vty
diff --git a/openbsc/src/debug.c b/openbsc/src/debug.c
index 6e99d41..d3d58f3 100644
--- a/openbsc/src/debug.c
+++ b/openbsc/src/debug.c
@@ -27,21 +27,53 @@
 #include <time.h>
 
 #include <openbsc/debug.h>
+#include <openbsc/talloc.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
 
-unsigned int debug_mask = 0xffffffff & ~(DMI|DMIB|DMEAS);
+/* default categories */
+static struct debug_category default_categories[Debug_LastEntry] = {
+    [DRLL]	= { .enabled = 1, .loglevel = 0},
+    [DCC]	= { .enabled = 1, .loglevel = 0},
+    [DMM]	= { .enabled = 1, .loglevel = 0},
+    [DRR]	= { .enabled = 1, .loglevel = 0},
+    [DRSL]	= { .enabled = 1, .loglevel = 0},
+    [DMM]	= { .enabled = 1, .loglevel = 0},
+    [DMNCC]	= { .enabled = 1, .loglevel = 0},
+    [DSMS]	= { .enabled = 1, .loglevel = 0},
+    [DPAG]	= { .enabled = 1, .loglevel = 0},
+    [DMEAS]	= { .enabled = 0, .loglevel = 0},
+    [DMI]	= { .enabled = 0, .loglevel = 0},
+    [DMIB]	= { .enabled = 0, .loglevel = 0},
+    [DMUX]	= { .enabled = 1, .loglevel = 0},
+    [DINP]	= { .enabled = 1, .loglevel = 0},
+    [DSCCP]	= { .enabled = 1, .loglevel = 0},
+    [DMSC]	= { .enabled = 1, .loglevel = 0},
+    [DMGCP]	= { .enabled = 1, .loglevel = 0},
+    [DHO]	= { .enabled = 1, .loglevel = 0},
+};
 
 struct debug_info {
 	const char *name;
 	const char *color;
 	const char *description;
 	int number;
+	int position;
 };
 
+struct debug_context {
+	struct gsm_lchan *lchan;
+	struct gsm_subscriber *subscr;
+	struct gsm_bts *bts;
+};
+
+static struct debug_context debug_context;
+static void *tall_dbg_ctx = NULL;
+static LLIST_HEAD(target_list);
+
 #define DEBUG_CATEGORY(NUMBER, NAME, COLOR, DESCRIPTION) \
 	{ .name = NAME, .color = COLOR, .description = DESCRIPTION, .number = NUMBER },
 
-#define ARRAY_SIZE(array) (sizeof(array)/sizeof(array[0]))
-
 static const struct debug_info debug_info[] = {
 	DEBUG_CATEGORY(DRLL,  "DRLL", "\033[1;31m", "")
 	DEBUG_CATEGORY(DCC,   "DCC",  "\033[1;32m", "")
@@ -63,50 +95,51 @@
 	DEBUG_CATEGORY(DHO, "DHO", "", "")
 };
 
-static int use_color = 1;
-
-void debug_use_color(int color)
-{
-	use_color = color;
-}
-
-static int print_timestamp = 0;
-
-void debug_timestamp(int enable)
-{
-	print_timestamp = enable;
-}
-
-
 /*
  * Parse the category mask.
- * category1:category2:category3
+ * The format can be this: category1:category2:category3
+ * or category1,2:category2,3:...
  */
-void debug_parse_category_mask(const char *_mask)
+void debug_parse_category_mask(struct debug_target* target, const char *_mask)
 {
-	unsigned int new_mask = 0;
 	int i = 0;
 	char *mask = strdup(_mask);
 	char *category_token = NULL;
 
+	/* Disable everything to enable it afterwards */
+	for (i = 0; i < ARRAY_SIZE(target->categories); ++i)
+		target->categories[i].enabled = 0;
+
 	category_token = strtok(mask, ":");
 	do {
 		for (i = 0; i < ARRAY_SIZE(debug_info); ++i) {
-			if (strcasecmp(debug_info[i].name, category_token) == 0)
-				new_mask |= debug_info[i].number;
+			char* colon = strstr(category_token, ",");
+			int length = strlen(category_token);
+
+			if (colon)
+			    length = colon - category_token;
+
+			if (strncasecmp(debug_info[i].name, category_token, length) == 0) {
+				int number = debug_info[i].number;
+				int level = 0;
+
+				if (colon)
+					level = atoi(colon+1);
+
+				target->categories[number].enabled = 1;
+				target->categories[number].loglevel = level;
+			}
 		}
 	} while ((category_token = strtok(NULL, ":")));
 
-
 	free(mask);
-	debug_mask = new_mask;
 }
 
-const char* color(int subsys)
+static const char* color(int subsys)
 {
 	int i = 0;
 
-	for (i = 0; use_color && i < ARRAY_SIZE(debug_info); ++i) {
+	for (i = 0; i < ARRAY_SIZE(debug_info); ++i) {
 		if (debug_info[i].number == subsys)
 			return debug_info[i].color;
 	}
@@ -114,35 +147,97 @@
 	return "";
 }
 
-void debugp(unsigned int subsys, char *file, int line, int cont, const char *format, ...)
+static void _output(struct debug_target *target, unsigned int subsys, char *file, int line,
+		    int cont, const char *format, va_list ap)
 {
-	va_list ap;
-	FILE *outfd = stderr;
+	char col[30];
+	char sub[30];
+	char tim[30];
+	char buf[4096];
+	char final[4096];
 
-	if (!(debug_mask & subsys))
-		return;
+	/* prepare the data */
+	col[0] = '\0';
+	sub[0] = '\0';
+	tim[0] = '\0';
+	buf[0] = '\0';
 
-	va_start(ap, format);
-
-	fprintf(outfd, "%s", color(subsys));
+	/* are we using color */
+	if (target->use_color)
+		snprintf(col, sizeof(col), "%s", color(subsys));
+	vsnprintf(buf, sizeof(buf), format, ap);
 
 	if (!cont) {
-		if (print_timestamp) {
+		if (target->print_timestamp) {
 			char *timestr;
 			time_t tm;
 			tm = time(NULL);
 			timestr = ctime(&tm);
 			timestr[strlen(timestr)-1] = '\0';
-			fprintf(outfd, "%s ", timestr);
+			snprintf(tim, sizeof(tim), "%s ", timestr);
 		}
-		fprintf(outfd, "<%4.4x> %s:%d ", subsys, file, line);
+		snprintf(sub, sizeof(sub), "<%4.4x> %s:%d ", subsys, file, line);
 	}
-	vfprintf(outfd, format, ap);
-	fprintf(outfd, "\033[0;m");
 
+	snprintf(final, sizeof(final), "%s%s%s%s\033[0;m", col, tim, sub, buf);
+	target->output(target, final);
+}
+
+
+static void _debugp(unsigned int subsys, int level, char *file, int line,
+		    int cont, const char *format, va_list ap)
+{
+	struct debug_target *tar;
+
+	llist_for_each_entry(tar, &target_list, entry) {
+		struct debug_category *category;
+		int output = 0;
+
+		category = &tar->categories[subsys];
+		/* subsystem is not supposed to be debugged */
+		if (!category->enabled)
+			continue;
+
+		/* Check the global log level */
+		if (tar->loglevel != 0 && level < tar->loglevel)
+			continue;
+
+		/* Check the category log level */
+		if (category->loglevel != 0 && level < category->loglevel)
+			continue;
+
+		/*
+		 * Apply filters here... if that becomes messy we will need to put
+		 * filters in a list and each filter will say stop, continue, output
+		 */
+		if ((tar->filter_map & DEBUG_FILTER_ALL) != 0) {
+			output = 1;
+		} else if ((tar->filter_map & DEBUG_FILTER_IMSI) != 0
+			      && debug_context.subscr && strcmp(debug_context.subscr->imsi, tar->imsi_filter) == 0) {
+			output = 1;
+		}
+
+		if (output)
+			_output(tar, subsys, file, line, cont, format, ap);
+	}
+}
+
+void debugp(unsigned int subsys, char *file, int line, int cont, const char *format, ...)
+{
+	va_list ap;
+
+	va_start(ap, format);
+	_debugp(subsys, LOGL_DEBUG, file, line, cont, format, ap);
 	va_end(ap);
+}
 
-	fflush(outfd);
+void debugp2(unsigned int subsys, unsigned int level, char *file, int line, int cont, const char *format, ...)
+{
+	va_list ap;
+
+	va_start(ap, format);
+	_debugp(subsys, level, file, line, cont, format, ap);
+	va_end(ap);
 }
 
 static char hexd_buff[4096];
@@ -164,3 +259,122 @@
 	return hexd_buff;
 }
 
+
+
+void debug_add_target(struct debug_target *target)
+{
+	llist_add_tail(&target->entry, &target_list);
+}
+
+void debug_del_target(struct debug_target *target)
+{
+	llist_del(&target->entry);
+}
+
+void debug_reset_context(void)
+{
+	memset(&debug_context, 0, sizeof(debug_context));
+}
+
+/* currently we are not reffing these */
+void debug_set_context(int ctx, void *value)
+{
+	switch (ctx) {
+	case BSC_CTX_LCHAN:
+		debug_context.lchan = (struct gsm_lchan *) value;
+		break;
+	case BSC_CTX_SUBSCR:
+		debug_context.subscr = (struct gsm_subscriber *) value;
+		break;
+	case BSC_CTX_BTS:
+		debug_context.bts = (struct gsm_bts *) value;
+		break;
+	case BSC_CTX_SCCP:
+		break;
+	default:
+		break;
+	}
+}
+
+void debug_set_imsi_filter(struct debug_target *target, const char *imsi)
+{
+	if (imsi) {
+		target->filter_map |= DEBUG_FILTER_IMSI;
+		target->imsi_filter = talloc_strdup(target, imsi);
+	} else if (target->imsi_filter) {
+		target->filter_map &= ~DEBUG_FILTER_IMSI;
+		talloc_free(target->imsi_filter);
+		target->imsi_filter = NULL;
+	}
+}
+
+void debug_set_all_filter(struct debug_target *target, int all)
+{
+	if (all)
+		target->filter_map |= DEBUG_FILTER_ALL;
+	else
+		target->filter_map &= ~DEBUG_FILTER_ALL;
+}
+
+void debug_set_use_color(struct debug_target *target, int use_color)
+{
+	target->use_color = use_color;
+}
+
+void debug_set_print_timestamp(struct debug_target *target, int print_timestamp)
+{
+	target->print_timestamp = print_timestamp;
+}
+
+void debug_set_log_level(struct debug_target *target, int log_level)
+{
+	target->loglevel = log_level;
+}
+
+void debug_set_category_filter(struct debug_target *target, int category, int enable, int level)
+{
+	if (category >= Debug_LastEntry)
+		return;
+	target->categories[category].enabled = !!enable;
+	target->categories[category].loglevel = level;
+}
+
+static void _stderr_output(struct debug_target *target, const char *log)
+{
+	fprintf(target->tgt_stdout.out, "%s", log);
+	fflush(target->tgt_stdout.out);
+}
+
+struct debug_target *debug_target_create(void)
+{
+	struct debug_target *target;
+
+	target = talloc_zero(tall_dbg_ctx, struct debug_target);
+	if (!target)
+		return NULL;
+
+	INIT_LLIST_HEAD(&target->entry);
+	memcpy(target->categories, default_categories, sizeof(default_categories));
+	target->use_color = 1;
+	target->print_timestamp = 0;
+	target->loglevel = 0;
+	return target;
+}
+
+struct debug_target *debug_target_create_stderr(void)
+{
+	struct debug_target *target;
+
+	target = debug_target_create();
+	if (!target)
+		return NULL;
+
+	target->tgt_stdout.out = stderr;
+	target->output = _stderr_output;
+	return target;
+}
+
+void debug_init(void)
+{
+	tall_dbg_ctx = talloc_named_const(NULL, 1, "debug");
+}