libqb/lib/log_format.c
Jan Pokorný c011b12fca
Low: fix internal object symbol's leak & expose run-time lib version
The object in question has never been published through the header file,
hence it's presumably safe to make it static as it's meant to be.

On the other hand, QB_LOG_INIT_DATA macro from qblog.h has already
started to depend on that symbol so as to locate the library handle
for libqb itself correctly.  This is trivially fixed by finally exposing
library versioning info in run-time ("online") as a structure with
members corresponding to compile-time ("offline") counterparts from
qbconfig.h header file, which are admittedly of very limited use
as opposed to the newly introduced dynamic info, plus lower-cased
equivalent of QB_VER_STR.  Better than to roll out a futile data object
serving as an artificial anchor for the above purpose, and this was
due for a while, afterall.

In turn, also bump "current" and "age" of fields of the libtool's
"-version-info" versioning system.

Signed-off-by: Jan Pokorný <jpokorny@redhat.com>
2017-12-12 23:23:46 +01:00

909 lines
20 KiB
C

/*
* Copyright (C) 2011,2016 Red Hat, Inc.
*
* All rights reserved.
*
* Author: Angus Salkeld <asalkeld@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/>.
*/
#include "os_base.h"
#include <ctype.h>
#include <qb/qbdefs.h>
#include "log_int.h"
static qb_log_tags_stringify_fn _user_tags_stringify_fn;
/*
* syslog prioritynames, facility names to value mapping
* Some C libraries build this in to their headers, but it is non-portable
* so logsys supplies its own version.
*/
struct syslog_names {
const char *c_name;
int32_t c_val;
};
static struct syslog_names prioritynames[] = {
{"emerg", LOG_EMERG},
{"alert", LOG_ALERT},
{"crit", LOG_CRIT},
{"error", LOG_ERR},
{"warning", LOG_WARNING},
{"notice", LOG_NOTICE},
{"info", LOG_INFO},
{"debug", LOG_DEBUG},
{"trace", LOG_TRACE},
{NULL, -1}
};
static struct syslog_names facilitynames[] = {
{"auth", LOG_AUTH},
#if defined(LOG_AUTHPRIV)
{"authpriv", LOG_AUTHPRIV},
#endif
{"cron", LOG_CRON},
{"daemon", LOG_DAEMON},
#if defined(LOG_FTP)
{"ftp", LOG_FTP},
#endif
{"kern", LOG_KERN},
{"lpr", LOG_LPR},
{"mail", LOG_MAIL},
{"news", LOG_NEWS},
{"syslog", LOG_SYSLOG},
{"user", LOG_USER},
{"uucp", LOG_UUCP},
{"local0", LOG_LOCAL0},
{"local1", LOG_LOCAL1},
{"local2", LOG_LOCAL2},
{"local3", LOG_LOCAL3},
{"local4", LOG_LOCAL4},
{"local5", LOG_LOCAL5},
{"local6", LOG_LOCAL6},
{"local7", LOG_LOCAL7},
{NULL, -1}
};
static const char log_month_name[][4] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
static pthread_rwlock_t _formatlock;
void
qb_log_format_init(void)
{
int32_t l;
struct qb_log_target *t;
enum qb_log_target_slot i;
l = pthread_rwlock_init(&_formatlock, NULL);
assert(l == 0);
for (i = QB_LOG_TARGET_START; i < QB_LOG_TARGET_MAX; i++) {
t = qb_log_target_get(i);
t->format = strdup("[%p] %b");
}
}
void
qb_log_format_fini(void)
{
struct qb_log_target *t;
enum qb_log_target_slot i;
pthread_rwlock_destroy(&_formatlock);
for (i = QB_LOG_TARGET_START; i < QB_LOG_TARGET_MAX; i++) {
t = qb_log_target_get(i);
free(t->format);
}
}
void
qb_log_format_set(int32_t target, const char *format)
{
char modified_format[256];
struct qb_log_target *t = qb_log_target_get(target);
pthread_rwlock_wrlock(&_formatlock);
free(t->format);
if (format) {
qb_log_target_format_static(target, format, modified_format);
t->format = strdup(modified_format);
} else {
t->format = strdup("[%p] %b");
}
assert(t->format != NULL);
pthread_rwlock_unlock(&_formatlock);
}
/* Convert string "auth" to equivalent number "LOG_AUTH" etc. */
int32_t
qb_log_facility2int(const char *fname)
{
int32_t i;
if (fname == NULL) {
return -EINVAL;
}
for (i = 0; facilitynames[i].c_name != NULL; i++) {
if (strcmp(fname, facilitynames[i].c_name) == 0) {
return facilitynames[i].c_val;
}
}
return -EINVAL;
}
/* Convert number "LOG_AUTH" to equivalent string "auth" etc. */
const char *
qb_log_facility2str(int32_t fnum)
{
int32_t i;
for (i = 0; facilitynames[i].c_name != NULL; i++) {
if (facilitynames[i].c_val == fnum) {
return facilitynames[i].c_name;
}
}
return NULL;
}
const char *
qb_log_priority2str(uint8_t priority)
{
if (priority > LOG_TRACE) {
return prioritynames[LOG_TRACE].c_name;
}
return prioritynames[priority].c_name;
}
void
qb_log_tags_stringify_fn_set(qb_log_tags_stringify_fn fn)
{
_user_tags_stringify_fn = fn;
}
static int
_strcpy_cutoff(char *dest, const char *src, size_t cutoff, int ralign,
size_t buf_len)
{
size_t len = strlen(src);
if (buf_len <= 1) {
if (buf_len == 0)
dest[0] = 0;
return 0;
}
if (cutoff == 0) {
cutoff = len;
}
cutoff = QB_MIN(cutoff, buf_len - 1);
len = QB_MIN(len, cutoff);
if (ralign) {
memset(dest, ' ', cutoff - len);
memcpy(dest + cutoff - len, src, len);
} else {
memcpy(dest, src, len);
memset(dest + len, ' ', cutoff - len);
}
dest[cutoff] = '\0';
return cutoff;
}
/*
* This function will do static formatting (for things that don't
* change on each log message).
*
* %P PID
* %N name passed into qb_log_init
* %H hostname
*
* any number between % and character specify field length to pad or chop
*/
void
qb_log_target_format_static(int32_t target, const char * format,
char *output_buffer)
{
char tmp_buf[255];
unsigned int format_buffer_idx = 0;
unsigned int output_buffer_idx = 0;
size_t cutoff;
uint32_t len;
int ralign;
int c;
struct qb_log_target *t = qb_log_target_get(target);
if (format == NULL) {
return;
}
while ((c = format[format_buffer_idx])) {
cutoff = 0;
ralign = QB_FALSE;
if (c != '%') {
output_buffer[output_buffer_idx++] = c;
format_buffer_idx++;
} else {
const char *p;
unsigned int percent_buffer_idx = format_buffer_idx;
format_buffer_idx += 1;
if (format[format_buffer_idx] == '-') {
ralign = QB_TRUE;
format_buffer_idx += 1;
}
if (isdigit(format[format_buffer_idx])) {
cutoff = atoi(&format[format_buffer_idx]);
}
while (isdigit(format[format_buffer_idx])) {
format_buffer_idx += 1;
}
switch (format[format_buffer_idx]) {
case 'P':
snprintf(tmp_buf, 30, "%d", getpid());
p = tmp_buf;
break;
case 'N':
p = t->name;
break;
case 'H':
if (gethostname(tmp_buf, sizeof(tmp_buf)) == 0) {
tmp_buf[sizeof(tmp_buf) - 1] = '\0';
} else {
(void)strlcpy(tmp_buf, "localhost",
sizeof(tmp_buf));
}
p = tmp_buf;
break;
default:
p = &format[percent_buffer_idx];
cutoff = (format_buffer_idx - percent_buffer_idx + 1);
ralign = QB_FALSE;
break;
}
len = _strcpy_cutoff(output_buffer + output_buffer_idx,
p, cutoff, ralign,
(QB_LOG_MAX_LEN -
output_buffer_idx));
output_buffer_idx += len;
format_buffer_idx += 1;
}
if (output_buffer_idx >= QB_LOG_MAX_LEN - 1) {
break;
}
}
output_buffer[output_buffer_idx] = '\0';
}
/*
* %n FUNCTION NAME
* %f FILENAME
* %l FILELINE
* %p PRIORITY
* %t TIMESTAMP
* %b BUFFER
* %g SUBSYSTEM
*
* any number between % and character specify field length to pad or chop
*/
void
qb_log_target_format(int32_t target,
struct qb_log_callsite *cs,
time_t current_time,
const char *formatted_message, char *output_buffer)
{
char tmp_buf[128];
struct tm tm_res;
unsigned int format_buffer_idx = 0;
unsigned int output_buffer_idx = 0;
size_t cutoff;
uint32_t len;
int ralign;
int c;
struct qb_log_target *t = qb_log_target_get(target);
pthread_rwlock_rdlock(&_formatlock);
if (t->format == NULL) {
pthread_rwlock_unlock(&_formatlock);
return;
}
while ((c = t->format[format_buffer_idx])) {
cutoff = 0;
ralign = QB_FALSE;
if (c != '%') {
output_buffer[output_buffer_idx++] = c;
format_buffer_idx++;
} else {
const char *p;
format_buffer_idx += 1;
if (t->format[format_buffer_idx] == '-') {
ralign = QB_TRUE;
format_buffer_idx += 1;
}
if (isdigit(t->format[format_buffer_idx])) {
cutoff = atoi(&t->format[format_buffer_idx]);
}
while (isdigit(t->format[format_buffer_idx])) {
format_buffer_idx += 1;
}
switch (t->format[format_buffer_idx]) {
case 'g':
if (_user_tags_stringify_fn) {
p = _user_tags_stringify_fn(cs->tags);
} else {
p = "";
}
break;
case 'n':
p = cs->function;
break;
case 'f':
#ifdef BUILDING_IN_PLACE
p = cs->filename;
#else
p = strrchr(cs->filename, '/');
if (p == NULL) {
p = cs->filename;
} else {
p++; /* move past the "/" */
}
#endif /* BUILDING_IN_PLACE */
break;
case 'l':
snprintf(tmp_buf, 30, "%d", cs->lineno);
p = tmp_buf;
break;
case 't':
(void)localtime_r(&current_time, &tm_res);
snprintf(tmp_buf, TIME_STRING_SIZE,
"%s %02d %02d:%02d:%02d",
log_month_name[tm_res.tm_mon],
tm_res.tm_mday, tm_res.tm_hour,
tm_res.tm_min, tm_res.tm_sec);
p = tmp_buf;
break;
case 'b':
p = formatted_message;
break;
case 'p':
if (cs->priority > LOG_TRACE) {
p = prioritynames[LOG_TRACE].c_name;
} else {
p = prioritynames[cs->priority].c_name;
}
break;
default:
p = "";
break;
}
len = _strcpy_cutoff(output_buffer + output_buffer_idx,
p, cutoff, ralign,
(QB_LOG_MAX_LEN -
output_buffer_idx));
output_buffer_idx += len;
format_buffer_idx += 1;
}
if (output_buffer_idx >= QB_LOG_MAX_LEN - 1) {
break;
}
}
pthread_rwlock_unlock(&_formatlock);
if (output_buffer[output_buffer_idx - 1] == '\n') {
output_buffer[output_buffer_idx - 1] = '\0';
} else {
output_buffer[output_buffer_idx] = '\0';
}
}
/*
* These wrappers around strl* functions just return the
* number of characters written, not the number of characters
* requested to be written.
*/
static size_t
my_strlcpy(char *dest, const char * src, size_t maxlen)
{
size_t rc = strlcpy(dest, src, maxlen);
/* maxlen includes NUL, so -1 */
return QB_MIN(rc, maxlen-1);
}
static size_t
my_strlcat(char *dest, const char * src, size_t maxlen)
{
size_t rc = strlcat(dest, src, maxlen);
return QB_MIN(rc, maxlen-1);
}
size_t
qb_vsnprintf_serialize(char *serialize, size_t max_len,
const char *fmt, va_list ap)
{
char *format;
char *p;
char *qb_xc;
int type_long = QB_FALSE;
int type_longlong = QB_FALSE;
int sformat_length = 0;
int sformat_precision = QB_FALSE;
uint32_t location = my_strlcpy(serialize, fmt, max_len) + 1;
/* Assume serialized output always wants extended information
* (@todo: add variant of this function that takes argument for whether
* to print extended information, and make this a macro with that
* argument set to QB_TRUE, so callers can honor extended setting)
*/
if ((qb_xc = strchr(serialize, QB_XC)) != NULL) {
*qb_xc = *(qb_xc + 1)? '|' : '\0';
}
format = (char *)fmt;
for (;;) {
type_long = QB_FALSE;
type_longlong = QB_FALSE;
p = strchrnul((const char *)format, '%');
if (*p == '\0') {
break;
}
format = p + 1;
reprocess:
switch (format[0]) {
case '#': /* alternate form conversion, ignore */
case '-': /* left adjust, ignore */
case ' ': /* a space, ignore */
case '+': /* a sign should be used, ignore */
case '\'': /* group in thousands, ignore */
case 'I': /* glibc-ism locale alternative, ignore */
format++;
goto reprocess;
case '.': /* precision, ignore */
format++;
sformat_precision = QB_TRUE;
goto reprocess;
case '0': /* field width, ignore */
case '1': /* field width, ignore */
case '2': /* field width, ignore */
case '3': /* field width, ignore */
case '4': /* field width, ignore */
case '5': /* field width, ignore */
case '6': /* field width, ignore */
case '7': /* field width, ignore */
case '8': /* field width, ignore */
case '9': /* field width, ignore */
if (sformat_precision) {
sformat_length *= 10;
sformat_length += (format[0] - '0');
}
format++;
goto reprocess;
case '*': /* variable field width, save */ {
int arg_int = va_arg(ap, int);
if (location + sizeof (int) > max_len) {
return max_len;
}
memcpy(&serialize[location], &arg_int, sizeof (int));
location += sizeof(int);
format++;
goto reprocess;
}
case 'l':
format++;
type_long = QB_TRUE;
if (*format == 'l') {
type_long = QB_FALSE;
type_longlong = QB_TRUE;
format++;
}
goto reprocess;
case 'z':
format++;
if (sizeof(size_t) == sizeof(long long)) {
type_longlong = QB_TRUE;
} else {
type_long = QB_TRUE;
}
goto reprocess;
case 't':
format++;
if (sizeof(ptrdiff_t) == sizeof(long long)) {
type_longlong = QB_TRUE;
} else {
type_long = QB_TRUE;
}
goto reprocess;
case 'j':
format++;
if (sizeof(intmax_t) == sizeof(long long)) {
type_longlong = QB_TRUE;
} else {
type_long = QB_TRUE;
}
goto reprocess;
case 'd': /* int argument */
case 'i': /* int argument */
case 'o': /* unsigned int argument */
case 'u':
case 'x':
case 'X':
if (type_long) {
long int arg_int;
if (location + sizeof (long int) > max_len) {
return max_len;
}
arg_int = va_arg(ap, long int);
memcpy(&serialize[location], &arg_int,
sizeof(long int));
location += sizeof(long int);
format++;
break;
} else if (type_longlong) {
long long int arg_int;
if (location + sizeof (long long int) > max_len) {
return max_len;
}
arg_int = va_arg(ap, long long int);
memcpy(&serialize[location], &arg_int,
sizeof(long long int));
location += sizeof(long long int);
format++;
break;
} else {
int arg_int;
if (location + sizeof (int) > max_len) {
return max_len;
}
arg_int = va_arg(ap, int);
memcpy(&serialize[location], &arg_int,
sizeof(int));
location += sizeof(int);
format++;
break;
}
case 'e':
case 'E':
case 'f':
case 'F':
case 'g':
case 'G':
case 'a':
case 'A':
{
double arg_double;
if (location + sizeof (double) > max_len) {
return max_len;
}
arg_double = va_arg(ap, double);
memcpy (&serialize[location], &arg_double, sizeof (double));
location += sizeof(double);
format++;
break;
}
case 'c':
{
int arg_int;
unsigned char arg_char;
if (location + sizeof (unsigned char) > max_len) {
return max_len;
}
/* va_arg only takes fully promoted types */
arg_int = va_arg(ap, unsigned int);
arg_char = (unsigned char)arg_int;
memcpy (&serialize[location], &arg_char, sizeof (unsigned char));
location += sizeof(unsigned char);
break;
}
case 's':
{
char *arg_string;
arg_string = va_arg(ap, char *);
if (arg_string == NULL) {
location += my_strlcpy(&serialize[location],
"(null)",
QB_MIN(strlen("(null)") + 1,
max_len - location));
} else if (sformat_length) {
location += my_strlcpy(&serialize[location],
arg_string,
QB_MIN(sformat_length + 1,
(max_len - location)));
} else {
location += my_strlcpy(&serialize[location],
arg_string,
QB_MIN(strlen(arg_string) + 1,
max_len - location));
}
location++;
break;
}
case 'p':
{
ptrdiff_t arg_pointer = va_arg(ap, ptrdiff_t);
if (location + sizeof (ptrdiff_t) > max_len) {
return max_len;
}
memcpy(&serialize[location], &arg_pointer, sizeof(ptrdiff_t));
location += sizeof(ptrdiff_t);
break;
}
case '%':
if (location + 1 > max_len) {
return max_len;
}
serialize[location++] = '%';
sformat_length = 0;
sformat_precision = QB_FALSE;
break;
}
}
return (location);
}
#define MINI_FORMAT_STR_LEN 20
size_t
qb_vsnprintf_deserialize(char *string, size_t str_len, const char *buf)
{
char *p;
char *format;
char fmt[MINI_FORMAT_STR_LEN];
int fmt_pos;
uint32_t location = 0;
uint32_t data_pos = strlen(buf) + 1;
int type_long = QB_FALSE;
int type_longlong = QB_FALSE;
int len;
string[0] = '\0';
format = (char *)buf;
for (;;) {
type_long = QB_FALSE;
type_longlong = QB_FALSE;
p = strchrnul((const char *)format, '%');
if (*p == '\0') {
return my_strlcat(string, format, str_len) + 1;
}
/* copy from current to the next % */
len = p - format;
memcpy(&string[location], format, len);
location += len;
format = p;
/* start building up the format for snprintf */
fmt_pos = 0;
fmt[fmt_pos++] = *format;
format++;
reprocess:
switch (format[0]) {
case '#': /* alternate form conversion, ignore */
case '-': /* left adjust, ignore */
case ' ': /* a space, ignore */
case '+': /* a sign should be used, ignore */
case '\'': /* group in thousands, ignore */
case 'I': /* glibc-ism locale alternative, ignore */
case '.': /* precision, ignore */
case '0': /* field width, ignore */
case '1': /* field width, ignore */
case '2': /* field width, ignore */
case '3': /* field width, ignore */
case '4': /* field width, ignore */
case '5': /* field width, ignore */
case '6': /* field width, ignore */
case '7': /* field width, ignore */
case '8': /* field width, ignore */
case '9': /* field width, ignore */
fmt[fmt_pos++] = *format;
format++;
goto reprocess;
case '*': {
int arg_int;
memcpy(&arg_int, &buf[data_pos], sizeof(int));
data_pos += sizeof(int);
fmt_pos += snprintf(&fmt[fmt_pos],
MINI_FORMAT_STR_LEN - fmt_pos,
"%d", arg_int);
format++;
goto reprocess;
}
case 'l':
fmt[fmt_pos++] = *format;
format++;
type_long = QB_TRUE;
if (*format == 'l') {
type_long = QB_FALSE;
type_longlong = QB_TRUE;
}
goto reprocess;
case 'z':
fmt[fmt_pos++] = *format;
format++;
if (sizeof(size_t) == sizeof(long long)) {
type_long = QB_FALSE;
type_longlong = QB_TRUE;
} else {
type_longlong = QB_FALSE;
type_long = QB_TRUE;
}
goto reprocess;
case 't':
fmt[fmt_pos++] = *format;
format++;
if (sizeof(ptrdiff_t) == sizeof(long long)) {
type_longlong = QB_TRUE;
} else {
type_long = QB_TRUE;
}
goto reprocess;
case 'j':
fmt[fmt_pos++] = *format;
format++;
if (sizeof(intmax_t) == sizeof(long long)) {
type_longlong = QB_TRUE;
} else {
type_long = QB_TRUE;
}
goto reprocess;
case 'd': /* int argument */
case 'i': /* int argument */
case 'o': /* unsigned int argument */
case 'u':
case 'x':
case 'X':
if (type_long) {
long int arg_int;
fmt[fmt_pos++] = *format;
fmt[fmt_pos++] = '\0';
memcpy(&arg_int, &buf[data_pos], sizeof(long int));
location += snprintf(&string[location],
str_len - location,
fmt, arg_int);
data_pos += sizeof(long int);
format++;
break;
} else if (type_longlong) {
long long int arg_int;
fmt[fmt_pos++] = *format;
fmt[fmt_pos++] = '\0';
memcpy(&arg_int, &buf[data_pos], sizeof(long long int));
location += snprintf(&string[location],
str_len - location,
fmt, arg_int);
data_pos += sizeof(long long int);
format++;
break;
} else {
int arg_int;
fmt[fmt_pos++] = *format;
fmt[fmt_pos++] = '\0';
memcpy(&arg_int, &buf[data_pos], sizeof(int));
location += snprintf(&string[location],
str_len - location,
fmt, arg_int);
data_pos += sizeof(int);
format++;
break;
}
case 'e':
case 'E':
case 'f':
case 'F':
case 'g':
case 'G':
case 'a':
case 'A':
{
double arg_double;
fmt[fmt_pos++] = *format;
fmt[fmt_pos++] = '\0';
memcpy(&arg_double, &buf[data_pos], sizeof(double));
location += snprintf(&string[location],
str_len - location,
fmt, arg_double);
data_pos += sizeof(double);
format++;
break;
}
case 'c':
{
unsigned char *arg_char;
fmt[fmt_pos++] = *format;
fmt[fmt_pos++] = '\0';
arg_char = (unsigned char*)&buf[data_pos];
location += snprintf(&string[location],
str_len - location,
fmt, *arg_char);
data_pos += sizeof(unsigned char);
format++;
break;
}
case 's':
{
fmt[fmt_pos++] = *format;
fmt[fmt_pos++] = '\0';
len = snprintf(&string[location],
str_len - location,
fmt, &buf[data_pos]);
location += len;
/* don't use len as there might be a len modifier */
data_pos += strlen(&buf[data_pos]) + 1;
format++;
break;
}
case 'p':
{
ptrdiff_t pt;
memcpy(&pt, &buf[data_pos],
sizeof(ptrdiff_t));
fmt[fmt_pos++] = *format;
fmt[fmt_pos++] = '\0';
location += snprintf(&string[location],
str_len - location,
fmt, pt);
data_pos += sizeof(void*);
format++;
break;
}
case '%':
string[location++] = '%';
format++;
break;
}
}
return location;
}