mirror of
https://github.com/moparisthebest/socat
synced 2024-11-15 21:45:05 -05:00
570 lines
16 KiB
C
570 lines
16 KiB
C
|
/* vsnprintf_r.c */
|
||
|
/* Copyright Gerhard Rieger */
|
||
|
|
||
|
/* 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 */
|
||
|
unsigned 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&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(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;
|
||
|
}
|
||
|
|