socat/vsnprintf_r.c
2023-06-12 23:01:54 +02:00

569 lines
16 KiB
C

/* vsnprintf_r.c */
/* Copyright Gerhard Rieger and contributors (see file CHANGES) */
/* a reduced but async-signal-safe and thread-safe version of vsnprintf */
#include "config.h"
#include <stddef.h> /* ptrdiff_t */
#include <ctype.h> /* isdigit() */
#include <stdarg.h>
#include <stdlib.h>
#include <errno.h>
#if HAVE_SYSLOG_H
#include <syslog.h>
#endif
#include <sys/utsname.h>
#include <time.h> /* time_t, strftime() */
#include <sys/time.h> /* gettimeofday() */
#include <stdio.h>
#include <string.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include "vsnprintf_r.h"
/* helper functions for vsnprintf_r():
e.g. convert an unsigned long to decimal string.
in: field (must be long enough for all digits and \0
n: length of field in bytes
ulo: the value
returns: the pointer to the result string (need not be ==field)
*/
/* this function converts an unsigned long number to decimal ASCII
it is async signal safe and thread safe
it returns NULL if n==0
it returns NULL if field is too short to hold the result
it returns a pointer to the result string (somewhere within field)
it terminates result with \0
*/
static char *_diag_ulong_to_dec(char *field, size_t n, unsigned long ulo) {
char *np = field+n; /* point to the end */
if (n == 0) return NULL;
*--np = '\0'; /* \0 in last char of string */
/* this is not optimal - uses much CPU, but simple to implement */
/* calculate the result from right to left */
do { if (np==field) return NULL; *--np = '0'+(ulo%10); } while (ulo/=10);
return np;
}
/* this function converts an unsigned long number to decimal ASCII
and pads it with space or '0' when size and leading0 are set appropriately
it is async signal safe and thread safe
it returns NULL if n==0
it returns NULL if field is too short to hold the result
it returns a pointer to the result string (somewhere within field)
it reduces size to n-1 if it is greater or equal
it terminates result with \0
*/
static char *diag_ulong_to_dec(char *field, size_t n, unsigned long ulo, int leading0, int size) {
char *np;
char c;
int i;
if (n == 0) return NULL;
np = _diag_ulong_to_dec(field, n, ulo);
if (np == NULL) return np;
if (size) {
if (size >= n) size = n-1;
if (leading0) {
c = '0';
} else {
c = ' ';
}
i = size - strlen(np);
while (--i >= 0) {
*--np = c;
}
}
return np;
}
/* this function converts a signed long number to decimal ASCII
and pads it with space or '0' when size and leading0 are set appropriately
it is async signal safe and thread safe
it returns NULL if n==0
it returns NULL if field is too short to hold the result
it returns a pointer to the result string (somewhere within field)
it reduces size to n-1 if it is greater or equal
it terminates result with \0
*/
/* like diag_ulong_to_dec but signed; fields need also space for '-' */
static char *diag_long_to_dec(char *field, size_t n, long lo, int leading0, int size) {
char *np;
int minus;
unsigned long ulo;
int i;
if ((minus = (lo < 0))) {
ulo = (~lo)+1;
} else {
ulo = lo;
}
np = _diag_ulong_to_dec(field, n, ulo);
if (np == NULL) return np;
if (size) {
if (size >= n) size = n-1;
i = size - strlen(np);
if (leading0) {
if (minus) --i;
while (--i >= 0) {
*--np = '0';
}
if (minus) *--np = '-';
} else {
if (minus) { *--np = '-'; --i; }
while (--i >= 0) {
*--np = ' ';
}
}
} else {
if (minus) *--np = '-';
}
return np;
}
/* this function converts an unsigned long number to hexadecimal ASCII
it is async signal safe and thread safe
it returns NULL if n==0
it returns NULL if field is too short to hold the result
it returns a pointer to the result string (somewhere within field)
it terminates result with \0
*/
static char *diag_ulong_to_hex(char *field, size_t n, unsigned long ulo, int leading0, size_t size) {
char *np = field+n; /* point to the end */
int i;
char c;
if (n == 0) return NULL;
*--np = '\0'; /* \0 in last char of string */
/* calculate the result from right to left */
do { if (np==field) return NULL; i = (ulo&0x0f);
*--np = (i<10?'0':('a'-10))+i; }
while (ulo>>=4);
if (size) {
if (size >= n) size = n-1;
if (leading0) {
c = '0';
} else {
c = ' ';
}
i = size - strlen(np);
while (--i >= 0) {
*--np = c;
}
}
return np;
}
/* this function converts an unsigned long number to octal ASCII
it is async signal safe and thread safe
it returns NULL if n==0
it returns NULL if field is too short to hold the result
it returns a pointer to the result string (somewhere within field)
it terminates result with \0
*/
static char *diag_ulong_to_oct(char *field, size_t n, unsigned long ulo, int leading0, size_t size) {
char *np = field+n; /* point to the end */
int i;
char c;
if (n == 0) return NULL;
*--np = '\0'; /* \0 in last char of string */
/* calculate the result from right to left */
do { if (np==field) return NULL; i = (ulo&0x07); *--np = '0'+i; }
while (ulo>>=3);
if (size) {
if (size >= n) size = n-1;
if (leading0) {
c = '0';
} else {
c = ' ';
}
i = size - strlen(np);
while (--i >= 0) {
*--np = c;
}
}
return np;
}
#if HAVE_TYPE_LONGLONG
/* this function converts an unsigned long long number to decimal ASCII
it is async signal safe and thread safe
it returns NULL if n==0
it returns NULL if field is too short to hold the result
it returns a pointer to the result string (somewhere within field)
it terminates result with \0
*/
static char *_diag_ulonglong_to_dec(char *field, size_t n, unsigned long long ull) {
char *np = field+n; /* point to the end */
if (n == 0) return NULL;
*--np = '\0'; /* \0 in last char of string */
/* this is not optimal - uses much CPU, but simple to implement */
/* calculate the result from right to left */
do { if (np==field) return NULL; *--np = '0'+(ull%10); } while (ull/=10);
return np;
}
/* this function converts an unsigned long long number to decimal ASCII
and pads it with space or '0' when size and leading0 are set appropriately
it is async signal safe and thread safe
it returns NULL if n==0
it returns NULL if field is too short to hold the result
it returns a pointer to the result string (somewhere within field)
it reduces size to n-1 if it is greater or equal
it terminates result with \0
*/
static char *diag_ulonglong_to_dec(char *field, size_t n, unsigned long long ull, int leading0, int size) {
char *np;
char c;
int i;
if (n == 0) return NULL;
np = _diag_ulonglong_to_dec(field, n, ull);
if (size) {
if (size >= n) size = n-1;
if (leading0) {
c = '0';
} else {
c = ' ';
}
i = size - strlen(np);
while (i-- > 0) {
*--np = c;
}
}
return np;
}
/* this function converts a signed long long number to decimal ASCII
and pads it with space or '0' when size and leading0 are set appropriately
it is async signal safe and thread safe
it returns NULL if n==0
it returns NULL if field is too short to hold the result
it returns a pointer to the result string (somewhere within field)
it reduces size to n-1 if it is greater or equal
it terminates result with \0
*/
/* like diag_ulonglong_to_dec but signed; fields need also space for '-' */
static char *diag_longlong_to_dec(char *field, size_t n, long long ll, int leading0, int size) {
char *np;
int minus;
unsigned long ull;
int i;
if ((minus = (ll < 0))) {
ull = (~ll)+1;
} else {
ull = ll;
}
np = _diag_ulonglong_to_dec(field, n, ull);
if (np == NULL) return np;
if (size) {
if (size >= n) size = n-1;
i = size - strlen(np);
if (leading0) {
if (minus) --i;
while (--i >= 0) {
*--np = '0';
}
if (minus) *--np = '-';
} else {
if (minus) { *--np = '-'; --i; }
while (--i >= 0) {
*--np = ' ';
}
}
}
return np;
}
/* this function converts an unsigned long long number to hexadecimal ASCII
it is async signal safe and thread safe
it returns NULL if n==0
it returns NULL if field is too short to hold the result
it returns a pointer to the result string (somewhere within field)
it terminates result with \0
*/
static char *diag_ulonglong_to_hex(char *field, size_t n, unsigned long long ull, int leading0, size_t size) {
char *np = field+n; /* point to the end */
ptrdiff_t i;
char c;
if (n == 0) return NULL;
*--np = '\0'; /* \0 in last char of string */
/* calculate the result from right to left */
do { if (np==field) return NULL; i = (ull&0x0f);
*--np = (i<10?'0':('a'-10))+i; }
while (ull>>=4);
if (size) {
if (size >= n) size = n-1;
if (leading0) {
c = '0';
} else {
c = ' ';
}
i = size - strlen(np);
while (i-- > 0) {
*--np = c;
}
}
return np;
}
/* this function converts an unsigned long long number to octal ASCII
it is async signal safe and thread safe
it returns NULL if n==0
it returns NULL if field is too short to hold the result
it returns a pointer to the result string (somewhere within field)
it terminates result with \0
*/
static char *diag_ulonglong_to_oct(char *field, size_t n, unsigned long long ull, int leading0, size_t size) {
char *np = field+n; /* point to the end */
int i;
char c;
if (n == 0) return NULL;
*--np = '\0'; /* \0 in last char of string */
/* calculate the result from right to left */
do { if (np==field) return NULL; i = (ull&0x07); *--np = '0'+i; }
while (ull>>=3);
if (size) {
if (size >= n) size = n-1;
if (leading0) {
c = '0';
} else {
c = ' ';
}
i = size - strlen(np);
while (--i >= 0) {
*--np = c;
}
}
return np;
}
#endif /* HAVE_TYPE_LONGLONG */
/* this function is designed as a variant of vsnprintf(3) but async signal safe
and thread safe
it currently only implements a subset of the format directives
returns <0 if an error occurred (no scenario know yet)
returns >=size if output is truncated (conforming to C99 standard)
*/
int vsnprintf_r(char *str, size_t size, const char *format, va_list ap) {
size_t i = 0;
char c;
int full = 0; /* indicate if output buffer full */
--size; /* without trailing \0 */
while (c = *format++) {
if (c == '\\') {
} else if (c == '%') {
#if HAVE_TYPE_LONGLONG
# define num_buff_len ((sizeof(unsigned long long)*8+2)/3+1) /* hold up to u long long in octal w/ \0 */
#else
# define num_buff_len ((sizeof(unsigned long)*8+2)/3+1) /* hold up to u long in octal w/ \0 */
#endif
char lengthmod = '\0'; /* 'h' 'l' 'L' 'z' */
int leading0 = 0; /* or 1 */
size_t fsize = 0; /* size of field */
const char *st; /* string */
long lo; unsigned long ulo;
#if HAVE_TYPE_LONGLONG
long long ll; unsigned long long ull;
#endif
char field[num_buff_len]; /* result of number conversion */
char *np; /* num pointer */
c = *format++;
if (c == '\0') { break; }
/* flag characters */
switch (c) {
case '0': leading0 = 1; c = *format++; break;
/* not handled: '#' '-' ' ' '+' '\'' */
}
if (c == '\0') { break; }
/* field width */
switch (c) {
case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
do {
fsize = 10*fsize+(c-'0');
c = *format++;
} while (c && isdigit((unsigned char)c));
break;
}
if (c == '\0') { break; }
/* precision - not handles */
/* length modifier */
switch (c) {
/* not handled: 'q' 'j' 't' */
/* handled: 'h' 'hh'->'H' 'z' 'Z'->'z' 'l' 'll'->'L' 'L' */
case 'Z': c = 'z'; /* fall through */
#if HAVE_TYPE_LONGLONG
case 'L':
#endif
case 'z': lengthmod = c; c = *format++; break;
case 'h':
lengthmod = c;
if ((c = *format++) == 'h') {
lengthmod = 'H'; c = *format++;
}
break;
case 'l':
lengthmod = c;
if ((c = *format++) == 'l') {
lengthmod = 'L'; c = *format++;
}
break;
}
if (c == '\0') { break; }
/* conversion specifier */
switch (c) {
case 'c': c = va_arg(ap, int); /* fall through */
case '%': *str++ = c; if (++i == size) { full = 1; } break;
case 's': st = va_arg(ap, const char *);
/* no modifier handled! */
while (c = *st++) {
*str++ = c;
if (++i == size) { full = 1; break; }
}
break;
case 'd':
#if HAVE_TYPE_LONGLONG
if (lengthmod == 'L') {
ll = va_arg(ap, long long);
np = diag_longlong_to_dec(field, num_buff_len, ll, leading0, fsize);
while (c = *np++) {
*str++ = c;
if (++i == size) { full = 1; break; }
}
} else
#endif
{
switch (lengthmod) {
case 'l': lo = va_arg(ap, long); break;
case 'z': lo = va_arg(ap, ptrdiff_t); break;
default: lo = va_arg(ap, int); break;
}
np = diag_long_to_dec(field, num_buff_len, lo, leading0, fsize);
while (c = *np++) { *str++ = c;
if (++i == size) { full = 1; break; }
}
}
break;
case 'u':
#if HAVE_TYPE_LONGLONG
if (lengthmod == 'L') {
ull = va_arg(ap, unsigned long long);
np = diag_ulonglong_to_dec(field, num_buff_len, ull, leading0, fsize);
while (c = *np++) { *str++ = c;
if (++i == size) { full = 1; break; }
}
} else
#endif
{
switch (lengthmod) {
case 'l': ulo = va_arg(ap, unsigned long); break;
case 'z': ulo = va_arg(ap, size_t); break;
default: ulo = va_arg(ap, unsigned int); break;
}
np = diag_ulong_to_dec(field, num_buff_len, ulo, leading0, fsize);
while (c = *np++) { *str++ = c;
if (++i == size) { full = 1; break; }
}
}
break;
case 'p':
ulo = va_arg(ap, size_t);
np = diag_ulong_to_hex(field, num_buff_len, ulo, leading0, fsize);
*str++ = '0'; if (++i == size) { full = 1; break; }
*str++ = 'x'; if (++i == size) { full = 1; break; }
while (c = *np++) { *str++ = c;
if (++i == size) { full = 1; break; }
}
break;
case 'x':
#if HAVE_TYPE_LONGLONG
if (lengthmod == 'L') {
ull = va_arg(ap, unsigned long long);
np = diag_ulonglong_to_hex(field, num_buff_len, ull, leading0, fsize);
while (c = *np++) { *str++ = c;
if (++i == size) { full = 1; break; }
}
} else
#endif
{
switch (lengthmod) {
case 'l': ulo = va_arg(ap, unsigned long); break;
case 'z': ulo = va_arg(ap, size_t); break;
default: ulo = va_arg(ap, unsigned int); break;
}
np = diag_ulong_to_hex(field, num_buff_len, ulo, leading0, fsize);
while (c = *np++) { *str++ = c;
if (++i == size) { full = 1; break; }
}
}
break;
case 'o':
#if HAVE_TYPE_LONGLONG
if (lengthmod == 'L') {
ull = va_arg(ap, unsigned long long);
np = diag_ulonglong_to_oct(field, num_buff_len, ull, leading0, fsize);
while (c = *np++) { *str++ = c;
if (++i == size) break;
}
} else
#endif
{
switch (lengthmod) {
case 'l': ulo = va_arg(ap, unsigned long); break;
case 'z': ulo = va_arg(ap, size_t); break;
default: ulo = va_arg(ap, unsigned int); break;
}
np = diag_ulong_to_oct(field, num_buff_len, ulo, leading0, fsize);
while (c = *np++) { *str++ = c;
if (++i == size) { full = 1; break; }
}
}
break;
default:
*str++ = c; if (++i == size) { full = 1; break; }
}
if (full) break;
} else {
*str++ = c;
if (++i == size) break;
}
}
*str = '\0';
return i;
}
int snprintf_r(char *str, size_t size, const char *format, ...) {
int result;
va_list ap;
va_start(ap, format);
result = vsnprintf_r(str, size, format, ap);
va_end(ap);
return result;
}