logging: introduce library-internal logging categories

We do this by using a trick: library-internal log categories use
negative subsystem numbers, which are converted into positive
array indexes at the time of logging.

library-internal log categories need to be knwo at compile-time,
while application-specified categories now are of unlimited number,
as they are dynamically allocated.
diff --git a/include/osmocom/core/logging.h b/include/osmocom/core/logging.h
index db02940..b477a66 100644
--- a/include/osmocom/core/logging.h
+++ b/include/osmocom/core/logging.h
@@ -5,7 +5,6 @@
 #include <stdint.h>
 #include <osmocom/core/linuxlist.h>
 
-#define LOG_MAX_CATEGORY	32
 #define LOG_MAX_CTX		8
 #define LOG_MAX_FILTERS	8
 
@@ -20,7 +19,7 @@
 #endif
 
 
-void logp(unsigned int subsys, char *file, int line, int cont, const char *format, ...) __attribute__ ((format (printf, 5, 6)));
+void logp(int subsys, char *file, int line, int cont, const char *format, ...) __attribute__ ((format (printf, 5, 6)));
 
 /* new logging interface */
 #define LOGP(ss, level, fmt, args...) \
@@ -37,6 +36,10 @@
 
 #define LOG_FILTER_ALL	0x0001
 
+/* logging levels defined by the library itself */
+#define DLGLOBAL	-1
+#define OSMO_NUM_DLIB	1
+
 struct log_category {
 	uint8_t loglevel;
 	uint8_t enabled;
@@ -65,8 +68,9 @@
 	log_filter *filter_fn;
 
 	/* per-category information */
-	const struct log_info_cat *cat;
+	struct log_info_cat *cat;
 	unsigned int num_cat;
+	unsigned int num_cat_user;
 };
 
 enum log_target_type {
@@ -82,7 +86,8 @@
 	int filter_map;
 	void *filter_data[LOG_MAX_FILTERS+1];
 
-	struct log_category categories[LOG_MAX_CATEGORY+1];
+	struct log_category *categories;
+
 	uint8_t loglevel;
 	int use_color:1;
 	int print_timestamp:1;
@@ -110,10 +115,10 @@
 };
 
 /* use the above macros */
-void logp2(unsigned int subsys, unsigned int level, char *file,
+void logp2(int subsys, unsigned int level, char *file,
 	   int line, int cont, const char *format, ...)
 				__attribute__ ((format (printf, 6, 7)));
-void log_init(const struct log_info *cat);
+int log_init(const struct log_info *inf, void *talloc_ctx);
 
 /* context management */
 void log_reset_context(void);
diff --git a/src/application.c b/src/application.c
index 5f8f447..b7e943d 100644
--- a/src/application.c
+++ b/src/application.c
@@ -44,7 +44,7 @@
 
 int osmo_init_logging(const struct log_info *log_info)
 {
-	log_init(log_info);
+	log_init(log_info, NULL);
 	osmo_stderr_target = log_target_create_stderr();
 	if (!osmo_stderr_target)
 		return -1;
diff --git a/src/logging.c b/src/logging.c
index 3c9dc03..def13b3 100644
--- a/src/logging.c
+++ b/src/logging.c
@@ -40,7 +40,7 @@
 
 #include <osmocom/vty/logging.h>	/* for LOGGING_STR. */
 
-const struct log_info *osmo_log_info;
+struct log_info *osmo_log_info;
 
 static struct log_context log_context;
 static void *tall_log_ctx = NULL;
@@ -58,6 +58,16 @@
 	{ 0, NULL },
 };
 
+#define INT2IDX(x)	(-1*(x)-1)
+static const struct log_info_cat internal_cat[OSMO_NUM_DLIB] = {
+	[INT2IDX(DLGLOBAL)] = {	/* -1 becomes 0 */
+		.name = "DLGLOBAL",
+		.description = "Library-internal global log family",
+		.loglevel = LOGL_NOTICE,
+		.enabled = 1,
+	},
+};
+
 /* You have to keep this in sync with the structure loglevel_strs. */
 const char *loglevel_descriptions[LOGLEVEL_DEFS+1] = {
 	"Log simply everything",
@@ -69,6 +79,12 @@
 	NULL,
 };
 
+/* special magic for negative (library-internal) log subsystem numbers */
+static int subsys_lib2index(int subsys)
+{
+	return (subsys * -1) + (osmo_log_info->num_cat_user-1);
+}
+
 int log_parse_level(const char *lvl)
 {
 	return get_string_value(loglevel_strs, lvl);
@@ -84,6 +100,8 @@
 	int i;
 
 	for (i = 0; i < osmo_log_info->num_cat; ++i) {
+		if (osmo_log_info->cat[i].name == NULL)
+			continue;
 		if (!strcasecmp(osmo_log_info->cat[i].name+1, category))
 			return i;
 	}
@@ -103,7 +121,7 @@
 	char *category_token = NULL;
 
 	/* Disable everything to enable it afterwards */
-	for (i = 0; i < ARRAY_SIZE(target->categories); ++i)
+	for (i = 0; i < osmo_log_info->num_cat; ++i)
 		target->categories[i].enabled = 0;
 
 	category_token = strtok(mask, ":");
@@ -112,6 +130,9 @@
 			char* colon = strstr(category_token, ",");
 			int length = strlen(category_token);
 
+			if (!osmo_log_info->cat[i].name)
+				continue;
+
 			if (colon)
 			    length = colon - category_token;
 
@@ -189,12 +210,17 @@
 	target->output(target, level, buf);
 }
 
-
-static void _logp(unsigned int subsys, int level, char *file, int line,
+static void _logp(int subsys, int level, char *file, int line,
 		  int cont, const char *format, va_list ap)
 {
 	struct log_target *tar;
 
+	if (subsys < 0)
+		subsys = subsys_lib2index(subsys);
+
+	if (subsys > osmo_log_info->num_cat)
+		subsys = DLGLOBAL;
+
 	llist_for_each_entry(tar, &osmo_log_target_list, entry) {
 		struct log_category *category;
 		int output = 0;
@@ -234,7 +260,7 @@
 	}
 }
 
-void logp(unsigned int subsys, char *file, int line, int cont,
+void logp(int subsys, char *file, int line, int cont,
 	  const char *format, ...)
 {
 	va_list ap;
@@ -244,7 +270,7 @@
 	va_end(ap);
 }
 
-void logp2(unsigned int subsys, unsigned int level, char *file, int line, int cont, const char *format, ...)
+void logp2(int subsys, unsigned int level, char *file, int line, int cont, const char *format, ...)
 {
 	va_list ap;
 
@@ -326,6 +352,14 @@
 	if (!target)
 		return NULL;
 
+	target->categories = talloc_zero_array(target, 
+						struct log_category,
+						osmo_log_info->num_cat);
+	if (!target->categories) {
+		talloc_free(target);
+		return NULL;
+	}
+
 	INIT_LLIST_HEAD(&target->entry);
 
 	/* initialize the per-category enabled/loglevel from defaults */
@@ -441,8 +475,11 @@
 	int size = strlen("logging level () ()") + 1;
 	char *str;
 
-	for (i = 0; i < info->num_cat; i++)
+	for (i = 0; i < info->num_cat; i++) {
+		if (info->cat[i].name == NULL)
+			continue;
 		size += strlen(info->cat[i].name) + 1;
+	}
 
 	for (i = 0; i < LOGLEVEL_DEFS; i++)
 		size += strlen(loglevel_strs[i].str) + 1;
@@ -458,17 +495,19 @@
 	OSMO_SNPRINTF_RET(ret, rem, offset, len);
 
 	for (i = 0; i < info->num_cat; i++) {
-		int j, name_len = strlen(info->cat[i].name)+1;
-		char name[name_len];
+		if (info->cat[i].name) {
+			int j, name_len = strlen(info->cat[i].name)+1;
+			char name[name_len];
 
-		for (j = 0; j < name_len; j++)
-			name[j] = tolower(info->cat[i].name[j]);
+			for (j = 0; j < name_len; j++)
+				name[j] = tolower(info->cat[i].name[j]);
 
-		name[name_len-1] = '\0';
-		ret = snprintf(str + offset, rem, "%s|", name+1);
-		if (ret < 0)
-			goto err;
-		OSMO_SNPRINTF_RET(ret, rem, offset, len);
+			name[name_len-1] = '\0';
+			ret = snprintf(str + offset, rem, "%s|", name+1);
+			if (ret < 0)
+				goto err;
+			OSMO_SNPRINTF_RET(ret, rem, offset, len);
+		}
 	}
 	offset--;	/* to remove the trailing | */
 	rem++;
@@ -512,8 +551,11 @@
 		strlen(LOGGING_STR
 		       "Set the log level for a specified category\n") + 1;
 
-	for (i = 0; i < info->num_cat; i++)
+	for (i = 0; i < info->num_cat; i++) {
+		if (info->cat[i].name == NULL)
+			continue;
 		size += strlen(info->cat[i].description) + 1;
+	}
 
 	for (i = 0; i < LOGLEVEL_DEFS; i++)
 		size += strlen(loglevel_descriptions[i]) + 1;
@@ -537,6 +579,8 @@
 	OSMO_SNPRINTF_RET(ret, rem, offset, len);
 
 	for (i = 0; i < info->num_cat; i++) {
+		if (info->cat[i].name == NULL)
+			continue;
 		ret = snprintf(str + offset, rem, "%s\n",
 				info->cat[i].description);
 		if (ret < 0)
@@ -555,8 +599,36 @@
 	return str;
 }
 
-void log_init(const struct log_info *cat)
+int log_init(const struct log_info *inf, void *ctx)
 {
-	tall_log_ctx = talloc_named_const(NULL, 1, "logging");
-	osmo_log_info = cat;
+	int i;
+
+	tall_log_ctx = talloc_named_const(ctx, 1, "logging");
+	if (!tall_log_ctx)
+		return -ENOMEM;
+
+	osmo_log_info = talloc_zero(tall_log_ctx, struct log_info);
+	if (!osmo_log_info)
+		return -ENOMEM;
+
+	osmo_log_info->num_cat_user = inf->num_cat;
+	/* total number = number of user cat + library cat */
+	osmo_log_info->num_cat = inf->num_cat + OSMO_NUM_DLIB;
+
+	osmo_log_info->cat = talloc_zero_array(osmo_log_info,
+					struct log_info_cat,
+					osmo_log_info->num_cat);
+	if (!osmo_log_info->cat) {
+		talloc_free(osmo_log_info);
+		osmo_log_info = NULL;
+		return -ENOMEM;
+	}
+
+	/* copy over the user part */
+	for (i = 0; i < inf->num_cat; i++) {
+		memcpy(&osmo_log_info->cat[i], &inf->cat[i],
+			sizeof(struct log_info_cat));
+	}
+
+	/* copy over the library part */
 }