779 lines
13 KiB
C
779 lines
13 KiB
C
/**
|
|
* @file fmt/print.c Formatted printing
|
|
*
|
|
* Copyright (C) 2010 Creytiv.com
|
|
*/
|
|
#define _GNU_SOURCE 1
|
|
#define __EXTENSIONS__ 1
|
|
#include <string.h>
|
|
#include <re_types.h>
|
|
#include <re_sa.h>
|
|
#include <re_fmt.h>
|
|
#include <re_mem.h>
|
|
#include <math.h>
|
|
#ifdef _MSC_VER
|
|
#include <float.h>
|
|
#define isinf(d) (!_finite(d))
|
|
#define isnan(d) _isnan(d)
|
|
#endif
|
|
#ifdef SOLARIS
|
|
#include <ieeefp.h>
|
|
#undef isinf
|
|
#define isinf(a) (fpclass((a)) == FP_NINF || fpclass((a)) == FP_PINF)
|
|
#undef isnan
|
|
#define isnan(a) isnand((a))
|
|
#endif
|
|
|
|
|
|
enum length_modifier {
|
|
LENMOD_NONE = 0,
|
|
LENMOD_LONG = 1,
|
|
LENMOD_LONG_LONG = 2,
|
|
LENMOD_SIZE = 42,
|
|
};
|
|
|
|
enum {
|
|
DEC_SIZE = 42,
|
|
NUM_SIZE = 64
|
|
};
|
|
|
|
static const char prfx_neg[] = "-";
|
|
static const char prfx_hex[] = "0x";
|
|
static const char str_nil[] = "(nil)";
|
|
|
|
|
|
static int write_padded(const char *p, size_t sz, size_t pad, char pch,
|
|
bool plr, const char *prfx, re_vprintf_h *vph,
|
|
void *arg)
|
|
{
|
|
const size_t prfx_len = str_len(prfx);
|
|
int err = 0;
|
|
|
|
pad -= MIN(pad, prfx_len);
|
|
|
|
if (prfx && pch == '0')
|
|
err |= vph(prfx, prfx_len, arg);
|
|
|
|
while (!plr && (pad-- > sz))
|
|
err |= vph(&pch, 1, arg);
|
|
|
|
if (prfx && pch != '0')
|
|
err |= vph(prfx, prfx_len, arg);
|
|
|
|
if (p && sz)
|
|
err |= vph(p, sz, arg);
|
|
|
|
while (plr && pad-- > sz)
|
|
err |= vph(&pch, 1, arg);
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
static uint32_t local_itoa(char *buf, uint64_t n, uint8_t base, bool uc)
|
|
{
|
|
char c, *p = buf + NUM_SIZE;
|
|
uint32_t len = 1;
|
|
const char a = uc ? 'A' : 'a';
|
|
|
|
*--p = '\0';
|
|
do {
|
|
const uint64_t dv = n / base;
|
|
const uint64_t mul = dv * base;
|
|
|
|
c = (char)(n - mul);
|
|
|
|
if (c < 10)
|
|
*--p = '0' + c;
|
|
else
|
|
*--p = a + (c - 10);
|
|
|
|
n = dv;
|
|
++len;
|
|
|
|
} while (n != 0);
|
|
|
|
memmove(buf, p, len);
|
|
|
|
return len - 1;
|
|
}
|
|
|
|
|
|
static size_t local_ftoa(char *buf, double n, size_t dp)
|
|
{
|
|
char *p = buf;
|
|
long long a = (long long)n;
|
|
double b = n - (double)a;
|
|
|
|
b = (b < 0) ? -b : b;
|
|
|
|
/* integral part */
|
|
p += local_itoa(p, (a < 0) ? -a : a, 10, false);
|
|
|
|
*p++ = '.';
|
|
|
|
/* decimal digits */
|
|
while (dp--) {
|
|
char v;
|
|
|
|
b *= 10;
|
|
v = (char)b;
|
|
b -= v;
|
|
|
|
*p++ = '0' + (char)v;
|
|
}
|
|
|
|
*p = '\0';
|
|
|
|
return p - buf;
|
|
}
|
|
|
|
|
|
/**
|
|
* Print a formatted string
|
|
*
|
|
* @param fmt Formatted string
|
|
* @param ap Variable argument
|
|
* @param vph Print handler
|
|
* @param arg Handler argument
|
|
*
|
|
* @return 0 if success, otherwise errorcode
|
|
*
|
|
* Extensions:
|
|
*
|
|
* <pre>
|
|
* %b (char *, size_t) Buffer string with pointer and length
|
|
* %r (struct pl) Pointer-length object
|
|
* %w (uint8_t *, size_t) Binary buffer to hexadecimal format
|
|
* %j (struct sa *) Socket address - address part only
|
|
* %J (struct sa *) Socket address and port - like 1.2.3.4:1234
|
|
* %H (re_printf_h *, void *) Print handler with argument
|
|
* %v (char *fmt, va_list *) Variable argument list
|
|
* %m (int) Describe an error code
|
|
* </pre>
|
|
*
|
|
* Reserved for the future:
|
|
*
|
|
* %k
|
|
* %y
|
|
*
|
|
*/
|
|
int re_vhprintf(const char *fmt, va_list ap, re_vprintf_h *vph, void *arg)
|
|
{
|
|
uint8_t base, *bptr;
|
|
char pch, ch, num[NUM_SIZE], addr[64];
|
|
enum length_modifier lenmod = LENMOD_NONE;
|
|
struct re_printf pf;
|
|
bool fm = false, plr = false;
|
|
const struct pl *pl;
|
|
size_t pad = 0, fpad = -1, len, i;
|
|
const char *str, *p = fmt, *p0 = fmt;
|
|
const struct sa *sa;
|
|
re_printf_h *ph;
|
|
void *ph_arg;
|
|
va_list *apl;
|
|
int err = 0;
|
|
void *ptr;
|
|
uint64_t n;
|
|
int64_t sn;
|
|
bool uc = false;
|
|
double dbl;
|
|
|
|
if (!fmt || !vph)
|
|
return EINVAL;
|
|
|
|
pf.vph = vph;
|
|
pf.arg = arg;
|
|
|
|
for (;*p && !err; p++) {
|
|
|
|
if (!fm) {
|
|
if (*p != '%')
|
|
continue;
|
|
|
|
pch = ' ';
|
|
plr = false;
|
|
pad = 0;
|
|
fpad = -1;
|
|
lenmod = LENMOD_NONE;
|
|
uc = false;
|
|
|
|
if (p > p0)
|
|
err |= vph(p0, p - p0, arg);
|
|
|
|
fm = true;
|
|
continue;
|
|
}
|
|
|
|
fm = false;
|
|
base = 10;
|
|
|
|
switch (*p) {
|
|
|
|
case '-':
|
|
plr = true;
|
|
fm = true;
|
|
break;
|
|
|
|
case '.':
|
|
fpad = pad;
|
|
pad = 0;
|
|
fm = true;
|
|
break;
|
|
|
|
case '%':
|
|
ch = '%';
|
|
|
|
err |= vph(&ch, 1, arg);
|
|
break;
|
|
|
|
case 'b':
|
|
str = va_arg(ap, const char *);
|
|
len = va_arg(ap, size_t);
|
|
|
|
err |= write_padded(str, str ? len : 0, pad, ' ',
|
|
plr, NULL, vph, arg);
|
|
break;
|
|
|
|
case 'c':
|
|
ch = va_arg(ap, int);
|
|
|
|
err |= write_padded(&ch, 1, pad, ' ', plr, NULL,
|
|
vph, arg);
|
|
break;
|
|
|
|
case 'd':
|
|
case 'i':
|
|
switch (lenmod) {
|
|
|
|
case LENMOD_SIZE:
|
|
sn = va_arg(ap, ssize_t);
|
|
break;
|
|
|
|
default:
|
|
case LENMOD_LONG_LONG:
|
|
sn = va_arg(ap, signed long long);
|
|
break;
|
|
|
|
case LENMOD_LONG:
|
|
sn = va_arg(ap, signed long);
|
|
break;
|
|
|
|
case LENMOD_NONE:
|
|
sn = va_arg(ap, signed);
|
|
break;
|
|
}
|
|
|
|
len = local_itoa(num, (sn < 0) ? -sn : sn, base,
|
|
false);
|
|
|
|
err |= write_padded(num, len, pad,
|
|
plr ? ' ' : pch, plr,
|
|
(sn < 0) ? prfx_neg : NULL,
|
|
vph, arg);
|
|
break;
|
|
|
|
case 'f':
|
|
case 'F':
|
|
dbl = va_arg(ap, double);
|
|
|
|
if (fpad == (size_t)-1) {
|
|
fpad = pad;
|
|
pad = 0;
|
|
}
|
|
|
|
if (isinf(dbl)) {
|
|
err |= write_padded("inf", 3, fpad,
|
|
' ', plr, NULL, vph, arg);
|
|
}
|
|
else if (isnan(dbl)) {
|
|
err |= write_padded("nan", 3, fpad,
|
|
' ', plr, NULL, vph, arg);
|
|
}
|
|
else {
|
|
len = local_ftoa(num, dbl,
|
|
pad ? min(pad, DEC_SIZE) : 6);
|
|
|
|
err |= write_padded(num, len, fpad,
|
|
plr ? ' ' : pch, plr,
|
|
(dbl<0) ? prfx_neg : NULL,
|
|
vph, arg);
|
|
}
|
|
break;
|
|
|
|
case 'H':
|
|
ph = va_arg(ap, re_printf_h *);
|
|
ph_arg = va_arg(ap, void *);
|
|
|
|
if (ph)
|
|
err |= ph(&pf, ph_arg);
|
|
break;
|
|
|
|
case 'l':
|
|
++lenmod;
|
|
fm = true;
|
|
break;
|
|
|
|
case 'm':
|
|
(void)strerror_r(va_arg(ap, int), addr, sizeof(addr));
|
|
addr[sizeof(addr)-1] = '\0';
|
|
|
|
err |= write_padded(addr, strlen(addr), pad, ' ',
|
|
plr, NULL, vph, arg);
|
|
break;
|
|
|
|
case 'p':
|
|
ptr = va_arg(ap, void *);
|
|
|
|
if (ptr) {
|
|
len = local_itoa(num, (unsigned long int)ptr,
|
|
16, false);
|
|
err |= write_padded(num, len, pad,
|
|
plr ? ' ' : pch, plr,
|
|
prfx_hex, vph, arg);
|
|
}
|
|
else {
|
|
err |= write_padded(str_nil,
|
|
sizeof(str_nil) - 1,
|
|
pad, ' ', plr, NULL,
|
|
vph, arg);
|
|
}
|
|
break;
|
|
|
|
case 'r':
|
|
pl = va_arg(ap, const struct pl *);
|
|
|
|
err |= write_padded(pl ? pl->p : NULL,
|
|
(pl && pl->p) ? pl->l : 0,
|
|
pad, ' ', plr, NULL, vph, arg);
|
|
break;
|
|
|
|
case 's':
|
|
str = va_arg(ap, const char *);
|
|
err |= write_padded(str, str_len(str), pad,
|
|
' ', plr, NULL, vph, arg);
|
|
break;
|
|
|
|
case 'X':
|
|
uc = true;
|
|
/*@fallthrough@*/
|
|
case 'x':
|
|
base = 16;
|
|
/*@fallthrough@*/
|
|
case 'u':
|
|
switch (lenmod) {
|
|
|
|
case LENMOD_SIZE:
|
|
n = va_arg(ap, size_t);
|
|
break;
|
|
|
|
default:
|
|
case LENMOD_LONG_LONG:
|
|
n = va_arg(ap, unsigned long long);
|
|
break;
|
|
|
|
case LENMOD_LONG:
|
|
n = va_arg(ap, unsigned long);
|
|
break;
|
|
|
|
case LENMOD_NONE:
|
|
n = va_arg(ap, unsigned);
|
|
break;
|
|
}
|
|
|
|
len = local_itoa(num, n, base, uc);
|
|
|
|
err |= write_padded(num, len, pad,
|
|
plr ? ' ' : pch, plr, NULL,
|
|
vph, arg);
|
|
break;
|
|
|
|
case 'v':
|
|
str = va_arg(ap, char *);
|
|
apl = va_arg(ap, va_list *);
|
|
|
|
if (!str || !apl)
|
|
break;
|
|
|
|
err |= re_vhprintf(str, *apl, vph, arg);
|
|
break;
|
|
|
|
case 'W':
|
|
uc = true;
|
|
/*@fallthrough@*/
|
|
case 'w':
|
|
bptr = va_arg(ap, uint8_t *);
|
|
len = va_arg(ap, size_t);
|
|
|
|
len = bptr ? len : 0;
|
|
pch = plr ? ' ' : pch;
|
|
|
|
while (!plr && pad-- > (len * 2))
|
|
err |= vph(&pch, 1, arg);
|
|
|
|
for (i=0; i<len; i++) {
|
|
const uint8_t v = *bptr++;
|
|
uint32_t l = local_itoa(num, v, 16, uc);
|
|
err |= write_padded(num, l, 2, '0',
|
|
false, NULL, vph, arg);
|
|
}
|
|
|
|
while (plr && pad-- > (len * 2))
|
|
err |= vph(&pch, 1, arg);
|
|
|
|
break;
|
|
|
|
case 'z':
|
|
lenmod = LENMOD_SIZE;
|
|
fm = true;
|
|
break;
|
|
|
|
case 'j':
|
|
sa = va_arg(ap, struct sa *);
|
|
if (!sa)
|
|
break;
|
|
if (sa_ntop(sa, addr, sizeof(addr))) {
|
|
err |= write_padded("?", 1, pad, ' ',
|
|
plr, NULL, vph, arg);
|
|
break;
|
|
}
|
|
err |= write_padded(addr, strlen(addr), pad, ' ',
|
|
plr, NULL, vph, arg);
|
|
break;
|
|
|
|
|
|
case 'J':
|
|
sa = va_arg(ap, struct sa *);
|
|
if (!sa)
|
|
break;
|
|
if (sa_ntop(sa, addr, sizeof(addr))) {
|
|
err |= write_padded("?", 1, pad, ' ',
|
|
plr, NULL, vph, arg);
|
|
break;
|
|
}
|
|
|
|
#ifdef HAVE_INET6
|
|
if (AF_INET6 == sa_af(sa)) {
|
|
ch = '[';
|
|
err |= vph(&ch, 1, arg);
|
|
}
|
|
#endif
|
|
err |= write_padded(addr, strlen(addr), pad, ' ',
|
|
plr, NULL, vph, arg);
|
|
#ifdef HAVE_INET6
|
|
if (AF_INET6 == sa_af(sa)) {
|
|
ch = ']';
|
|
err |= vph(&ch, 1, arg);
|
|
}
|
|
#endif
|
|
|
|
ch = ':';
|
|
err |= vph(&ch, 1, arg);
|
|
len = local_itoa(num, sa_port(sa), 10, false);
|
|
err |= write_padded(num, len, pad,
|
|
plr ? ' ' : pch, plr, NULL,
|
|
vph, arg);
|
|
|
|
break;
|
|
|
|
default:
|
|
if (('0' <= *p) && (*p <= '9')) {
|
|
if (!pad && ('0' == *p)) {
|
|
pch = '0';
|
|
}
|
|
else {
|
|
pad *= 10;
|
|
pad += *p - '0';
|
|
}
|
|
fm = true;
|
|
break;
|
|
}
|
|
|
|
ch = '?';
|
|
|
|
err |= vph(&ch, 1, arg);
|
|
break;
|
|
}
|
|
|
|
if (!fm)
|
|
p0 = p + 1;
|
|
}
|
|
|
|
if (!fm && p > p0)
|
|
err |= vph(p0, p - p0, arg);
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
static int print_handler(const char *p, size_t size, void *arg)
|
|
{
|
|
struct pl *pl = arg;
|
|
|
|
if (size > pl->l)
|
|
return ENOMEM;
|
|
|
|
memcpy((void *)pl->p, p, size);
|
|
|
|
pl_advance(pl, size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
struct dyn_print {
|
|
char *str;
|
|
char *p;
|
|
size_t l;
|
|
size_t size;
|
|
};
|
|
|
|
|
|
static int print_handler_dyn(const char *p, size_t size, void *arg)
|
|
{
|
|
struct dyn_print *dp = arg;
|
|
|
|
if (size > dp->l - 1) {
|
|
const size_t new_size = MAX(dp->size + size, dp->size * 2);
|
|
char *str = mem_realloc(dp->str, new_size);
|
|
if (!str)
|
|
return ENOMEM;
|
|
|
|
dp->str = str;
|
|
dp->l += new_size - dp->size;
|
|
dp->p = dp->str + new_size - dp->l;
|
|
dp->size = new_size;
|
|
}
|
|
|
|
memcpy(dp->p, p, size);
|
|
|
|
dp->p += size;
|
|
dp->l -= size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Print a formatted string to a file stream, using va_list
|
|
*
|
|
* @param stream File stream for the output
|
|
* @param fmt Formatted string
|
|
* @param ap Variable-arguments list
|
|
*
|
|
* @return The number of characters printed, or -1 if error
|
|
*/
|
|
int re_vfprintf(FILE *stream, const char *fmt, va_list ap)
|
|
{
|
|
char buf[4096]; /* TODO: avoid static, use print_handler_dyn ? */
|
|
struct pl pl;
|
|
size_t n;
|
|
|
|
if (!stream)
|
|
return -1;
|
|
|
|
pl.p = buf;
|
|
pl.l = sizeof(buf);
|
|
|
|
if (0 != re_vhprintf(fmt, ap, print_handler, &pl))
|
|
return -1;
|
|
|
|
n = sizeof(buf) - pl.l;
|
|
|
|
if (1 != fwrite(buf, n, 1, stream))
|
|
return -1;
|
|
|
|
return (int)n;
|
|
}
|
|
|
|
|
|
/**
|
|
* Print a formatted string to stdout, using va_list
|
|
*
|
|
* @param fmt Formatted string
|
|
* @param ap Variable-arguments list
|
|
*
|
|
* @return The number of characters printed, or -1 if error
|
|
*/
|
|
int re_vprintf(const char *fmt, va_list ap)
|
|
{
|
|
return re_vfprintf(stdout, fmt, ap);
|
|
}
|
|
|
|
|
|
/**
|
|
* Print a formatted string to a buffer, using va_list
|
|
*
|
|
* @param str Buffer for output string
|
|
* @param size Size of buffer
|
|
* @param fmt Formatted string
|
|
* @param ap Variable-arguments list
|
|
*
|
|
* @return The number of characters printed, or -1 if error
|
|
*/
|
|
int re_vsnprintf(char *str, size_t size, const char *fmt, va_list ap)
|
|
{
|
|
struct pl pl;
|
|
int err;
|
|
|
|
if (!str || !size)
|
|
return -1;
|
|
|
|
pl.p = str;
|
|
pl.l = size - 1;
|
|
|
|
err = re_vhprintf(fmt, ap, print_handler, &pl);
|
|
|
|
str[size - pl.l - 1] = '\0';
|
|
|
|
return err ? -1 : (int)(size - pl.l - 1);
|
|
}
|
|
|
|
|
|
/**
|
|
* Print a formatted string to a dynamically allocated buffer, using va_list
|
|
*
|
|
* @param strp Pointer for output string
|
|
* @param fmt Formatted string
|
|
* @param ap Variable-arguments list
|
|
*
|
|
* @return 0 if success, otherwise errorcode
|
|
*/
|
|
int re_vsdprintf(char **strp, const char *fmt, va_list ap)
|
|
{
|
|
struct dyn_print dp;
|
|
int err;
|
|
|
|
if (!strp)
|
|
return EINVAL;
|
|
|
|
dp.size = 16;
|
|
dp.str = mem_alloc(dp.size, NULL);
|
|
if (!dp.str)
|
|
return ENOMEM;
|
|
|
|
dp.p = dp.str;
|
|
dp.l = dp.size;
|
|
|
|
err = re_vhprintf(fmt, ap, print_handler_dyn, &dp);
|
|
if (err)
|
|
goto out;
|
|
|
|
*dp.p = '\0';
|
|
|
|
out:
|
|
if (err)
|
|
mem_deref(dp.str);
|
|
else
|
|
*strp = dp.str;
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
/**
|
|
* Print a formatted string
|
|
*
|
|
* @param pf Print backend
|
|
* @param fmt Formatted string
|
|
*
|
|
* @return 0 if success, otherwise errorcode
|
|
*/
|
|
int re_hprintf(struct re_printf *pf, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int err;
|
|
|
|
if (!pf)
|
|
return EINVAL;
|
|
|
|
va_start(ap, fmt);
|
|
err = re_vhprintf(fmt, ap, pf->vph, pf->arg);
|
|
va_end(ap);
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
/**
|
|
* Print a formatted string to a file stream
|
|
*
|
|
* @param stream File stream for output
|
|
* @param fmt Formatted string
|
|
*
|
|
* @return The number of characters printed, or -1 if error
|
|
*/
|
|
int re_fprintf(FILE *stream, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int n;
|
|
|
|
va_start(ap, fmt);
|
|
n = re_vfprintf(stream, fmt, ap);
|
|
va_end(ap);
|
|
|
|
return n;
|
|
}
|
|
|
|
|
|
/**
|
|
* Print a formatted string to stdout
|
|
*
|
|
* @param fmt Formatted string
|
|
*
|
|
* @return The number of characters printed, or -1 if error
|
|
*/
|
|
int re_printf(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int n;
|
|
|
|
va_start(ap, fmt);
|
|
n = re_vprintf(fmt, ap);
|
|
va_end(ap);
|
|
|
|
return n;
|
|
}
|
|
|
|
|
|
/**
|
|
* Print a formatted string to a buffer
|
|
*
|
|
* @param str Buffer for output string
|
|
* @param size Size of buffer
|
|
* @param fmt Formatted string
|
|
*
|
|
* @return The number of characters printed, or -1 if error
|
|
*/
|
|
int re_snprintf(char *str, size_t size, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int n;
|
|
|
|
va_start(ap, fmt);
|
|
n = re_vsnprintf(str, size, fmt, ap);
|
|
va_end(ap);
|
|
|
|
return n;
|
|
}
|
|
|
|
|
|
/**
|
|
* Print a formatted string to a buffer
|
|
*
|
|
* @param strp Buffer pointer for output string
|
|
* @param fmt Formatted string
|
|
*
|
|
* @return 0 if success, otherwise errorcode
|
|
*/
|
|
int re_sdprintf(char **strp, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int err;
|
|
|
|
va_start(ap, fmt);
|
|
err = re_vsdprintf(strp, fmt, ap);
|
|
va_end(ap);
|
|
|
|
return err;
|
|
}
|