| /* |
| * 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': |
| #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; |
| } |
| |