986ba33c7a
I've been holding back on this because 1.7.0 requires OpenSSL 1.1.0 or newer for full DANE support. But we can't wait forever, and nothing in base uses DANE anyway, so here we go.
1037 lines
29 KiB
C
1037 lines
29 KiB
C
/* snprintf - compatibility implementation of snprintf, vsnprintf
|
|
*
|
|
* Copyright (c) 2013, NLnet Labs. All rights reserved.
|
|
*
|
|
* This software is open source.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
*
|
|
* 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.
|
|
*
|
|
* Neither the name of the NLNET LABS nor the names of its contributors may
|
|
* be used to endorse or promote products derived from this software without
|
|
* specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "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 COPYRIGHT
|
|
* HOLDER OR CONTRIBUTORS 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.
|
|
*/
|
|
|
|
#include <ldns/config.h>
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#ifdef HAVE_STDINT_H
|
|
#include <stdint.h>
|
|
#endif
|
|
|
|
/* for test */
|
|
/* #define SNPRINTF_TEST 1 */
|
|
#ifdef SNPRINTF_TEST
|
|
#define snprintf my_snprintf
|
|
#define vsnprintf my_vsnprintf
|
|
#endif /* SNPRINTF_TEST */
|
|
|
|
int snprintf(char* str, size_t size, const char* format, ...);
|
|
int vsnprintf(char* str, size_t size, const char* format, va_list arg);
|
|
|
|
/**
|
|
* Very portable snprintf implementation, limited in functionality,
|
|
* esp. for %[capital] %[nonportable] and so on. Reduced float functionality,
|
|
* mostly in formatting and range (e+-16), for %f and %g.
|
|
*
|
|
* %s, %d, %u, %i, %x, %c, %n and %% are fully supported.
|
|
* This includes width, precision, flags 0- +, and *(arg for wid,prec).
|
|
* %f, %g, %m, %p have reduced support, support for wid,prec,flags,*, but
|
|
* less floating point range, no %e formatting for %g.
|
|
*/
|
|
int snprintf(char* str, size_t size, const char* format, ...)
|
|
{
|
|
int r;
|
|
va_list args;
|
|
va_start(args, format);
|
|
r = vsnprintf(str, size, format, args);
|
|
va_end(args);
|
|
return r;
|
|
}
|
|
|
|
/** add padding to string */
|
|
static void
|
|
print_pad(char** at, size_t* left, int* ret, char p, int num)
|
|
{
|
|
while(num--) {
|
|
if(*left > 1) {
|
|
*(*at)++ = p;
|
|
(*left)--;
|
|
}
|
|
(*ret)++;
|
|
}
|
|
}
|
|
|
|
/** get negative symbol, 0 if none */
|
|
static char
|
|
get_negsign(int negative, int plus, int space)
|
|
{
|
|
if(negative)
|
|
return '-';
|
|
if(plus)
|
|
return '+';
|
|
if(space)
|
|
return ' ';
|
|
return 0;
|
|
}
|
|
|
|
#define PRINT_DEC_BUFSZ 32 /* 20 is enough for 64 bit decimals */
|
|
/** print decimal into buffer, returns length */
|
|
static int
|
|
print_dec(char* buf, int max, unsigned int value)
|
|
{
|
|
int i = 0;
|
|
if(value == 0) {
|
|
if(max > 0) {
|
|
buf[0] = '0';
|
|
i = 1;
|
|
}
|
|
} else while(value && i < max) {
|
|
buf[i++] = '0' + value % 10;
|
|
value /= 10;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
/** print long decimal into buffer, returns length */
|
|
static int
|
|
print_dec_l(char* buf, int max, unsigned long value)
|
|
{
|
|
int i = 0;
|
|
if(value == 0) {
|
|
if(max > 0) {
|
|
buf[0] = '0';
|
|
i = 1;
|
|
}
|
|
} else while(value && i < max) {
|
|
buf[i++] = '0' + value % 10;
|
|
value /= 10;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
/** print long decimal into buffer, returns length */
|
|
static int
|
|
print_dec_ll(char* buf, int max, unsigned long long value)
|
|
{
|
|
int i = 0;
|
|
if(value == 0) {
|
|
if(max > 0) {
|
|
buf[0] = '0';
|
|
i = 1;
|
|
}
|
|
} else while(value && i < max) {
|
|
buf[i++] = '0' + value % 10;
|
|
value /= 10;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
/** print hex into buffer, returns length */
|
|
static int
|
|
print_hex(char* buf, int max, unsigned int value)
|
|
{
|
|
const char* h = "0123456789abcdef";
|
|
int i = 0;
|
|
if(value == 0) {
|
|
if(max > 0) {
|
|
buf[0] = '0';
|
|
i = 1;
|
|
}
|
|
} else while(value && i < max) {
|
|
buf[i++] = h[value & 0x0f];
|
|
value >>= 4;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
/** print long hex into buffer, returns length */
|
|
static int
|
|
print_hex_l(char* buf, int max, unsigned long value)
|
|
{
|
|
const char* h = "0123456789abcdef";
|
|
int i = 0;
|
|
if(value == 0) {
|
|
if(max > 0) {
|
|
buf[0] = '0';
|
|
i = 1;
|
|
}
|
|
} else while(value && i < max) {
|
|
buf[i++] = h[value & 0x0f];
|
|
value >>= 4;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
/** print long long hex into buffer, returns length */
|
|
static int
|
|
print_hex_ll(char* buf, int max, unsigned long long value)
|
|
{
|
|
const char* h = "0123456789abcdef";
|
|
int i = 0;
|
|
if(value == 0) {
|
|
if(max > 0) {
|
|
buf[0] = '0';
|
|
i = 1;
|
|
}
|
|
} else while(value && i < max) {
|
|
buf[i++] = h[value & 0x0f];
|
|
value >>= 4;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
/** copy string into result, reversed */
|
|
static void
|
|
spool_str_rev(char** at, size_t* left, int* ret, const char* buf, int len)
|
|
{
|
|
int i = len;
|
|
while(i) {
|
|
if(*left > 1) {
|
|
*(*at)++ = buf[--i];
|
|
(*left)--;
|
|
} else --i;
|
|
(*ret)++;
|
|
}
|
|
}
|
|
|
|
/** copy string into result */
|
|
static void
|
|
spool_str(char** at, size_t* left, int* ret, const char* buf, int len)
|
|
{
|
|
int i;
|
|
for(i=0; i<len; i++) {
|
|
if(*left > 1) {
|
|
*(*at)++ = buf[i];
|
|
(*left)--;
|
|
}
|
|
(*ret)++;
|
|
}
|
|
}
|
|
|
|
/** print number formatted */
|
|
static void
|
|
print_num(char** at, size_t* left, int* ret, int minw, int precision,
|
|
int prgiven, int zeropad, int minus, int plus, int space,
|
|
int zero, int negative, char* buf, int len)
|
|
{
|
|
int w = len; /* excludes minus sign */
|
|
char s = get_negsign(negative, plus, space);
|
|
if(minus) {
|
|
/* left adjust the number into the field, space padding */
|
|
/* calc numw = [sign][zeroes][number] */
|
|
int numw = w;
|
|
if(precision == 0 && zero) numw = 0;
|
|
if(numw < precision) numw = precision;
|
|
if(s) numw++;
|
|
|
|
/* sign */
|
|
if(s) print_pad(at, left, ret, s, 1);
|
|
|
|
/* number */
|
|
if(precision == 0 && zero) {
|
|
/* "" for the number */
|
|
} else {
|
|
if(w < precision)
|
|
print_pad(at, left, ret, '0', precision - w);
|
|
spool_str_rev(at, left, ret, buf, len);
|
|
}
|
|
/* spaces */
|
|
if(numw < minw)
|
|
print_pad(at, left, ret, ' ', minw - numw);
|
|
} else {
|
|
/* pad on the left of the number */
|
|
/* calculate numw has width of [sign][zeroes][number] */
|
|
int numw = w;
|
|
if(precision == 0 && zero) numw = 0;
|
|
if(numw < precision) numw = precision;
|
|
if(!prgiven && zeropad && numw < minw) numw = minw;
|
|
else if(s) numw++;
|
|
|
|
/* pad with spaces */
|
|
if(numw < minw)
|
|
print_pad(at, left, ret, ' ', minw - numw);
|
|
/* print sign (and one less zeropad if so) */
|
|
if(s) {
|
|
print_pad(at, left, ret, s, 1);
|
|
numw--;
|
|
}
|
|
/* pad with zeroes */
|
|
if(w < numw)
|
|
print_pad(at, left, ret, '0', numw - w);
|
|
if(precision == 0 && zero)
|
|
return;
|
|
/* print the characters for the value */
|
|
spool_str_rev(at, left, ret, buf, len);
|
|
}
|
|
}
|
|
|
|
/** print %d and %i */
|
|
static void
|
|
print_num_d(char** at, size_t* left, int* ret, int value,
|
|
int minw, int precision, int prgiven, int zeropad, int minus,
|
|
int plus, int space)
|
|
{
|
|
char buf[PRINT_DEC_BUFSZ];
|
|
int negative = (value < 0);
|
|
int zero = (value == 0);
|
|
int len = print_dec(buf, (int)sizeof(buf),
|
|
(unsigned int)(negative?-value:value));
|
|
print_num(at, left, ret, minw, precision, prgiven, zeropad, minus,
|
|
plus, space, zero, negative, buf, len);
|
|
}
|
|
|
|
/** print %ld and %li */
|
|
static void
|
|
print_num_ld(char** at, size_t* left, int* ret, long value,
|
|
int minw, int precision, int prgiven, int zeropad, int minus,
|
|
int plus, int space)
|
|
{
|
|
char buf[PRINT_DEC_BUFSZ];
|
|
int negative = (value < 0);
|
|
int zero = (value == 0);
|
|
int len = print_dec_l(buf, (int)sizeof(buf),
|
|
(unsigned long)(negative?-value:value));
|
|
print_num(at, left, ret, minw, precision, prgiven, zeropad, minus,
|
|
plus, space, zero, negative, buf, len);
|
|
}
|
|
|
|
/** print %lld and %lli */
|
|
static void
|
|
print_num_lld(char** at, size_t* left, int* ret, long long value,
|
|
int minw, int precision, int prgiven, int zeropad, int minus,
|
|
int plus, int space)
|
|
{
|
|
char buf[PRINT_DEC_BUFSZ];
|
|
int negative = (value < 0);
|
|
int zero = (value == 0);
|
|
int len = print_dec_ll(buf, (int)sizeof(buf),
|
|
(unsigned long long)(negative?-value:value));
|
|
print_num(at, left, ret, minw, precision, prgiven, zeropad, minus,
|
|
plus, space, zero, negative, buf, len);
|
|
}
|
|
|
|
/** print %u */
|
|
static void
|
|
print_num_u(char** at, size_t* left, int* ret, unsigned int value,
|
|
int minw, int precision, int prgiven, int zeropad, int minus,
|
|
int plus, int space)
|
|
{
|
|
char buf[PRINT_DEC_BUFSZ];
|
|
int negative = 0;
|
|
int zero = (value == 0);
|
|
int len = print_dec(buf, (int)sizeof(buf), value);
|
|
print_num(at, left, ret, minw, precision, prgiven, zeropad, minus,
|
|
plus, space, zero, negative, buf, len);
|
|
}
|
|
|
|
/** print %lu */
|
|
static void
|
|
print_num_lu(char** at, size_t* left, int* ret, unsigned long value,
|
|
int minw, int precision, int prgiven, int zeropad, int minus,
|
|
int plus, int space)
|
|
{
|
|
char buf[PRINT_DEC_BUFSZ];
|
|
int negative = 0;
|
|
int zero = (value == 0);
|
|
int len = print_dec_l(buf, (int)sizeof(buf), value);
|
|
print_num(at, left, ret, minw, precision, prgiven, zeropad, minus,
|
|
plus, space, zero, negative, buf, len);
|
|
}
|
|
|
|
/** print %llu */
|
|
static void
|
|
print_num_llu(char** at, size_t* left, int* ret, unsigned long long value,
|
|
int minw, int precision, int prgiven, int zeropad, int minus,
|
|
int plus, int space)
|
|
{
|
|
char buf[PRINT_DEC_BUFSZ];
|
|
int negative = 0;
|
|
int zero = (value == 0);
|
|
int len = print_dec_ll(buf, (int)sizeof(buf), value);
|
|
print_num(at, left, ret, minw, precision, prgiven, zeropad, minus,
|
|
plus, space, zero, negative, buf, len);
|
|
}
|
|
|
|
/** print %x */
|
|
static void
|
|
print_num_x(char** at, size_t* left, int* ret, unsigned int value,
|
|
int minw, int precision, int prgiven, int zeropad, int minus,
|
|
int plus, int space)
|
|
{
|
|
char buf[PRINT_DEC_BUFSZ];
|
|
int negative = 0;
|
|
int zero = (value == 0);
|
|
int len = print_hex(buf, (int)sizeof(buf), value);
|
|
print_num(at, left, ret, minw, precision, prgiven, zeropad, minus,
|
|
plus, space, zero, negative, buf, len);
|
|
}
|
|
|
|
/** print %lx */
|
|
static void
|
|
print_num_lx(char** at, size_t* left, int* ret, unsigned long value,
|
|
int minw, int precision, int prgiven, int zeropad, int minus,
|
|
int plus, int space)
|
|
{
|
|
char buf[PRINT_DEC_BUFSZ];
|
|
int negative = 0;
|
|
int zero = (value == 0);
|
|
int len = print_hex_l(buf, (int)sizeof(buf), value);
|
|
print_num(at, left, ret, minw, precision, prgiven, zeropad, minus,
|
|
plus, space, zero, negative, buf, len);
|
|
}
|
|
|
|
/** print %llx */
|
|
static void
|
|
print_num_llx(char** at, size_t* left, int* ret, unsigned long long value,
|
|
int minw, int precision, int prgiven, int zeropad, int minus,
|
|
int plus, int space)
|
|
{
|
|
char buf[PRINT_DEC_BUFSZ];
|
|
int negative = 0;
|
|
int zero = (value == 0);
|
|
int len = print_hex_ll(buf, (int)sizeof(buf), value);
|
|
print_num(at, left, ret, minw, precision, prgiven, zeropad, minus,
|
|
plus, space, zero, negative, buf, len);
|
|
}
|
|
|
|
/** print %llp */
|
|
static void
|
|
print_num_llp(char** at, size_t* left, int* ret, void* value,
|
|
int minw, int precision, int prgiven, int zeropad, int minus,
|
|
int plus, int space)
|
|
{
|
|
char buf[PRINT_DEC_BUFSZ];
|
|
int negative = 0;
|
|
int zero = (value == 0);
|
|
#if defined(UINTPTR_MAX) && defined(UINT32_MAX) && (UINTPTR_MAX == UINT32_MAX)
|
|
/* avoid warning about upcast on 32bit systems */
|
|
unsigned long long llvalue = (unsigned long)value;
|
|
#else
|
|
unsigned long long llvalue = (unsigned long long)value;
|
|
#endif
|
|
int len = print_hex_ll(buf, (int)sizeof(buf), llvalue);
|
|
if(zero) {
|
|
buf[0]=')';
|
|
buf[1]='l';
|
|
buf[2]='i';
|
|
buf[3]='n';
|
|
buf[4]='(';
|
|
len = 5;
|
|
} else {
|
|
/* put '0x' in front of the (reversed) buffer result */
|
|
if(len < PRINT_DEC_BUFSZ)
|
|
buf[len++] = 'x';
|
|
if(len < PRINT_DEC_BUFSZ)
|
|
buf[len++] = '0';
|
|
}
|
|
print_num(at, left, ret, minw, precision, prgiven, zeropad, minus,
|
|
plus, space, zero, negative, buf, len);
|
|
}
|
|
|
|
#define PRINT_FLOAT_BUFSZ 64 /* xx.yy with 20.20 about the max */
|
|
/** spool remainder after the decimal point to buffer, in reverse */
|
|
static int
|
|
print_remainder(char* buf, int max, double r, int prec)
|
|
{
|
|
unsigned long long cap = 1;
|
|
unsigned long long value;
|
|
int len, i;
|
|
if(prec > 19) prec = 19; /* max we can do */
|
|
if(max < prec) return 0;
|
|
for(i=0; i<prec; i++) {
|
|
cap *= 10;
|
|
}
|
|
r *= (double)cap;
|
|
value = (unsigned long long)r;
|
|
/* see if we need to round up */
|
|
if(((unsigned long long)((r - (double)value)*10.0)) >= 5) {
|
|
value++;
|
|
/* that might carry to numbers before the comma, if so,
|
|
* just ignore that rounding. failure because 64bitprintout */
|
|
if(value >= cap)
|
|
value = cap-1;
|
|
}
|
|
len = print_dec_ll(buf, max, value);
|
|
while(len < prec) { /* pad with zeroes, e.g. if 0.0012 */
|
|
buf[len++] = '0';
|
|
}
|
|
if(len < max)
|
|
buf[len++] = '.';
|
|
return len;
|
|
}
|
|
|
|
/** spool floating point to buffer */
|
|
static int
|
|
print_float(char* buf, int max, double value, int prec)
|
|
{
|
|
/* as xxx.xxx if prec==0, no '.', with prec decimals after . */
|
|
/* no conversion for NAN and INF, because we do not want to require
|
|
linking with -lm. */
|
|
/* Thus, the conversions use 64bit integers to convert the numbers,
|
|
* which makes 19 digits before and after the decimal point the max */
|
|
unsigned long long whole = (unsigned long long)value;
|
|
double remain = value - (double)whole;
|
|
int len = 0;
|
|
if(prec != 0)
|
|
len = print_remainder(buf, max, remain, prec);
|
|
len += print_dec_ll(buf+len, max-len, whole);
|
|
return len;
|
|
}
|
|
|
|
/** print %f */
|
|
static void
|
|
print_num_f(char** at, size_t* left, int* ret, double value,
|
|
int minw, int precision, int prgiven, int zeropad, int minus,
|
|
int plus, int space)
|
|
{
|
|
char buf[PRINT_FLOAT_BUFSZ];
|
|
int negative = (value < 0);
|
|
int zero = 0;
|
|
int len;
|
|
if(!prgiven) precision = 6;
|
|
len = print_float(buf, (int)sizeof(buf), negative?-value:value,
|
|
precision);
|
|
print_num(at, left, ret, minw, 1, 0, zeropad, minus,
|
|
plus, space, zero, negative, buf, len);
|
|
}
|
|
|
|
/* rudimentary %g support */
|
|
static int
|
|
print_float_g(char* buf, int max, double value, int prec)
|
|
{
|
|
unsigned long long whole = (unsigned long long)value;
|
|
double remain = value - (double)whole;
|
|
int before = 0;
|
|
int len = 0;
|
|
|
|
/* number of digits before the decimal point */
|
|
while(whole > 0) {
|
|
before++;
|
|
whole /= 10;
|
|
}
|
|
whole = (unsigned long long)value;
|
|
|
|
if(prec > before && remain != 0.0) {
|
|
/* see if the last decimals are zero, if so, skip them */
|
|
len = print_remainder(buf, max, remain, prec-before);
|
|
while(len > 0 && buf[0]=='0') {
|
|
memmove(buf, buf+1, --len);
|
|
}
|
|
}
|
|
len += print_dec_ll(buf+len, max-len, whole);
|
|
return len;
|
|
}
|
|
|
|
|
|
/** print %g */
|
|
static void
|
|
print_num_g(char** at, size_t* left, int* ret, double value,
|
|
int minw, int precision, int prgiven, int zeropad, int minus,
|
|
int plus, int space)
|
|
{
|
|
char buf[PRINT_FLOAT_BUFSZ];
|
|
int negative = (value < 0);
|
|
int zero = 0;
|
|
int len;
|
|
if(!prgiven) precision = 6;
|
|
if(precision == 0) precision = 1;
|
|
len = print_float_g(buf, (int)sizeof(buf), negative?-value:value,
|
|
precision);
|
|
print_num(at, left, ret, minw, 1, 0, zeropad, minus,
|
|
plus, space, zero, negative, buf, len);
|
|
}
|
|
|
|
|
|
/** strnlen (compat implementation) */
|
|
static int
|
|
my_strnlen(const char* s, int max)
|
|
{
|
|
int i;
|
|
for(i=0; i<max; i++)
|
|
if(s[i]==0)
|
|
return i;
|
|
return max;
|
|
}
|
|
|
|
/** print %s */
|
|
static void
|
|
print_str(char** at, size_t* left, int* ret, char* s,
|
|
int minw, int precision, int prgiven, int minus)
|
|
{
|
|
int w;
|
|
/* with prec: no more than x characters from this string, stop at 0 */
|
|
if(prgiven)
|
|
w = my_strnlen(s, precision);
|
|
else w = (int)strlen(s); /* up to the nul */
|
|
if(w < minw && !minus)
|
|
print_pad(at, left, ret, ' ', minw - w);
|
|
spool_str(at, left, ret, s, w);
|
|
if(w < minw && minus)
|
|
print_pad(at, left, ret, ' ', minw - w);
|
|
}
|
|
|
|
/** print %c */
|
|
static void
|
|
print_char(char** at, size_t* left, int* ret, int c,
|
|
int minw, int minus)
|
|
{
|
|
if(1 < minw && !minus)
|
|
print_pad(at, left, ret, ' ', minw - 1);
|
|
print_pad(at, left, ret, c, 1);
|
|
if(1 < minw && minus)
|
|
print_pad(at, left, ret, ' ', minw - 1);
|
|
}
|
|
|
|
|
|
/**
|
|
* Print to string.
|
|
* str: string buffer for result. result will be null terminated.
|
|
* size: size of the buffer. null is put inside buffer.
|
|
* format: printf format string.
|
|
* arg: '...' arguments to print.
|
|
* returns number of characters. a null is printed after this.
|
|
* return number of bytes that would have been written
|
|
* if the buffer had been large enough.
|
|
*
|
|
* supported format specifiers:
|
|
* %s, %u, %d, %x, %i, %f, %g, %c, %p, %n.
|
|
* length: l, ll (for d, u, x).
|
|
* precision: 6.6d (for d, u, x)
|
|
* %f, %g precisions, 0.3f
|
|
* %20s, '.*s'
|
|
* and %%.
|
|
*/
|
|
int vsnprintf(char* str, size_t size, const char* format, va_list arg)
|
|
{
|
|
char* at = str;
|
|
size_t left = size;
|
|
int ret = 0;
|
|
const char* fmt = format;
|
|
int conv, minw, precision, prgiven, zeropad, minus, plus, space, length;
|
|
while(*fmt) {
|
|
/* copy string before % */
|
|
while(*fmt && *fmt!='%') {
|
|
if(left > 1) {
|
|
*at++ = *fmt++;
|
|
left--;
|
|
} else fmt++;
|
|
ret++;
|
|
}
|
|
|
|
/* see if we are at end */
|
|
if(!*fmt) break;
|
|
|
|
/* fetch next argument % designation from format string */
|
|
fmt++; /* skip the '%' */
|
|
|
|
/********************************/
|
|
/* get the argument designation */
|
|
/********************************/
|
|
/* we must do this vararg stuff inside this function for
|
|
* portability. Hence, get_designation, and print_designation
|
|
* are not their own functions. */
|
|
|
|
/* printout designation:
|
|
* conversion specifier: x, d, u, s, c, n, m, p
|
|
* flags: # not supported
|
|
* 0 zeropad (on the left)
|
|
* - left adjust (right by default)
|
|
* ' ' printspace for positive number (in - position).
|
|
* + alwayssign
|
|
* fieldwidth: [1-9][0-9]* minimum field width.
|
|
* if this is * then type int next argument specifies the minwidth.
|
|
* if this is negative, the - flag is set (with positive width).
|
|
* precision: period[digits]*, %.2x.
|
|
* if this is * then type int next argument specifies the precision.
|
|
* just '.' or negative value means precision=0.
|
|
* this is mindigits to print for d, i, u, x
|
|
* this is aftercomma digits for f
|
|
* this is max number significant digits for g
|
|
* maxnumber characters to be printed for s
|
|
* length: 0-none (int), 1-l (long), 2-ll (long long)
|
|
* notsupported: hh (char), h (short), L (long double), q, j, z, t
|
|
* Does not support %m$ and *m$ argument designation as array indices.
|
|
* Does not support %#x
|
|
*
|
|
*/
|
|
minw = 0;
|
|
precision = 1;
|
|
prgiven = 0;
|
|
zeropad = 0;
|
|
minus = 0;
|
|
plus = 0;
|
|
space = 0;
|
|
length = 0;
|
|
|
|
/* get flags in any order */
|
|
for(;;) {
|
|
if(*fmt == '0')
|
|
zeropad = 1;
|
|
else if(*fmt == '-')
|
|
minus = 1;
|
|
else if(*fmt == '+')
|
|
plus = 1;
|
|
else if(*fmt == ' ')
|
|
space = 1;
|
|
else break;
|
|
fmt++;
|
|
}
|
|
|
|
/* field width */
|
|
if(*fmt == '*') {
|
|
fmt++; /* skip char */
|
|
minw = va_arg(arg, int);
|
|
if(minw < 0) {
|
|
minus = 1;
|
|
minw = -minw;
|
|
}
|
|
} else while(*fmt >= '0' && *fmt <= '9') {
|
|
minw = minw*10 + (*fmt++)-'0';
|
|
}
|
|
|
|
/* precision */
|
|
if(*fmt == '.') {
|
|
fmt++; /* skip period */
|
|
prgiven = 1;
|
|
precision = 0;
|
|
if(*fmt == '*') {
|
|
fmt++; /* skip char */
|
|
precision = va_arg(arg, int);
|
|
if(precision < 0)
|
|
precision = 0;
|
|
} else while(*fmt >= '0' && *fmt <= '9') {
|
|
precision = precision*10 + (*fmt++)-'0';
|
|
}
|
|
}
|
|
|
|
/* length */
|
|
if(*fmt == 'l') {
|
|
fmt++; /* skip char */
|
|
length = 1;
|
|
if(*fmt == 'l') {
|
|
fmt++; /* skip char */
|
|
length = 2;
|
|
}
|
|
}
|
|
|
|
/* get the conversion */
|
|
if(!*fmt) conv = 0;
|
|
else conv = *fmt++;
|
|
|
|
/***********************************/
|
|
/* print that argument designation */
|
|
/***********************************/
|
|
switch(conv) {
|
|
case 'i':
|
|
case 'd':
|
|
if(length == 0)
|
|
print_num_d(&at, &left, &ret, va_arg(arg, int),
|
|
minw, precision, prgiven, zeropad, minus, plus, space);
|
|
else if(length == 1)
|
|
print_num_ld(&at, &left, &ret, va_arg(arg, long),
|
|
minw, precision, prgiven, zeropad, minus, plus, space);
|
|
else if(length == 2)
|
|
print_num_lld(&at, &left, &ret,
|
|
va_arg(arg, long long),
|
|
minw, precision, prgiven, zeropad, minus, plus, space);
|
|
break;
|
|
case 'u':
|
|
if(length == 0)
|
|
print_num_u(&at, &left, &ret,
|
|
va_arg(arg, unsigned int),
|
|
minw, precision, prgiven, zeropad, minus, plus, space);
|
|
else if(length == 1)
|
|
print_num_lu(&at, &left, &ret,
|
|
va_arg(arg, unsigned long),
|
|
minw, precision, prgiven, zeropad, minus, plus, space);
|
|
else if(length == 2)
|
|
print_num_llu(&at, &left, &ret,
|
|
va_arg(arg, unsigned long long),
|
|
minw, precision, prgiven, zeropad, minus, plus, space);
|
|
break;
|
|
case 'x':
|
|
if(length == 0)
|
|
print_num_x(&at, &left, &ret,
|
|
va_arg(arg, unsigned int),
|
|
minw, precision, prgiven, zeropad, minus, plus, space);
|
|
else if(length == 1)
|
|
print_num_lx(&at, &left, &ret,
|
|
va_arg(arg, unsigned long),
|
|
minw, precision, prgiven, zeropad, minus, plus, space);
|
|
else if(length == 2)
|
|
print_num_llx(&at, &left, &ret,
|
|
va_arg(arg, unsigned long long),
|
|
minw, precision, prgiven, zeropad, minus, plus, space);
|
|
break;
|
|
case 's':
|
|
print_str(&at, &left, &ret, va_arg(arg, char*),
|
|
minw, precision, prgiven, minus);
|
|
break;
|
|
case 'c':
|
|
print_char(&at, &left, &ret, va_arg(arg, int),
|
|
minw, minus);
|
|
break;
|
|
case 'n':
|
|
*va_arg(arg, int*) = ret;
|
|
break;
|
|
case 'm':
|
|
print_str(&at, &left, &ret, strerror(errno),
|
|
minw, precision, prgiven, minus);
|
|
break;
|
|
case 'p':
|
|
print_num_llp(&at, &left, &ret, va_arg(arg, void*),
|
|
minw, precision, prgiven, zeropad, minus, plus, space);
|
|
break;
|
|
case '%':
|
|
print_pad(&at, &left, &ret, '%', 1);
|
|
break;
|
|
case 'f':
|
|
print_num_f(&at, &left, &ret, va_arg(arg, double),
|
|
minw, precision, prgiven, zeropad, minus, plus, space);
|
|
break;
|
|
case 'g':
|
|
print_num_g(&at, &left, &ret, va_arg(arg, double),
|
|
minw, precision, prgiven, zeropad, minus, plus, space);
|
|
break;
|
|
/* unknown */
|
|
default:
|
|
case 0: break;
|
|
}
|
|
}
|
|
|
|
/* zero terminate */
|
|
if(left > 0)
|
|
*at = 0;
|
|
return ret;
|
|
}
|
|
|
|
#ifdef SNPRINTF_TEST
|
|
|
|
/** do tests */
|
|
#undef snprintf
|
|
#define DOTEST(bufsz, result, retval, ...) do { \
|
|
char buf[bufsz]; \
|
|
printf("now test %s\n", #__VA_ARGS__); \
|
|
int r=my_snprintf(buf, sizeof(buf), __VA_ARGS__); \
|
|
if(r != retval || strcmp(buf, result) != 0) { \
|
|
printf("error test(%s) was \"%s\":%d\n", \
|
|
""#bufsz", "#result", "#retval", "#__VA_ARGS__, \
|
|
buf, r); \
|
|
exit(1); \
|
|
} \
|
|
r=snprintf(buf, sizeof(buf), __VA_ARGS__); \
|
|
if(r != retval || strcmp(buf, result) != 0) { \
|
|
printf("error test(%s) differs with system, \"%s\":%d\n", \
|
|
""#bufsz", "#result", "#retval", "#__VA_ARGS__, \
|
|
buf, r); \
|
|
exit(1); \
|
|
} \
|
|
printf("test(\"%s\":%d) passed\n", buf, r); \
|
|
} while(0);
|
|
|
|
/** test program */
|
|
int main(void)
|
|
{
|
|
int x = 0;
|
|
|
|
/* bufsize, expectedstring, expectedretval, snprintf arguments */
|
|
DOTEST(1024, "hello", 5, "hello");
|
|
DOTEST(1024, "h", 1, "h");
|
|
/* warning from gcc for format string, but it does work
|
|
* DOTEST(1024, "", 0, ""); */
|
|
|
|
DOTEST(3, "he", 5, "hello");
|
|
DOTEST(1, "", 7, "%d", 7823089);
|
|
|
|
/* test positive numbers */
|
|
DOTEST(1024, "0", 1, "%d", 0);
|
|
DOTEST(1024, "1", 1, "%d", 1);
|
|
DOTEST(1024, "9", 1, "%d", 9);
|
|
DOTEST(1024, "15", 2, "%d", 15);
|
|
DOTEST(1024, "ab15cd", 6, "ab%dcd", 15);
|
|
DOTEST(1024, "167", 3, "%d", 167);
|
|
DOTEST(1024, "7823089", 7, "%d", 7823089);
|
|
DOTEST(1024, " 12", 3, "%3d", 12);
|
|
DOTEST(1024, "012", 3, "%.3d", 12);
|
|
DOTEST(1024, "012", 3, "%3.3d", 12);
|
|
DOTEST(1024, "012", 3, "%03d", 12);
|
|
DOTEST(1024, " 012", 4, "%4.3d", 12);
|
|
DOTEST(1024, "", 0, "%.0d", 0);
|
|
|
|
/* test negative numbers */
|
|
DOTEST(1024, "-1", 2, "%d", -1);
|
|
DOTEST(1024, "-12", 3, "%3d", -12);
|
|
DOTEST(1024, " -2", 3, "%3d", -2);
|
|
DOTEST(1024, "-012", 4, "%.3d", -12);
|
|
DOTEST(1024, "-012", 4, "%3.3d", -12);
|
|
DOTEST(1024, "-012", 4, "%4.3d", -12);
|
|
DOTEST(1024, " -012", 5, "%5.3d", -12);
|
|
DOTEST(1024, "-12", 3, "%03d", -12);
|
|
DOTEST(1024, "-02", 3, "%03d", -2);
|
|
DOTEST(1024, "-15", 3, "%d", -15);
|
|
DOTEST(1024, "-7307", 5, "%d", -7307);
|
|
DOTEST(1024, "-12 ", 5, "%-5d", -12);
|
|
DOTEST(1024, "-00012", 6, "%-.5d", -12);
|
|
|
|
/* test + and space flags */
|
|
DOTEST(1024, "+12", 3, "%+d", 12);
|
|
DOTEST(1024, " 12", 3, "% d", 12);
|
|
|
|
/* test %u */
|
|
DOTEST(1024, "12", 2, "%u", 12);
|
|
DOTEST(1024, "0", 1, "%u", 0);
|
|
DOTEST(1024, "4294967295", 10, "%u", 0xffffffff);
|
|
|
|
/* test %x */
|
|
DOTEST(1024, "0", 1, "%x", 0);
|
|
DOTEST(1024, "c", 1, "%x", 12);
|
|
DOTEST(1024, "12ab34cd", 8, "%x", 0x12ab34cd);
|
|
|
|
/* test %llu, %lld */
|
|
DOTEST(1024, "18446744073709551615", 20, "%llu",
|
|
(long long)0xffffffffffffffff);
|
|
DOTEST(1024, "-9223372036854775808", 20, "%lld",
|
|
(long long)0x8000000000000000);
|
|
DOTEST(1024, "9223372036854775808", 19, "%llu",
|
|
(long long)0x8000000000000000);
|
|
|
|
/* test %s */
|
|
DOTEST(1024, "hello", 5, "%s", "hello");
|
|
DOTEST(1024, " hello", 10, "%10s", "hello");
|
|
DOTEST(1024, "hello ", 10, "%-10s", "hello");
|
|
DOTEST(1024, "he", 2, "%.2s", "hello");
|
|
DOTEST(1024, " he", 4, "%4.2s", "hello");
|
|
DOTEST(1024, " h", 4, "%4.2s", "h");
|
|
|
|
/* test %c */
|
|
DOTEST(1024, "a", 1, "%c", 'a');
|
|
/* warning from gcc for format string, but it does work
|
|
DOTEST(1024, " a", 5, "%5c", 'a');
|
|
DOTEST(1024, "a", 1, "%.0c", 'a'); */
|
|
|
|
/* test %n */
|
|
DOTEST(1024, "hello", 5, "hello%n", &x);
|
|
if(x != 5) { printf("the %%n failed\n"); exit(1); }
|
|
|
|
/* test %m */
|
|
errno = 0;
|
|
DOTEST(1024, "Success", 7, "%m");
|
|
|
|
/* test %p */
|
|
DOTEST(1024, "0x10", 4, "%p", (void*)0x10);
|
|
DOTEST(1024, "(nil)", 5, "%p", (void*)0x0);
|
|
|
|
/* test %% */
|
|
DOTEST(1024, "%", 1, "%%");
|
|
|
|
/* test %f */
|
|
DOTEST(1024, "0.000000", 8, "%f", 0.0);
|
|
DOTEST(1024, "0.00", 4, "%.2f", 0.0);
|
|
/* differs, "-0.00" DOTEST(1024, "0.00", 4, "%.2f", -0.0); */
|
|
DOTEST(1024, "234.00", 6, "%.2f", 234.005);
|
|
DOTEST(1024, "8973497.1246", 12, "%.4f", 8973497.12456);
|
|
DOTEST(1024, "-12.000000", 10, "%f", -12.0);
|
|
DOTEST(1024, "6", 1, "%.0f", 6.0);
|
|
|
|
DOTEST(1024, "6", 1, "%g", 6.0);
|
|
DOTEST(1024, "6.1", 3, "%g", 6.1);
|
|
DOTEST(1024, "6.15", 4, "%g", 6.15);
|
|
|
|
/* These format strings are from the code of NSD, Unbound, ldns */
|
|
|
|
DOTEST(1024, "abcdef", 6, "%s", "abcdef");
|
|
DOTEST(1024, "005", 3, "%03u", 5);
|
|
DOTEST(1024, "12345", 5, "%03u", 12345);
|
|
DOTEST(1024, "5", 1, "%d", 5);
|
|
DOTEST(1024, "(nil)", 5, "%p", NULL);
|
|
DOTEST(1024, "12345", 5, "%ld", (long)12345);
|
|
DOTEST(1024, "12345", 5, "%lu", (long)12345);
|
|
DOTEST(1024, " 12345", 12, "%12u", (unsigned)12345);
|
|
DOTEST(1024, "12345", 5, "%u", (unsigned)12345);
|
|
DOTEST(1024, "12345", 5, "%llu", (unsigned long long)12345);
|
|
DOTEST(1024, "12345", 5, "%x", 0x12345);
|
|
DOTEST(1024, "12345", 5, "%llx", (long long)0x12345);
|
|
DOTEST(1024, "012345", 6, "%6.6d", 12345);
|
|
DOTEST(1024, "012345", 6, "%6.6u", 12345);
|
|
DOTEST(1024, "1234.54", 7, "%g", 1234.54);
|
|
DOTEST(1024, "123456789.54", 12, "%.12g", 123456789.54);
|
|
DOTEST(1024, "3456789123456.54", 16, "%.16g", 3456789123456.54);
|
|
/* %24g does not work with 24 digits, not enough accuracy,
|
|
* the first 16 digits are correct */
|
|
DOTEST(1024, "12345", 5, "%3.3d", 12345);
|
|
DOTEST(1024, "000", 3, "%3.3d", 0);
|
|
DOTEST(1024, "001", 3, "%3.3d", 1);
|
|
DOTEST(1024, "012", 3, "%3.3d", 12);
|
|
DOTEST(1024, "-012", 4, "%3.3d", -12);
|
|
DOTEST(1024, "he", 2, "%.2s", "hello");
|
|
DOTEST(1024, "helloworld", 10, "%s%s", "hello", "world");
|
|
DOTEST(1024, "he", 2, "%.*s", 2, "hello");
|
|
DOTEST(1024, " hello", 7, "%*s", 7, "hello");
|
|
DOTEST(1024, "hello ", 7, "%*s", -7, "hello");
|
|
DOTEST(1024, "0", 1, "%c", '0');
|
|
DOTEST(1024, "A", 1, "%c", 'A');
|
|
DOTEST(1024, "", 1, "%c", 0);
|
|
DOTEST(1024, "\010", 1, "%c", 8);
|
|
DOTEST(1024, "%", 1, "%%");
|
|
DOTEST(1024, "0a", 2, "%02x", 0x0a);
|
|
DOTEST(1024, "bd", 2, "%02x", 0xbd);
|
|
DOTEST(1024, "12", 2, "%02ld", (long)12);
|
|
DOTEST(1024, "02", 2, "%02ld", (long)2);
|
|
DOTEST(1024, "02", 2, "%02u", (unsigned)2);
|
|
DOTEST(1024, "765432", 6, "%05u", (unsigned)765432);
|
|
DOTEST(1024, "10.234", 6, "%0.3f", 10.23421);
|
|
DOTEST(1024, "123456.234", 10, "%0.3f", 123456.23421);
|
|
DOTEST(1024, "123456789.234", 13, "%0.3f", 123456789.23421);
|
|
DOTEST(1024, "123456.23", 9, "%.2f", 123456.23421);
|
|
DOTEST(1024, "123456", 6, "%.0f", 123456.23421);
|
|
DOTEST(1024, "0123", 4, "%.4x", 0x0123);
|
|
DOTEST(1024, "00000123", 8, "%.8x", 0x0123);
|
|
DOTEST(1024, "ffeb0cde", 8, "%.8x", 0xffeb0cde);
|
|
DOTEST(1024, " 987654321", 10, "%10lu", (unsigned long)987654321);
|
|
DOTEST(1024, " 987654321", 12, "%12lu", (unsigned long)987654321);
|
|
DOTEST(1024, "987654321", 9, "%i", 987654321);
|
|
DOTEST(1024, "-87654321", 9, "%i", -87654321);
|
|
DOTEST(1024, "hello ", 16, "%-16s", "hello");
|
|
DOTEST(1024, " ", 16, "%-16s", "");
|
|
DOTEST(1024, "a ", 16, "%-16s", "a");
|
|
DOTEST(1024, "foobarfoobar ", 16, "%-16s", "foobarfoobar");
|
|
DOTEST(1024, "foobarfoobarfoobar", 18, "%-16s", "foobarfoobarfoobar");
|
|
|
|
/* combined expressions */
|
|
DOTEST(1024, "foo 1.0 size 512 edns", 21,
|
|
"foo %s size %d %s%s", "1.0", 512, "", "edns");
|
|
DOTEST(15, "foo 1.0 size 5", 21,
|
|
"foo %s size %d %s%s", "1.0", 512, "", "edns");
|
|
DOTEST(1024, "packet 1203ceff id", 18,
|
|
"packet %2.2x%2.2x%2.2x%2.2x id", 0x12, 0x03, 0xce, 0xff);
|
|
DOTEST(1024, "/tmp/testbound_123abcd.tmp", 26, "/tmp/testbound_%u%s%s.tmp", 123, "ab", "cd");
|
|
|
|
return 0;
|
|
}
|
|
#endif /* SNPRINTF_TEST */
|