mirror of
git://git.gnupg.org/gnupg.git
synced 2024-09-06 12:46:22 +02:00
![Werner Koch](/assets/img/avatar_default.png)
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.
1838 lines
51 KiB
C
1838 lines
51 KiB
C
/* 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;
|
||
}
|