Page MenuHomeClusterLabs Projects

No OneTemporary

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, &params_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, &params_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, &params_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, &params_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, &params_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, &notetext);
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, &notetext);
}
/* 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, &params_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, &params_list, returntext, notetext);
}
else {
print_text(name, def, brief, args, detailed, &params_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, &params_list, returntext, notetext);
}
else {
print_text(name, def, brief, args, detailed, &params_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(&params_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

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)

Event Timeline