/* * Copyright (C) 2011,2016 Red Hat, Inc. * * All rights reserved. * * Author: Angus Salkeld * * 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 . */ #include "os_base.h" #include #include #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': #ifndef S_SPLINT_S snprintf(tmp_buf, 30, "%" PRIu32, cs->lineno); #endif /* S_SPLINT_S */ p = tmp_buf; break; case 't': (void)localtime_r(¤t_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; size_t 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; }