my_printf calling printf

Steve Summit scs at adam.mit.edu
Thu Feb 28 10:40:41 AEST 1991


In article <31530036 at hpcvia.CV.HP.COM> brianh at hpcvia.CV.HP.COM (brian_helterline) writes:
>jik at athena.mit.edu (Jonathan I. Kamens) writes:
>> Is there a win_vprintf?  If so, that's what you're going to have to use.
>> If not, you're in trouble.
>Not necesarily, you're only in trouble if you don't have vsprintf() since
>you can use that to create a single char array argument and pass that to
>win_printf.  Of course, figuring out how large of a char array is needed
>is not always obvious.

It is not always *possible*.  In fact, it is usually impossible,
which is why any solution involving vsprintf is decidedly inferior.
However, if you don't have something you can hand a va_list to
(what Jon suggested as win_vprintf), vsprintf is all you're left with.

Here's how the vsprintf/win_printf example might "work:"

	my_printf(char *fmt, ...)
	{
	va_list argp;
	char tmpbuf[80];		/* XXX */
	va_start(argp, fmt);
	vsprintf(tmpbuf, fmt, argp);
	va_end(argp);
	win_printf("%s", tmpbuf);
	}

If you think this is acceptable code, let me tell you about the
Internet worm.

There is NO WAY to declare/allocate that temporary buffer
"correctly."  If we are writing win_printf as a black box, with
no knowledge of its likely calling patterns, we can't put any
kind of upper bound on how much output one call to it might
generate.

When I have to allocate temporary buffers under these
circumstances, I usually do so dynamically and very
conservatively:

	char *tmpbuf = malloc(strlen(fmt) * 3 + 100);

but this is still vulnerable, because the fmt might contain %s
format specifiers which pull in arbitrary-length strings.

The problem is worse because we can't even arrange for the code
to fail gracefully if the temporary buffer overflows.  About the
best we could do is

	if(vsprintf(tmpbuf, fmt, argp) + 1 > 80)	/* or whatever */
		_exit(1);

because by the time we can tell that the length of the formatted
result exceeds the buffer size, it's too late: the buffer has
already been overfilled, and we don't know what else in memory
has been wiped out.

In light of the above difficulties, I would offer a plea to
library writers everywhere: if you are providing any printf-like
routines, *always* provide v*printf counterparts.  If you are
implementing your printf lookalikes correctly, it is impossible
for it to be difficult to provide companion versions which accept
a va_list.  (I'm not going to prove this assertion.  After
thinking about va_list-using code in general, and printf/vprintf
in particular, and after reading the FAQ list if you have to, if
you still think that writing win_vprintf might be any harder than
writing win_printf, send me mail.)

Until third-party library vendors everywhere start providing
vprintf-like functions regularly, there are a couple of routines
in the standard library which could help us, except that they are
not actually in the standard library (yet).

First, note that sprintf is just as bad as gets (to re-evoke
Internet worm paranoia).  There is NO WAY to prevent buffer
overflow when using sprintf (and, equivalently, vsprintf), except
of course if you have complete control over the format string and
optional arguments.

A simple solution, which ought to be more widespread by now, is
to provide

	snprintf(char *buf, int buflen, char *fmt, ...)

and

	vsnprintf(char *buf, int buflen, char *fmt, va_list argp)

which are just like sprintf and vsprintf except that they LET YOU
SPECIFY THE BUFFER SIZE so that it can't be overflowed (what a
concept).  If you know how sprintf is implemented, you know that
implementing snprintf is trivial.  (You have to watch out that
you never try to fflush the string buffer, which becomes
somewhat more likely when the buffer size isn't 32767 /* XXX */,
but if that isn't a good use for the undocumented, internal
_IOSTRG flag, which many stdio's carefully define and set for
sprintf FILEs but then never check, I don't know what is.)

I believe that gcc comes with a snprintf, and it's also in Chris
Torek's recent 4bsd stdio implementation.  Get the word out!
These improvements will never be standardized until there is
prior art.

For an even better solution, Wouldn't It Be Loverly if there were a

	char *saprintf(char *fmt, ...)

and a companion

	char *vsaprintf(char *fmt, va_list argp)

which return a pointer to malloc'ed memory just large enough for,
and containing, the resulting, formatted string?  If you had
something like vsaprintf available, you could write my_printf in
terms of win_printf without fear of buffer overflow and without
truncating any messages (as a vsnprintf solution might do).

I have to admit that the names saprintf and vsaprintf are not
ideal (the "a" is supposed to stand for "allocated"), and that
there is not much prior art.  (The stdio package I posted to
alt.sources a year or so ago, which implemented them, seems not
to have seen much use.)

Of course, while I'm fantasizing about standard library
extensions, there's another one which solves the underlying
problem here even more cleanly.  Since we would need new,
nonstandard mechanisms (vsnprintf or vsaprintf) before we could
write a proper, black-box implementation of my_printf in terms of
win_printf, we might as well shoot for a granddaddy new,
nonstandard mechanism which would let us write a routine like

	FILE *win_fopen(window_descriptor wd);

which returns a FILE * that we can perform *any* stdio (output)
operation on.  my_printf is now unnecessary; plain old fprintf
(on the FILE * returned by win_fopen) suffices.

Most of the people who have stayed with me this far know what I'm
leading up to: the so-called "function" stdio packages (Chris
Torek talks about 'em all the time, and of course his recent
implementation is one, as was mine).  Once you can rig it up so
that an I/O stream is implemented not necessarily in terms of
low-level read and write, but rather in terms of any function you
want, you can write win_fopen with ease: it just just returns a
FILE * which is arranged to perform fflushes using win_printf
(or, more likely, win_write).

Furthermore, you can also write

	FILE *stropen(char *buf)

and

	FILE *strnopen(char *buf, int buflen)

and the pair

	FILE *straopen()
	char *straclose(FILE *fp)

which return FILE *'s "open" on, respectively, a string, a string
with length checking, and a dynamically-allocated string which
grows while you do output to it and returns the final (malloc'ed)
pointer when closed.  With these in hand, you can write sprintf,
snprintf, and saprintf all in terms of a simple vfprintf, and of
course you can also perform any long, involved sequence of stdio
operations while building up (or reading) a string.

                                            Steve Summit
                                            scs at adam.mit.edu



More information about the Comp.lang.c mailing list