Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F1842182
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
57 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/doxygen2man/doxygen2man.c b/doxygen2man/doxygen2man.c
index 4253aee..47fb3c1 100644
--- a/doxygen2man/doxygen2man.c
+++ b/doxygen2man/doxygen2man.c
@@ -1,1195 +1,1206 @@
/*
* Copyright (C) 2018-2020 Red Hat, Inc. All rights reserved.
*
* Author: Christine Caulfield <ccaulfie@redhat.com>
*
* This software licensed under GPL-2.0+
*/
/*
* NOTE: this code is very rough, it does the bare minimum to parse the
* XML out from doxygen and is probably very fragile to changes in that XML
* schema. It probably leaks memory all over the place too.
*
* In its favour, it *does* generate nice man pages and should only be run very ocasionally
*/
#define _DEFAULT_SOURCE
#define _BSD_SOURCE
#define _XOPEN_SOURCE
#define _XOPEN_SOURCE_EXTENDED
#include <stdlib.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <time.h>
#include <stdio.h>
#include <limits.h>
#include <string.h>
#include <getopt.h>
#include <errno.h>
#include <ctype.h>
#include <libxml/tree.h>
#include <qb/qblist.h>
#include <qb/qbmap.h>
/*
* This isn't a maximum size, it just defines how long a parameter
* type can get before we decide it's not worth lining everything up.
* It's mainly to stop function pointer types (which can get VERY long because
* of all *their* parameters) making everything else 'line-up' over separate lines
*/
#define LINE_LENGTH 80
static int print_ascii = 1;
static int print_man = 0;
static int print_params = 0;
static int print_general = 0;
static int num_functions = 0;
static int quiet = 0;
static int use_header_copyright = 0;
static const char *man_section="3";
static const char *package_name="Package";
static const char *header="Programmer's Manual";
static const char *company="Red Hat";
static const char *output_dir="./";
static const char *xml_dir = "./xml/";
static const char *xml_file;
static const char *manpage_date = NULL;
static const char *headerfile = NULL;
static const char *header_prefix = "";
static const char *header_src_dir = "./";
static char header_copyright[256] = "\0";
static long manpage_year = LONG_MIN;
static long start_year = 2010;
static struct qb_list_head params_list;
static struct qb_list_head retval_list;
static qb_map_t *function_map;
static qb_map_t *structures_map;
static qb_map_t *used_structures_map;
struct param_info {
char *paramname;
char *paramtype;
char *paramdesc;
struct param_info *next;
struct qb_list_head list;
};
struct struct_info {
enum {STRUCTINFO_STRUCT, STRUCTINFO_ENUM} kind;
char *structname;
char *description;
char *brief_description;
struct qb_list_head params_list; /* our params */
struct qb_list_head list;
};
static char *get_texttree(int *type, xmlNode *cur_node, char **returntext, char **notetext);
static void traverse_node(xmlNode *parentnode, const char *leafname, void (do_members(xmlNode*, void*)), void *arg);
static void free_paraminfo(struct param_info *pi)
{
free(pi->paramname);
free(pi->paramtype);
free(pi->paramdesc);
free(pi);
}
static char *get_attr(xmlNode *node, const char *tag)
{
xmlAttr *this_attr;
for (this_attr = node->properties; this_attr; this_attr = this_attr->next) {
if (this_attr->type == XML_ATTRIBUTE_NODE && strcmp((char *)this_attr->name, tag) == 0) {
return strdup((char *)this_attr->children->content);
}
}
return NULL;
}
static char *get_child(xmlNode *node, const char *tag)
{
xmlNode *this_node;
xmlNode *child;
char buffer[1024] = {'\0'};
char *refid = NULL;
char *declname = NULL;
for (this_node = node->children; this_node; this_node = this_node->next) {
if ((strcmp( (char*)this_node->name, "declname") == 0)) {
declname = strdup((char*)this_node->children->content);
}
if ((this_node->type == XML_ELEMENT_NODE && this_node->children) && ((strcmp((char *)this_node->name, tag) == 0))) {
refid = NULL;
for (child = this_node->children; child; child = child->next) {
if (child->content) {
strncat(buffer, (char *)child->content, sizeof(buffer)-1);
}
if ((strcmp( (char*)child->name, "ref") == 0)) {
if (child->children->content) {
strncat(buffer,(char *)child->children->content, sizeof(buffer)-1);
}
refid = get_attr(child, "refid");
}
}
}
if (declname && refid) {
qb_map_put(used_structures_map, refid, declname);
}
}
return strdup(buffer);
}
static struct param_info *find_param_by_name(struct qb_list_head *list, const char *name)
{
struct qb_list_head *iter;
struct param_info *pi;
qb_list_for_each(iter, list) {
pi = qb_list_entry(iter, struct param_info, list);
if (strcmp(pi->paramname, name) == 0) {
return pi;
}
}
return NULL;
}
static int not_all_whitespace(char *string)
{
unsigned int i;
for (i=0; i<strlen(string); i++) {
if (string[i] != ' ' &&
string[i] != '\n' &&
string[i] != '\r' &&
string[i] != '\t')
return 1;
}
return 0;
}
static void get_param_info(xmlNode *cur_node, struct qb_list_head *list)
{
xmlNode *this_tag;
xmlNode *sub_tag;
char *paramname = NULL;
char *paramdesc = NULL;
struct param_info *pi;
/* This is not robust, and very inflexible */
for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) {
for (sub_tag = this_tag->children; sub_tag; sub_tag = sub_tag->next) {
if (sub_tag->type == XML_ELEMENT_NODE && strcmp((char *)sub_tag->name, "parameternamelist") == 0 &&
sub_tag->children->next->children) {
paramname = (char*)sub_tag->children->next->children->content;
}
if (sub_tag->type == XML_ELEMENT_NODE && strcmp((char *)sub_tag->name, "parameterdescription") == 0 &&
paramname && sub_tag->children->next->children) {
paramdesc = (char*)sub_tag->children->next->children->content;
/* Add text to the param_map */
pi = find_param_by_name(list, paramname);
if (pi) {
pi->paramdesc = paramdesc;
}
else {
pi = malloc(sizeof(struct param_info));
if (pi) {
pi->paramname = paramname;
pi->paramdesc = paramdesc;
pi->paramtype = NULL; /* retval */
qb_list_add_tail(&pi->list, list);
}
}
}
}
}
}
static char *get_text(xmlNode *cur_node, char **returntext, char **notetext)
{
xmlNode *this_tag;
xmlNode *sub_tag;
char *kind;
char buffer[4096] = {'\0'};
for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) {
if (this_tag->type == XML_TEXT_NODE && strcmp((char *)this_tag->name, "text") == 0) {
if (not_all_whitespace((char*)this_tag->content)) {
strncat(buffer, (char*)this_tag->content, sizeof(buffer)-1);
}
}
if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "emphasis") == 0) {
if (print_man) {
strncat(buffer, "\\fB", sizeof(buffer)-1);
}
strncat(buffer, (char*)this_tag->children->content, sizeof(buffer)-1);
if (print_man) {
strncat(buffer, "\\fR", sizeof(buffer)-1);
}
}
if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "ref") == 0) {
if (print_man) {
strncat(buffer, "\\fI", sizeof(buffer)-1);
}
strncat(buffer, (char*)this_tag->children->content, sizeof(buffer)-1);
if (print_man) {
strncat(buffer, "\\fR", sizeof(buffer)-1);
}
}
+ if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "computeroutput") == 0) {
+ if (print_man) {
+ strncat(buffer, "\\fB", sizeof(buffer)-1);
+ }
+ strncat(buffer, (char*)this_tag->children->content, sizeof(buffer)-1);
+ if (print_man) {
+ strncat(buffer, "\\fP", sizeof(buffer)-1);
+ }
+ }
if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "itemizedlist") == 0) {
for (sub_tag = this_tag->children; sub_tag; sub_tag = sub_tag->next) {
if (sub_tag->type == XML_ELEMENT_NODE && strcmp((char *)sub_tag->name, "listitem") == 0) {
strncat(buffer, (char*)sub_tag->children->children->content, sizeof(buffer)-1);
strncat(buffer, "\n", sizeof(buffer)-1);
}
}
}
/* Look for subsections - return value & params */
if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "simplesect") == 0) {
char *tmp;
kind = get_attr(this_tag, "kind");
tmp = get_text(this_tag->children, NULL, NULL);
if (returntext && strcmp(kind, "return") == 0) {
*returntext = tmp;
}
if (notetext && strcmp(kind, "note") == 0) {
*notetext = tmp;
}
}
if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "parameterlist") == 0) {
kind = get_attr(this_tag, "kind");
if (strcmp(kind, "param") == 0) {
get_param_info(this_tag, ¶ms_list);
}
if (strcmp(kind, "retval") == 0) {
get_param_info(this_tag, &retval_list);
}
}
}
return strdup(buffer);
}
static void read_structname(xmlNode *cur_node, void *arg)
{
struct struct_info *si=arg;
xmlNode *this_tag;
for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) {
if (strcmp((char*)this_tag->name, "compoundname") == 0) {
si->structname = strdup((char*)this_tag->children->content);
}
}
}
static void read_structdesc(xmlNode *cur_node, void *arg)
{
struct struct_info *si=arg;
xmlNode *this_tag;
for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) {
if (strcmp((char*)this_tag->name, "detaileddescription") == 0) {
char *desc = get_texttree(NULL, this_tag, NULL, NULL);
if (desc) {
si->description = strdup((char*)desc);
}
}
if (strcmp((char*)this_tag->name, "briefdescription") == 0) {
char *brief = get_texttree(NULL, this_tag, NULL, NULL);
if (brief) {
si->brief_description = brief;
}
}
}
}
static void read_headername(xmlNode *cur_node, void *arg)
{
char **h_file = arg;
xmlNode *this_tag;
for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) {
if (strcmp((char*)this_tag->name, "compoundname") == 0) {
*h_file = strdup((char*)this_tag->children->content);
}
}
}
/* Called from traverse_node() */
static void read_struct(xmlNode *cur_node, void *arg)
{
xmlNode *this_tag;
struct struct_info *si=arg;
struct param_info *pi;
char fullname[1024];
char *type = NULL;
char *name = NULL;
const char *args="";
for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) {
if (strcmp((char*)this_tag->name, "type") == 0) {
type = (char*)this_tag->children->content;
/* If type is NULL then look for a ref - it's probably an external struct or typedef */
if (type == NULL) {
type = get_child(this_tag, "ref");
}
}
if (strcmp((char*)this_tag->name, "name") == 0) {
name = (char*)this_tag->children->content;
}
if (this_tag->children && strcmp((char*)this_tag->name, "argsstring") == 0) {
args = (char*)this_tag->children->content;
}
}
if (name) {
pi = malloc(sizeof(struct param_info));
if (pi) {
snprintf(fullname, sizeof(fullname), "%s%s", name, args);
pi->paramtype = type?strdup(type):strdup("");
pi->paramname = strdup(fullname);
pi->paramdesc = NULL;
qb_list_add_tail(&pi->list, &si->params_list);
}
}
}
static int read_structure_from_xml(const char *refid, const char *name)
{
char fname[PATH_MAX];
xmlNode *rootdoc;
xmlDocPtr doc;
struct struct_info *si;
struct stat st;
int ret = -1;
snprintf(fname, sizeof(fname), "%s/%s.xml", xml_dir, refid);
/* Don't call into libxml if the file does not exist - saves unwanted error messages */
if (stat(fname, &st) == -1) {
return -1;
}
doc = xmlParseFile(fname);
if (doc == NULL) {
fprintf(stderr, "Error: unable to open xml file for %s\n", refid);
return -1;
}
rootdoc = xmlDocGetRootElement(doc);
if (!rootdoc) {
fprintf(stderr, "Can't find \"document root\"\n");
return -1;
}
si = malloc(sizeof(struct struct_info));
if (si) {
memset(si, 0, sizeof(*si));
si->kind = STRUCTINFO_STRUCT;
qb_list_init(&si->params_list);
traverse_node(rootdoc, "memberdef", read_struct, si);
traverse_node(rootdoc, "compounddef", read_structdesc, si);
traverse_node(rootdoc, "compounddef", read_structname, si);
ret = 0;
qb_map_put(structures_map, refid, si);
}
xmlFreeDoc(doc);
return ret;
}
static char *allcaps(const char *name)
{
static char buffer[1024] = {'\0'};
size_t i;
for (i=0; i< strlen(name); i++) {
buffer[i] = toupper(name[i]);
}
buffer[strlen(name)] = '\0';
return buffer;
}
static void print_param(FILE *manfile, struct param_info *pi, int field_width, int bold, const char *delimiter)
{
const char *asterisks = " ";
char *type = pi->paramtype;
int typelength = strlen(type);
/* Reformat pointer params so they look nicer */
if (typelength > 0 && pi->paramtype[typelength-1] == '*') {
asterisks=" *";
type = strdup(pi->paramtype);
type[typelength-1] = '\0';
/* Cope with double pointers */
if (typelength > 1 && pi->paramtype[typelength-2] == '*') {
asterisks="**";
type[typelength-2] = '\0';
}
/* Tidy function pointers */
if (typelength > 1 && pi->paramtype[typelength-2] == '(') {
asterisks="(*";
type[typelength-2] = '\0';
}
}
fprintf(manfile, " %s%-*s%s%s\\fI%s\\fP%s\n",
bold?"\\fB":"", field_width, type,
asterisks, bold?"\\fP":"", pi->paramname, delimiter);
if (type != pi->paramtype) {
free(type);
}
}
static void print_structure(FILE *manfile, struct struct_info *si)
{
struct param_info *pi;
struct qb_list_head *iter;
unsigned int max_param_length=0;
fprintf(manfile, ".nf\n");
fprintf(manfile, "\\fB\n");
if (si->brief_description) {
fprintf(manfile, "%s\n", si->brief_description);
}
if (si->description) {
fprintf(manfile, "%s\n", si->description);
}
qb_list_for_each(iter, &si->params_list) {
pi = qb_list_entry(iter, struct param_info, list);
if (strlen(pi->paramtype) > max_param_length) {
max_param_length = strlen(pi->paramtype);
}
}
if (si->kind == STRUCTINFO_STRUCT) {
fprintf(manfile, "struct %s {\n", si->structname);
} else if (si->kind == STRUCTINFO_ENUM) {
fprintf(manfile, "enum %s {\n", si->structname);
} else {
fprintf(manfile, "%s {\n", si->structname);
}
qb_list_for_each(iter, &si->params_list) {
pi = qb_list_entry(iter, struct param_info, list);
print_param(manfile, pi, max_param_length, 0,";");
}
fprintf(manfile, "};\n");
fprintf(manfile, "\\fP\n");
fprintf(manfile, ".fi\n");
}
char *get_texttree(int *type, xmlNode *cur_node, char **returntext, char **notetext)
{
xmlNode *this_tag;
char *tmp = NULL;
char buffer[4096] = {'\0'};
for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) {
if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "para") == 0) {
tmp = get_text(this_tag, returntext, notetext);
strncat(buffer, tmp, sizeof(buffer)-1);
strncat(buffer, "\n", sizeof(buffer)-1);
free(tmp);
}
}
if (buffer[0]) {
return strdup(buffer);
} else {
return NULL;
}
}
/* The text output is VERY basic and just a check that it's working really */
static void print_text(char *name, char *def, char *brief, char *args, char *detailed,
struct qb_list_head *param_list, char *returntext, char *notetext)
{
printf(" ------------------ %s --------------------\n", name);
printf("NAME\n");
if (brief) {
printf(" %s - %s\n", name, brief);
} else {
printf(" %s\n", name);
}
printf("SYNOPSIS\n");
printf(" #include <%s%s>\n", header_prefix, headerfile);
if (args) {
printf(" %s %s\n\n", name, args);
}
if (detailed) {
printf("DESCRIPTION\n");
printf(" %s\n", detailed);
}
if (returntext) {
printf("RETURN VALUE\n");
printf(" %s\n", returntext);
}
if (notetext) {
printf("NOTE\n");
printf(" %s\n", notetext);
}
}
/* Print a long string with para marks in it. */
static void man_print_long_string(FILE *manfile, char *text)
{
char *next_nl;
char *current = text;
next_nl = strchr(text, '\n');
while (next_nl && *next_nl != '\0') {
*next_nl = '\0';
if (strlen(current)) {
fprintf(manfile, ".PP\n%s\n", current);
}
*next_nl = '\n';
current = next_nl+1;
next_nl = strchr(current, '\n');
}
/* The bit at the end */
if (strlen(current)) {
fprintf(manfile, ".PP\n%s\n", current);
}
}
static void print_manpage(char *name, char *def, char *brief, char *args, char *detailed,
struct qb_list_head *param_map, char *returntext, char *notetext)
{
char manfilename[PATH_MAX];
char gendate[64];
const char *dateptr = gendate;
FILE *manfile;
time_t t;
struct tm *tm;
qb_map_iter_t *map_iter;
struct qb_list_head *iter;
struct qb_list_head *tmp;
const char *p;
void *data;
unsigned int max_param_type_len;
unsigned int max_param_name_len;
unsigned int num_param_descs;
int param_count = 0;
int param_num = 0;
struct param_info *pi;
t = time(NULL);
tm = localtime(&t);
if (!tm) {
perror("unable to get localtime");
exit(1);
}
strftime(gendate, sizeof(gendate), "%Y-%m-%d", tm);
if (manpage_date) {
dateptr = manpage_date;
}
if (manpage_year == LONG_MIN) {
manpage_year = tm->tm_year+1900;
}
snprintf(manfilename, sizeof(manfilename), "%s/%s.%s", output_dir, name, man_section);
manfile = fopen(manfilename, "w+");
if (!manfile) {
perror("unable to open output file");
printf("%s", manfilename);
exit(1);
}
/* Work out the length of the parameters, so we can line them up */
max_param_type_len = 0;
max_param_name_len = 0;
num_param_descs = 0;
qb_list_for_each(iter, ¶ms_list) {
pi = qb_list_entry(iter, struct param_info, list);
/* It's mainly macros that break this,
* macros need more work
*/
if (!pi->paramtype) {
pi->paramtype = strdup("");
}
if ((strlen(pi->paramtype) < LINE_LENGTH) &&
(strlen(pi->paramtype) > max_param_type_len)) {
max_param_type_len = strlen(pi->paramtype);
}
if (strlen(pi->paramname) > max_param_name_len) {
max_param_name_len = strlen(pi->paramname);
}
if (pi->paramdesc) {
num_param_descs++;
}
param_count++;
}
/* Off we go */
fprintf(manfile, ".\\\" Automatically generated man page, do not edit\n");
fprintf(manfile, ".TH %s %s %s \"%s\" \"%s\"\n", allcaps(name), man_section, dateptr, package_name, header);
fprintf(manfile, ".SH NAME\n");
if (brief) {
fprintf(manfile, "%s \\- %s\n", name, brief);
} else {
fprintf(manfile, "%s\n", name);
}
fprintf(manfile, ".SH SYNOPSIS\n");
fprintf(manfile, ".nf\n");
fprintf(manfile, ".B #include <%s%s>\n", header_prefix, headerfile);
if (def) {
fprintf(manfile, ".sp\n");
fprintf(manfile, "\\fB%s\\fP(\n", def);
qb_list_for_each(iter, ¶ms_list) {
pi = qb_list_entry(iter, struct param_info, list);
print_param(manfile, pi, max_param_type_len, 1, ++param_num < param_count?",":"");
}
fprintf(manfile, ");\n");
fprintf(manfile, ".fi\n");
}
if (print_params && num_param_descs) {
fprintf(manfile, ".SH PARAMS\n");
qb_list_for_each(iter, ¶ms_list) {
pi = qb_list_entry(iter, struct param_info, list);
fprintf(manfile, "\\fB%-*s \\fP\\fI%s\\fP\n", (int)max_param_name_len, pi->paramname,
pi->paramdesc);
fprintf(manfile, ".PP\n");
}
}
if (detailed) {
fprintf(manfile, ".SH DESCRIPTION\n");
man_print_long_string(manfile, detailed);
}
if (qb_map_count_get(used_structures_map)) {
int first_struct = 1;
map_iter = qb_map_iter_create(used_structures_map);
for (p = qb_map_iter_next(map_iter, &data); p; p = qb_map_iter_next(map_iter, &data)) {
struct struct_info *si;
const char *refid = p;
char *refname = data;
/* If it's not been read in - go and look for it */
si = qb_map_get(structures_map, refid);
if (!si) {
if (!read_structure_from_xml(refid, refname)) {
si = qb_map_get(structures_map, refid);
}
}
/* Only print header if the struct files exist - sometimes they don't */
if (si && first_struct) {
fprintf(manfile, ".SH STRUCTURES\n");
first_struct = 0;
}
if (si) {
print_structure(manfile, si);
fprintf(manfile, ".PP\n");
}
}
qb_map_iter_free(map_iter);
fprintf(manfile, ".RE\n");
}
- if (returntext) {
+ if (returntext || !qb_list_empty(&retval_list)) {
fprintf(manfile, ".SH RETURN VALUE\n");
- man_print_long_string(manfile, returntext);
+ if (returntext) {
+ man_print_long_string(manfile, returntext);
+ }
fprintf(manfile, ".PP\n");
}
qb_list_for_each(iter, &retval_list) {
pi = qb_list_entry(iter, struct param_info, list);
fprintf(manfile, "\\fB%-*s \\fP%s\n", 10, pi->paramname,
pi->paramdesc);
fprintf(manfile, ".PP\n");
}
if (notetext) {
fprintf(manfile, ".SH NOTE\n");
man_print_long_string(manfile, notetext);
}
fprintf(manfile, ".SH SEE ALSO\n");
fprintf(manfile, ".PP\n");
fprintf(manfile, ".nh\n");
fprintf(manfile, ".ad l\n");
param_num = 0;
map_iter = qb_map_iter_create(function_map);
for (p = qb_map_iter_next(map_iter, &data); p; p = qb_map_iter_next(map_iter, &data)) {
/* Exclude us! */
if (strcmp(data, name)) {
fprintf(manfile, "\\fI%s\\fR(%s)%s", (char *)data, man_section,
param_num < (num_functions - 1)?", ":"");
}
param_num++;
}
qb_map_iter_free(map_iter);
fprintf(manfile, "\n");
fprintf(manfile, ".ad\n");
fprintf(manfile, ".hy\n");
fprintf(manfile, ".SH \"COPYRIGHT\"\n");
fprintf(manfile, ".PP\n");
if (header_copyright[0] == 'C') {
fprintf(manfile, "%s", header_copyright); /* String already contains trailing NL */
} else {
fprintf(manfile, "Copyright (C) %4ld-%4ld %s, Inc. All rights reserved.\n", start_year, manpage_year, company);
}
fclose(manfile);
/* Free the params & retval info */
qb_list_for_each_safe(iter, tmp, ¶ms_list) {
pi = qb_list_entry(iter, struct param_info, list);
qb_list_del(&pi->list);
free_paraminfo(pi);
}
qb_list_for_each_safe(iter, tmp, &retval_list) {
pi = qb_list_entry(iter, struct param_info, list);
qb_list_del(&pi->list);
free_paraminfo(pi);
}
/* Free used-structures map */
map_iter = qb_map_iter_create(used_structures_map);
for (p = qb_map_iter_next(map_iter, &data); p; p = qb_map_iter_next(map_iter, &data)) {
qb_map_rm(used_structures_map, p);
free(data);
}
}
/* Same as traverse_members, but to collect function names */
static void collect_functions(xmlNode *cur_node, void *arg)
{
xmlNode *this_tag;
char *kind;
char *name = NULL;
if (cur_node->name && strcmp((char *)cur_node->name, "memberdef") == 0) {
kind = get_attr(cur_node, "kind");
if (kind && strcmp(kind, "function") == 0) {
for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) {
if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "name") == 0) {
name = strdup((char *)this_tag->children->content);
}
}
if (name) {
qb_map_put(function_map, name, name);
num_functions++;
}
}
}
}
/* Same as traverse_members, but to collect enums. The behave like structures for,
but, for some reason, are in the main XML file rather than their own */
static void collect_enums(xmlNode *cur_node, void *arg)
{
xmlNode *this_tag;
struct struct_info *si;
char *kind;
char *refid = NULL;
char *name = NULL;
if (cur_node->name && strcmp((char *)cur_node->name, "memberdef") == 0) {
kind = get_attr(cur_node, "kind");
if (kind && strcmp(kind, "enum") == 0) {
refid = get_attr(cur_node, "id");
for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) {
if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "name") == 0) {
name = strdup((char *)this_tag->children->content);
}
}
if (name) {
si = malloc(sizeof(struct struct_info));
if (si) {
memset(si, 0, sizeof(*si));
si->kind = STRUCTINFO_ENUM;
qb_list_init(&si->params_list);
si->structname = strdup(name);
traverse_node(cur_node, "enumvalue", read_struct, si);
qb_map_put(structures_map, refid, si);
}
}
}
}
}
static void traverse_members(xmlNode *cur_node, void *arg)
{
xmlNode *this_tag;
/* if arg == NULL then we're generating a page for the whole header file */
if ((cur_node->name && (strcmp((char *)cur_node->name, "memberdef") == 0)) ||
((arg == NULL) && cur_node->name && strcmp((char *)cur_node->name, "compounddef")) == 0) {
char *kind = NULL;
char *def = NULL;
char *args = NULL;
char *name = NULL;
char *brief = NULL;
char *detailed = NULL;
char *returntext = NULL;
char *notetext = NULL;
int type;
kind=def=args=name=NULL;
kind = get_attr(cur_node, "kind");
for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next)
{
if (!this_tag->children || !this_tag->children->content)
continue;
if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "definition") == 0)
def = strdup((char *)this_tag->children->content);
if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "argsstring") == 0)
args = strdup((char *)this_tag->children->content);
if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "name") == 0)
name = strdup((char *)this_tag->children->content);
if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "briefdescription") == 0) {
brief = get_texttree(&type, this_tag, &returntext, ¬etext);
if (brief) {
/*
* apparently brief text contains extra trailing space and a \n.
* remove them.
*/
brief[strlen(brief) - 2] = '\0';
}
}
if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "detaileddescription") == 0) {
detailed = get_texttree(&type, this_tag, &returntext, ¬etext);
}
/* Get all the params */
if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "param") == 0) {
char *param_type = get_child(this_tag, "type");
char *param_name = get_child(this_tag, "declname");
struct param_info *pi = malloc(sizeof(struct param_info));
if (pi) {
pi->paramname = param_name;
pi->paramtype = param_type;
pi->paramdesc = NULL;
qb_list_add_tail(&pi->list, ¶ms_list);
}
}
}
if (arg == headerfile) {
/* Print header page */
name = (char*)headerfile;
if (print_man) {
if (!quiet) {
printf("Printing header manpage for %s\n", name);
}
print_manpage(name, def, brief, args, detailed, ¶ms_list, returntext, notetext);
}
else {
print_text(name, def, brief, args, detailed, ¶ms_list, returntext, notetext);
}
}
if (kind && strcmp(kind, "function") == 0) {
/* Make sure function has a doxygen description */
if (!detailed) {
fprintf(stderr, "No detailed description for function '%s' - please fix this\n", name);
}
if (!name) {
fprintf(stderr, "Internal error - no name found for function\n");
} else {
if (print_man) {
if (!quiet) {
printf("Printing manpage for %s\n", name);
}
print_manpage(name, def, brief, args, detailed, ¶ms_list, returntext, notetext);
}
else {
print_text(name, def, brief, args, detailed, ¶ms_list, returntext, notetext);
}
}
}
free(kind);
free(def);
free(args);
free(name);
}
}
static void traverse_node(xmlNode *parentnode, const char *leafname, void (do_members(xmlNode*, void*)), void *arg)
{
xmlNode *cur_node;
for (cur_node = parentnode->children; cur_node; cur_node = cur_node->next) {
if (cur_node->type == XML_ELEMENT_NODE && cur_node->name
&& strcmp((char*)cur_node->name, leafname)==0) {
do_members(cur_node, arg);
continue;
}
if (cur_node->type == XML_ELEMENT_NODE) {
traverse_node(cur_node, leafname, do_members, arg);
}
}
}
static void usage(char *name)
{
printf("Usage:\n");
printf(" %s [OPTIONS] <XML file>\n", name);
printf("\n");
printf(" This is a tool to generate API manpages from a doxygen-annotated header file.\n");
printf(" First run doxygen on the file and then run this program against the main XML file\n");
printf(" it created and the directory containing the ancilliary files. It will then\n");
printf(" output a lot of *.3 man page files which you can then ship with your library.\n");
printf("\n");
printf(" You will need to invoke this program once for each .h file in your library,\n");
printf(" using the name of the generated .xml file. This file will usually be called\n");
printf(" something like <include-file>_8h.xml, eg qbipcs_8h.xml\n");
printf("\n");
printf(" If you want HTML output then simpy use nroff on the generated files as you\n");
printf(" would do with any other man page.\n");
printf("\n");
printf(" -a Print ASCII dump of man pages to stdout\n");
printf(" -m Write man page files to <output dir>\n");
printf(" -P Print PARAMS section\n");
printf(" -g Print general man page for the whole header file\n");
printf(" -c Use the Copyright date from the header file (if one can be found)\n");
printf(" -O <dir> Directory for the orignal header file. Often needed by -c above\n");
printf(" -s <s> Write man pages into section <s> <default 3)\n");
printf(" -p <package> Use <package> name. default <Package>\n");
printf(" -H <header> Set header (default \"Programmer's Manual\"\n");
printf(" -I <include> Set include filename (default taken from xml)\n");
printf(" -i <prefix> Prefix for include files. eg qb/ (default \"\")\n");
printf(" -C <company> Company name in copyright (defaults to Red Hat)\n");
printf(" -D <date> Date to print at top of man pages (format not checked, default: today)\n");
printf(" -S <year> Start year to print at end of copyright line (default: 2010)\n");
printf(" -Y <year> Year to print at end of copyright line (default: today's year)\n");
printf(" -o <dir> Write all man pages to <dir> (default .)\n");
printf(" -d <dir> Directory for XML files (./xml/)\n");
printf(" -h Print this usage text\n");
}
static long get_year(char *optionarg, char optionchar)
{
long year = strtol(optionarg, NULL, 10);
/*
* Don't make too many assumptions about the year. I was on call at the
* 2000 rollover. #experience
*/
if (year == LONG_MIN || year == LONG_MAX ||
year < 1900) {
fprintf(stderr, "Value passed to -%c is not a valid year number\n", optionchar);
return 0;
}
return year;
}
int main(int argc, char *argv[])
{
xmlNode *rootdoc;
xmlDocPtr doc;
int opt;
char xml_filename[PATH_MAX];
while ( (opt = getopt_long(argc, argv, "H:amqgcPD:Y:s:S:d:o:p:f:I:i:C:O:h?", NULL, NULL)) != EOF)
{
switch(opt)
{
case 'a':
print_ascii = 1;
print_man = 0;
break;
case 'm':
print_man = 1;
print_ascii = 0;
break;
case 'P':
print_params = 1;
break;
case 'g':
print_general = 1;
break;
case 'q':
quiet = 1;
break;
case 'c':
use_header_copyright = 1;
break;
case 'I':
headerfile = optarg;
break;
case 'i':
header_prefix = optarg;
break;
case 'C':
company = optarg;
break;
case 's':
man_section = optarg;
break;
case 'S':
start_year = get_year(optarg, 'S');
if (start_year == 0) {
return 1;
}
break;
case 'd':
xml_dir = optarg;
break;
case 'D':
manpage_date = optarg;
break;
case 'Y':
manpage_year = get_year(optarg, 'Y');
if (manpage_year == 0) {
return 1;
}
break;
case 'p':
package_name = optarg;
break;
case 'H':
header = optarg;
break;
case 'o':
output_dir = optarg;
break;
case 'O':
header_src_dir = optarg;
break;
case '?':
case 'h':
usage(argv[0]);
return 0;
}
}
if (argv[optind]) {
xml_file = argv[optind];
}
if (!xml_file) {
usage(argv[0]);
exit(1);
}
if (!quiet) {
printf("reading %s ... ", xml_file);
}
snprintf(xml_filename, sizeof(xml_filename), "%s/%s", xml_dir, xml_file);
doc = xmlParseFile(xml_filename);
if (doc == NULL) {
fprintf(stderr, "Error: unable to read xml file %s\n", xml_filename);
exit(1);
}
rootdoc = xmlDocGetRootElement(doc);
if (!rootdoc) {
fprintf(stderr, "Can't find \"document root\"\n");
exit(1);
}
if (!quiet)
printf("done.\n");
/* Get our header file name */
if (!headerfile) {
traverse_node(rootdoc, "compounddef", read_headername, &headerfile);
if (use_header_copyright) {
/* And get the copyright line from this file if we can */
char file_path[PATH_MAX];
char file_line[256];
FILE *hfile;
int lineno = 0;
snprintf(file_path, sizeof(file_path), "%s/%s", header_src_dir, headerfile);
hfile = fopen(file_path, "r");
if (hfile) {
/* Don't look too far, this should be at the top */
while (!feof(hfile) && (lineno++ < 10)) {
if (fgets(file_line, sizeof(file_line)-1, hfile)) {
if (strncmp(file_line, " * Copyright", 12) == 0) {
/* Keep the NL at the end of the buffer, it save us printing one */
strncpy(header_copyright, file_line+3, sizeof(header_copyright)-1);
break;
}
}
}
fclose(hfile);
}
}
}
/* Default to *something* if it all goes wrong */
if (!headerfile) {
headerfile = "unknown.h";
}
qb_list_init(¶ms_list);
qb_list_init(&retval_list);
structures_map = qb_hashtable_create(10);
function_map = qb_hashtable_create(10);
used_structures_map = qb_hashtable_create(10);
/* Collect functions */
traverse_node(rootdoc, "memberdef", collect_functions, NULL);
/* Collect enums */
traverse_node(rootdoc, "memberdef", collect_enums, NULL);
/* print pages */
traverse_node(rootdoc, "memberdef", traverse_members, NULL);
if (print_general) {
/* Generate and print a page for the headerfile itself */
traverse_node(rootdoc, "compounddef", traverse_members, (char *)headerfile);
}
return 0;
}
diff --git a/include/qb/qblog.h b/include/qb/qblog.h
index 31981b8..d8f0882 100644
--- a/include/qb/qblog.h
+++ b/include/qb/qblog.h
@@ -1,748 +1,758 @@
/*
* Copyright (c) 2017 Red Hat, Inc.
*
* All rights reserved.
*
* Author: Angus Salkeld <asalkeld@redhat.com>
* Jan Pokorny <jpokorny@redhat.com>
*
* 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/>.
*/
#ifndef QB_LOG_H_DEFINED
#define QB_LOG_H_DEFINED
/* *INDENT-OFF* */
#ifdef __cplusplus
extern "C" {
#endif
/* *INDENT-ON* */
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <errno.h>
#include <syslog.h>
#include <string.h>
#include <qb/qbutil.h>
#include <qb/qbconfig.h>
/**
* @file qblog.h
* The logging API provides four main parts (basics, filtering, threading & blackbox).
*
* The idea behind this logging system is not to be prescriptive but to provide a
* set of tools to help the developer achieve what they want quickly and easily.
*
* @par Basic logging API.
* Call qb_log() to generate a log message. Then to write the message
* somewhere meaningful call qb_log_ctl() to configure the targets.
*
* Simplest possible use:
* @code
* main() {
* qb_log_init("simple-log", LOG_DAEMON, LOG_INFO);
* // ...
* qb_log(LOG_WARNING, "watch out");
* // ...
* qb_log_fini();
* }
* @endcode
*
* @par Configuring log targets.
* A log target can be syslog, stderr, the blackbox, stdout, or a text file.
* By default, only syslog is enabled. While this is usual for daemons,
* it is rarely appropriate for ordinary programs, which should
* disable it when other targets (see below) are to be used:
* @code
* qb_log_ctl(B_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_FALSE);
* @endcode
*
* To enable a target do the following:
* @code
* qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE);
* @endcode
*
* syslog, stderr, the blackbox, and stdout are static (they don't need
* to be created, just enabled or disabled). However, you can open multiple
* logfiles (falling within inclusive range @c QB_LOG_TARGET_DYNAMIC_START
* up to @c QB_LOG_TARGET_DYNAMIC_END). To do this, use the following code:
* @code
* mytarget = qb_log_file_open("/var/log/mylogfile");
* qb_log_ctl(mytarget, QB_LOG_CONF_ENABLED, QB_TRUE);
* @endcode
*
* Once your targets are enabled/opened, you can configure them as follows:
* Configure the size of blackbox:
* @code
* qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_SIZE, 1024*10);
* @endcode
*
* Make logging to file threaded:
* @code
* qb_log_ctl(mytarget, QB_LOG_CONF_THREADED, QB_TRUE);
* @endcode
*
* Sometimes, syslog daemons are (pre)configured to filter messages not
* exceeding a particular priority. When this happens to be the logging
* target, the designated priority of the message is passed along unchanged,
* possibly resulting in message loss. For messages up to @c LOG_DEBUG
* importance, this can be worked around by proportionally bumping the
* priorities to be passed to syslog (here, the step is such that
* @c LOG_DEBUG gets promoted to @c LOG_INFO):
* @code
* qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_PRIORITY_BUMP,
* LOG_INFO - LOG_DEBUG);
* @endcode
*
* To ensure all logs to file targets are fsync'ed (new messages expressly
* transferred to the storage device as they keep coming, otherwise defaults
* to @c QB_FALSE):
* @code
* qb_log_ctl(mytarget, QB_LOG_CONF_FILE_SYNC, QB_TRUE);
* @endcode
*
*
* @par Filtering messages.
* To have more power over what log messages go to which target you can apply
* filters to the targets. What happens is the desired callsites have the
* correct bit set. Then when the log message is generated it gets sent to the
* targets based on which bit is set in the callsite's "target" bitmap.
* Messages can be filtered based on the:
* -# filename + priority
* -# function name + priority
* -# format string + priority
*
* So to make all logs from evil_function() go to stderr, do the following:
* @code
* qb_log_filter_ctl(QB_LOG_STDERR, QB_LOG_FILTER_ADD,
* QB_LOG_FILTER_FUNCTION, "evil_function", LOG_TRACE);
* @endcode
*
* So to make all logs from totem* (with a priority <= LOG_INFO) go to stderr,
* do the following:
* @code
* qb_log_filter_ctl(QB_LOG_STDERR, QB_LOG_FILTER_ADD,
* QB_LOG_FILTER_FILE, "totem", LOG_INFO);
* @endcode
*
* So to make all logs with the substring "ringbuffer" go to stderr,
* do the following:
* @code
* qb_log_filter_ctl(QB_LOG_STDERR, QB_LOG_FILTER_ADD,
* QB_LOG_FILTER_FORMAT, "ringbuffer", LOG_TRACE);
* @endcode
*
* @par Thread safe non-blocking logging.
* Logging is only thread safe when threaded logging is in use. If you plan
* on logging from multiple threads, you must initialize libqb's logger thread
* and use qb_log_filter_ctl to set the QB_LOG_CONF_THREADED flag on all the
* logging targets in use.
*
* To achieve non-blocking logging, so that any calls to write() or syslog()
* will not hold up your program, you can use threaded logging as well.
*
* Threaded logging use:
* @code
* main() {
* qb_log_init("simple-log", LOG_DAEMON, LOG_INFO);
* qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_THREADED, QB_TRUE);
* // ...
* daemonize();
* // call this after you fork()
* qb_log_thread_start();
* // ...
* qb_log(LOG_WARNING, "watch out");
* // ...
* qb_log_fini();
* }
* @endcode
*
* @par A blackbox for in-field diagnosis.
* This stores log messages in a ringbuffer so they can be written to
* file if the program crashes (you will need to catch SIGSEGV). These
* can then be easily printed out later.
*
* @note the blackbox is not enabled by default.
*
* Blackbox usage:
* @code
*
* static void sigsegv_handler(int sig)
* {
* (void)signal (SIGSEGV, SIG_DFL);
* qb_log_blackbox_write_to_file("simple-log.fdata");
* qb_log_fini();
* raise(SIGSEGV);
* }
*
* main() {
*
* signal(SIGSEGV, sigsegv_handler);
*
* qb_log_init("simple-log", LOG_DAEMON, LOG_INFO);
* qb_log_filter_ctl(QB_LOG_BLACKBOX, QB_LOG_FILTER_ADD,
* QB_LOG_FILTER_FILE, "*", LOG_DEBUG);
* qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_SIZE, 1024*10);
* qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE);
* // ...
* qb_log(LOG_WARNING, "watch out");
* // ...
* qb_log_fini();
* }
* @endcode
*
* @par Tagging messages.
* You can tag messages using the second argument to qb_logt() or
* by using qb_log_filter_ctl().
* This can be used to add feature or sub-system information to the logs.
*
* @code
* const char* my_tags_stringify(uint32_t tags) {
* if (qb_bit_is_set(tags, QB_LOG_TAG_LIBQB_MSG_BIT) {
* return "libqb";
* } else if (tags == 3) {
* return "three";
* } else {
* return "MAIN";
* }
* }
* main() {
* // ...
* qb_log_tags_stringify_fn_set(my_tags_stringify);
* qb_log_format_set(QB_LOG_STDERR, "[%5g] %p %b");
* // ...
* qb_logt(LOG_INFO, 3, "hello");
* qb_logt(LOG_INFO, 0, "hello");
* }
* @endcode
* The code above will produce:
* @code
* [libqb] some message
* [three] info hello
* [MAIN ] info hello
* @endcode
*
* @example simplelog.c
*/
#undef LOG_TRACE
#define LOG_TRACE (LOG_DEBUG + 1)
#define QB_LOG_MAX_LEN 512
#define QB_LOG_ABSOLUTE_MAX_LEN 4096
#define QB_LOG_STRERROR_MAX_LEN 128
typedef const char *(*qb_log_tags_stringify_fn)(uint32_t tags);
/**
* An instance of this structure is created for each log message
*/
struct qb_log_callsite {
const char *function;
const char *filename;
const char *format;
uint8_t priority;
uint32_t lineno;
uint32_t targets;
uint32_t tags;
} __attribute__((aligned(8)));
typedef void (*qb_log_filter_fn)(struct qb_log_callsite * cs);
#define QB_LOG_INIT_DATA(name)
/**
* Internal function: use qb_log() or qb_logt()
*/
void qb_log_real_(struct qb_log_callsite *cs, ...);
void qb_log_real_va_(struct qb_log_callsite *cs, va_list ap);
#define QB_LOG_TAG_LIBQB_MSG_BIT 31
#define QB_LOG_TAG_LIBQB_MSG (1U << QB_LOG_TAG_LIBQB_MSG_BIT)
/**
* This function is to import logs from other code (like libraries)
* that provide a callback with their logs.
*
* @note the performance of this will not impress you, as
* the filtering is done on each log message, not
* beforehand. So try doing basic pre-filtering.
*
* @param function originating function name
* @param filename originating filename
* @param format format string
* @param priority this takes syslog priorities.
* @param lineno file line number
* @param tags this is a uint32_t that you can use with
* qb_log_tags_stringify_fn_set() to "tag" a log message
* with a feature or sub-system then you can use "%g"
* in the format specifer to print it out.
*/
void qb_log_from_external_source(const char *function,
const char *filename,
const char *format,
uint8_t priority,
uint32_t lineno,
uint32_t tags,
...)
__attribute__ ((format (printf, 3, 7)));
/**
* Get or create a callsite at the given position.
*
* The result can then be passed into qb_log_real_()
*
* @param function originating function name
* @param filename originating filename
* @param format format string
* @param priority this takes syslog priorities.
* @param lineno file line number
* @param tags the tag
*/
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);
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)
__attribute__ ((format (printf, 3, 0)));
/**
* This is the function to generate a log message if you want to
* manually add tags.
*
* @param priority this takes syslog priorities.
* @param tags this is a uint32_t that you can use with
* qb_log_tags_stringify_fn_set() to "tag" a log message
* with a feature or sub-system then you can use "%g"
* in the format specifer to print it out.
* @param fmt usual printf style format specifiers
* @param args usual printf style args
*/
#define qb_logt(priority, tags, fmt, args...) do { \
struct qb_log_callsite* descriptor_pt = \
qb_log_callsite_get(__func__, __FILE__, fmt, \
priority, __LINE__, tags); \
qb_log_real_(descriptor_pt, ##args); \
} while(0)
/**
* This is the main function to generate a log message.
*
* @param priority this takes syslog priorities.
* @param fmt usual printf style format specifiers
* @param args usual printf style args
*/
#define qb_log(priority, fmt, args...) qb_logt(priority, 0, fmt, ##args)
/* Define the character used to mark the beginning of "extended" information;
* a string equivalent is also defined so clients can use it like:
* qb_log(level, "blah blah "QB_XS" yada yada", __func__);
*/
#define QB_XC '\a'
#define QB_XS "\a"
/**
* This is similar to perror except it goes into the logging system.
*
* @param priority this takes syslog priorities.
* @param fmt usual printf style format specifiers
* @param args usual printf style args
*
* @note Because qb_perror() adds the system error message and error number onto
* the end of the given fmt, that information will become extended
* information if QB_XS is used inside fmt and will not show up in any
* logs that strip extended information.
*/
#ifndef S_SPLINT_S
#define qb_perror(priority, fmt, args...) do { \
char _perr_buf_[QB_LOG_STRERROR_MAX_LEN]; \
const char *_perr_str_ = qb_strerror_r(errno, _perr_buf_, sizeof(_perr_buf_)); \
qb_logt(priority, 0, fmt ": %s (%d)", ##args, _perr_str_, errno); \
} while(0)
#else
#define qb_perror
#endif
#define qb_enter() qb_log(LOG_TRACE, "ENTERING %s()", __func__)
#define qb_leave() qb_log(LOG_TRACE, "LEAVING %s()", __func__)
/*
* Note that QB_LOG_TARGET_{STATIC_,}MAX are sentinel indexes
* as non-inclusive higher bounds of the respective categories
* (static and all the log targets) and also denote the number
* of (reserved) items in the category. Both are possibly subject
* to change, so you should always refer to them using
* these defined values.
* Similarly, there are QB_LOG_TARGET_{STATIC_,DYNAMIC_,}START
* and QB_LOG_TARGET_{STATIC_,DYNAMIC_,}END values, but these
* are inclusive lower and higher bounds, respectively.
*/
enum qb_log_target_slot {
QB_LOG_TARGET_START,
/* static */
QB_LOG_TARGET_STATIC_START = QB_LOG_TARGET_START,
QB_LOG_SYSLOG = QB_LOG_TARGET_STATIC_START,
QB_LOG_STDERR,
QB_LOG_BLACKBOX,
QB_LOG_STDOUT,
QB_LOG_TARGET_STATIC_MAX,
QB_LOG_TARGET_STATIC_END = QB_LOG_TARGET_STATIC_MAX - 1,
/* dynamic */
QB_LOG_TARGET_DYNAMIC_START = QB_LOG_TARGET_STATIC_MAX,
QB_LOG_TARGET_MAX = 32,
QB_LOG_TARGET_DYNAMIC_END = QB_LOG_TARGET_MAX - 1,
QB_LOG_TARGET_END = QB_LOG_TARGET_DYNAMIC_END,
};
enum qb_log_target_state {
QB_LOG_STATE_UNUSED = 1,
QB_LOG_STATE_DISABLED = 2,
QB_LOG_STATE_ENABLED = 3,
};
enum qb_log_conf {
QB_LOG_CONF_ENABLED,
QB_LOG_CONF_FACILITY,
QB_LOG_CONF_DEBUG,
QB_LOG_CONF_SIZE,
QB_LOG_CONF_THREADED,
QB_LOG_CONF_PRIORITY_BUMP,
QB_LOG_CONF_STATE_GET,
QB_LOG_CONF_FILE_SYNC,
QB_LOG_CONF_EXTENDED,
QB_LOG_CONF_IDENT,
QB_LOG_CONF_MAX_LINE_LEN,
QB_LOG_CONF_ELLIPSIS,
QB_LOG_CONF_USE_JOURNAL,
};
enum qb_log_filter_type {
QB_LOG_FILTER_FILE,
QB_LOG_FILTER_FUNCTION,
QB_LOG_FILTER_FORMAT,
QB_LOG_FILTER_FILE_REGEX,
QB_LOG_FILTER_FUNCTION_REGEX,
QB_LOG_FILTER_FORMAT_REGEX,
};
enum qb_log_filter_conf {
QB_LOG_FILTER_ADD,
QB_LOG_FILTER_REMOVE,
QB_LOG_FILTER_CLEAR_ALL,
QB_LOG_TAG_SET,
QB_LOG_TAG_CLEAR,
QB_LOG_TAG_CLEAR_ALL,
};
typedef void (*qb_log_logger_fn)(int32_t t,
struct qb_log_callsite *cs,
struct timespec *timestamp,
const char *msg);
typedef void (*qb_log_vlogger_fn)(int32_t t,
struct qb_log_callsite *cs,
struct timespec *timestamp,
va_list ap);
typedef void (*qb_log_close_fn)(int32_t t);
typedef void (*qb_log_reload_fn)(int32_t t);
/**
* Init the logging system.
*
* @param name will be passed into openlog()
* @param facility default for all new targets.
* @param priority a basic filter with this priority will be added.
*/
void qb_log_init(const char *name,
int32_t facility,
uint8_t priority);
/**
* Logging system finalization function.
*
* It releases any shared memory.
* Stops the logging thread if running.
* Flushes the last messages to their destinations.
*/
void qb_log_fini(void);
/**
* If you are using dynamically loadable modules via dlopen() and
* you load them after qb_log_init() then after you load the module
* you will need to do the following to get the filters to work
* in that module:
* @code
* _start = dlsym (dl_handle, QB_ATTR_SECTION_START_STR);
* _stop = dlsym (dl_handle, QB_ATTR_SECTION_STOP_STR);
* qb_log_callsites_register(_start, _stop);
* @endcode
*/
int32_t qb_log_callsites_register(struct qb_log_callsite *_start, struct qb_log_callsite *_stop);
/**
* Dump the callsite info to stdout.
*/
void qb_log_callsites_dump(void);
/**
* Main logging control function.
*
* @param target QB_LOG_SYSLOG, QB_LOG_STDERR or result from qb_log_file_open()
* @param conf_type configuration directive ("what to configure") that accepts
* @c int32_t argument determining the new value unless ignored
* for particular directive altogether
* (incompatible directives: QB_LOG_CONF_IDENT)
* @param arg the new value for a state-changing configuration directive,
* ignored otherwise
* @see qb_log_conf
*
* @retval -errno on error
* @retval 0 on success
* @retval qb_log_target_state for QB_LOG_CONF_STATE_GET
*/
int32_t qb_log_ctl(int32_t target, enum qb_log_conf conf_type, int32_t arg);
typedef union {
int32_t i32;
const char *s;
} qb_log_ctl2_arg_t;
/**
* Extension of main logging control function accepting also strings.
*
* @param target QB_LOG_SYSLOG, QB_LOG_STDERR or result from qb_log_file_open()
* @param conf_type configuration directive ("what to configure") that accepts
* either @c int32_t or a null-terminated string argument
* determining the new value unless ignored for particular directive
* (compatible directives: those valid for qb_log_ctl
* + QB_LOG_CONF_IDENT)
* @param arg the new value for a state-changing configuration directive,
* ignored otherwise; for QB_LOG_CONF_IDENT, 's' member as new
* identifier to openlog(), for all qb_log_ctl-compatible ones,
* 'i32' member is assumed (although a preferred way is to use
* that original function directly as it allows for more type safety)
* @see qb_log_ctl
*
* @note You can use @ref QB_LOG_CTL2_I32 and @ref QB_LOG_CTL2_S macros
* for a convenient on-the-fly construction of the object
* to be passed as an @p arg argument.
*/
int32_t qb_log_ctl2(int32_t target, enum qb_log_conf conf_type,
qb_log_ctl2_arg_t arg);
# ifndef S_SPLINT_S
#define QB_LOG_CTL2_I32(a) ((qb_log_ctl2_arg_t) { .i32 = (a) })
#define QB_LOG_CTL2_S(a) ((qb_log_ctl2_arg_t) { .s = (a) })
#else
#define QB_LOG_CTL2_I32(a) ((qb_log_ctl2_arg_t)(a))
#define QB_LOG_CTL2_S(a) ((qb_log_ctl2_arg_t)(a))
#endif
/**
* This allows you modify the 'tags' and 'targets' callsite fields at runtime.
*/
int32_t qb_log_filter_ctl(int32_t value, enum qb_log_filter_conf c,
enum qb_log_filter_type type, const char * text,
uint8_t low_priority);
/**
* This extends qb_log_filter_ctl() by been able to provide a high_priority.
*/
int32_t qb_log_filter_ctl2(int32_t value, enum qb_log_filter_conf c,
enum qb_log_filter_type type, const char * text,
uint8_t high_priority, uint8_t low_priority);
/**
* Instead of using the qb_log_filter_ctl() functions you
* can apply the filters manually by defining a callback
* and setting the targets field using qb_bit_set() and
* qb_bit_clear() like the following below:
* @code
* static void
* m_filter(struct qb_log_callsite *cs)
* {
* if ((cs->priority >= LOG_ALERT &&
* cs->priority <= LOG_DEBUG) &&
* strcmp(cs->filename, "my_c_file.c") == 0) {
* qb_bit_set(cs->targets, QB_LOG_SYSLOG);
* } else {
* qb_bit_clear(cs->targets, QB_LOG_SYSLOG);
* }
* }
* @endcode
*/
int32_t qb_log_filter_fn_set(qb_log_filter_fn fn);
/**
* Set the callback to map the 'tags' bit map to a string.
*/
void qb_log_tags_stringify_fn_set(qb_log_tags_stringify_fn fn);
/**
*This is a Feature Test macro so that calling applications know that
* millisecond timestamps are implemented. Because %T a string in
* function call with an indirect effect, there is no easy test for it
* beyond the library version (which is a very blunt instrument)
*/
#define QB_FEATURE_LOG_HIRES_TIMESTAMPS 1
/**
* Set the format specifiers.
*
- * %n FUNCTION NAME
- * %f FILENAME
- * %l FILELINE
- * %p PRIORITY
- * %t TIMESTAMP
- * %T TIMESTAMP with milliseconds
- * %b BUFFER
- * %g TAGS
- * %N name (passed into qb_log_init)
- * %P PID
- * %H hostname
+ * \%n FUNCTION NAME
+ *
+ * \%f FILENAME
+ *
+ * \%l FILELINE
+ *
+ * \%p PRIORITY
+ *
+ * \%t TIMESTAMP
+ *
+ * \%T TIMESTAMP with milliseconds
+ *
+ * \%b BUFFER
+ *
+ * \%g TAGS
+ *
+ * \%N name (passed into qb_log_init)
+ *
+ * \%P PID
+ *
+ * \%H hostname
*
* Any number between % and character specify field length to pad or chop.
*
* @note Some of the fields are immediately evaluated and remembered
* for performance reasons, so whenlog messages carry PIDs (not the default)
* this function needs to be reinvoked following @c fork
* (@c clone) in the respective children. When already linking
* with @c libpthread, @c pthread_atfork callback registration
* could be useful.
*/
void qb_log_format_set(int32_t t, const char* format);
/**
* Open a log file.
*
* @retval -errno on error
* @retval value in inclusive range QB_LOG_TARGET_DYNAMIC_START
* to QB_LOG_TARGET_DYNAMIC_END
* (to be passed into other qb_log_* functions)
*/
int32_t qb_log_file_open(const char *filename);
/**
* Close a log file and release its resources.
*/
void qb_log_file_close(int32_t t);
/**
* Open a new log file for an existing target
* @param t target
* @param filename may be NULL to use existing file name
*
* @retval -errno on error
*
*/
int32_t qb_log_file_reopen(int32_t t, const char *filename);
/**
* When using threaded logging set the pthread policy and priority.
*
* @retval -errno on error
* @retval 0 success
*/
int32_t qb_log_thread_priority_set(int32_t policy, int32_t priority);
/**
* Start the logging pthread.
*/
int32_t qb_log_thread_start(void);
/**
* Write the blackbox to file.
*/
ssize_t qb_log_blackbox_write_to_file(const char *filename);
/**
* Read the blackbox for file and print it out.
*/
int qb_log_blackbox_print_from_file(const char* filename);
/**
* Open a custom log target.
*
* @retval -errno on error
* @retval value in inclusive range QB_LOG_TARGET_DYNAMIC_START
* to QB_LOG_TARGET_DYNAMIC_END
* (to be passed into other qb_log_* functions)
*/
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);
/**
* Close a custom log target and release its resources.
*/
void qb_log_custom_close(int32_t t);
/**
* Retrieve the user data set by either qb_log_custom_open or
* qb_log_target_user_data_set.
*/
void *qb_log_target_user_data_get(int32_t t);
/**
* Associate user data with this log target.
* @note only use this with custom targets
*/
int32_t qb_log_target_user_data_set(int32_t t, void *user_data);
/**
* Format the callsite and timestamp info according to the format.
* set using qb_log_format_set()
* It is intended to be used from your custom logger function.
*/
void qb_log_target_format(int32_t target,
struct qb_log_callsite *cs,
struct timespec *timestamp,
const char* formatted_message,
char *output_buffer);
/**
* Convert string "auth" to equivalent number "LOG_AUTH" etc.
*/
int32_t qb_log_facility2int(const char *fname);
/**
* Convert number "LOG_AUTH" to equivalent string "auth" etc.
*/
const char * qb_log_facility2str(int32_t fnum);
/* *INDENT-OFF* */
#ifdef __cplusplus
}
#endif /* __cplusplus */
/* *INDENT-ON* */
#endif /* QB_LOG_H_DEFINED */
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Nov 23, 1:34 PM (17 h, 56 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1014763
Default Alt Text
(57 KB)
Attached To
Mode
rQ LibQB
Attached
Detach File
Event Timeline
Log In to Comment