1
0
mirror of git://git.gnupg.org/gnupg.git synced 2024-10-31 20:08:43 +01:00
gnupg/common/estream-printf.c
Werner Koch b008274afd Nuked almost all trailing white space.
We better do this once and for all instead of cluttering all future
commits with diffs of trailing white spaces.  In the majority of cases
blank or single lines are affected and thus this change won't disturb
a git blame too much.  For future commits the pre-commit scripts
checks that this won't happen again.
2011-02-04 12:57:53 +01:00

1838 lines
51 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* estream-printf.c - Versatile mostly C-99 compliant printf formatting
* Copyright (C) 2007, 2008, 2009, 2010 g10 Code GmbH
*
* This file is part of Libestream.
*
* Libestream is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2 of the License,
* or (at your option) any later version.
*
* Libestream 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Libestream; if not, see <http://www.gnu.org/licenses/>.
*
* ALTERNATIVELY, Libestream may be distributed under the terms of the
* following license, in which case the provisions of this license are
* required INSTEAD OF the GNU General Public License. If you wish to
* allow use of your version of this file only under the terms of the
* GNU General Public License, and not to allow others to use your
* version of this file under the terms of the following license,
* indicate your decision by deleting this paragraph and the license
* below.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, and the entire permission notice in its entirety,
* including the disclaimer of warranties.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* Required autoconf tests:
AC_TYPE_LONG_LONG_INT defines HAVE_LONG_LONG_INT
AC_TYPE_LONG_DOUBLE defines HAVE_LONG_DOUBLE
AC_TYPE_INTMAX_T defines HAVE_INTMAX_T
AC_TYPE_UINTMAX_T defines HAVE_UINTMAX_T
AC_CHECK_TYPES([ptrdiff_t]) defines HAVE_PTRDIFF_T
AC_CHECK_SIZEOF([unsigned long]) defines SIZEOF_UNSIGNED_LONG
AC_CHECK_SIZEOF([void *]) defines SIZEOF_VOID_P
HAVE_LANGINFO_THOUSANDS_SEP
Note that the file estream.m4 provides the autoconf macro
ESTREAM_PRINTF_INIT which runs all required checks.
See estream-printf.h for ways to tune this code.
Missing stuff: wchar and wint_t
thousands_sep in pr_float.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#if defined(_WIN32) && !defined(HAVE_W32_SYSTEM)
# define HAVE_W32_SYSTEM 1
# if defined(__MINGW32CE__) && !defined (HAVE_W32CE_SYSTEM)
# define HAVE_W32CE_SYSTEM
# endif
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
#include <errno.h>
#include <stddef.h>
#include <assert.h>
#if defined(HAVE_INTMAX_T) || defined(HAVE_UINTMAX_T)
# ifdef HAVE_STDINT_H
# include <stdint.h>
# endif
#endif
#ifdef HAVE_LANGINFO_THOUSANDS_SEP
#include <langinfo.h>
#endif
#ifdef HAVE_W32CE_SYSTEM
#include <gpg-error.h> /* ERRNO replacement. */
#endif
#ifdef _ESTREAM_PRINTF_EXTRA_INCLUDE
# include _ESTREAM_PRINTF_EXTRA_INCLUDE
#endif
#include "estream-printf.h"
/* #define DEBUG 1 */
/* Allow redefinition of asprintf used malloc functions. */
#if defined(_ESTREAM_PRINTF_MALLOC)
#define my_printf_malloc(a) _ESTREAM_PRINTF_MALLOC((a))
#else
#define my_printf_malloc(a) malloc((a))
#endif
#if defined(_ESTREAM_PRINTF_FREE)
#define my_printf_free(a) _ESTREAM_PRINTF_FREE((a))
#else
#define my_printf_free(a) free((a))
#endif
/* A wrapper to set ERRNO. */
#ifdef HAVE_W32CE_SYSTEM
# define _set_errno(a) gpg_err_set_errno ((a))
#else
# define _set_errno(a) do { errno = (a); } while (0)
#endif
/* Calculate array dimension. */
#ifndef DIM
#define DIM(array) (sizeof (array) / sizeof (*array))
#endif
/* We allow for that many args without requiring malloced memory. */
#define DEFAULT_MAX_ARGSPECS 5
/* We allow for that many values without requiring malloced memory. */
#define DEFAULT_MAX_VALUES 8
/* We allocate this many new array argspec elements each time. */
#define ARGSPECS_BUMP_VALUE 10
/* Special values for the field width and the precision. */
#define NO_FIELD_VALUE (-1)
#define STAR_FIELD_VALUE (-2)
/* Bit valuues used for the conversion flags. */
#define FLAG_GROUPING 1
#define FLAG_LEFT_JUST 2
#define FLAG_PLUS_SIGN 4
#define FLAG_SPACE_PLUS 8
#define FLAG_ALT_CONV 16
#define FLAG_ZERO_PAD 32
/* Constants used the length modifiers. */
typedef enum
{
LENMOD_NONE = 0,
LENMOD_CHAR, /* "hh" */
LENMOD_SHORT, /* "h" */
LENMOD_LONG, /* "l" */
LENMOD_LONGLONG, /* "ll" */
LENMOD_INTMAX, /* "j" */
LENMOD_SIZET, /* "z" */
LENMOD_PTRDIFF, /* "t" */
LENMOD_LONGDBL /* "L" */
} lenmod_t;
/* All the conversion specifiers. */
typedef enum
{
CONSPEC_UNKNOWN = 0,
CONSPEC_DECIMAL,
CONSPEC_OCTAL,
CONSPEC_UNSIGNED,
CONSPEC_HEX,
CONSPEC_HEX_UP,
CONSPEC_FLOAT,
CONSPEC_FLOAT_UP,
CONSPEC_EXP,
CONSPEC_EXP_UP,
CONSPEC_F_OR_G,
CONSPEC_F_OR_G_UP,
CONSPEC_HEX_EXP,
CONSPEC_HEX_EXP_UP,
CONSPEC_CHAR,
CONSPEC_STRING,
CONSPEC_POINTER,
CONSPEC_STRERROR,
CONSPEC_BYTES_SO_FAR
} conspec_t;
/* Constants describing all the suppoorted types. Note that we list
all the types we know about even if certain types are not available
on this system. */
typedef enum
{
VALTYPE_UNSUPPORTED = 0, /* Artificial type for error detection. */
VALTYPE_CHAR,
VALTYPE_SCHAR,
VALTYPE_UCHAR,
VALTYPE_SHORT,
VALTYPE_USHORT,
VALTYPE_INT,
VALTYPE_UINT,
VALTYPE_LONG,
VALTYPE_ULONG,
VALTYPE_LONGLONG,
VALTYPE_ULONGLONG,
VALTYPE_DOUBLE,
VALTYPE_LONGDOUBLE,
VALTYPE_STRING,
VALTYPE_INTMAX,
VALTYPE_UINTMAX,
VALTYPE_SIZE,
VALTYPE_PTRDIFF,
VALTYPE_POINTER,
VALTYPE_CHAR_PTR,
VALTYPE_SCHAR_PTR,
VALTYPE_SHORT_PTR,
VALTYPE_INT_PTR,
VALTYPE_LONG_PTR,
VALTYPE_LONGLONG_PTR,
VALTYPE_INTMAX_PTR,
VALTYPE_SIZE_PTR,
VALTYPE_PTRDIFF_PTR
} valtype_t;
/* A union used to store the actual values. */
typedef union
{
char a_char;
signed char a_schar;
unsigned char a_uchar;
short a_short;
unsigned short a_ushort;
int a_int;
unsigned int a_uint;
long int a_long;
unsigned long int a_ulong;
#ifdef HAVE_LONG_LONG_INT
long long int a_longlong;
unsigned long long int a_ulonglong;
#endif
double a_double;
#ifdef HAVE_LONG_DOUBLE
long double a_longdouble;
#endif
const char *a_string;
#ifdef HAVE_INTMAX_T
intmax_t a_intmax;
#endif
#ifdef HAVE_UINTMAX_T
intmax_t a_uintmax;
#endif
size_t a_size;
#ifdef HAVE_PTRDIFF_T
ptrdiff_t a_ptrdiff;
#endif
void *a_void_ptr;
char *a_char_ptr;
signed char *a_schar_ptr;
short *a_short_ptr;
int *a_int_ptr;
long *a_long_ptr;
#ifdef HAVE_LONG_LONG_INT
long long int *a_longlong_ptr;
#endif
#ifdef HAVE_INTMAX_T
intmax_t *a_intmax_ptr;
#endif
size_t *a_size_ptr;
#ifdef HAVE_PTRDIFF_T
ptrdiff_t *a_ptrdiff_ptr;
#endif
} value_t;
/* An object used to keep track of a format option and arguments. */
struct argspec_s
{
size_t length; /* The length of these args including the percent. */
unsigned int flags; /* The conversion flags (bits defined by FLAG_foo). */
int width; /* The field width. */
int precision; /* The precision. */
lenmod_t lenmod; /* The length modifier. */
conspec_t conspec; /* The conversion specifier. */
int arg_pos; /* The position of the argument. This one may
be -1 to indicate that no value is expected
(e.g. for "%m"). */
int width_pos; /* The position of the argument for a field
width star's value. 0 for not used. */
int precision_pos; /* The position of the argument for the a
precision star's value. 0 for not used. */
valtype_t vt; /* The type of the corresponding argument. */
};
typedef struct argspec_s *argspec_t;
/* An object to build up a table of values and their types. */
struct valueitem_s
{
valtype_t vt; /* The type of the value. */
value_t value; /* The value. */
};
typedef struct valueitem_s *valueitem_t;
#ifdef DEBUG
static void
dump_argspecs (argspec_t arg, size_t argcount)
{
int idx;
for (idx=0; argcount; argcount--, arg++, idx++)
fprintf (stderr,
"%2d: len=%u flags=%u width=%d prec=%d mod=%d "
"con=%d vt=%d pos=%d-%d-%d\n",
idx,
(unsigned int)arg->length,
arg->flags,
arg->width,
arg->precision,
arg->lenmod,
arg->conspec,
arg->vt,
arg->arg_pos,
arg->width_pos,
arg->precision_pos);
}
#endif /*DEBUG*/
/* Set the vt field for ARG. */
static void
compute_type (argspec_t arg)
{
switch (arg->conspec)
{
case CONSPEC_UNKNOWN:
arg->vt = VALTYPE_UNSUPPORTED;
break;
case CONSPEC_DECIMAL:
switch (arg->lenmod)
{
case LENMOD_CHAR: arg->vt = VALTYPE_SCHAR; break;
case LENMOD_SHORT: arg->vt = VALTYPE_SHORT; break;
case LENMOD_LONG: arg->vt = VALTYPE_LONG; break;
case LENMOD_LONGLONG: arg->vt = VALTYPE_LONGLONG; break;
case LENMOD_INTMAX: arg->vt = VALTYPE_INTMAX; break;
case LENMOD_SIZET: arg->vt = VALTYPE_SIZE; break;
case LENMOD_PTRDIFF: arg->vt = VALTYPE_PTRDIFF; break;
default: arg->vt = VALTYPE_INT; break;
}
break;
case CONSPEC_OCTAL:
case CONSPEC_UNSIGNED:
case CONSPEC_HEX:
case CONSPEC_HEX_UP:
switch (arg->lenmod)
{
case LENMOD_CHAR: arg->vt = VALTYPE_UCHAR; break;
case LENMOD_SHORT: arg->vt = VALTYPE_USHORT; break;
case LENMOD_LONG: arg->vt = VALTYPE_ULONG; break;
case LENMOD_LONGLONG: arg->vt = VALTYPE_ULONGLONG; break;
case LENMOD_INTMAX: arg->vt = VALTYPE_UINTMAX; break;
case LENMOD_SIZET: arg->vt = VALTYPE_SIZE; break;
case LENMOD_PTRDIFF: arg->vt = VALTYPE_PTRDIFF; break;
default: arg->vt = VALTYPE_UINT; break;
}
break;
case CONSPEC_FLOAT:
case CONSPEC_FLOAT_UP:
case CONSPEC_EXP:
case CONSPEC_EXP_UP:
case CONSPEC_F_OR_G:
case CONSPEC_F_OR_G_UP:
case CONSPEC_HEX_EXP:
case CONSPEC_HEX_EXP_UP:
switch (arg->lenmod)
{
case LENMOD_LONGDBL: arg->vt = VALTYPE_LONGDOUBLE; break;
case LENMOD_LONG: arg->vt = VALTYPE_DOUBLE; break;
default: arg->vt = VALTYPE_DOUBLE; break;
}
break;
case CONSPEC_CHAR:
arg->vt = VALTYPE_INT;
break;
case CONSPEC_STRING:
arg->vt = VALTYPE_STRING;
break;
case CONSPEC_POINTER:
arg->vt = VALTYPE_POINTER;
break;
case CONSPEC_STRERROR:
arg->vt = VALTYPE_STRING;
break;
case CONSPEC_BYTES_SO_FAR:
switch (arg->lenmod)
{
case LENMOD_CHAR: arg->vt = VALTYPE_SCHAR_PTR; break;
case LENMOD_SHORT: arg->vt = VALTYPE_SHORT_PTR; break;
case LENMOD_LONG: arg->vt = VALTYPE_LONG_PTR; break;
case LENMOD_LONGLONG: arg->vt = VALTYPE_LONGLONG_PTR; break;
case LENMOD_INTMAX: arg->vt = VALTYPE_INTMAX_PTR; break;
case LENMOD_SIZET: arg->vt = VALTYPE_SIZE_PTR; break;
case LENMOD_PTRDIFF: arg->vt = VALTYPE_PTRDIFF_PTR; break;
default: arg->vt = VALTYPE_INT_PTR; break;
}
break;
}
}
/* Parse the FORMAT string and populate the specification array stored
at the address ARGSPECS_ADDR. The caller has provided enough space
to store up to MAX_ARGSPECS in that buffer. The function may
however ignore the provided buffer and malloc a larger one. On
success the addrrss of that larger buffer will be stored at
ARGSPECS_ADDR. The actual number of specifications will be
returned at R_ARGSPECS_COUNT. */
static int
parse_format (const char *format,
argspec_t *argspecs_addr, size_t max_argspecs,
size_t *r_argspecs_count)
{
const char *s;
argspec_t argspecs = *argspecs_addr;
argspec_t arg;
size_t argcount = 0;
if (!format)
goto leave_einval;
for (; *format; format++)
{
unsigned int flags;
int width, precision;
lenmod_t lenmod;
conspec_t conspec;
int arg_pos, width_pos, precision_pos;
if (*format != '%')
continue;
s = ++format;
if (!*s)
goto leave_einval;
if (*s == '%')
continue; /* Just a quoted percent. */
/* First check whether there is a positional argument. */
arg_pos = 0; /* No positional argument given. */
if (*s >= '1' && *s <= '9')
{
const char *save_s = s;
arg_pos = (*s++ - '0');
for (; *s >= '0' && *s <= '9'; s++)
arg_pos = 10*arg_pos + (*s - '0');
if (arg_pos < 0)
goto leave_einval; /* Overflow during conversion. */
if (*s == '$')
s++;
else
{
arg_pos = 0;
s = save_s;
}
}
/* Parse the flags. */
flags = 0;
for ( ; *s; s++)
{
switch (*s)
{
case '\'': flags |= FLAG_GROUPING; break;
case '-': flags |= FLAG_LEFT_JUST; break;
case '+': flags |= FLAG_PLUS_SIGN; break;
case ' ': flags |= FLAG_SPACE_PLUS; break;
case '#': flags |= FLAG_ALT_CONV; break;
case '0': flags |= FLAG_ZERO_PAD; break;
default:
goto flags_parsed;
}
}
flags_parsed:
/* Parse the field width. */
width_pos = 0;
if (*s == '*')
{
width = STAR_FIELD_VALUE;
s++;
/* If we have a positional argument, another one might also
be used to give the position of the star's value. */
if (arg_pos && *s >= '1' && *s <= '9')
{
width_pos = (*s++ - '0');
for (; *s >= '0' && *s <= '9'; s++)
width_pos = 10*width_pos + (*s - '0');
if (width_pos < 1)
goto leave_einval; /* Overflow during conversion. */
if (*s != '$')
goto leave_einval; /* Not followed by $. */
s++;
}
}
else if ( *s >= '0' && *s <= '9')
{
width = (*s++ - '0');
for (; *s >= '0' && *s <= '9'; s++)
{
if (!width && *s == '0')
goto leave_einval; /* Leading zeroes are not allowed.
Fixme: check what other
implementations do. */
width = 10*width + (*s - '0');
}
if (width < 0)
goto leave_einval; /* Overflow during conversion. */
}
else
width = NO_FIELD_VALUE;
/* Parse the precision. */
precision_pos = 0;
precision = NO_FIELD_VALUE;
if (*s == '.')
{
int ignore_value = (s[1] == '-');
s++;
if (*s == '*')
{
precision = STAR_FIELD_VALUE;
s++;
/* If we have a positional argument, another one might also
be used to give the position of the star's value. */
if (arg_pos && *s >= '1' && *s <= '9')
{
precision_pos = (*s++ - '0');
for (; *s >= '0' && *s <= '9'; s++)
precision_pos = 10*precision_pos + (*s - '0');
if (precision_pos < 1)
goto leave_einval; /* Overflow during conversion. */
if (*s != '$')
goto leave_einval; /* Not followed by $. */
s++;
}
}
else if ( *s >= '0' && *s <= '9')
{
precision = (*s++ - '0');
for (; *s >= '0' && *s <= '9'; s++)
{
if (!precision && *s == '0')
goto leave_einval; /* Leading zeroes are not allowed.
Fixme: check what other
implementations do. */
precision = 10*precision + (*s - '0');
}
if (precision < 0)
goto leave_einval; /* Overflow during conversion. */
}
else
precision = 0;
if (ignore_value)
precision = NO_FIELD_VALUE;
}
/* Parse the length modifiers. */
switch (*s)
{
case 'h':
if (s[1] == 'h')
{
lenmod = LENMOD_CHAR;
s++;
}
else
lenmod = LENMOD_SHORT;
s++;
break;
case 'l':
if (s[1] == 'l')
{
lenmod = LENMOD_LONGLONG;
s++;
}
else
lenmod = LENMOD_LONG;
s++;
break;
case 'j': lenmod = LENMOD_INTMAX; s++; break;
case 'z': lenmod = LENMOD_SIZET; s++; break;
case 't': lenmod = LENMOD_PTRDIFF; s++; break;
case 'L': lenmod = LENMOD_LONGDBL; s++; break;
default: lenmod = LENMOD_NONE; break;
}
/* Parse the conversion specifier. */
switch (*s)
{
case 'd':
case 'i': conspec = CONSPEC_DECIMAL; break;
case 'o': conspec = CONSPEC_OCTAL; break;
case 'u': conspec = CONSPEC_UNSIGNED; break;
case 'x': conspec = CONSPEC_HEX; break;
case 'X': conspec = CONSPEC_HEX_UP; break;
case 'f': conspec = CONSPEC_FLOAT; break;
case 'F': conspec = CONSPEC_FLOAT_UP; break;
case 'e': conspec = CONSPEC_EXP; break;
case 'E': conspec = CONSPEC_EXP_UP; break;
case 'g': conspec = CONSPEC_F_OR_G; break;
case 'G': conspec = CONSPEC_F_OR_G_UP; break;
case 'a': conspec = CONSPEC_HEX_EXP; break;
case 'A': conspec = CONSPEC_HEX_EXP_UP; break;
case 'c': conspec = CONSPEC_CHAR; break;
case 's': conspec = CONSPEC_STRING; break;
case 'p': conspec = CONSPEC_POINTER; break;
case 'n': conspec = CONSPEC_BYTES_SO_FAR; break;
case 'C': conspec = CONSPEC_CHAR; lenmod = LENMOD_LONG; break;
case 'S': conspec = CONSPEC_STRING; lenmod = LENMOD_LONG; break;
case 'm': conspec = CONSPEC_STRERROR; arg_pos = -1; break;
default: conspec = CONSPEC_UNKNOWN;
}
/* Save the args. */
if (argcount >= max_argspecs)
{
/* We either need to allocate a new array instead of the
caller provided one or realloc the array. Instead of
using realloc we allocate a new one and release the
original one then. */
size_t n, newmax;
argspec_t newarg;
newmax = max_argspecs + ARGSPECS_BUMP_VALUE;
if (newmax <= max_argspecs)
goto leave_einval; /* Too many arguments. */
newarg = calloc (newmax, sizeof *newarg);
if (!newarg)
goto leave;
for (n=0; n < argcount; n++)
newarg[n] = argspecs[n];
if (argspecs != *argspecs_addr)
free (argspecs);
argspecs = newarg;
max_argspecs = newmax;
}
arg = argspecs + argcount;
arg->length = s - format + 2;
arg->flags = flags;
arg->width = width;
arg->precision = precision;
arg->lenmod = lenmod;
arg->conspec = conspec;
arg->arg_pos = arg_pos;
arg->width_pos = width_pos;
arg->precision_pos = precision_pos;
compute_type (arg);
argcount++;
format = s;
}
*argspecs_addr = argspecs;
*r_argspecs_count = argcount;
return 0; /* Success. */
leave_einval:
_set_errno (EINVAL);
leave:
if (argspecs != *argspecs_addr)
free (argspecs);
*argspecs_addr = NULL;
return -1;
}
/* This function reads all the values as specified by VALUETABLE into
VALUETABLE. The values are expected in VAARGS. The function
returns -1 if a specified type is not supported. */
static int
read_values (valueitem_t valuetable, size_t valuetable_len, va_list vaargs)
{
int validx;
for (validx=0; validx < valuetable_len; validx++)
{
value_t *value = &valuetable[validx].value;
valtype_t vt = valuetable[validx].vt;
switch (vt)
{
case VALTYPE_CHAR: value->a_char = va_arg (vaargs, int); break;
case VALTYPE_CHAR_PTR:
value->a_char_ptr = va_arg (vaargs, char *);
break;
case VALTYPE_SCHAR: value->a_schar = va_arg (vaargs, int); break;
case VALTYPE_SCHAR_PTR:
value->a_schar_ptr = va_arg (vaargs, signed char *);
break;
case VALTYPE_UCHAR: value->a_uchar = va_arg (vaargs, int); break;
case VALTYPE_SHORT: value->a_short = va_arg (vaargs, int); break;
case VALTYPE_USHORT: value->a_ushort = va_arg (vaargs, int); break;
case VALTYPE_SHORT_PTR:
value->a_short_ptr = va_arg (vaargs, short *);
break;
case VALTYPE_INT:
value->a_int = va_arg (vaargs, int);
break;
case VALTYPE_INT_PTR:
value->a_int_ptr = va_arg (vaargs, int *);
break;
case VALTYPE_UINT:
value->a_uint = va_arg (vaargs, unsigned int);
break;
case VALTYPE_LONG:
value->a_long = va_arg (vaargs, long);
break;
case VALTYPE_ULONG:
value->a_ulong = va_arg (vaargs, unsigned long);
break;
case VALTYPE_LONG_PTR:
value->a_long_ptr = va_arg (vaargs, long *);
break;
#ifdef HAVE_LONG_LONG_INT
case VALTYPE_LONGLONG:
value->a_longlong = va_arg (vaargs, long long int);
break;
case VALTYPE_ULONGLONG:
value->a_ulonglong = va_arg (vaargs, unsigned long long int);
break;
case VALTYPE_LONGLONG_PTR:
value->a_longlong_ptr = va_arg (vaargs, long long *);
break;
#endif
case VALTYPE_DOUBLE:
value->a_double = va_arg (vaargs, double);
break;
#ifdef HAVE_LONG_DOUBLE
case VALTYPE_LONGDOUBLE:
value->a_longdouble = va_arg (vaargs, long double);
break;
#endif
case VALTYPE_STRING:
value->a_string = va_arg (vaargs, const char *);
break;
case VALTYPE_POINTER:
value->a_void_ptr = va_arg (vaargs, void *);
break;
#ifdef HAVE_INTMAX_T
case VALTYPE_INTMAX:
value->a_intmax = va_arg (vaargs, intmax_t);
break;
case VALTYPE_INTMAX_PTR:
value->a_intmax_ptr = va_arg (vaargs, intmax_t *);
break;
#endif
#ifdef HAVE_UINTMAX_T
case VALTYPE_UINTMAX:
value->a_uintmax = va_arg (vaargs, uintmax_t);
break;
#endif
case VALTYPE_SIZE:
value->a_size = va_arg (vaargs, size_t);
break;
case VALTYPE_SIZE_PTR:
value->a_size_ptr = va_arg (vaargs, size_t *);
break;
#ifdef HAVE_PTRDIFF_T
case VALTYPE_PTRDIFF:
value->a_ptrdiff = va_arg (vaargs, ptrdiff_t);
break;
case VALTYPE_PTRDIFF_PTR:
value->a_ptrdiff_ptr = va_arg (vaargs, ptrdiff_t *);
break;
#endif
default: /* Unsupported type. */
return -1;
}
}
return 0;
}
/* Output COUNT padding characters PADCHAR and update NBYTES by the
number of bytes actually written. */
static int
pad_out (estream_printf_out_t outfnc, void *outfncarg,
int padchar, int count, size_t *nbytes)
{
char buf[32];
size_t n;
int rc;
while (count > 0)
{
n = (count <= sizeof buf)? count : sizeof buf;
memset (buf, padchar, n);
rc = outfnc (outfncarg, buf, n);
if (rc)
return rc;
*nbytes += n;
count -= n;
}
return 0;
}
/* "d,i,o,u,x,X" formatting. OUTFNC and OUTFNCARG describes the
output routine, ARG gives the argument description and VALUE the
actual value (its type is available through arg->vt). */
static int
pr_integer (estream_printf_out_t outfnc, void *outfncarg,
argspec_t arg, value_t value, size_t *nbytes)
{
int rc;
#ifdef HAVE_LONG_LONG_INT
unsigned long long aulong;
#else
unsigned long aulong;
#endif
char numbuf[100];
char *p, *pend;
size_t n;
char signchar = 0;
int n_prec; /* Number of extra precision digits required. */
int n_extra; /* Extra number of prefix or sign characters. */
if (arg->conspec == CONSPEC_DECIMAL)
{
#ifdef HAVE_LONG_LONG_INT
long long along;
#else
long along;
#endif
switch (arg->vt)
{
case VALTYPE_SHORT: along = value.a_short; break;
case VALTYPE_INT: along = value.a_int; break;
case VALTYPE_LONG: along = value.a_long; break;
#ifdef HAVE_LONG_LONG_INT
case VALTYPE_LONGLONG: along = value.a_longlong; break;
case VALTYPE_SIZE: along = value.a_size; break;
# ifdef HAVE_INTMAX_T
case VALTYPE_INTMAX: along = value.a_intmax; break;
# endif
# ifdef HAVE_PTRDIFF_T
case VALTYPE_PTRDIFF: along = value.a_ptrdiff; break;
# endif
#endif /*HAVE_LONG_LONG_INT*/
default:
return -1;
}
if (along < 0)
{
aulong = -along;
signchar = '-';
}
else
aulong = along;
}
else
{
switch (arg->vt)
{
case VALTYPE_USHORT: aulong = value.a_ushort; break;
case VALTYPE_UINT: aulong = value.a_uint; break;
case VALTYPE_ULONG: aulong = value.a_ulong; break;
#ifdef HAVE_LONG_LONG_INT
case VALTYPE_ULONGLONG: aulong = value.a_ulonglong; break;
case VALTYPE_SIZE: aulong = value.a_size; break;
# ifdef HAVE_UINTMAX_T
case VALTYPE_UINTMAX: aulong = value.a_uintmax; break;
# endif
# ifdef HAVE_PTRDIFF_T
case VALTYPE_PTRDIFF: aulong = value.a_ptrdiff; break;
# endif
#endif /*HAVE_LONG_LONG_INT*/
default:
return -1;
}
}
if (signchar == '-')
;
else if ((arg->flags & FLAG_PLUS_SIGN))
signchar = '+';
else if ((arg->flags & FLAG_SPACE_PLUS))
signchar = ' ';
n_extra = !!signchar;
/* We build the string up backwards. */
p = pend = numbuf + DIM(numbuf);
if ((!aulong && !arg->precision))
;
else if (arg->conspec == CONSPEC_DECIMAL
|| arg->conspec == CONSPEC_UNSIGNED)
{
int grouping = -1;
const char * grouping_string =
#ifdef HAVE_LANGINFO_THOUSANDS_SEP
nl_langinfo(THOUSANDS_SEP);
#else
"'";
#endif
do
{
if ((arg->flags & FLAG_GROUPING)
&& (++grouping == 3) && *grouping_string)
{
*--p = *grouping_string;
grouping = 0;
}
*--p = '0' + (aulong % 10);
aulong /= 10;
}
while (aulong);
}
else if (arg->conspec == CONSPEC_OCTAL)
{
do
{
*--p = '0' + (aulong % 8);
aulong /= 8;
}
while (aulong);
if ((arg->flags & FLAG_ALT_CONV) && *p != '0')
*--p = '0';
}
else /* HEX or HEXUP */
{
const char *digits = ((arg->conspec == CONSPEC_HEX)
? "0123456789abcdef" : "0123456789ABCDEF");
do
{
*--p = digits[(aulong % 16)];
aulong /= 16;
}
while (aulong);
if ((arg->flags & FLAG_ALT_CONV))
n_extra += 2;
}
n = pend - p;
if ((arg->flags & FLAG_ZERO_PAD)
&& arg->precision == NO_FIELD_VALUE && !(arg->flags & FLAG_LEFT_JUST)
&& n && arg->width - n_extra > n )
n_prec = arg->width - n_extra - n;
else if (arg->precision > 0 && arg->precision > n)
n_prec = arg->precision - n;
else
n_prec = 0;
if (!(arg->flags & FLAG_LEFT_JUST)
&& arg->width >= 0 && arg->width - n_extra > n
&& arg->width - n_extra - n >= n_prec )
{
rc = pad_out (outfnc, outfncarg, ' ',
arg->width - n_extra - n - n_prec, nbytes);
if (rc)
return rc;
}
if (signchar)
{
rc = outfnc (outfncarg, &signchar, 1);
if (rc)
return rc;
*nbytes += 1;
}
if ((arg->flags & FLAG_ALT_CONV)
&& (arg->conspec == CONSPEC_HEX || arg->conspec == CONSPEC_HEX_UP))
{
rc = outfnc (outfncarg, arg->conspec == CONSPEC_HEX? "0x": "0X", 2);
if (rc)
return rc;
*nbytes += 2;
}
if (n_prec)
{
rc = pad_out (outfnc, outfncarg, '0', n_prec, nbytes);
if (rc)
return rc;
}
rc = outfnc (outfncarg, p, pend - p);
if (rc)
return rc;
*nbytes += pend - p;
if ((arg->flags & FLAG_LEFT_JUST)
&& arg->width >= 0 && arg->width - n_extra - n_prec > n)
{
rc = pad_out (outfnc, outfncarg, ' ',
arg->width - n_extra - n_prec - n, nbytes);
if (rc)
return rc;
}
return 0;
}
/* "e,E,f,F,g,G,a,A" formatting. OUTFNC and OUTFNCARG describes the
output routine, ARG gives the argument description and VALUE the
actual value (its type is available through arg->vt). For
portability reasons sprintf is used for the actual formatting.
This is useful because sprint is the only standard function to
convert a floating number into its ascii representation. To avoid
using malloc we just pass the precision to sprintf and do the final
formatting with our own code. */
static int
pr_float (estream_printf_out_t outfnc, void *outfncarg,
argspec_t arg, value_t value, size_t *nbytes)
{
int rc;
#ifdef HAVE_LONG_DOUBLE
long double adblfloat = 0; /* Just to please gcc. */
int use_dbl = 0;
#endif
double afloat;
char numbuf[350];
char formatstr[20];
char *p, *pend;
size_t n;
char signchar = 0;
int n_extra; /* Extra number of prefix or sign characters. */
switch (arg->vt)
{
case VALTYPE_DOUBLE: afloat = value.a_double; break;
#ifdef HAVE_LONG_DOUBLE
case VALTYPE_LONGDOUBLE:
afloat = 0; /* Just to please gcc. */
adblfloat = value.a_longdouble;
use_dbl=1; break;
#endif
default:
return -1;
}
/* We build the string using sprint. */
p = formatstr + sizeof formatstr;
*--p = 0;
switch (arg->conspec)
{
case CONSPEC_FLOAT: *--p = 'f'; break;
case CONSPEC_FLOAT_UP: *--p = 'F'; break;
case CONSPEC_EXP: *--p = 'e'; break;
case CONSPEC_EXP_UP: *--p = 'E'; break;
case CONSPEC_F_OR_G: *--p = 'g'; break;
case CONSPEC_F_OR_G_UP: *--p = 'G'; break;
case CONSPEC_HEX_EXP: *--p = 'a'; break;
case CONSPEC_HEX_EXP_UP: *--p = 'A'; break;
default:
return -1; /* Actually a bug. */
}
#ifdef HAVE_LONG_DOUBLE
if (use_dbl)
*--p = 'L';
#endif
if (arg->precision != NO_FIELD_VALUE)
{
/* Limit it to a meaningful value so that even a stupid sprintf
won't overflow our buffer. */
n = arg->precision <= 100? arg->precision : 100;
do
{
*--p = '0' + (n % 10);
n /= 10;
}
while (n);
*--p = '.';
}
if ((arg->flags & FLAG_ALT_CONV))
*--p = '#';
*--p = '%';
#ifdef HAVE_LONG_DOUBLE
if (use_dbl)
sprintf (numbuf, p, adblfloat);
else
#endif /*HAVE_LONG_DOUBLE*/
sprintf (numbuf, p, afloat);
p = numbuf;
n = strlen (numbuf);
pend = p + n;
if (*p =='-')
{
signchar = '-';
p++;
n--;
}
else if ((arg->flags & FLAG_PLUS_SIGN))
signchar = '+';
else if ((arg->flags & FLAG_SPACE_PLUS))
signchar = ' ';
n_extra = !!signchar;
if (!(arg->flags & FLAG_LEFT_JUST)
&& arg->width >= 0 && arg->width - n_extra > n)
{
rc = pad_out (outfnc, outfncarg, ' ', arg->width - n_extra - n, nbytes);
if (rc)
return rc;
}
if (signchar)
{
rc = outfnc (outfncarg, &signchar, 1);
if (rc)
return rc;
*nbytes += 1;
}
rc = outfnc (outfncarg, p, pend - p);
if (rc)
return rc;
*nbytes += pend - p;
if ((arg->flags & FLAG_LEFT_JUST)
&& arg->width >= 0 && arg->width - n_extra > n)
{
rc = pad_out (outfnc, outfncarg, ' ', arg->width - n_extra - n, nbytes);
if (rc)
return rc;
}
return 0;
}
/* "c" formatting. */
static int
pr_char (estream_printf_out_t outfnc, void *outfncarg,
argspec_t arg, value_t value, size_t *nbytes)
{
int rc;
char buf[1];
if (arg->vt != VALTYPE_INT)
return -1;
buf[0] = (unsigned int)value.a_int;
rc = outfnc (outfncarg, buf, 1);
if(rc)
return rc;
*nbytes += 1;
return 0;
}
/* "s" formatting. */
static int
pr_string (estream_printf_out_t outfnc, void *outfncarg,
argspec_t arg, value_t value, size_t *nbytes)
{
int rc;
size_t n;
const char *string, *s;
if (arg->vt != VALTYPE_STRING)
return -1;
string = value.a_string;
if (!string)
string = "(null)";
if (arg->precision >= 0)
{
for (n=0,s=string; *s && n < arg->precision; s++)
n++;
}
else
n = strlen (string);
if (!(arg->flags & FLAG_LEFT_JUST)
&& arg->width >= 0 && arg->width > n )
{
rc = pad_out (outfnc, outfncarg, ' ', arg->width - n, nbytes);
if (rc)
return rc;
}
rc = outfnc (outfncarg, string, n);
if (rc)
return rc;
*nbytes += n;
if ((arg->flags & FLAG_LEFT_JUST)
&& arg->width >= 0 && arg->width > n)
{
rc = pad_out (outfnc, outfncarg, ' ', arg->width - n, nbytes);
if (rc)
return rc;
}
return 0;
}
/* "p" formatting. */
static int
pr_pointer (estream_printf_out_t outfnc, void *outfncarg,
argspec_t arg, value_t value, size_t *nbytes)
{
int rc;
#ifdef HAVE_LONG_LONG_INT
unsigned long long aulong;
#else
unsigned long aulong;
#endif
char numbuf[100];
char *p, *pend;
if (arg->vt != VALTYPE_POINTER)
return -1;
/* We assume that a pointer can be converted to an unsigned long.
That is not correct for a 64 bit Windows, but then we assume that
long long is supported and usable for storing a pointer. */
#if defined(HAVE_LONG_LONG_INT) && (SIZEOF_UNSIGNED_LONG < SIZEOF_VOID_P)
aulong = (unsigned long long)value.a_void_ptr;
#else
aulong = (unsigned long)value.a_void_ptr;
#endif
p = pend = numbuf + DIM(numbuf);
do
{
*--p = "0123456789abcdefx"[(aulong % 16)];
aulong /= 16;
}
while (aulong);
while ((pend-p) < 2*sizeof (aulong))
*--p = '0';
*--p = 'x';
*--p = '0';
rc = outfnc (outfncarg, p, pend - p);
if (rc)
return rc;
*nbytes += pend - p;
return 0;
}
/* "n" pesudo format operation. */
static int
pr_bytes_so_far (estream_printf_out_t outfnc, void *outfncarg,
argspec_t arg, value_t value, size_t *nbytes)
{
(void)outfnc;
(void)outfncarg;
switch (arg->vt)
{
case VALTYPE_SCHAR_PTR:
*value.a_schar_ptr = (signed char)(unsigned int)(*nbytes);
break;
case VALTYPE_SHORT_PTR:
*value.a_short_ptr = (short)(unsigned int)(*nbytes);
break;
case VALTYPE_LONG_PTR:
*value.a_long_ptr = (long)(*nbytes);
break;
#ifdef HAVE_LONG_LONG_INT
case VALTYPE_LONGLONG_PTR:
*value.a_longlong_ptr = (long long)(*nbytes);
break;
#endif
#ifdef HAVE_INTMAX_T
case VALTYPE_INTMAX_PTR:
*value.a_intmax_ptr = (intmax_t)(*nbytes);
break;
#endif
case VALTYPE_SIZE_PTR:
*value.a_size_ptr = (*nbytes);
break;
#ifdef HAVE_PTRDIFF_T
case VALTYPE_PTRDIFF_PTR:
*value.a_ptrdiff_ptr = (ptrdiff_t)(*nbytes);
break;
#endif
case VALTYPE_INT_PTR:
*value.a_int_ptr = (int)(*nbytes);
break;
default:
return -1; /* An unsupported type has been used. */
}
return 0;
}
/* Run the actual formatting. OUTFNC and OUTFNCARG are the output
functions. FORMAT is format string ARGSPECS is the parsed format
string, ARGSPECS_LEN the number of items in ARGSPECS. VALUETABLE
holds the values and may be directly addressed using the position
arguments given by ARGSPECS. MYERRNO is used for the "%m"
conversion. NBYTES well be updated to reflect the number of bytes
send to the output function. */
static int
do_format (estream_printf_out_t outfnc, void *outfncarg,
const char *format, argspec_t argspecs, size_t argspecs_len,
valueitem_t valuetable, int myerrno, size_t *nbytes)
{
int rc = 0;
const char *s;
argspec_t arg = argspecs;
int argidx = 0; /* Only used for assertion. */
size_t n;
value_t value;
s = format;
while ( *s )
{
if (*s != '%')
{
s++;
continue;
}
if (s != format)
{
rc = outfnc (outfncarg, format, (n=s-format));
if (rc)
return rc;
*nbytes += n;
}
if (s[1] == '%')
{
/* Note that this code ignores one trailing percent escape -
this is however okay as the args parser must have
detected this already. */
rc = outfnc (outfncarg, s, 1);
if (rc)
return rc;
*nbytes += 1;
s += 2;
format = s;
continue;
}
/* Save the next start. */
s += arg->length;
format = s;
assert (argidx < argspecs_len);
argidx++;
/* Apply indirect field width and precision values. */
if (arg->width == STAR_FIELD_VALUE)
{
assert (valuetable[arg->width_pos-1].vt == VALTYPE_INT);
arg->width = valuetable[arg->width_pos-1].value.a_int;
if (arg->width < 0)
{
arg->width = -arg->width;
arg->flags |= FLAG_LEFT_JUST;
}
}
if (arg->precision == STAR_FIELD_VALUE)
{
assert (valuetable[arg->precision_pos-1].vt == VALTYPE_INT);
arg->precision = valuetable[arg->precision_pos-1].value.a_int;
if (arg->precision < 0)
arg->precision = NO_FIELD_VALUE;
}
if (arg->arg_pos == -1 && arg->conspec == CONSPEC_STRERROR)
value.a_string = strerror (myerrno);
else
{
assert (arg->vt == valuetable[arg->arg_pos-1].vt);
value = valuetable[arg->arg_pos-1].value;
}
switch (arg->conspec)
{
case CONSPEC_UNKNOWN: assert (!"bug"); break;
case CONSPEC_DECIMAL:
case CONSPEC_UNSIGNED:
case CONSPEC_OCTAL:
case CONSPEC_HEX:
case CONSPEC_HEX_UP:
rc = pr_integer (outfnc, outfncarg, arg, value, nbytes);
break;
case CONSPEC_FLOAT:
case CONSPEC_FLOAT_UP:
case CONSPEC_EXP:
case CONSPEC_EXP_UP:
case CONSPEC_F_OR_G:
case CONSPEC_F_OR_G_UP:
case CONSPEC_HEX_EXP:
case CONSPEC_HEX_EXP_UP:
rc = pr_float (outfnc, outfncarg, arg, value, nbytes);
break;
case CONSPEC_CHAR:
rc = pr_char (outfnc, outfncarg, arg, value, nbytes);
break;
case CONSPEC_STRING:
case CONSPEC_STRERROR:
rc = pr_string (outfnc, outfncarg, arg, value, nbytes);
break;
case CONSPEC_POINTER:
rc = pr_pointer (outfnc, outfncarg, arg, value, nbytes);
break;
case CONSPEC_BYTES_SO_FAR:
rc = pr_bytes_so_far (outfnc, outfncarg, arg, value, nbytes);
break;
}
if (rc)
return rc;
arg++;
}
/* Print out any trailing stuff. */
n = s - format;
rc = n? outfnc (outfncarg, format, n) : 0;
if (!rc)
*nbytes += n;
return rc;
}
/* The versatile printf formatting routine. It expects a callback
function OUTFNC and an opaque argument OUTFNCARG used for actual
output of the formatted stuff. FORMAT is the format specification
and VAARGS a variable argumemt list matching the arguments of
FORMAT. */
int
estream_format (estream_printf_out_t outfnc,
void *outfncarg,
const char *format, va_list vaargs)
{
/* Buffer to hold the argspecs and a pointer to it.*/
struct argspec_s argspecs_buffer[DEFAULT_MAX_ARGSPECS];
argspec_t argspecs = argspecs_buffer;
size_t argspecs_len; /* Number of specifications in ARGSPECS. */
/* Buffer to hold the description for the values. */
struct valueitem_s valuetable_buffer[DEFAULT_MAX_VALUES];
valueitem_t valuetable = valuetable_buffer;
int rc; /* Return code. */
size_t argidx; /* Used to index the argspecs array. */
size_t validx; /* Used to index the valuetable. */
int max_pos;/* Highest argument position. */
size_t nbytes = 0; /* Keep track of the number of bytes passed to
the output function. */
int myerrno = errno; /* Save the errno for use with "%m". */
/* Parse the arguments to come up with descriptive list. We can't
do this on the fly because we need to support positional
arguments. */
rc = parse_format (format, &argspecs, DIM(argspecs_buffer), &argspecs_len);
if (rc)
goto leave;
/* Check that all ARG_POS fields are set. */
for (argidx=0,max_pos=0; argidx < argspecs_len; argidx++)
{
if (argspecs[argidx].arg_pos != -1
&& argspecs[argidx].arg_pos > max_pos)
max_pos = argspecs[argidx].arg_pos;
if (argspecs[argidx].width_pos > max_pos)
max_pos = argspecs[argidx].width_pos;
if (argspecs[argidx].precision_pos > max_pos)
max_pos = argspecs[argidx].precision_pos;
}
if (!max_pos)
{
/* Fill in all the positions. */
for (argidx=0; argidx < argspecs_len; argidx++)
{
if (argspecs[argidx].width == STAR_FIELD_VALUE)
argspecs[argidx].width_pos = ++max_pos;
if (argspecs[argidx].precision == STAR_FIELD_VALUE)
argspecs[argidx].precision_pos = ++max_pos;
if (argspecs[argidx].arg_pos != -1 )
argspecs[argidx].arg_pos = ++max_pos;
}
}
else
{
/* Check that they are all filled. More test are done later. */
for (argidx=0; argidx < argspecs_len; argidx++)
{
if (!argspecs[argidx].arg_pos
|| (argspecs[argidx].width == STAR_FIELD_VALUE
&& !argspecs[argidx].width_pos)
|| (argspecs[argidx].precision == STAR_FIELD_VALUE
&& !argspecs[argidx].precision_pos))
goto leave_einval;
}
}
/* Check that there is no overflow in max_pos and that it has a
reasonable length. There may never be more elements than the
number of characters in FORMAT. */
if (max_pos < 0 || max_pos >= strlen (format))
goto leave_einval;
#ifdef DEBUG
dump_argspecs (argspecs, argspecs_len);
#endif
/* Allocate a table to hold the values. If it is small enough we
use a stack allocated buffer. */
if (max_pos > DIM(valuetable_buffer))
{
valuetable = calloc (max_pos, sizeof *valuetable);
if (!valuetable)
goto leave_error;
}
else
{
for (validx=0; validx < DIM(valuetable_buffer); validx++)
valuetable[validx].vt = VALTYPE_UNSUPPORTED;
}
for (argidx=0; argidx < argspecs_len; argidx++)
{
if (argspecs[argidx].arg_pos != - 1)
{
validx = argspecs[argidx].arg_pos - 1;
if (valuetable[validx].vt)
goto leave_einval; /* Already defined. */
valuetable[validx].vt = argspecs[argidx].vt;
}
if (argspecs[argidx].width == STAR_FIELD_VALUE)
{
validx = argspecs[argidx].width_pos - 1;
if (valuetable[validx].vt)
goto leave_einval; /* Already defined. */
valuetable[validx].vt = VALTYPE_INT;
}
if (argspecs[argidx].precision == STAR_FIELD_VALUE)
{
validx = argspecs[argidx].precision_pos - 1;
if (valuetable[validx].vt)
goto leave_einval; /* Already defined. */
valuetable[validx].vt = VALTYPE_INT;
}
}
/* Read all the arguments. This will error out for unsupported
types and for not given positional arguments. */
rc = read_values (valuetable, max_pos, vaargs);
if (rc)
goto leave_einval;
/* for (validx=0; validx < max_pos; validx++) */
/* fprintf (stderr, "%2d: vt=%d\n", validx, valuetable[validx].vt); */
/* Everything has been collected, go ahead with the formatting. */
rc = do_format (outfnc, outfncarg, format,
argspecs, argspecs_len, valuetable, myerrno, &nbytes);
goto leave;
leave_einval:
_set_errno (EINVAL);
leave_error:
rc = -1;
leave:
if (valuetable != valuetable_buffer)
free (valuetable);
if (argspecs != argspecs_buffer)
free (argspecs);
return rc;
}
/* A simple output handler utilizing stdio. */
static int
plain_stdio_out (void *outfncarg, const char *buf, size_t buflen)
{
FILE *fp = (FILE*)outfncarg;
if ( fwrite (buf, buflen, 1, fp) != 1 )
return -1;
return 0;
}
/* A replacement for printf. */
int
estream_printf (const char *format, ...)
{
int rc;
va_list arg_ptr;
va_start (arg_ptr, format);
rc = estream_format (plain_stdio_out, stderr, format, arg_ptr);
va_end (arg_ptr);
return rc;
}
/* A replacement for fprintf. */
int
estream_fprintf (FILE *fp, const char *format, ...)
{
int rc;
va_list arg_ptr;
va_start (arg_ptr, format);
rc = estream_format (plain_stdio_out, fp, format, arg_ptr);
va_end (arg_ptr);
return rc;
}
/* A replacement for vfprintf. */
int
estream_vfprintf (FILE *fp, const char *format, va_list arg_ptr)
{
return estream_format (plain_stdio_out, fp, format, arg_ptr);
}
/* Communication object used between estream_snprintf and
fixed_buffer_out. */
struct fixed_buffer_parm_s
{
size_t size; /* Size of the buffer. */
size_t count; /* Number of bytes requested for output. */
size_t used; /* Used size of the buffer. */
char *buffer; /* Provided buffer. */
};
/* A simple malloced buffer output handler. */
static int
fixed_buffer_out (void *outfncarg, const char *buf, size_t buflen)
{
struct fixed_buffer_parm_s *parm = outfncarg;
parm->count += buflen;
if (!parm->buffer)
;
else if (parm->used + buflen < parm->size)
{
/* Handle the common case that everything fits into the buffer
separately. */
memcpy (parm->buffer + parm->used, buf, buflen);
parm->used += buflen;
}
else
{
/* The slow version of above. */
for ( ;buflen && parm->used < parm->size; buflen--)
parm->buffer[parm->used++] = *buf++;
}
return 0;
}
/* A replacement for vsnprintf. */
int
estream_vsnprintf (char *buf, size_t bufsize,
const char *format, va_list arg_ptr)
{
struct fixed_buffer_parm_s parm;
int rc;
parm.size = bufsize;
parm.count = 0;
parm.used = 0;
parm.buffer = bufsize?buf:NULL;
rc = estream_format (fixed_buffer_out, &parm, format, arg_ptr);
if (!rc)
rc = fixed_buffer_out (&parm, "", 1); /* Print terminating Nul. */
if (rc == -1)
return -1;
if (bufsize && buf && parm.size && parm.count >= parm.size)
buf[parm.size-1] = 0;
parm.count--; /* Do not count the trailing nul. */
return (int)parm.count; /* Return number of bytes which would have
been written. */
}
/* A replacement for snprintf. */
int
estream_snprintf (char *buf, size_t bufsize, const char *format, ...)
{
int rc;
va_list arg_ptr;
va_start (arg_ptr, format);
rc = estream_vsnprintf (buf, bufsize, format, arg_ptr);
va_end (arg_ptr);
return rc;
}
/* Communication object used between estream_asprintf and
dynamic_buffer_out. */
struct dynamic_buffer_parm_s
{
int error_flag; /* Internal helper. */
size_t alloced; /* Allocated size of the buffer. */
size_t used; /* Used size of the buffer. */
char *buffer; /* Malloced buffer. */
};
/* A simple malloced buffer output handler. */
static int
dynamic_buffer_out (void *outfncarg, const char *buf, size_t buflen)
{
struct dynamic_buffer_parm_s *parm = outfncarg;
if (parm->error_flag)
{
/* Just in case some formatting routine did not checked for an
error. */
_set_errno (parm->error_flag);
return -1;
}
if (parm->used + buflen >= parm->alloced)
{
char *p;
parm->alloced += buflen + 512;
p = realloc (parm->buffer, parm->alloced);
if (!p)
{
parm->error_flag = errno ? errno : ENOMEM;
/* Wipe out what we already accumulated. This is useful in
case sensitive data is formated. */
memset (parm->buffer, 0, parm->used);
return -1;
}
parm->buffer = p;
}
memcpy (parm->buffer + parm->used, buf, buflen);
parm->used += buflen;
return 0;
}
/* A replacement for vasprintf. As with the BSD of vasprintf version -1
will be returned on error and NULL stored at BUFP. On success the
number of bytes printed will be returned. */
int
estream_vasprintf (char **bufp, const char *format, va_list arg_ptr)
{
struct dynamic_buffer_parm_s parm;
int rc;
parm.error_flag = 0;
parm.alloced = 512;
parm.used = 0;
parm.buffer = my_printf_malloc (parm.alloced);
if (!parm.buffer)
{
*bufp = NULL;
return -1;
}
rc = estream_format (dynamic_buffer_out, &parm, format, arg_ptr);
if (!rc)
rc = dynamic_buffer_out (&parm, "", 1); /* Print terminating Nul. */
/* Fixme: Should we shrink the resulting buffer? */
if (rc != -1 && parm.error_flag)
{
rc = -1;
_set_errno (parm.error_flag);
}
if (rc == -1)
{
memset (parm.buffer, 0, parm.used);
my_printf_free (parm.buffer);
*bufp = NULL;
return -1;
}
assert (parm.used); /* We have at least the terminating Nul. */
*bufp = parm.buffer;
return parm.used - 1; /* Do not include that Nul. */
}
/* A replacement for asprintf. As with the BSD of asprintf version -1
will be returned on error and NULL stored at BUFP. On success the
number of bytes printed will be returned. */
int
estream_asprintf (char **bufp, const char *format, ...)
{
int rc;
va_list arg_ptr;
va_start (arg_ptr, format);
rc = estream_vasprintf (bufp, format, arg_ptr);
va_end (arg_ptr);
return rc;
}