4 hour countdown

Started by Marc G. Fournieralmost 21 years ago15 messages
#1Marc G. Fournier
scrappy@postgresql.org

At ~16:00 ADT this afternoon, I will branch, tag and package up 8.0.0 ...
if anyone has any 'show stoppers', let us know within the next 4 hours :)

----
Marc G. Fournier Hub.Org Networking Services (http://www.hub.org)
Email: scrappy@hub.org Yahoo!: yscrappy ICQ: 7615664

#2Nicolai Tufar
ntufar@gmail.com
In reply to: Marc G. Fournier (#1)
%2$, %1$ gettext placeholder replacement is not working under Win32

Sorry for such a late submission.

I just downloaded the latest postgresql-8.0.0-rc5-3.zip installer
for windows and it appears that Windows' printf() does not
support placeholder replacement as described in
http://developer.postgresql.org/docs/postgres/nls.html#AEN57284

I searched list archives but found no explanation for this.
Original string is <<%s at or near \"%s\">> and we
replaced it in Turkish for <<\"%2$s\" yerinde %1$s>>.

It works perfectly fine on Linux but fails miserably under
Windows with something like:
<<HATA: "$s" yerinde $sat character 1>>
while on Linux the same version works fine:
<<HATA: "sdfassfsdfasd" yerinde söz dizim hatasý at character 1 >>

What is it? I suppose that Windows' printf() does
not support changing the order of placeholder
characters with %2$ .... %1$. Did some one else
deal with it? Is it because of compilation flags?

In any case PostgreSQL version in current pginstaller
is broken.

Best regards,
Nicolai

#3Fx
weimaraner@gmail.com
In reply to: Nicolai Tufar (#2)
Re: [HACKERS] %2$, %1$ gettext placeholder replacement is not working under Win32

unix/win32 libc doesnt support "$n" variables..

On Mon, 17 Jan 2005 21:03:56 +0200, Nicolai Tufar <ntufar@gmail.com> wrote:

Sorry for such a late submission.

I just downloaded the latest postgresql-8.0.0-rc5-3.zip installer
for windows and it appears that Windows' printf() does not
support placeholder replacement as described in
http://developer.postgresql.org/docs/postgres/nls.html#AEN57284

I searched list archives but found no explanation for this.
Original string is <<%s at or near \"%s\">> and we
replaced it in Turkish for <<\"%2$s\" yerinde %1$s>>.

It works perfectly fine on Linux but fails miserably under
Windows with something like:
<<HATA: "$s" yerinde $sat character 1>>
while on Linux the same version works fine:
<<HATA: "sdfassfsdfasd" yerinde söz dizim hatasý at character 1 >>

What is it? I suppose that Windows' printf() does
not support changing the order of placeholder
characters with %2$ .... %1$. Did some one else
deal with it? Is it because of compilation flags?

In any case PostgreSQL version in current pginstaller
is broken.

Best regards,
Nicolai

---------------------------(end of broadcast)---------------------------
TIP 1: subscribe and unsubscribe commands go to majordomo@postgresql.org

--
/* Jumping Flowers */

#4Nicolai Tufar
ntufar@gmail.com
In reply to: Fx (#3)
Re: [HACKERS] %2$, %1$ gettext placeholder replacement is not working under Win32

On Mon, 17 Jan 2005 16:17:33 -0300, Fx <weimaraner@gmail.com> wrote:

unix/win32 libc doesnt support "$n" variables..

What can we do then?

#5Tom Lane
tgl@sss.pgh.pa.us
In reply to: Nicolai Tufar (#2)
Re: %2$, %1$ gettext placeholder replacement is not working under Win32

Nicolai Tufar <ntufar@gmail.com> writes:

Sorry for such a late submission.
I just downloaded the latest postgresql-8.0.0-rc5-3.zip installer
for windows and it appears that Windows' printf() does not
support placeholder replacement as described in
http://developer.postgresql.org/docs/postgres/nls.html#AEN57284

Original string is <<%s at or near \"%s\">> and we
replaced it in Turkish for <<\"%2$s\" yerinde %1$s>>.

Hmm. Looking around, it seems that %n$ support is required by the
Single Unix Spec:
http://www.opengroup.org/onlinepubs/007908799/xsh/fprintf.html
but it is *not* required by C99 as far as I can tell. I don't see any
mention of support for it in my HPUX fprintf man page, either. So this
construct may not be as portable as we could wish.

There appear to be about 150 affected messages, in these files:

src/backend/po/pt_BR.po
src/backend/po/de.po
src/backend/po/es.po
src/backend/po/zh_CN.po
src/backend/po/tr.po
src/bin/pg_dump/po/zh_CN.po
src/bin/pg_dump/po/tr.po
src/bin/psql/po/zh_CN.po
src/bin/psql/po/zh_TW.po
src/bin/psql/po/tr.po
src/bin/scripts/po/zh_CN.po

I don't think we'll hold up release to fix this, but the affected
translators may want to think about whether they can avoid the problem
or not.

regards, tom lane

#6Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#5)
Re: [HACKERS] %2$, %1$ gettext placeholder replacement is not working under Win32

I wrote:

I don't think we'll hold up release to fix this, but the affected
translators may want to think about whether they can avoid the problem
or not.

Also, it looks like src/port/snprintf.c is not %n$ capable either.
I'm not sure which platforms that affects.

A possible route to a solution is to upgrade snprintf.c and then use
it on platforms that don't have this support. This only fixes those
cases where we go through snprintf, which is probably not all of the
affected messages, but it might be enough. It's not happening for
8.0.0 though.

regards, tom lane

#7Devrim GUNDUZ
devrim@gunduz.org
In reply to: Tom Lane (#5)
Re: [pgsql-hackers-win32] %2$, %1$ gettext placeholder

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi,

On Mon, 17 Jan 2005, Tom Lane wrote:

<snip>

I don't think we'll hold up release to fix this,

:-) It seems nothing seems to stop you from holding up this release
anymore: Neither ARC problem nor this one :)

Regards,
- --
Devrim GUNDUZ
devrim~gunduz.org, devrim~PostgreSQL.org, devrim.gunduz~linux.org.tr
http://www.tdmsoft.com http://www.gunduz.org
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.1 (GNU/Linux)

iD8DBQFB7B0Ktl86P3SPfQ4RAqCFAJ9YLdaUP8ALAetKHQcpPxHfrlcXcQCgpKmX
gKsYQYdu8nh4rGQOo2DQl3c=
=g6q/
-----END PGP SIGNATURE-----

#8Nicolai Tufar
ntufar@gmail.com
In reply to: Tom Lane (#6)
Re: [HACKERS] %2$, %1$ gettext placeholder replacement is not working under Win32

On Mon, 17 Jan 2005 14:54:44 -0500, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Also, it looks like src/port/snprintf.c is not %n$ capable either.
I'm not sure which platforms that affects.

A possible route to a solution is to upgrade snprintf.c and then use
it on platforms that don't have this support. This only fixes those
cases where we go through snprintf, which is probably not all of the
affected messages, but it might be enough. It's not happening for
8.0.0 though.

Bad news for the Turks. Turkish language syntax
is lake Latin: the verb is always at the end. Because
of that almost all messages that have two or more
placeholders are broken under Windows.

The solution you propose seem to be a trivial exercise:
1. write a replacement function, say, pg_snprintf() that
would shuffle input arguments, replace %x$ with %
and then call libc's snprintf();
2. replace snprintf() in all source files with pg_snprintf();
3. Update Makefiles to use our custom function on broken
platforms or default to snprinf(). (Or maybe pg_snprintf()
should be a #define);
4. Tell everyone to use pg_snprintf() instead of snprintf()
from now on.

I would like volunteer to implement this for 8.1 and
would be honoured if I get a chance to contribute.

regards, tom lane

Best regards,
Nicolai Tufar

#9Magnus Hagander
mha@sollentuna.net
In reply to: Nicolai Tufar (#8)
Re: [HACKERS] %2$, %1$ gettext placeholder replacement is not working under Win32

I don't think we'll hold up release to fix this, but the affected
translators may want to think about whether they can avoid

the problem

or not.

Also, it looks like src/port/snprintf.c is not %n$ capable either.
I'm not sure which platforms that affects.

A possible route to a solution is to upgrade snprintf.c and then use
it on platforms that don't have this support. This only fixes those
cases where we go through snprintf, which is probably not all of the
affected messages, but it might be enough. It's not happening for
8.0.0 though.

Might want to track down which platforms are affected by the file in
port/, and then add win32 to it, and put it up somewhere on a list of
known issues in 8.0?

//Magnus

#10Nicolai Tufar
ntufar@gmail.com
In reply to: Magnus Hagander (#9)
Re: %2$, %1$ gettext placeholder replacement is not working under Win32

Greetings,
for a couple of days I have been hacking on src/port/snprintf.c.
With Magnus' help I have managed to implement argument replacement
in snprintf(). The code is very crude and not quite optimised,
any suggestions will be more than welcome.

Here is what I did:

1. I renamed snprintf() to pg_snprintf(), vsnprintf() to pg_vsnprintf()
and introduced pg_printf() that calls pg_vsnprintf().

2. After running configure I manually added snprintf.o to src/Makefile.global's
LIBOBJ declaration and -lpgport to Makefile.shlib's DLLWRAP declaration

3. To make sure these functions are used everywhere I introduced the following
lines at the end of src/include/c.h:

#define snprintf pg_snprintf
#define vsnprintf pg_vsnprintf
#define printf pg_printf

4. I introduced a volatile static char[] variable in snprintf.c code so I can
grep executables for this string and be sure that it is included.

5. Before running regression test I always ran make install, apparently because
libpq is read from /usr/local/.

During compilation the following warnings were reported:
../../../src/include/utils/elog.h:121: warning: `pg_printf' is an
unrecognized format function type

which is perfectly fine because we replace printf with pg_printf and
gcc's format() does
know anything about it.

On Linux, PostgreSQL passed regression tests with flying colours and
prints messages
with %n$ just fine. On win32: int8, timestamp, timestamptz, abstime, horology,
constraints, vacuum, and many others failed. To check my code, I
reverted snprintf.c
to the original one from CVS and forced win32 port to use these
functions and it
fails in same places. After examining regression tests diff I came to
conclusion that problem is in fmtnum() function when it operates with
particularly
long integers.

In snprintf() file I changed only and only dopr() function, neve touching
fmtnum(), fmtstr() or fmtfolat().

I would like to kindly ask these questions:

1. Am I on the right to implement %n$ ? Can it be accepted?
2. Why would we not just take FreeBSD's vfprintf() and use it instead?
3. What is wrong with fmtnum() on Win32?
4. What %m format string is used for? And where is it handled. Do I
need to implement it?

I am attaching my version if snprintf.c (because it is too different
from the original to
make a patch) and regression.diff of a failed Win32 regression test
produced wither with
my or with original snprintf.c.

Best regards,
Nicolai Tufar

#11Peter Eisentraut
peter_e@gmx.net
In reply to: Nicolai Tufar (#10)
Re: [HACKERS] %2$, %1$ gettext placeholder replacement is not working under Win32

Nicolai Tufar wrote:

1. I renamed snprintf() to pg_snprintf(), vsnprintf() to
pg_vsnprintf() and introduced pg_printf() that calls pg_vsnprintf().

This is not necessary. You will notice that we already have configure
tests of snprintf format specifiers (%lld etc.), so using the original
function names is OK even in that case.

4. I introduced a volatile static char[] variable in snprintf.c code
so I can grep executables for this string and be sure that it is
included.

Remove that when finalizing the code.

5. Before running regression test I always ran make install,
apparently because libpq is read from /usr/local/.

That's because of the -rpath.

2. Why would we not just take FreeBSD's vfprintf() and use it
instead?

Try it. It's painful.

4. What %m format string is used for? And where is it handled. Do I
need to implement it?

It's only used in the error reporting functions in the server and is
handled there. You don't need to worry about it.

--
Peter Eisentraut
http://developer.postgresql.org/~petere/

#12Nicolai Tufar
ntufar@gmail.com
In reply to: Peter Eisentraut (#11)
Re: [HACKERS] %2$, %1$ gettext placeholder replacement is not working under Win32

On Sat, 22 Jan 2005 15:31:39 +0100, Peter Eisentraut <peter_e@gmx.net> wrote:

Nicolai Tufar wrote:

1. I renamed snprintf() to pg_snprintf(), vsnprintf() to
pg_vsnprintf() and introduced pg_printf() that calls pg_vsnprintf().

This is not necessary. You will notice that we already have configure
tests of snprintf format specifiers (%lld etc.), so using the original
function names is OK even in that case.

how about a test for the thing like:
printf("%2$s %1$s!\n", "world", "Hello");
It is what I am trying to solve :(

5. Before running regression test I always ran make install,
apparently because libpq is read from /usr/local/.

That's because of the -rpath.

I see. Thanks for information.

2. Why would we not just take FreeBSD's vfprintf() and use it
instead?

Try it. It's painful.

src/port/snprintf.c is not not a particularly pretty. And
after my rework it got even uglier. I have looked at
FreeBDS's one and it is not *that* complicated. Would
you like me to try to incorporate into PostgreSQL.
NetBSD's one is somewhat simplier but does not
support %n$ feature by the way.

4. What %m format string is used for? And where is it handled. Do I
need to implement it?

It's only used in the error reporting functions in the server and is
handled there. You don't need to worry about it.

Oh, that is a relief.

Peter Eisentraut

Nicolai

#13Tom Lane
tgl@sss.pgh.pa.us
In reply to: Nicolai Tufar (#12)
Re: [HACKERS] %2$, %1$ gettext placeholder replacement is not working under Win32

Nicolai Tufar <ntufar@gmail.com> writes:

On Sat, 22 Jan 2005 15:31:39 +0100, Peter Eisentraut <peter_e@gmx.net> wrote:

Nicolai Tufar wrote:

2. Why would we not just take FreeBSD's vfprintf() and use it
instead?

Try it. It's painful.

src/port/snprintf.c is not not a particularly pretty. And
after my rework it got even uglier. I have looked at
FreeBDS's one and it is not *that* complicated.

If you can get it to work then it'd be a better bet than writing (and
having to maintain) our own.

regards, tom lane

#14Nicolai Tufar
ntufar@gmail.com
In reply to: Tom Lane (#13)
Re: [HACKERS] %2$, %1$ gettext placeholder replacement is not working under Win32

On Sat, 22 Jan 2005 17:05:22 -0500, Tom Lane <tgl@sss.pgh.pa.us> wrote:

src/port/snprintf.c is not not a particularly pretty. And
after my rework it got even uglier. I have looked at
FreeBDS's one and it is not *that* complicated.

If you can get it to work then it'd be a better bet than writing (and
having to maintain) our own.

Very well, I am starting to work on it.
I will put everything necessary in on file.
So far it is 1500-odd lines and will probably
grow a little bit. Plus we would probably need to
include Double-to-ASCII package too:
http://cvsup.pt.freebsd.org/cgi-bin/cvsweb/cvsweb.cgi/src/contrib/gdtoa/
A daunting task indeed. I will do it but I am afraid
it would not be too portable.

I was wandering if reimplementing print formatting
is the right thing to do. The problem I have is
in fmtnum() and probably in fmtfloat() functions
which are very platform-dependent.

The code to shuffle xxprinf() arguments based on
%n$ formatting stirngs is ready why don't shuffle
formatting placeholders too and call OS's vsnprintf()
with modified formatting formatting strings and
va_list?

Or, a similar solution, for every parameter
extract formatting placeholder and value,
call snprintf() and the combine the resulting string?

The first solution requires doing nasty manipulations
with va_list, the second will be slower because of
multiple calls to snprintf().

Which one is better?

Best regards,
Nicolai Tufar

Show quoted text

regards, tom lane

#15Nicolai Tufar
ntufar@gmail.com
In reply to: Nicolai Tufar (#14)
1 attachment(s)
Re: [HACKERS] %2$, %1$ gettext placeholder replacement is not working under Win32

Greetings,

I would like to submit a new version of src/port/snprintf.c
It passes regression tests on Linux and Win32 and
prints all of %n$ messages finely.

I added printf() function too because --help usage-type
output is printed with printf().

I have no experience with autoconf so I would ask you
for help on what changes to do to include libpgport to
src/Makefile.global and src/Makefile.shlib.

PostgreSQL uses snprintf() in more places than I expected.
So these changes need a through testing. I have no 64-bit
to able to run regression test on it would be nice to run
it on a 64-bit platform too.

Best regards,
Nicolai Tufar

Attachments:

snprintf.ctext/plain; name=snprintf.cDownload
/*
 * Copyright (c) 1983, 1995, 1996 Eric P. Allman
 * Copyright (c) 1988, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * 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, this list of conditions and the following disclaimer.
 * 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. All advertising materials mentioning features or use of this software
 *	  must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University 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 REGENTS 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 REGENTS 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.
 */

/* might be in either frontend or backend */
#include "postgres_fe.h"

#ifdef ENABLE_THREAD_SAFETY
#error	The replacement snprintf() is not thread-safe.	\
Your platform must have a thread-safe snprintf() to compile with threads.
#endif

#if !defined(WIN32) && !defined(__CYGWIN__)
	#include <sys/ioctl.h>
#endif
#include <sys/param.h>


/*
 * We do all internal arithmetic in the widest available integer type,
 * here called long_long (or ulong_long for unsigned).
 */
#ifdef HAVE_LONG_LONG_INT_64
typedef long long long_long;
typedef unsigned long long ulong_long;

#else
typedef long long_long;
typedef unsigned long ulong_long;
#endif

#ifndef NL_ARGMAX
#define NL_ARGMAX 4096
#endif

/*
**	SNPRINTF, VSNPRINT -- counted versions of printf
**
**	These versions have been grabbed off the net.  They have been
**	cleaned up to compile properly and support for .precision and
**	%lx has been added.
*/

/**************************************************************
 * Original:
 * Patrick Powell Tue Apr 11 09:48:21 PDT 1995
 * A bombproof version of doprnt (dopr) included.
 * Sigh.  This sort of thing is always nasty do deal with.	Note that
 * the version here does not include floating point. (now it does ... tgl)
 *
 * snprintf() is used instead of sprintf() as it does limit checks
 * for string length.  This covers a nasty loophole.
 *
 * The other functions are there to prevent NULL pointers from
 * causing nast effects.
 **************************************************************/

/*static char _id[] = "$PostgreSQL: pgsql/src/port/snprintf.c,v 1.4 2004/08/29 05:07:02 momjian Exp $";*/
static char *end;
static int	SnprfOverflow;

int			snprintf(char *str, size_t count, const char *fmt,...);
int			vsnprintf(char *str, size_t count, const char *fmt, va_list args);
int			printf(const char *format, ...);
static void dopr(char *buffer, const char *format, va_list args);

int
printf(const char *fmt,...)
{
	int			len;
	va_list			args;
	static char*		buffer[4096];
	char*			p;

	va_start(args, fmt);
	len = vsnprintf((char*)buffer, (size_t)4096, fmt, args);
	va_end(args);
	p = (char*)buffer;
	for(;*p;p++)
		putchar(*p);
	return len;
}

int
snprintf(char *str, size_t count, const char *fmt,...)
{
	int			len;
	va_list		args;

	va_start(args, fmt);
	len = vsnprintf(str, count, fmt, args);
	va_end(args);
	return len;
}


int
vsnprintf(char *str, size_t count, const char *fmt, va_list args)
{
	str[0] = '\0';
	end = str + count - 1;
	SnprfOverflow = 0;
	dopr(str, fmt, args);
	if (count > 0)
		end[0] = '\0';
	return strlen(str);
}

/*
 * dopr(): poor man's version of doprintf
 */

static void fmtstr(char *value, int ljust, int len, int zpad, int maxwidth);
static void fmtnum(long_long value, int base, int dosign, int ljust, int len, int zpad);
static void fmtfloat(double value, char type, int ljust, int len, int precision, int pointflag);
static void dostr(char *str, int cut);
static void dopr_outch(int c);

static char *output;

#define	FMTSTR		1
#define	FMTNUM		2
#define	FMTFLOAT	3
#define	FMTCHAR		4

static void
dopr(char *buffer, const char *format, va_list args)
{
	int			ch;
	long_long	value;
	double		fvalue;
	int			longlongflag = 0;
	int			longflag = 0;
	int			pointflag = 0;
	int			maxwidth = 0;
	char	   *strvalue;
	int			ljust;
	int			len;
	int			zpad;
	int			i;
	const char*		format_save;
	const char*		fmtbegin;
	int			fmtpos = 1;
	int			realpos = 0;
	int			position;
	static struct{
		const char*	fmtbegin;
		const char*	fmtend;
		union{
			void*	value;
			long_long	numvalue;
			double		fvalue;
			int		charvalue;
		};
		int	ljust;
		int	len;
		int	zpad;
		int	maxwidth;
		int	base;
		int	dosign;
		char	type;
		int	precision;
		int	pointflag;
		char	func;
		int	realpos;
	}fmtpar[NL_ARGMAX+1], *fmtparptr[NL_ARGMAX+1];


	format_save = format;
	output = buffer;
	while ((ch = *format++))
	{
		switch (ch)
		{
			case '%':
				ljust = len = zpad = maxwidth = 0;
				longflag = longlongflag = pointflag = 0;
				fmtbegin = format - 1;
				realpos = 0;
				position = 0;
		nextch:
				ch = *format++;
				switch (ch)
				{
					case 0:
						goto performpr;
					case '-':
						ljust = 1;
						goto nextch;
					case '0':	/* set zero padding if len not set */
						if (len == 0 && !pointflag)
							zpad = '0';
					case '1':
					case '2':
					case '3':
					case '4':
					case '5':
					case '6':
					case '7':
					case '8':
					case '9':
						if (pointflag)
							maxwidth = maxwidth * 10 + ch - '0';
						else
						{ 
							len = len * 10 + ch - '0';
							position = position * 10 + ch - '0';
						}
						goto nextch;
					case '$':
						realpos = position;
						len = 0;
						goto nextch;
					case '*':
						if (pointflag)
							maxwidth = va_arg(args, int);
						else
							len = va_arg(args, int);
						goto nextch;
					case '.':
						pointflag = 1;
						goto nextch;
					case 'l':
						if (longflag)
							longlongflag = 1;
						else
							longflag = 1;
						goto nextch;
					case 'u':
					case 'U':
						/* fmtnum(value,base,dosign,ljust,len,zpad) */
						if (longflag)
						{
							if (longlongflag)
								value = va_arg(args, ulong_long);
							else
								value = va_arg(args, unsigned long);
						}
						else
							value = va_arg(args, unsigned int);
						fmtpar[fmtpos].fmtbegin = fmtbegin;
						fmtpar[fmtpos].fmtend = format;
						fmtpar[fmtpos].numvalue = value;
						fmtpar[fmtpos].base = 10;
						fmtpar[fmtpos].dosign = 0;
						fmtpar[fmtpos].ljust = ljust;
						fmtpar[fmtpos].len = len;
						fmtpar[fmtpos].zpad = zpad;
						fmtpar[fmtpos].func = FMTNUM;
						fmtpar[fmtpos].realpos = realpos?realpos:fmtpos;
						fmtpos++;
						break;
					case 'o':
					case 'O':
						/* fmtnum(value,base,dosign,ljust,len,zpad) */
						if (longflag)
						{
							if (longlongflag)
								value = va_arg(args, ulong_long);
							else
								value = va_arg(args, unsigned long);
						}
						else
							value = va_arg(args, unsigned int);
						fmtpar[fmtpos].fmtbegin = fmtbegin;
						fmtpar[fmtpos].fmtend = format;
						fmtpar[fmtpos].numvalue = value;
						fmtpar[fmtpos].base = 8;
						fmtpar[fmtpos].dosign = 0;
						fmtpar[fmtpos].ljust = ljust;
						fmtpar[fmtpos].len = len;
						fmtpar[fmtpos].zpad = zpad;
						fmtpar[fmtpos].func = FMTNUM;
						fmtpar[fmtpos].realpos = realpos?realpos:fmtpos;
						fmtpos++;
						break;
					case 'd':
					case 'D':
						if (longflag)
						{
							if (longlongflag)
								value = va_arg(args, long_long);
							else
								value = va_arg(args, long);
						}
						else
							value = va_arg(args, int);
						fmtpar[fmtpos].fmtbegin = fmtbegin;
						fmtpar[fmtpos].fmtend = format;
						fmtpar[fmtpos].numvalue = value;
						fmtpar[fmtpos].base = 10;
						fmtpar[fmtpos].dosign = 1;
						fmtpar[fmtpos].ljust = ljust;
						fmtpar[fmtpos].len = len;
						fmtpar[fmtpos].zpad = zpad;
						fmtpar[fmtpos].func = FMTNUM;
						fmtpar[fmtpos].realpos = realpos?realpos:fmtpos;
						fmtpos++;
						break;
					case 'x':
						if (longflag)
						{
							if (longlongflag)
								value = va_arg(args, ulong_long);
							else
								value = va_arg(args, unsigned long);
						}
						else
							value = va_arg(args, unsigned int);
						fmtpar[fmtpos].fmtbegin = fmtbegin;
						fmtpar[fmtpos].fmtend = format;
						fmtpar[fmtpos].numvalue = value;
						fmtpar[fmtpos].base = 16;
						fmtpar[fmtpos].dosign = 0;
						fmtpar[fmtpos].ljust = ljust;
						fmtpar[fmtpos].len = len;
						fmtpar[fmtpos].zpad = zpad;
						fmtpar[fmtpos].func = FMTNUM;
						fmtpar[fmtpos].realpos = realpos?realpos:fmtpos;
						fmtpos++;
						break;
					case 'X':
						if (longflag)
						{
							if (longlongflag)
								value = va_arg(args, ulong_long);
							else
								value = va_arg(args, unsigned long);
						}
						else
							value = va_arg(args, unsigned int);
						fmtpar[fmtpos].fmtbegin = fmtbegin;
						fmtpar[fmtpos].fmtend = format;
						fmtpar[fmtpos].numvalue = value;
						fmtpar[fmtpos].base = -16;
						fmtpar[fmtpos].dosign = 1;
						fmtpar[fmtpos].ljust = ljust;
						fmtpar[fmtpos].len = len;
						fmtpar[fmtpos].zpad = zpad;
						fmtpar[fmtpos].func = FMTNUM;
						fmtpar[fmtpos].realpos = realpos?realpos:fmtpos;
						fmtpos++;
						break;
					case 's':
						strvalue = va_arg(args, char *);
						if (maxwidth > 0 || !pointflag)
						{
							if (pointflag && len > maxwidth)
								len = maxwidth; /* Adjust padding */
							fmtpar[fmtpos].fmtbegin = fmtbegin;
							fmtpar[fmtpos].fmtend = format;
							fmtpar[fmtpos].value = strvalue;
							fmtpar[fmtpos].ljust = ljust;
							fmtpar[fmtpos].len = len;
							fmtpar[fmtpos].zpad = zpad;
							fmtpar[fmtpos].maxwidth = maxwidth;
							fmtpar[fmtpos].func = FMTSTR;
							fmtpar[fmtpos].realpos = realpos?realpos:fmtpos;
							fmtpos++;
						}
						break;
					case 'c':
						ch = va_arg(args, int);
						fmtpar[fmtpos].fmtbegin = fmtbegin;
						fmtpar[fmtpos].fmtend = format;
						fmtpar[fmtpos].charvalue = ch;
						fmtpar[fmtpos].func = FMTCHAR;
						fmtpar[fmtpos].realpos = realpos?realpos:fmtpos;
						fmtpos++;
						break;
					case 'e':
					case 'E':
					case 'f':
					case 'g':
					case 'G':
						fvalue = va_arg(args, double);
						fmtpar[fmtpos].fmtbegin = fmtbegin;
						fmtpar[fmtpos].fmtend = format;
						fmtpar[fmtpos].fvalue = fvalue;
						fmtpar[fmtpos].type = ch;
						fmtpar[fmtpos].ljust = ljust;
						fmtpar[fmtpos].len = len;
						fmtpar[fmtpos].maxwidth = maxwidth;
						fmtpar[fmtpos].pointflag = pointflag;
						fmtpar[fmtpos].func = FMTFLOAT;
						fmtpar[fmtpos].realpos = realpos?realpos:fmtpos;
						fmtpos++;
						break;
					case '%':
						break;
					default:
						dostr("???????", 0);
				}
				break;
			default:
				dopr_outch(ch);
				break;
		}
	}
performpr:
	/* shuffle pointers */
	for(i = 1; i < fmtpos; i++)
	{
		fmtparptr[i] = &fmtpar[fmtpar[i].realpos];
	}
	output = buffer;
	format = format_save;
	while ((ch = *format++))
	{
		for(i = 1; i < fmtpos; i++)
		{
			if(ch == '%' && *format == '%')
			{
				format++;
				continue;
			}
			if(fmtpar[i].fmtbegin == format - 1)
			{
				switch(fmtparptr[i]->func){
				case FMTSTR:
					fmtstr(fmtparptr[i]->value, fmtparptr[i]->ljust,
						fmtparptr[i]->len, fmtparptr[i]->zpad,
						fmtparptr[i]->maxwidth);
					break;
				case FMTNUM:
					fmtnum(fmtparptr[i]->numvalue, fmtparptr[i]->base,
						fmtparptr[i]->dosign, fmtparptr[i]->ljust,
						fmtparptr[i]->len, fmtparptr[i]->zpad);
					break;
				case FMTFLOAT:
					fmtfloat(fmtparptr[i]->fvalue, fmtparptr[i]->type,
						fmtparptr[i]->ljust, fmtparptr[i]->len,
						fmtparptr[i]->precision, fmtparptr[i]->pointflag);
					break;
				case FMTCHAR:
					dopr_outch(fmtparptr[i]->charvalue);
					break;
				}
				format = fmtpar[i].fmtend;
				goto nochar;
			}
		}
		dopr_outch(ch);
nochar:
	/* nothing */
	}
	*output = '\0';
}

static void
fmtstr(char *value, int ljust, int len, int zpad, int maxwidth)
{
	int			padlen,
				strlen;			/* amount to pad */

	if (value == 0)
		value = "<NULL>";
	for (strlen = 0; value[strlen]; ++strlen);	/* strlen */
	if (strlen > maxwidth && maxwidth)
		strlen = maxwidth;
	padlen = len - strlen;
	if (padlen < 0)
		padlen = 0;
	if (ljust)
		padlen = -padlen;
	while (padlen > 0)
	{
		dopr_outch(' ');
		--padlen;
	}
	dostr(value, maxwidth);
	while (padlen < 0)
	{
		dopr_outch(' ');
		++padlen;
	}
}

static void
fmtnum(long_long value, int base, int dosign, int ljust, int len, int zpad)
{
	int			signvalue = 0;
	ulong_long	uvalue;
	char		convert[64];
	int			place = 0;
	int			padlen = 0;		/* amount to pad */
	int			caps = 0;

	/*
	 * DEBUGP(("value 0x%x, base %d, dosign %d, ljust %d, len %d, zpad
	 * %d\n", value, base, dosign, ljust, len, zpad ));
	 */
	uvalue = value;
	if (dosign)
	{
		if (value < 0)
		{
			signvalue = '-';
			uvalue = -value;
		}
	}
	if (base < 0)
	{
		caps = 1;
		base = -base;
	}
	do
	{
		convert[place++] = (caps ? "0123456789ABCDEF" : "0123456789abcdef")
			[uvalue % (unsigned) base];
		uvalue = (uvalue / (unsigned) base);
	} while (uvalue);
	convert[place] = 0;

	if (len < 0)
	{
		/* this could happen with a "*" width spec */
		ljust = 1;
		len = -len;
	}
	padlen = len - place;
	if (padlen < 0)
		padlen = 0;
	if (ljust)
		padlen = -padlen;

	/*
	 * DEBUGP(( "str '%s', place %d, sign %c, padlen %d\n",
	 * convert,place,signvalue,padlen));
	 */
	if (zpad && padlen > 0)
	{
		if (signvalue)
		{
			dopr_outch(signvalue);
			--padlen;
			signvalue = 0;
		}
		while (padlen > 0)
		{
			dopr_outch(zpad);
			--padlen;
		}
	}
	while (padlen > 0)
	{
		dopr_outch(' ');
		--padlen;
	}
	if (signvalue)
		dopr_outch(signvalue);
	while (place > 0)
		dopr_outch(convert[--place]);
	while (padlen < 0)
	{
		dopr_outch(' ');
		++padlen;
	}
}

static void
fmtfloat(double value, char type, int ljust, int len, int precision, int pointflag)
{
	char		fmt[32];
	char		convert[512];
	int			padlen = 0;		/* amount to pad */

	/* we rely on regular C library's sprintf to do the basic conversion */
	if (pointflag)
		sprintf(fmt, "%%.%d%c", precision, type);
	else
		sprintf(fmt, "%%%c", type);
	sprintf(convert, fmt, value);

	if (len < 0)
	{
		/* this could happen with a "*" width spec */
		ljust = 1;
		len = -len;
	}
	padlen = len - strlen(convert);
	if (padlen < 0)
		padlen = 0;
	if (ljust)
		padlen = -padlen;

	while (padlen > 0)
	{
		dopr_outch(' ');
		--padlen;
	}
	dostr(convert, 0);
	while (padlen < 0)
	{
		dopr_outch(' ');
		++padlen;
	}
}

static void
dostr(char *str, int cut)
{
	if (cut)
	{
		while (*str && cut-- > 0)
			dopr_outch(*str++);
	}
	else
	{
		while (*str)
			dopr_outch(*str++);
	}
}

static void
dopr_outch(int c)
{
#ifdef NOT_USED
	if (iscntrl((unsigned char) c) && c != '\n' && c != '\t')
	{
		c = '@' + (c & 0x1F);
		if (end == 0 || output < end)
			*output++ = '^';
	}
#endif
	if (end == 0 || output < end)
		*output++ = c;
	else
		SnprfOverflow++;
}