# HG changeset patch # User Timo Sirainen # Date 1181517690 -10800 # Node ID 09415e6a089293da0ff5fc221dd7d96b6e3eea73 # Parent 7577fb89916a3e5ae1d1cc364989abcdafbf2e82 Require C99 compatible vsnprintf(). Removed printf_string_upper_bound() and replaced the code with printf_format_fix*() and vsnprintf(). diff -r 7577fb89916a -r 09415e6a0892 AUTHORS --- a/AUTHORS Mon Jun 11 02:16:59 2007 +0300 +++ b/AUTHORS Mon Jun 11 02:21:30 2007 +0300 @@ -14,20 +14,6 @@ at Carnegie Mellon University (http://www.cmu.edu/computing/). (src/lib/base64.c, src/lib/utc-mktime.c) -GLib Team (src/lib/printf-upper-bound.c) ---------- -Shawn T. Amundson -Jeff Garzik -Raja R Harinath -Tim Janik -Elliot Lee -Tor Lillqvist -Paolo Molaro -Havoc Pennington -Manish Singh -Owen Taylor -Sebastian Wilhelmi - Simon Tatham (src/imap/imap-thread.c merge sorting) Vaclav Haisman (src/lib/ioloop-kqueue.c) diff -r 7577fb89916a -r 09415e6a0892 configure.in --- a/configure.in Mon Jun 11 02:16:59 2007 +0300 +++ b/configure.in Mon Jun 11 02:21:30 2007 +0300 @@ -433,7 +433,7 @@ dnl * after -lsocket and -lnsl tests, inet_aton() may be in them AC_CHECK_FUNCS(fcntl flock lockf inet_aton sigaction getpagesize madvise \ - strcasecmp stricmp vsnprintf vsyslog writev pread \ + strcasecmp stricmp vsyslog writev pread \ setrlimit setproctitle seteuid setreuid setegid setresgid \ strtoull strtouq setpriority quotactl getmntent kqueue kevent \ getrusage backtrace_symbols walkcontext dirfd \ @@ -1311,6 +1311,35 @@ ]) dnl *** +dnl *** C99 vsnprintf()? +dnl *** + +AC_CACHE_CHECK([for C99 vsnprintf()],c99_vsnprintf,[ + AC_RUN_IFELSE([AC_LANG_SOURCE([[ + #include + #include + static int f(const char *fmt, ...) { + va_list args; + char buf[13]; + int ret; + + va_start(args, fmt); + ret = vsnprintf(buf, 11, fmt, args) != 12 || buf[11-1] != '\0'; + va_end(args); + return ret; + } + int main() { + return f("hello %s%d", "world", 1); + }]])], + [c99_vsnprintf=yes], + [c99_vsnprintf=no], + []) +]) +if test $c99_vsnprintf = no; then + AC_ERROR([You don't appear to have C99 compatible vsnprintf() call]) +fi + +dnl *** dnl *** va_copy checks (from GLIB) dnl *** diff -r 7577fb89916a -r 09415e6a0892 src/lib/Makefile.am --- a/src/lib/Makefile.am Mon Jun 11 02:16:59 2007 +0300 +++ b/src/lib/Makefile.am Mon Jun 11 02:21:30 2007 +0300 @@ -67,7 +67,6 @@ ostream-crlf.c \ primes.c \ printf-format-fix.c \ - printf-upper-bound.c \ process-title.c \ randgen.c \ read-full.c \ @@ -148,7 +147,6 @@ ostream-internal.h \ primes.h \ printf-format-fix.h \ - printf-upper-bound.h \ process-title.h \ randgen.h \ read-full.h \ diff -r 7577fb89916a -r 09415e6a0892 src/lib/failures.c --- a/src/lib/failures.c Mon Jun 11 02:16:59 2007 +0300 +++ b/src/lib/failures.c Mon Jun 11 02:21:30 2007 +0300 @@ -83,7 +83,7 @@ if (recursed == 2) { /* we're being called from some signal handler, or - printf_format_fix() killed us again */ + printf_format_fix_unsafe() killed us again */ return -1; } @@ -98,9 +98,8 @@ VA_COPY(args2, args); - t_push(); if (recursed == 2) { - /* printf_format_fix() probably killed us last time, + /* printf_format_fix_unsafe() probably killed us last time, just write the format now. */ fputs("recursed: ", f); @@ -114,14 +113,11 @@ errno = old_errno; /* make sure there's no %n in there and fix %m */ - (void)printf_format_fix(&format); - vfprintf(f, format, args2); + vfprintf(f, printf_format_fix_unsafe(format), args2); } fputc('\n', f); - t_pop(); - errno = old_errno; recursed--; @@ -290,8 +286,7 @@ /* make sure there's no %n in there. vsyslog() supports %m, but since we'll convert it ourself anyway, we might as well it */ - (void)printf_format_fix(&format); - vsyslog(level, format, args); + vsyslog(level, printf_format_fix_unsafe(format), args); recursed--; return 0; diff -r 7577fb89916a -r 09415e6a0892 src/lib/printf-format-fix.c --- a/src/lib/printf-format-fix.c Mon Jun 11 02:16:59 2007 +0300 +++ b/src/lib/printf-format-fix.c Mon Jun 11 02:21:30 2007 +0300 @@ -3,68 +3,81 @@ #include "lib.h" #include "printf-format-fix.h" -static const char *fix_format_real(const char *fmt, const char *p) +static const char * +fix_format_real(const char *fmt, const char *p, unsigned int *len_r) { const char *errstr; char *buf; - size_t pos, alloc, errlen; + unsigned int len1, len2, len3; + + i_assert((size_t)(p - fmt) < INT_MAX); errstr = strerror(errno); - errlen = strlen(errstr); - pos = (size_t) (p-fmt); - i_assert(pos < SSIZE_T_MAX); - - alloc = pos + errlen + 128; - buf = t_buffer_get(alloc); - - memcpy(buf, fmt, pos); - - while (*p != '\0') { - if (*p == '%' && p[1] == 'm') { - if (pos+errlen+1 > alloc) { - alloc += errlen+1 + 128; - buf = t_buffer_get(alloc); - } + /* we'll assume that there's only one %m in the format string. + this simplifies the code and there's really no good reason to have + it multiple times. */ + len1 = p - fmt; + len2 = strlen(errstr); + len3 = strlen(fmt + 1); - memcpy(buf+pos, errstr, errlen); - pos += errlen; - p += 2; - } else { - /* p + \0 */ - if (pos+2 > alloc) { - alloc += 128; - buf = t_buffer_get(alloc); - } + /* @UNSAFE */ + buf = t_buffer_get(len1 + len2 + len3 + 1); + memcpy(buf, fmt, len1); + memcpy(buf + len1, errstr, len2); + memcpy(buf + len1 + len2, p + 1, len3); + buf[len1 + len2 + len3] = '\0'; - buf[pos++] = *p; - p++; - } - } - - buf[pos++] = '\0'; - t_buffer_alloc(pos); + *len_r = len1 + len2 + len3; return buf; } -bool printf_format_fix(const char **format) +static const char * +printf_format_fix_noalloc(const char *format, unsigned int *len_r) { const char *p; - for (p = *format; *p != '\0'; p++) { + for (p = format; *p != '\0'; p++) { if (*p++ == '%') { switch (*p) { case 'n': i_panic("%%n modifier used"); case 'm': - *format = fix_format_real(*format, p-1); - return TRUE; + return fix_format_real(format, p-1, len_r); case '\0': i_panic("%% modifier missing"); } } } - return FALSE; + *len_r = p - format; + return format; +} + +const char *printf_format_fix_get_len(const char *format, unsigned int *len_r) +{ + const char *ret; + + ret = printf_format_fix_noalloc(format, len_r); + if (ret != format) + t_buffer_alloc(*len_r + 1); + return ret; } +const char *printf_format_fix(const char *format) +{ + const char *ret; + unsigned int len; + + ret = printf_format_fix_noalloc(format, &len); + if (ret != format) + t_buffer_alloc(len + 1); + return ret; +} + +const char *printf_format_fix_unsafe(const char *format) +{ + unsigned int len; + + return printf_format_fix_noalloc(format, &len); +} diff -r 7577fb89916a -r 09415e6a0892 src/lib/printf-format-fix.h --- a/src/lib/printf-format-fix.h Mon Jun 11 02:16:59 2007 +0300 +++ b/src/lib/printf-format-fix.h Mon Jun 11 02:21:30 2007 +0300 @@ -2,7 +2,14 @@ #define __PRINTF_FORMAT_FIX_H /* Replaces %m in format with strerror(errno) and panics if %n modifier is - used. Returns TRUE if format was modified. */ -bool printf_format_fix(const char **format); + used. If the format string was modified, it's returned from data stack. */ +const char *printf_format_fix(const char *format) __attr_format_arg__(1); +/* Like printf_format_fix(), except return also the format string's length. */ +const char *printf_format_fix_get_len(const char *format, unsigned int *len_r) + __attr_format_arg__(1); +/* Like printf_format_fix(), except the format string is written to data + stack without actually allocating it. Data stack must not be used until + format string is no longer needed. */ +const char *printf_format_fix_unsafe(const char *format) __attr_format_arg__(1); #endif diff -r 7577fb89916a -r 09415e6a0892 src/lib/printf-upper-bound.c --- a/src/lib/printf-upper-bound.c Mon Jun 11 02:16:59 2007 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,326 +0,0 @@ -/* - Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald - Modified by the GLib Team and others 1997-1999. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public - License along with this library; if not, write to the - Free Software Foundation, Inc., 59 Temple Place - Suite 330, - Boston, MA 02111-1307, USA. -*/ - -#include "lib.h" -#include "printf-format-fix.h" -#include "printf-upper-bound.h" - -typedef union _GDoubleIEEE754 GDoubleIEEE754; -#define G_IEEE754_DOUBLE_BIAS (1023) -/* multiply with base2 exponent to get base10 exponent (nomal numbers) */ -#define G_LOG_2_BASE_10 (0.30102999566398119521) - -#ifndef WORDS_BIGENDIAN -union _GDoubleIEEE754 -{ - double v_double; - struct { - unsigned int mantissa_low : 32; - unsigned int mantissa_high : 20; - unsigned int biased_exponent : 11; - unsigned int sign : 1; - } mpn; -}; -#else -union _GDoubleIEEE754 -{ - double v_double; - struct { - unsigned int sign : 1; - unsigned int biased_exponent : 11; - unsigned int mantissa_high : 20; - unsigned int mantissa_low : 32; - } mpn; -}; -#endif - -typedef struct -{ - unsigned int min_width; - unsigned int precision; - int alternate_format, locale_grouping; - int add_space, add_sign, possible_sign, seen_precision; - int mod_long, mod_extra_long; -} PrintfArgSpec; - -#if (SIZEOF_LONG > 4) || (SIZEOF_VOID_P > 4) -# define HONOUR_LONGS 1 -#else -# define HONOUR_LONGS 0 -#endif - -size_t printf_string_upper_bound(const char **format_p, va_list args) -{ - const char *format = *format_p; - size_t len = 1; - bool fix_format = FALSE; - - if (!format) - return len; - - while (*format) - { - if (*format++ != '%') - len += 1; - else if (*format == 's') - { - /* most commonly used modifier, optimize for it */ - const char *v_string = va_arg (args, const char*); - if (!v_string) - len += 8; /* hold "(null)" */ - else - len += strlen(v_string); - } - else if (*format == 'u') - { - /* second most commonly used modifier */ - (void) va_arg (args, unsigned int); - len += MAX_INT_STRLEN; - } - else - { - PrintfArgSpec spec; - bool seen_l = FALSE, conv_done = FALSE; - unsigned int conv_len = 0; - - memset(&spec, 0, sizeof(spec)); - do - { - char c = *format++; - switch (c) - { - GDoubleIEEE754 u_double; - unsigned int v_uint; - int v_int; - const char *v_string; - - /* beware of positional parameters - */ - case '$': - i_panic("unable to handle positional parameters (%%n$)"); - break; - - /* parse flags - */ - case '#': - spec.alternate_format = TRUE; - break; - case '0': - case '-': - break; - case ' ': - spec.add_space = TRUE; - break; - case '+': - spec.add_sign = TRUE; - break; - case '\'': - spec.locale_grouping = TRUE; - break; - - /* parse output size specifications - */ - case '.': - spec.seen_precision = TRUE; - break; - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - v_uint = c - '0'; - c = *format; - while (c >= '0' && c <= '9') - { - format++; - v_uint = v_uint * 10 + (c - '0'); - c = *format; - } - if (spec.seen_precision) - spec.precision = I_MAX (spec.precision, v_uint); - else - spec.min_width = I_MAX (spec.min_width, v_uint); - break; - case '*': - v_int = va_arg (args, int); - if (spec.seen_precision) - { - /* forget about negative precision */ - if (v_int >= 0) - spec.precision = I_MAX ((int)spec.precision, v_int); - } - else - { - if (v_int < 0) - v_int = - v_int; - spec.min_width = I_MAX ((int)spec.min_width, v_int); - } - break; - - /* parse type modifiers - */ - case 'h': - /* ignore */ - break; - case 'l': - if (!seen_l) - { - spec.mod_long = TRUE; - seen_l = TRUE; - break; - } - /* else, fall through */ - case 'L': - spec.mod_long = TRUE; - spec.mod_extra_long = TRUE; - break; - - /* parse output conversions - */ - case '%': - conv_len += 1; - break; - case 'o': - conv_len += 2; - /* fall through */ - case 'd': - case 'i': - conv_len += 1; /* sign */ - /* fall through */ - case 'u': - conv_len += 4; - /* fall through */ - case 'x': - case 'X': - spec.possible_sign = TRUE; - conv_len += 10; - if (spec.mod_long && HONOUR_LONGS) - conv_len *= 2; - if (spec.mod_extra_long) - conv_len *= 2; - if (spec.mod_extra_long) - { -#if SIZEOF_LONG_LONG > 0 - (void) va_arg (args, long long); -#else - i_panic("mod_extra_long not supported"); -#endif - } - else if (spec.mod_long) - (void) va_arg (args, long); - else - (void) va_arg (args, int); - break; - case 'A': - case 'a': - /* 0x */ - conv_len += 2; - /* fall through */ - case 'g': - case 'G': - case 'e': - case 'E': - case 'f': - spec.possible_sign = TRUE; - /* n . dddddddddddddddddddddddd E +- eeee */ - conv_len += 1 + 1 + I_MAX (24, spec.precision) + 1 + 1 + 4; - if (spec.mod_extra_long) - i_panic("unable to handle long double"); -#ifdef HAVE_LONG_DOUBLE -#error need to implement special handling for long double -#endif - u_double.v_double = va_arg (args, double); - /* %f can expand up to all significant digits before '.' (308) */ - if (c == 'f' && - u_double.mpn.biased_exponent > 0 && u_double.mpn.biased_exponent < 2047) - { - int exp = u_double.mpn.biased_exponent; - - exp -= G_IEEE754_DOUBLE_BIAS; - exp = exp * G_LOG_2_BASE_10 + 1; - conv_len += exp; - } - /* some printf() implementations require extra padding for rounding */ - conv_len += 2; - /* we can't really handle locale specific grouping here */ - if (spec.locale_grouping) - conv_len *= 2; - break; - case 'c': - conv_len += spec.mod_long ? MB_LEN_MAX : 1; - (void) va_arg (args, int); - break; - case 's': - v_string = va_arg (args, char*); - if (!v_string) - conv_len += 8; /* hold "(null)" */ - else if (spec.seen_precision) - conv_len += spec.precision; - else - conv_len += strlen (v_string); - conv_done = TRUE; - if (spec.mod_long) - i_panic("unable to handle wide char strings"); - break; - case 'p': - spec.alternate_format = TRUE; - conv_len += 10; - if (HONOUR_LONGS) - conv_len *= 2; - conv_done = TRUE; - (void) va_arg (args, void*); - break; - case 'm': - /* %m, replace it with strerror() later */ - conv_len += strlen(strerror(errno)) + 256; - fix_format = TRUE; - break; - - /* handle invalid cases - */ - case '\000': - /* no conversion specification, bad bad */ - i_panic("Missing conversion specifier"); - break; - default: - i_panic("unable to handle `%c' while parsing format", c); - break; - } - conv_done |= conv_len > 0; - } - while (!conv_done); - /* handle width specifications */ - conv_len = I_MAX (conv_len, I_MAX (spec.precision, spec.min_width)); - /* handle flags */ - conv_len += spec.alternate_format ? 2 : 0; - conv_len += (spec.add_space || spec.add_sign || spec.possible_sign); - /* finally done */ - len += conv_len; - } /* else (c == '%') */ - } /* while (*format) */ - - if (fix_format) - (void)printf_format_fix(format_p); - return len; -} diff -r 7577fb89916a -r 09415e6a0892 src/lib/printf-upper-bound.h --- a/src/lib/printf-upper-bound.h Mon Jun 11 02:16:59 2007 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,10 +0,0 @@ -#ifndef __PRINTF_UPPER_BOUND_H -#define __PRINTF_UPPER_BOUND_H - -/* Returns the maximum length of given format string when expanded. - If the format is invalid, i_fatal() is called. - - If format contains %m, it's replaced with the real error message. */ -size_t printf_string_upper_bound(const char **format, va_list args); - -#endif diff -r 7577fb89916a -r 09415e6a0892 src/lib/str.c --- a/src/lib/str.c Mon Jun 11 02:16:59 2007 +0300 +++ b/src/lib/str.c Mon Jun 11 02:21:30 2007 +0300 @@ -2,7 +2,7 @@ #include "lib.h" #include "buffer.h" -#include "printf-upper-bound.h" +#include "printf-format-fix.h" #include "str.h" #include @@ -102,29 +102,15 @@ void str_vprintfa(string_t *str, const char *fmt, va_list args) { - char *buf; - int ret; - va_list args2; - size_t len, append_len; - - VA_COPY(args2, args); - - len = buffer_get_used_size(str); - - append_len = printf_string_upper_bound(&fmt, args); - buf = buffer_append_space_unsafe(str, append_len); + const char *tmp; + unsigned int size; -#ifdef HAVE_VSNPRINTF - ret = vsnprintf(buf, append_len, fmt, args2); - i_assert(ret >= 0 && (size_t)ret <= append_len); -#else - ret = vsprintf(buf, fmt, args2); - i_assert(ret >= 0); -#endif - - len += ret; - - buffer_set_used_size(str, len); + tmp = t_noalloc_strdup_vprintf(fmt, args, &size); + if (buffer_get_pool(str)->datastack_pool) { + /* appending to buffer may allocate more data from data stack */ + t_buffer_alloc(size); + } + buffer_append(str, tmp, size - 1); } void str_insert(string_t *str, size_t pos, const char *cstr) diff -r 7577fb89916a -r 09415e6a0892 src/lib/strfuncs.c --- a/src/lib/strfuncs.c Mon Jun 11 02:16:59 2007 +0300 +++ b/src/lib/strfuncs.c Mon Jun 11 02:21:30 2007 +0300 @@ -3,7 +3,7 @@ /* @UNSAFE: whole file */ #include "lib.h" -#include "printf-upper-bound.h" +#include "printf-format-fix.h" #include "strfuncs.h" #include @@ -14,51 +14,18 @@ int i_snprintf(char *dest, size_t max_chars, const char *format, ...) { -#ifndef HAVE_VSNPRINTF - char *buf; -#endif - va_list args, args2; - ssize_t len; + va_list args; int ret; i_assert(max_chars < INT_MAX); - t_push(); - va_start(args, format); - VA_COPY(args2, args); - - len = printf_string_upper_bound(&format, args); - - i_assert(len >= 0); - -#ifdef HAVE_VSNPRINTF - len = vsnprintf(dest, max_chars, format, args2); -#else - buf = t_buffer_get(len); - len = vsprintf(buf, format, args2); -#endif + ret = vsnprintf(dest, max_chars, printf_format_fix_unsafe(format), + args); va_end(args); - if (len < 0) { - /* some error occurred */ - len = 0; - ret = -1; - } else if ((size_t)len >= max_chars) { - /* too large */ - len = max_chars-1; - ret = -1; - } else { - ret = 0; - } - -#ifndef HAVE_VSNPRINTF - memcpy(dest, buf, len); -#endif - dest[len] = '\0'; - - t_pop(); - return ret; + i_assert(ret >= 0); + return (unsigned int)ret < max_chars ? 0 : -1; } char *p_strdup(pool_t pool, const char *str) @@ -132,28 +99,52 @@ return ret; } -char *p_strdup_vprintf(pool_t pool, const char *format, va_list args) +char *t_noalloc_strdup_vprintf(const char *format, va_list args, + unsigned int *size_r) { - char *ret; +#define SNPRINTF_INITIAL_EXTRA_SIZE 256 va_list args2; - size_t len; - - if (!pool->datastack_pool) - t_push(); + char *tmp; + unsigned int init_size; + int ret; VA_COPY(args2, args); - len = printf_string_upper_bound(&format, args); - ret = p_malloc(pool, len); + /* the format string is modified only if %m exists in it. it happens + only in error conditions, so don't try to t_push() here since it'll + just slow down the normal code path. */ + format = printf_format_fix_get_len(format, &init_size); + init_size += SNPRINTF_INITIAL_EXTRA_SIZE; + + tmp = t_buffer_get(init_size); + ret = vsnprintf(tmp, init_size, format, args); + i_assert(ret >= 0); -#ifdef HAVE_VSNPRINTF - vsnprintf(ret, len, format, args2); -#else - vsprintf(ret, format, args2); -#endif - if (!pool->datastack_pool) - t_pop(); - return ret; + *size_r = ret + 1; + if ((unsigned int)ret >= init_size) { + /* didn't fit with the first guess. now we know the size, + so try again. */ + tmp = t_buffer_get(*size_r); + ret = vsnprintf(tmp, *size_r, format, args2); + i_assert((unsigned int)ret == *size_r-1); + } + return tmp; +} + +char *p_strdup_vprintf(pool_t pool, const char *format, va_list args) +{ + char *tmp, *buf; + unsigned int size; + + tmp = t_noalloc_strdup_vprintf(format, args, &size); + if (pool->datastack_pool) { + t_buffer_alloc(size); + return tmp; + } else { + buf = p_malloc(pool, size); + memcpy(buf, tmp, size - 1); + return buf; + } } char *_vstrconcat(const char *str1, va_list args, size_t *ret_len) diff -r 7577fb89916a -r 09415e6a0892 src/lib/strfuncs.h --- a/src/lib/strfuncs.h Mon Jun 11 02:16:59 2007 +0300 +++ b/src/lib/strfuncs.h Mon Jun 11 02:21:30 2007 +0300 @@ -92,6 +92,8 @@ bool strarray_remove(const char **arr, const char *value); /* INTERNAL */ +char *t_noalloc_strdup_vprintf(const char *format, va_list args, + unsigned int *size_r); char *_vstrconcat(const char *str1, va_list args, size_t *ret_len) __attr_malloc__;