634 lines
24 KiB
C
634 lines
24 KiB
C
/* vqsprintf.c
|
||
Author: Steven Augart <swa@isi.edu>
|
||
Written: 7/18/92 -- 7/24/92
|
||
Long support added, 10/2/92
|
||
vqsprintf() added, 10/6/92
|
||
Support for field widths and zero-padded field widths added, 4/20/94 --swa
|
||
|
||
|
||
I am really interested in comments on this code, suggestions for making it
|
||
faster, and criticism of my style. Please send polite suggestions for
|
||
improvement to swa@isi.edu.
|
||
|
||
*/
|
||
|
||
/* Copyright (c) 1992 by the University of Southern California. */
|
||
/* For copying and distribution information, see the file <usc-copyr.h> */
|
||
#include <usc-copyr.h>
|
||
|
||
/* qsprintf() is not like the regular sprintf(). "qsprintf()" is faster than
|
||
real sprintf. It only handles a few commands:
|
||
%d, %s, %%, and %c.
|
||
%b -- for bstring argument (like %s)
|
||
%x -- lower-case HEX digits
|
||
%X -- upper-case HEX digits
|
||
|
||
It also handles the ' modifier, which may be applied to %d or %s or %c.
|
||
thusly: "%'d", "%'s", and "%c". The ' modifier means "print this message
|
||
using Prospero quoting". The ' modifier will also treat empty strings as
|
||
separate objects, so that the format sequence "%'s", when given the empty
|
||
string as an argument, prints "''". This is used for quoting tokens in
|
||
text-based protocols such as Prospero.
|
||
|
||
It handles minimum field widths. If no zero precedes a field width, the
|
||
output will be space-padded. Otherwise, zero-padded.
|
||
|
||
Discussing quoting requires some discussion of how we handle string
|
||
concatenation. Consider the format string "%'s%'s" and the two argument
|
||
strings "This+" and "More Stuff". The qsprintf invocation is:
|
||
|
||
qsprintf(buf, sizeof buf, "%'s%'s", "This+", "More Stuff")
|
||
|
||
qsprintf() will concatenate quoted output into one long quoted string. This
|
||
allows one to construct quoted strings from several components. After the
|
||
above call to qsprintf(), buf should contain:
|
||
|
||
"This'+More Stuff'"
|
||
|
||
This example also demonstrates that qsprintf() currently turns on quoting in
|
||
the middle of strings, rather than always turning it on at the start of a
|
||
string. This allows the qsprintf() implementation to be faster; alternative
|
||
implementations are equally valid. So, the quoted strings "'a b'c",
|
||
"a' 'bc", and "'a bc'" are equivalent representations of the same
|
||
underlying character sequence.
|
||
|
||
Note that concatenating quoted and unquoted strings could lead to bizzare
|
||
results. If you print an unquoted object immediately following a quoted
|
||
string, you get what you deserve. For example, if you qsprintf using the
|
||
format: "%'s'", you will get a dangling single quote at the end, which the
|
||
qsscanf() at the other end will be unable to handle.
|
||
|
||
|
||
Regular sprintf returns the # of characters written, not including the
|
||
trailing '\0' (NUL). qsprintf() returns the # of characters it wrote to the
|
||
string, OR the # of characters it would have written if there had been
|
||
enough room. Its return value INCLUDES the NUL ('\0'). Unlike regular
|
||
sprintf(), qsprintf() takes the SIZE of the buffer it writes to as an
|
||
argument. This allows us to guard against buffer overflow. qsprintf() will
|
||
write partial strings to buffers which are not long enough. This seems to
|
||
be reasonable behavior to me.
|
||
|
||
Moreover, the partial strings written by qsprintf() are guaranteed to be NUL
|
||
('\0') terminated. This means that you can qsprintf() without checking the
|
||
return code, and still safely hand the output buffer to other commands.
|
||
|
||
Like in regular sprintf(), %c expects an int argument (not a char). The int
|
||
argument is then converted to an unsigned char when it is printed. An
|
||
unsigned char would have fit my intuition better, but I opted for historical
|
||
compatibility.
|
||
|
||
qsprintf() will call internal_error() if it encounters a malformed format
|
||
string. It could return 0 instead, but that would require lots of testing,
|
||
and it's not clear to me why it is useful to return an error flag upon
|
||
encountering a programmer error.
|
||
*/
|
||
|
||
/*
|
||
** Implementation notes & future directions **
|
||
|
||
The implementation of qsprintf() uses the inline function (or macro or
|
||
regular function, if you are among the unfortunates who don't have GNU C)
|
||
"needs_quoting(char c)" to define which characters need quoting in your
|
||
particular protocol. needs_quoting() is currently defined for the Prospero
|
||
protocol. Note that, if you define needs_quoting() to always say that a
|
||
character needs quoting, the behavior of qsprintf will still be correct; it
|
||
is never erroneous to quote conservatively. It merely wastes a bit of
|
||
buffer space.
|
||
|
||
At some point, we will want qsprintf() to be able to write a partial output
|
||
string to a short buffer, and then write more of the output string to
|
||
another buffer. The need is not yet here, though.
|
||
|
||
vqsprintf() is the varargs version, analagous to vsprintf() in ANSI C.
|
||
|
||
qsprintf_stalloc() will be the name of the version that calls the memory
|
||
allocator to allocate enough room to print. It returns NULL upon failure.
|
||
And, of course, to complete the group, there will be vqsprintf_stalloc().
|
||
|
||
This function is written without function calls where I might have
|
||
otherwise used them. That's because it has to be fast.
|
||
|
||
*/
|
||
|
||
/*
|
||
** Maintainer **
|
||
|
||
Yes, we are actually maintaining qsprintf() and qsscanf() as part of the
|
||
Prospero project. We genuinely want your bug reports, bug fixes, and
|
||
complaints. We figure that improving qsprintf()'s portability will help us
|
||
make all of the Prospero software more portable. Send complaints and
|
||
comments and questions to bug-prospero@isi.edu.
|
||
|
||
In the Prospero project, we use GNU C because it is the best C compiler we
|
||
know of for UNIX systems. However, if you port this code to a non-GNU C
|
||
compiler, we will incorporate your changes into the source.
|
||
*/
|
||
|
||
|
||
|
||
#include <ardp.h> /* this is bad. This defines size_t already,
|
||
indirectly. This is an incompatibly with
|
||
GCC's stddef.h. */
|
||
|
||
#ifndef __sys_stdtypes_h
|
||
#include <stddef.h> /* size_t */
|
||
#endif
|
||
#include <stdarg.h> /* for varargs facility */
|
||
#include <limits.h>
|
||
#include <pfs.h>
|
||
#include "charset.h"
|
||
|
||
/* Set MAX_INT_DECIMAL_DIGITS to the # of decimal digits the largest int on
|
||
your machine might possibly need to represent it in printed form. This is
|
||
used to allocate an internal buffer. You do not need space for the sign,
|
||
nor for a trailing '\0' (NUL). */
|
||
#define MAX_INT_DECIMAL_DIGITS 10 /* enough for 32-bit ints. */
|
||
#define MAX_LONG_DECIMAL_DIGITS 20 /* enough for 64-bit longs. */
|
||
|
||
|
||
/* This page contains macro definitions that replace the inline auto functions.
|
||
If you're using GNU C, you get to skip this page. I detest macros and
|
||
argument passing in regular C. */
|
||
|
||
#define GNUC_SIGSEGV_BUG 1 /* GNU C seems to be giving me these error
|
||
messages with inline nested functions, for
|
||
reasons which I don't fully understand. I
|
||
believe it was not correctly installed on
|
||
our system; */
|
||
#if __GNUC__ && !GNUC_SIGSEGV_BUG
|
||
#define NESTED_FUNCTIONS 1 /* 1 if we have inline nested functions (A
|
||
GNU C feature). */
|
||
#else
|
||
#define NESTED_FUNCTIONS 0 /* no inline nested functions */
|
||
#endif
|
||
|
||
#if !NESTED_FUNCTIONS
|
||
/* If we're not using GNUC, don't use inline nested functions. */
|
||
|
||
#define needs_quoting(c) in_charset(nq_cs, (c))
|
||
|
||
#define rawput(c) do { \
|
||
register char c2 = (c); /* in case (c) has side effects. */ \
|
||
if (buflen) \
|
||
*buf++ = c2, --buflen; \
|
||
++n; \
|
||
} while (0)
|
||
|
||
#define enable_quoting() do { \
|
||
if (!am_quoting) { \
|
||
rawput('\''); \
|
||
++am_quoting; \
|
||
}\
|
||
} while(0)
|
||
|
||
#define disable_quoting() do { \
|
||
if (am_quoting) {\
|
||
rawput('\'');\
|
||
am_quoting = 0;\
|
||
}\
|
||
} while(0)
|
||
|
||
#define qput(c) do {\
|
||
char c1 = (c); /* in case (c) has side effects. */ \
|
||
if (needs_quoting(c1)) {\
|
||
enable_quoting();\
|
||
if (c1 == '\'') /* quote the quote */\
|
||
rawput('\'');\
|
||
}\
|
||
rawput(c1);\
|
||
} while(0)
|
||
|
||
#define uqput(c) do {\
|
||
disable_quoting();\
|
||
rawput(c);\
|
||
} while(0)
|
||
|
||
#define put(c) { \
|
||
if (do_quoting)\
|
||
qput(c);\
|
||
else\
|
||
uqput(c);\
|
||
}
|
||
#endif /*!NESTED_FUNCTIONS */
|
||
|
||
|
||
size_t
|
||
vqsprintf(char *buf, size_t buflen, const char *fmt, va_list ap)
|
||
{
|
||
register int n = 0; /* n is # of chars that we wrote, or that we
|
||
would write if there were room enough.
|
||
Includes '\0'. */
|
||
int am_quoting = 0; /* If we are quoting a string and
|
||
enable_quoting has already been called. */
|
||
char *buf_lastpos = buf + buflen - 1; /* Last character position of the
|
||
buffer. We will write a
|
||
terminating '\0' here. */
|
||
|
||
#if NESTED_FUNCTIONS
|
||
/* These are all of our inline local functions. If we have
|
||
a bug-free GNU C, then these are preferable to macros. The
|
||
corresponding macro definitions occur at the top of the file. */
|
||
|
||
/* This would be faster to code as a table lookup. However, we don't need
|
||
the speed that badly yet, and there's other work to be done. Perhaps
|
||
later. --swa */
|
||
inline int needs_quoting(char c) {
|
||
return in_charset(nq_cs, c);
|
||
}
|
||
|
||
inline void rawput(char c) {
|
||
if (buflen)
|
||
*buf++ = c, --buflen;
|
||
++n;
|
||
}
|
||
|
||
inline void enable_quoting(void) {
|
||
if (!am_quoting) {
|
||
rawput('\'');
|
||
++am_quoting;
|
||
}
|
||
}
|
||
|
||
inline void disable_quoting(void) {
|
||
if (am_quoting) {
|
||
rawput('\'');
|
||
am_quoting = 0;
|
||
}
|
||
}
|
||
|
||
inline void qput(char c) {
|
||
if (needs_quoting(c)) {
|
||
enable_quoting();
|
||
if (c == '\'') /* quote the quote */
|
||
rawput('\'');
|
||
}
|
||
rawput(c);
|
||
}
|
||
|
||
inline void uqput(char c) {
|
||
disable_quoting();
|
||
rawput(c);
|
||
}
|
||
|
||
#endif
|
||
static int nq_cs_initialized = 0; /* set to 1 after nq_cs is initialized */
|
||
static charset nq_cs; /* Charset of characters that need quoting */
|
||
|
||
if (buflen <= 0) buf_lastpos = NULL;
|
||
if (!nq_cs_initialized) {
|
||
p_th_mutex_lock(p_th_mutexPFS_VQSPRINTF_NQ_CS);
|
||
/* Initialize nq_cs with the characters that must be quoted. */
|
||
if (!nq_cs_initialized) { /* test again now that we're synchronized */
|
||
new_empty_charset(nq_cs);
|
||
/* traditional whitespace characters. */
|
||
add_char(nq_cs, ' ');
|
||
add_char(nq_cs, '\t');
|
||
add_char(nq_cs, '\f');
|
||
add_char(nq_cs, '\v');
|
||
add_char(nq_cs, '\n');
|
||
add_char(nq_cs, '\r');
|
||
/* additional characters treated specially by the Prospero protocol. */
|
||
add_char(nq_cs, '/');
|
||
add_char(nq_cs, '\'');
|
||
add_char(nq_cs, '+');
|
||
add_char(nq_cs, ',');
|
||
++nq_cs_initialized;
|
||
}
|
||
p_th_mutex_unlock(p_th_mutexPFS_VQSPRINTF_NQ_CS);
|
||
}
|
||
|
||
do {
|
||
if (*fmt == '%') {
|
||
int do_quoting = 0; /* flag: will we do quoting? */
|
||
int use_long = 0; /* flag: is the numeric arg a long? */
|
||
int pad_with_zeroes = 0; /* flag: If we pad, do we do so with
|
||
zeroes? */
|
||
int min_field_width = 0; /* What's the minimum field width we need?
|
||
*/
|
||
|
||
#if NESTED_FUNCTIONS
|
||
inline void put(char c) {
|
||
if (do_quoting)
|
||
qput(c);
|
||
else
|
||
uqput(c);
|
||
}
|
||
#endif
|
||
|
||
rescan:
|
||
switch(*++fmt) {
|
||
case '0': /* might be a modifier */
|
||
if (!min_field_width) {
|
||
++pad_with_zeroes;
|
||
goto rescan;
|
||
}
|
||
/* Deliberate FALLTHROUGH */
|
||
case '1':
|
||
case '2':
|
||
case '3':
|
||
case '4':
|
||
case '5':
|
||
case '6':
|
||
case '7':
|
||
case '8':
|
||
case '9':
|
||
min_field_width *= 10;
|
||
min_field_width += (*fmt - '0'); /* works for ASCII character
|
||
set; not necessarily for
|
||
others. */
|
||
goto rescan;
|
||
case '\'': /* modifier */
|
||
#ifndef NDEBUG
|
||
if (do_quoting)
|
||
internal_error("qsprintf: two ' modifiers in a row");
|
||
#endif
|
||
++do_quoting;
|
||
goto rescan;
|
||
case 'l': /* modifier */
|
||
#ifndef NDEBUG
|
||
if (use_long)
|
||
internal_error("qsprintf: two l modifiers in a row");
|
||
#endif
|
||
++use_long;
|
||
goto rescan;
|
||
|
||
case '%':
|
||
#ifndef NDEBUG
|
||
if (do_quoting)
|
||
internal_error("qsprintf: ' modifier does not apply to %");
|
||
#endif
|
||
uqput('%');
|
||
break;
|
||
|
||
case 'd':
|
||
if (use_long) {
|
||
long i = va_arg(ap, long);
|
||
char itoabuf[MAX_LONG_DECIMAL_DIGITS + 1] ;
|
||
register char *bp;
|
||
|
||
if (i < 0) {
|
||
put('-');
|
||
i = -i;
|
||
}
|
||
bp = itoabuf;
|
||
do {
|
||
*bp++ = "0123456789"[i % 10];
|
||
} while ((i /= 10) > 0);
|
||
/* Use the min_field_width as an index variable.
|
||
bp - itoabuf is the length of the string about to be
|
||
printed. As long as it's not long enough, print a space or
|
||
zero and go on. */
|
||
while (min_field_width-- > bp - itoabuf) {
|
||
put(pad_with_zeroes ? '0' : ' ');
|
||
}
|
||
/* Reverse the string. At least 1 char is in itoabuf. */
|
||
do {
|
||
put(*--bp);
|
||
} while (bp > itoabuf);
|
||
} else {
|
||
int i = va_arg(ap, int);
|
||
char itoabuf[MAX_INT_DECIMAL_DIGITS];
|
||
register char *bp;
|
||
|
||
if (i < 0) {
|
||
put('-');
|
||
i = -i;
|
||
}
|
||
bp = itoabuf;
|
||
do {
|
||
*bp++ = "0123456789"[i % 10];
|
||
} while ((i /= 10) > 0);
|
||
/* Use the min_field_width as an index variable.
|
||
bp - itoabuf is the length of the string about to be
|
||
printed. As long as it's not long enough, print a space or
|
||
zero and go on. */
|
||
while (min_field_width-- > bp - itoabuf) {
|
||
put(pad_with_zeroes ? '0' : ' ');
|
||
}
|
||
/* Reverse the string. At least 1 char is in itoabuf. */
|
||
do {
|
||
put(*--bp);
|
||
} while (bp > itoabuf);
|
||
}
|
||
break;
|
||
|
||
|
||
case 'x': /* print in hex */
|
||
if (use_long) {
|
||
unsigned long i = va_arg(ap, unsigned long);
|
||
char itoabuf[MAX_LONG_DECIMAL_DIGITS]; /* long enuf for hex */
|
||
register char *bp;
|
||
|
||
bp = itoabuf;
|
||
do {
|
||
*bp++ = "0123456789abcdef"[i % 16];
|
||
} while ((i /= 16) > 0);
|
||
/* Use the min_field_width as an index variable.
|
||
bp - itoabuf is the length of the string about to be
|
||
printed. As long as it's not long enough, print a space or
|
||
zero and go on. */
|
||
while (min_field_width-- > bp - itoabuf) {
|
||
put(pad_with_zeroes ? '0' : ' ');
|
||
}
|
||
/* Reverse the string. At least 1 char is in itoabuf. */
|
||
do {
|
||
put(*--bp);
|
||
} while (bp > itoabuf);
|
||
} else {
|
||
unsigned int i = va_arg(ap, unsigned int);
|
||
char itoabuf[MAX_INT_DECIMAL_DIGITS];
|
||
register char *bp;
|
||
|
||
bp = itoabuf;
|
||
do {
|
||
*bp++ = "0123456789abcdef"[i % 16];
|
||
} while ((i /= 16) > 0);
|
||
/* Use the min_field_width as an index variable.
|
||
bp - itoabuf is the length of the string about to be
|
||
printed. As long as it's not long enough, print a space or
|
||
zero and go on. */
|
||
while (min_field_width-- > bp - itoabuf) {
|
||
put(pad_with_zeroes ? '0' : ' ');
|
||
}
|
||
/* Reverse the string. At least 1 char is in itoabuf. */
|
||
do {
|
||
put(*--bp);
|
||
} while (bp > itoabuf);
|
||
}
|
||
break;
|
||
|
||
case 'X': /* print in capitalized hex */
|
||
if (use_long) {
|
||
unsigned long i = va_arg(ap, unsigned long);
|
||
char itoabuf[MAX_LONG_DECIMAL_DIGITS]; /* long enuf for hex */
|
||
register char *bp;
|
||
|
||
bp = itoabuf;
|
||
do {
|
||
*bp++ = "0123456789ABCDEF"[i % 16];
|
||
} while ((i /= 16) > 0);
|
||
/* Use the min_field_width as an index variable.
|
||
bp - itoabuf is the length of the string about to be
|
||
printed. As long as it's not long enough, print a space or
|
||
zero and go on. */
|
||
while (min_field_width-- > bp - itoabuf) {
|
||
put(pad_with_zeroes ? '0' : ' ');
|
||
}
|
||
/* Reverse the string. At least 1 char is in itoabuf. */
|
||
do {
|
||
put(*--bp);
|
||
} while (bp > itoabuf);
|
||
} else {
|
||
unsigned int i = va_arg(ap, unsigned int);
|
||
char itoabuf[MAX_INT_DECIMAL_DIGITS];
|
||
register char *bp;
|
||
|
||
bp = itoabuf;
|
||
do {
|
||
*bp++ = "0123456789ABCDEF"[i % 16];
|
||
} while ((i /= 16) > 0);
|
||
/* Use the min_field_width as an index variable.
|
||
bp - itoabuf is the length of the string about to be
|
||
printed. As long as it's not long enough, print a space or
|
||
zero and go on. */
|
||
while (min_field_width-- > bp - itoabuf) {
|
||
put(pad_with_zeroes ? '0' : ' ');
|
||
}
|
||
/* Reverse the string. At least 1 char is in itoabuf. */
|
||
do {
|
||
put(*--bp);
|
||
} while (bp > itoabuf);
|
||
}
|
||
break;
|
||
|
||
case 'c':
|
||
/* Use the min_field_width as an index variable.
|
||
bp - itoabuf is the length of the string about to be
|
||
printed. As long as it's not long enough, print a space or
|
||
zero and go on. */
|
||
while (min_field_width-- > 1) {
|
||
put(pad_with_zeroes ? '0' : ' ');
|
||
}
|
||
put(va_arg(ap, int));
|
||
break;
|
||
|
||
case 's':
|
||
{
|
||
register char *s = va_arg(ap, char *);
|
||
|
||
#ifndef NDEBUG
|
||
/* special case for null pointer (why not?). Catch a common
|
||
bug. */
|
||
if (s == NULL)
|
||
s = "(NULL POINTER)";
|
||
#endif
|
||
|
||
/* This test ensures that concatenation with a null string will
|
||
work. It also ensures that a null string will be displayed
|
||
signly as '', if it is surrounded by whitespace or unquoted
|
||
characters. */
|
||
if (do_quoting && *s == '\0')
|
||
enable_quoting();
|
||
|
||
/* Use the min_field_width as an index variable.
|
||
printed. As long as it's not long enough, print a space or
|
||
zero and go on. */
|
||
while (min_field_width-- > strlen(s)) {
|
||
put(pad_with_zeroes ? '0' : ' ');
|
||
}
|
||
while (*s)
|
||
put(*s++);
|
||
}
|
||
break;
|
||
|
||
case 'b':
|
||
{
|
||
register char *b = va_arg(ap, char *);
|
||
int len = p_bstlen(b);
|
||
|
||
/* No need to test for null pointer; p_bstlen() includes a
|
||
consistency check. */
|
||
/* This test ensures that concatenation with a null string will
|
||
work. It also ensures that a null string will be displayed
|
||
signly as '', if it is surrounded by whitespace or unquoted
|
||
characters. */
|
||
if (do_quoting && len == 0)
|
||
enable_quoting();
|
||
|
||
/* Use the min_field_width as an index variable.
|
||
len is the length of the string about to be
|
||
printed. As long as it's not long enough, print a space or
|
||
zero and go on. */
|
||
while (min_field_width-- > len ) {
|
||
put(pad_with_zeroes ? '0' : ' ');
|
||
}
|
||
while (len--)
|
||
put(*b++);
|
||
}
|
||
break;
|
||
|
||
#ifndef NDEBUG
|
||
default:
|
||
internal_error("qsprintf: unrecognized format directive");
|
||
/*NOTREACHED*/
|
||
break;
|
||
#endif
|
||
}
|
||
} else { /* *fmt != '%' */
|
||
uqput(*fmt);
|
||
}
|
||
} while (*fmt++);
|
||
/* Note that structuring this as a do ... while loop means that '\0' will
|
||
be stuck at the end of the output string (space permitting). */
|
||
/* Stick on terminating NUL in case there was no space to write one. This
|
||
means we can avoid checking the return value from qsprintf() in routine
|
||
cases, and still be guaranteed that the output buffer contains
|
||
meaningful (i.e., NUL-terminated) data. */
|
||
if (buf_lastpos) *buf_lastpos = '\0';
|
||
return n;
|
||
}
|
||
|
||
#if 0
|
||
/* This is just for debugging. */
|
||
|
||
#include <stdio.h>
|
||
|
||
int
|
||
main()
|
||
{
|
||
char sbuf[1000];
|
||
char ibuf[80];
|
||
int d;
|
||
int i;
|
||
|
||
fputs("Gimme string -->>", stdout);
|
||
fflush(stdout);
|
||
gets(ibuf);
|
||
fputs("Gimme number -->", stdout);
|
||
fflush(stdout);
|
||
if (scanf("%d", &d)!= 1) {
|
||
fputs("That's not a number!\n", stdout);
|
||
exit(1);
|
||
}
|
||
i = qsprintf(sbuf, sizeof sbuf, "String*2: |%s%s|\nQuoted String*2: |%'s%'s|\n\
|
||
Number: %d\n\
|
||
Number appearing in a %%02d field width: |%02d|\n\
|
||
Number appearing in a %%2d field width: |%2d|\n\
|
||
Quoted Number: %'d\nHex number: %x\nUpper hex number:%X\n\
|
||
Number as Character: %c\nNumber as quoted character: %'c\n",
|
||
ibuf, ibuf, ibuf, ibuf, d, d, d, d, d, d, d, d);
|
||
fputs("Made it past the qsprintf!\n", stderr);
|
||
fflush(stdout);
|
||
puts("Just flushed STDOUT. Here's the string:");
|
||
fwrite(sbuf, 1, i - 1, stdout);
|
||
puts("EOS");
|
||
printf("qsprintf says sbuf had %d chars; strlen(sbuf) (if appropriate) said
|
||
%d\n", i, i > sizeof sbuf ? -1 : strlen(sbuf));
|
||
return 0;
|
||
}
|
||
/* Local Variables: */
|
||
/* compile-command: "gcc -o t -I../../include -ggdb3 -pipe qsprintf.c vqsprintf.c libpfs.a ../ardp/libardp.a" */
|
||
/* End: */
|
||
|
||
#endif
|