changeset 5681:09415e6a0892 HEAD

Require C99 compatible vsnprintf(). Removed printf_string_upper_bound() and replaced the code with printf_format_fix*() and vsnprintf().
author Timo Sirainen <tss@iki.fi>
date Mon, 11 Jun 2007 02:21:30 +0300
parents 7577fb89916a
children ff5ba9cb6cd0
files AUTHORS configure.in src/lib/Makefile.am src/lib/failures.c src/lib/printf-format-fix.c src/lib/printf-format-fix.h src/lib/printf-upper-bound.c src/lib/printf-upper-bound.h src/lib/str.c src/lib/strfuncs.c src/lib/strfuncs.h
diffstat 11 files changed, 151 insertions(+), 480 deletions(-) [+]
line wrap: on
line diff
--- 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  <amundson@gimp.org>
-Jeff Garzik        <jgarzik@pobox.com>
-Raja R Harinath    <harinath@cs.umn.edu>
-Tim Janik          <timj@gtk.org>
-Elliot Lee         <sopwith@redhat.com>
-Tor Lillqvist      <tml@iki.fi>
-Paolo Molaro       <lupus@debian.org>
-Havoc Pennington   <hp@pobox.com>
-Manish Singh       <yosh@gimp.org>
-Owen Taylor        <otaylor@gtk.org>
-Sebastian Wilhelmi <wilhelmi@ira.uka.de>
-
 Simon Tatham (src/imap/imap-thread.c merge sorting)
 
 Vaclav Haisman <v.haisman@sh.cvut.cz> (src/lib/ioloop-kqueue.c)
--- 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 <stdio.h>
+  #include <stdarg.h>
+  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 ***
 
--- 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 \
--- 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;
--- 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);
+}
--- 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
--- 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;
-}
--- 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
--- 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 <stdio.h>
@@ -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)
--- 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 <stdio.h>
@@ -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)
--- 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__;