]> git.lyx.org Git - lyx.git/blob - src/support/snprintf.c
small cleanup, doxygen, formatting changes
[lyx.git] / src / support / snprintf.c
1 /*
2  * snprintf.c - a portable implementation of snprintf
3  *
4  * AUTHOR
5  *   Mark Martinec <mark.martinec@ijs.si>, April 1999.
6  *
7  *   Copyright 1999, Mark Martinec. All rights reserved.
8  *
9  * TERMS AND CONDITIONS
10  *   This program is free software; you can redistribute it and/or modify
11  *   it under the terms of the "Frontier Artistic License" which comes
12  *   with this Kit.
13  *
14  *   This program is distributed in the hope that it will be useful,
15  *   but WITHOUT ANY WARRANTY; without even the implied warranty
16  *   of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17  *   See the Frontier Artistic License for more details.
18  *
19  *   You should have received a copy of the Frontier Artistic License
20  *   with this Kit in the file named LICENSE.txt .
21  *   If not, I'll be glad to provide one.
22  *
23  * FEATURES
24  * - careful adherence to specs regarding flags, field width and precision;
25  * - good performance for large string handling (large format, large
26  *   argument or large paddings). Performance is similar to system's sprintf
27  *   and in several cases significantly better (make sure you compile with
28  *   optimizations turned on, tell the compiler the code is strict ANSI
29  *   if necessary to give it more freedom for optimizations);
30  * - return value semantics as per ISO C9X;
31  * - written in standard ISO/ANSI C - requires an ANSI C compiler.
32  *
33  * SUPPORTED FORMATS AND DATA TYPES
34  *
35  * This snprintf only supports format specifiers:
36  * s, c, d, o, u, x, X, p  (and synonyms: i, D, U, O - see below)
37  * with flags: '-', '+', ' ', '0' and '#'.
38  * An asterisk is supported for field width as well as precision.
39  *
40  * Data type modifiers 'h' (short int), 'l' (long int)
41  * and 'll' (long long int) are supported.
42  * NOTE:
43  *   If macro SNPRINTF_LONGLONG_SUPPORT is not defined (default) the
44  *   data type modifier 'll' is recognized but treated the same as 'l',
45  *   which may cause argument value truncation! Defining
46  *   SNPRINTF_LONGLONG_SUPPORT requires that your system's sprintf also
47  *   handles data type modifier 'll'. long long int is a language
48  *   extension which may not be portable.
49  *
50  * Conversion of numeric data (formats d, o, u, x, X, p) with data type
51  * modifiers (none or h, l, ll) is left to the system routine sprintf,
52  * but all handling of flags, field width and precision as well as c and
53  * s formats is done very carefully by this portable routine. If a string
54  * precision (truncation) is specified (e.g. %.8s) it is guaranteed the
55  * string beyond the specified precision will not be referenced.
56  *
57  * Data type modifiers h, l and ll are ignored for c and s formats (data
58  * types wint_t and wchar_t are not supported).
59  *
60  * The following common synonyms for conversion characters are supported:
61  *   - i is a synonym for d
62  *   - D is a synonym for ld, explicit data type modifiers are ignored
63  *   - U is a synonym for lu, explicit data type modifiers are ignored
64  *   - O is a synonym for lo, explicit data type modifiers are ignored
65  *
66  * The following is specifically not supported:
67  *   - flag ' (thousands' grouping character) is recognized but ignored
68  *   - numeric formats: f, e, E, g, G and synonym F
69  *   - data type modifier 'L' (long double) and 'q' (quad - use 'll' instead)
70  *   - wide character/string formats: C, lc, S, ls
71  *   - writeback of converted string length: conversion character n
72  *   - the n$ specification for direct reference to n-th argument
73  *   - locales
74  *
75  * It is permitted for str_m to be zero, and it is permitted to specify NULL
76  * pointer for resulting string argument if str_m is zero (as per ISO C9X).
77  *
78  * The return value is the number of characters which would be generated
79  * for the given input, excluding the trailing null. If this value
80  * is greater or equal to str_m, not all characters from the result
81  * have been stored in str. If str_m is greater than zero it is
82  * guaranteed the resulting string will be null-terminated.
83  *
84  * NOTE that this matches the ISO C9X and GNU C library 2.1,
85  * but is different from some older implementations!
86  *
87  * Routines asprintf and vasprintf return a pointer (in the ptr argument)
88  * to a buffer sufficiently large to hold the resulting string. This pointer
89  * should be passed to free(3) to release the allocated storage when it is
90  * no longer needed. If sufficient space cannot be allocated, these functions
91  * will return -1 and set ptr to be a NULL pointer. These two routines are a
92  * GNU C library extensions (glibc).
93  *
94  * Routines asnprintf and vasnprintf are similar to asprintf and vasprintf,
95  * yet, like snprintf and vsnprintf counterparts, will write at most str_m-1
96  * characters into the allocated output string, the last character in the
97  * allocated buffer then gets the terminating null. If the formatted string
98  * length (the return value) is greater than or equal to the str_m argument,
99  * the resulting string was truncated and some of the formatted characters
100  * were discarded. These routines present a handy way to limit the amount
101  * of allocated memory to some sane value.
102  *
103  * AVAILABILITY
104  *   http://www.ijs.si/software/snprintf/
105  *
106  * REVISION HISTORY
107  * 1999-04      V0.9  Mark Martinec
108  *              - initial version, some modifications after comparing printf
109  *                man pages for Digital Unix 4.0, Solaris 2.6 and HPUX 10,
110  *                and checking how Perl handles sprintf (differently!);
111  * 1999-04-09   V1.0  Mark Martinec <mark.martinec@ijs.si>
112  *              - added main test program, fixed remaining inconsistencies,
113  *                added optional (long long int) support;
114  * 1999-04-12   V1.1  Mark Martinec <mark.martinec@ijs.si>
115  *              - support the 'p' format (pointer to void);
116  *              - if a string precision is specified
117  *                make sure the string beyond the specified precision
118  *                will not be referenced (e.g. by strlen);
119  * 1999-04-13   V1.2  Mark Martinec <mark.martinec@ijs.si>
120  *              - support synonyms %D=%ld, %U=%lu, %O=%lo;
121  *              - speed up the case of long format string with few conversions;
122  * 1999-06-30   V1.3  Mark Martinec <mark.martinec@ijs.si>
123  *              - fixed runaway loop (eventually crashing when str_l wraps
124  *                beyond 2*31) while copying format string without
125  *                conversion specifiers to a buffer that is too short
126  *                (thanks to Edwin Young <edwiny@autonomy.com> for
127  *                spotting the problem);
128  *              - added macros PORTABLE_SNPRINTF_VERSION_(MAJOR|MINOR)
129  *                to snprintf.h
130  * 2000-02-14   V2.0 (never released) Mark Martinec <mark.martinec@ijs.si>
131  *              - relaxed license terms: The Artistic License now applies.
132  *                You may still apply the GNU GENERAL PUBLIC LICENSE
133  *                as was distributed with previous versions, if you prefer;
134  *              - changed REVISION HISTORY dates to use ISO 8601 date format;
135  *              - added vsnprintf (patch also independently proposed by
136  *                Caolan McNamara 2000-05-04, and Keith M Willenson 2000-06-01)
137  * 2000-06-27   V2.1  Mark Martinec <mark.martinec@ijs.si>
138  *              - removed POSIX check for str_m<1; value 0 for str_m is
139  *                allowed by ISO C9X (and GNU C library 2.1) - (pointed out
140  *                on 2000-05-04 by Caolan McNamara, caolan@ csn dot ul dot ie).
141  *                Besides relaxed license this change in standards adherence
142  *                is the main reason to bump up the major version number;
143  *              - added nonstandard routines asnprintf, vasnprintf, asprintf,
144  *                vasprintf that dynamically allocate storage for the
145  *                resulting string; these routines are not compiled by default,
146  *                see comments where NEED_V?ASN?PRINTF macros are defined;
147  *              - autoconf contributed by Caolan McNamara
148  */
149
150
151 /* Define HAVE_SNPRINTF if your system already has snprintf and vsnprintf.
152  *
153  * If HAVE_SNPRINTF is defined this module will not produce code for
154  * snprintf and vsnprintf, unless PREFER_PORTABLE_SNPRINTF is defined as well,
155  * causing this portable version of snprintf to be called portable_snprintf
156  * (and portable_vsnprintf).
157  */
158 /* #define HAVE_SNPRINTF */
159
160 /* Define PREFER_PORTABLE_SNPRINTF if your system does have snprintf and
161  * vsnprintf but you would prefer to use the portable routine(s) instead.
162  * In this case the portable routine is declared as portable_snprintf
163  * (and portable_vsnprintf) and a macro 'snprintf' (and 'vsnprintf')
164  * is defined to expand to 'portable_v?snprintf' - see file snprintf.h .
165  * Defining this macro is only useful if HAVE_SNPRINTF is also defined,
166  * but does does no harm if defined nevertheless.
167  */
168 /* #define PREFER_PORTABLE_SNPRINTF */
169
170 /* Define SNPRINTF_LONGLONG_SUPPORT if you want to support
171  * data type (long long int) and data type modifier 'll' (e.g. %lld).
172  * If undefined, 'll' is recognized but treated as a single 'l'.
173  *
174  * If the system's sprintf does not handle 'll'
175  * the SNPRINTF_LONGLONG_SUPPORT must not be defined!
176  *
177  * This is off by default since (long long int) is a language extension.
178  */
179 /* #define SNPRINTF_LONGLONG_SUPPORT */
180
181 /* Define NEED_SNPRINTF_ONLY if you only need snprintf, and not vsnprintf.
182  * If NEED_SNPRINTF_ONLY is defined, the snprintf will be defined directly,
183  * otherwise both snprintf and vsnprintf routines will be defined
184  * and snprintf will be a simple wrapper around vsnprintf, at the expense
185  * of an extra procedure call.
186  */
187 /* #define NEED_SNPRINTF_ONLY */
188
189 /* Define NEED_V?ASN?PRINTF macros if you need library extension
190  * routines asprintf, vasprintf, asnprintf, vasnprintf respectively,
191  * and your system library does not provide them. They are all small
192  * wrapper routines around portable_vsnprintf. Defining any of the four
193  * NEED_V?ASN?PRINTF macros automatically turns off NEED_SNPRINTF_ONLY
194  * and turns on PREFER_PORTABLE_SNPRINTF.
195  *
196  * Watch for name conflicts with the system library if these routines
197  * are already present there.
198  *
199  * NOTE: vasprintf and vasnprintf routines need va_copy() from stdarg.h, as
200  * specified by C9X, to be able to traverse the same list of arguments twice.
201  * I don't know of any other standard and portable way of achieving the same.
202  * With some versions of gcc you may use __va_copy(). You might even get away
203  * with "ap2 = ap", in this case you must not call va_end(ap2) !
204  */
205 /* #define NEED_ASPRINTF   */
206 /* #define NEED_ASNPRINTF  */
207 /* #define NEED_VASPRINTF  */
208 /* #define NEED_VASNPRINTF */
209
210
211 /* Define the following macros if desired:
212  *   SOLARIS_COMPATIBLE, SOLARIS_BUG_COMPATIBLE,
213  *   HPUX_COMPATIBLE, HPUX_BUG_COMPATIBLE,
214  *   DIGITAL_UNIX_COMPATIBLE, DIGITAL_UNIX_BUG_COMPATIBLE,
215  *   PERL_COMPATIBLE, PERL_BUG_COMPATIBLE,
216  *
217  * - For portable applications it is best not to rely on peculiarities
218  *   of a given implementation so it may be best not to define any
219  *   of the macros that select compatibility and to avoid features
220  *   that vary among the systems.
221  *
222  * - Selecting compatibility with more than one operating system
223  *   is not strictly forbidden but is not recommended.
224  *
225  * - 'x'_BUG_COMPATIBLE implies 'x'_COMPATIBLE .
226  *
227  * - 'x'_COMPATIBLE refers to (and enables) a behaviour that is
228  *   documented in a sprintf man page on a given operating system
229  *   and actually adhered to by the system's sprintf (but not on
230  *   most other operating systems). It may also refer to and enable
231  *   a behaviour that is declared 'undefined' or 'implementation specific'
232  *   in the man page but a given implementation behaves predictably
233  *   in a certain way.
234  *
235  * - 'x'_BUG_COMPATIBLE refers to (and enables) a behaviour of system's sprintf
236  *   that contradicts the sprintf man page on the same operating system.
237  *
238  * - I do not claim that the 'x'_COMPATIBLE and 'x'_BUG_COMPATIBLE
239  *   conditionals take into account all idiosyncrasies of a particular
240  *   implementation, there may be other incompatibilities.
241  */
242
243 /* added by Lgb, the LyX Project */
244 #ifdef HAVE_CONFIG_H
245 #include <config.h>
246 #endif
247
248 \f
249 /* ============================================= */
250 /* NO USER SERVICABLE PARTS FOLLOWING THIS POINT */
251 /* ============================================= */
252
253 #define PORTABLE_SNPRINTF_VERSION_MAJOR 2
254 #define PORTABLE_SNPRINTF_VERSION_MINOR 1
255
256 #if defined(NEED_ASPRINTF) || defined(NEED_ASNPRINTF) || defined(NEED_VASPRINTF) || defined(NEED_VASNPRINTF)
257 # if defined(NEED_SNPRINTF_ONLY)
258 # undef NEED_SNPRINTF_ONLY
259 # endif
260 # if !defined(PREFER_PORTABLE_SNPRINTF)
261 # define PREFER_PORTABLE_SNPRINTF
262 # endif
263 #endif
264
265 #if defined(SOLARIS_BUG_COMPATIBLE) && !defined(SOLARIS_COMPATIBLE)
266 #define SOLARIS_COMPATIBLE
267 #endif
268
269 #if defined(HPUX_BUG_COMPATIBLE) && !defined(HPUX_COMPATIBLE)
270 #define HPUX_COMPATIBLE
271 #endif
272
273 #if defined(DIGITAL_UNIX_BUG_COMPATIBLE) && !defined(DIGITAL_UNIX_COMPATIBLE)
274 #define DIGITAL_UNIX_COMPATIBLE
275 #endif
276
277 #if defined(PERL_BUG_COMPATIBLE) && !defined(PERL_COMPATIBLE)
278 #define PERL_COMPATIBLE
279 #endif
280
281 #include <sys/types.h>
282 #include <string.h>
283 #include <stdlib.h>
284 #include <stdio.h>
285 #include <stdarg.h>
286 #include <assert.h>
287 #include <errno.h>
288
289 #ifdef isdigit
290 #undef isdigit
291 #endif
292 #define isdigit(c) ((c) >= '0' && (c) <= '9')
293
294 /* prototypes */
295
296 #if defined(NEED_ASPRINTF)
297 int asprintf   (char **ptr, const char *fmt, /*args*/ ...);
298 #endif
299 #if defined(NEED_VASPRINTF)
300 int vasprintf  (char **ptr, const char *fmt, va_list ap);
301 #endif
302 #if defined(NEED_ASNPRINTF)
303 int asnprintf  (char **ptr, size_t str_m, const char *fmt, /*args*/ ...);
304 #endif
305 #if defined(NEED_VASNPRINTF)
306 int vasnprintf (char **ptr, size_t str_m, const char *fmt, va_list ap);
307 #endif
308
309 #ifndef va_copy
310 #define va_copy(ap2,ap) ap2 = ap
311 #endif
312
313 #if defined(HAVE_SNPRINTF)
314 /* declare our portable snprintf  routine under name portable_snprintf  */
315 /* declare our portable vsnprintf routine under name portable_vsnprintf */
316 #else
317 /* declare our portable routines under names snprintf and vsnprintf */
318 #define portable_snprintf snprintf
319 #if !defined(NEED_SNPRINTF_ONLY)
320 #define portable_vsnprintf vsnprintf
321 #endif
322 #endif
323
324 #if !defined(HAVE_SNPRINTF) || defined(PREFER_PORTABLE_SNPRINTF)
325 int portable_snprintf(char *str, size_t str_m, const char *fmt, /*args*/ ...);
326 #if !defined(NEED_SNPRINTF_ONLY)
327 int portable_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap);
328 #endif
329 #endif
330
331 /* declarations */
332
333 #if !defined(lint)
334 static char credits[] = "\n\
335 @(#)snprintf.c, v2.1: Mark Martinec, <mark.martinec@ijs.si>\n\
336 @(#)snprintf.c, v2.1: Copyright 1999, Mark Martinec. Artistic license applies.\n\
337 @(#)snprintf.c, v2.1: http://www.ijs.si/software/snprintf/\n";
338 #endif
339
340 #if defined(NEED_ASPRINTF)
341 int asprintf(char **ptr, const char *fmt, /*args*/ ...) {
342   va_list ap;
343   size_t str_m;
344   int str_l;
345
346   *ptr = NULL;
347   va_start(ap, fmt);                            /* measure the required size */
348   str_l = portable_vsnprintf(NULL, (size_t) 0, fmt, ap);
349   va_end(ap);
350   assert(str_l >= 0);        /* possible integer overflow if str_m > INT_MAX */
351   *ptr = (char *) malloc(str_m = (size_t)str_l + 1);
352   if (*ptr == NULL) { errno = ENOMEM; str_l = -1; }
353   else {
354     int str_l2;
355     va_start(ap, fmt);
356     str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap);
357     va_end(ap);
358     assert(str_l2 == str_l);
359   }
360   return str_l;
361 }
362 #endif
363
364 #if defined(NEED_VASPRINTF)
365 int vasprintf(char **ptr, const char *fmt, va_list ap) {
366   size_t str_m;
367   int str_l;
368
369   *ptr = NULL;
370   { va_list ap2;
371     va_copy(ap2, ap);  /* don't consume the original ap, we'll need it again */
372                        /* measure the required size: */
373     str_l = portable_vsnprintf(NULL, (size_t) 0, fmt, ap2);
374     va_end(ap2);
375   }
376   assert(str_l >= 0);        /* possible integer overflow if str_m > INT_MAX */
377   *ptr = (char *) malloc(str_m = (size_t)str_l + 1);
378   if (*ptr == NULL) { errno = ENOMEM; str_l = -1; }
379   else {
380     int str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap);
381     assert(str_l2 == str_l);
382   }
383   return str_l;
384 }
385 #endif
386
387 #if defined(NEED_ASNPRINTF)
388 int asnprintf (char **ptr, size_t str_m, const char *fmt, /*args*/ ...) {
389   va_list ap;
390   int str_l;
391
392   *ptr = NULL;
393   va_start(ap, fmt);                            /* measure the required size */
394   str_l = portable_vsnprintf(NULL, (size_t) 0, fmt, ap);
395   va_end(ap);
396   assert(str_l >= 0);        /* possible integer overflow if str_m > INT_MAX */
397   if ((size_t)str_l + 1 < str_m) str_m = (size_t)str_l + 1;      /* truncate */
398   /* if str_m is 0, no buffer is allocated, just set *ptr to NULL */
399   if (str_m == 0) {  /* not interested in resulting string, just return size */
400   } else {
401     *ptr = (char *) malloc(str_m);
402     if (*ptr == NULL) { errno = ENOMEM; str_l = -1; }
403     else {
404       int str_l2;
405       va_start(ap, fmt);
406       str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap);
407       va_end(ap);
408       assert(str_l2 == str_l);
409     }
410   }
411   return str_l;
412 }
413 #endif
414
415 #if defined(NEED_VASNPRINTF)
416 int vasnprintf (char **ptr, size_t str_m, const char *fmt, va_list ap) {
417   int str_l;
418
419   *ptr = NULL;
420   { va_list ap2;
421     va_copy(ap2, ap);  /* don't consume the original ap, we'll need it again */
422                        /* measure the required size: */
423     str_l = portable_vsnprintf(NULL, (size_t) 0, fmt, ap2);
424     va_end(ap2);
425   }
426   assert(str_l >= 0);        /* possible integer overflow if str_m > INT_MAX */
427   if ((size_t)str_l + 1 < str_m) str_m = (size_t)str_l + 1;      /* truncate */
428   /* if str_m is 0, no buffer is allocated, just set *ptr to NULL */
429   if (str_m == 0) {  /* not interested in resulting string, just return size */
430   } else {
431     *ptr = (char *) malloc(str_m);
432     if (*ptr == NULL) { errno = ENOMEM; str_l = -1; }
433     else {
434       int str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap);
435       assert(str_l2 == str_l);
436     }
437   }
438   return str_l;
439 }
440 #endif
441
442 /*
443  * If the system does have snprintf and the portable routine is not
444  * specifically required, this module produces no code for snprintf/vsnprintf.
445  */
446 #if !defined(HAVE_SNPRINTF) || defined(PREFER_PORTABLE_SNPRINTF)
447
448 #if !defined(NEED_SNPRINTF_ONLY)
449 int portable_snprintf(char *str, size_t str_m, const char *fmt, /*args*/ ...) {
450   va_list ap;
451   int str_l;
452
453   va_start(ap, fmt);
454   str_l = portable_vsnprintf(str, str_m, fmt, ap);
455   va_end(ap);
456   return str_l;
457 }
458 #endif
459
460 #if defined(NEED_SNPRINTF_ONLY)
461 int portable_snprintf(char *str, size_t str_m, const char *fmt, /*args*/ ...) {
462 #else
463 int portable_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap) {
464 #endif
465
466 #if defined(NEED_SNPRINTF_ONLY)
467   va_list ap;
468 #endif
469   size_t str_l = 0;
470   const char *p = fmt;
471
472 /* In contrast with POSIX, the ISO C9X now says
473  * that str can be NULL and str_m can be 0. This is more useful. */
474 /*if (str_m < 1) return -1;*/
475
476 #if defined(NEED_SNPRINTF_ONLY)
477   va_start(ap, fmt);
478 #endif
479   if (!p) p = "";
480   while (*p) {
481     if (*p != '%') {
482    /* if (str_l < str_m) str[str_l++] = *p++;    -- this would be sufficient */
483    /* but the following code achieves better performance for cases
484     * where format string is long and contains few conversions */
485       const char *q = strchr(p+1,'%');
486       int n = !q ? strlen(p) : (q-p);
487       int avail = (int)(str_m-str_l);
488       if (avail > 0) {
489         register int k; register char *r; register const char* p1;
490         for (p1=p, r=str+str_l, k=(n>avail?avail:n); k>0; k--) *r++ = *p1++;
491       }
492       p += n; str_l += n;
493     } else {
494       const char *starting_p;
495       int min_field_width = 0, precision = 0;
496       int zero_padding = 0, precision_specified = 0, justify_left = 0;
497       int alternative_form = 0, force_sign = 0;
498       int space_for_positive = 1; /* If both the ' ' and '+' flags appear,
499                                      the ' ' flag should be ignored. */
500       char data_type_modifier = '\0';      /* allowed valued: \0, h, l, L, p */
501       char tmp[32];/* temporary buffer for simple numeric->string conversion */
502
503       const char *str_arg = 0;/* string address in case of string arguments  */
504       int str_arg_l;  /* natural field width of arg without padding and sign */
505
506       long int long_arg;  /* long int argument value - always defined
507         in case of numeric arguments, regardless of data type modifiers.
508         In case of data type modifier 'll' the value is stored in long_long_arg
509         and only the sign of long_arg is guaranteed to be correct */
510       void *ptr_arg; /* pointer argument value - only defined for p format   */
511       int int_arg;   /* int argument value - only defined if no h or l modif.*/
512 #ifdef SNPRINTF_LONGLONG_SUPPORT
513       long long int long_long_arg = 0;  /* long long argument value - only
514                                            defined if ll modifier is present */
515 #endif
516       int number_of_zeros_to_pad = 0;
517       int zero_padding_insertion_ind = 0;
518       char fmt_spec = '\0';            /* current format specifier character */
519
520       starting_p = p; p++;  /* skip '%' */
521    /* parse flags */
522       while (*p == '0' || *p == '-' || *p == '+' ||
523              *p == ' ' || *p == '#' || *p == '\'') {
524         switch (*p) {
525         case '0': zero_padding = 1; break;
526         case '-': justify_left = 1; break;
527         case '+': force_sign = 1; space_for_positive = 0; break;
528         case ' ': force_sign = 1;
529      /* If both the ' ' and '+' flags appear, the ' ' flag should be ignored */
530 #ifdef PERL_COMPATIBLE
531      /* ... but in Perl the last of ' ' and '+' applies */
532                   space_for_positive = 1;
533 #endif
534                   break;
535         case '#': alternative_form = 1; break;
536         case '\'': break;
537         }
538         p++;
539       }
540    /* If the '0' and '-' flags both appear, the '0' flag should be ignored. */
541
542    /* parse field width */
543       if (*p == '*') {
544         p++; min_field_width = va_arg(ap, int);
545         if (min_field_width < 0)
546           { min_field_width = -min_field_width; justify_left = 1; }
547       } else if (isdigit((int)(*p))) {
548         min_field_width = *p++ - '0';
549         while (isdigit((int)(*p)))
550           min_field_width = 10*min_field_width + (*p++ - '0');
551       }
552    /* parse precision */
553       if (*p == '.') {
554         p++; precision_specified = 1;
555         if (*p == '*') {
556           p++; precision = va_arg(ap, int);
557           if (precision < 0) {
558             precision_specified = 0; precision = 0;
559          /* NOTE:
560           *   Solaris 2.6 man page claims that in this case the precision
561           *   should be set to 0.  Digital Unix 4.0 and HPUX 10 man page
562           *   claim that this case should be treated as unspecified precision,
563           *   which is what we do here.
564           */
565           }
566         } else if (isdigit((int)(*p))) {
567           precision = *p++ - '0';
568           while (isdigit((int)(*p))) precision = 10*precision + (*p++ - '0');
569         }
570       }
571    /* parse 'h', 'l' and 'll' data type modifiers */
572       if (*p == 'h' || *p == 'l') {
573         data_type_modifier = *p; p++;
574         if (data_type_modifier == 'l' && *p == 'l') {/* double l = long long */
575 #ifdef SNPRINTF_LONGLONG_SUPPORT
576           data_type_modifier = '2';               /* double l encoded as '2' */
577 #else
578           data_type_modifier = 'l';                /* treat it as single 'l' */
579 #endif
580           p++;
581         }
582       }
583       fmt_spec = *p;
584    /* common synonyms: */
585       switch (fmt_spec) {
586       case 'i': fmt_spec = 'd'; break;
587       case 'D': fmt_spec = 'd'; data_type_modifier = 'l'; break;
588       case 'U': fmt_spec = 'u'; data_type_modifier = 'l'; break;
589       case 'O': fmt_spec = 'o'; data_type_modifier = 'l'; break;
590       default: break;
591       }
592    /* get parameter value, do initial processing */
593       switch (fmt_spec) {
594       case '%': /* % behaves similar to 's' regarding flags and field widths */
595       case 'c': /* c behaves similar to 's' regarding flags and field widths */
596       case 's':
597         data_type_modifier = '\0';       /* wint_t and wchar_t not supported */
598      /* the result of zero padding flag with non-numeric format is undefined */
599      /* Solaris and HPUX 10 does zero padding in this case, Digital Unix not */
600 #ifdef DIGITAL_UNIX_COMPATIBLE
601         zero_padding = 0;        /* turn zero padding off for string formats */
602 #endif
603         str_arg_l = 1;
604         switch (fmt_spec) {
605         case '%':
606           str_arg = p; break;
607         case 'c':
608           { int j = va_arg(ap, int); str_arg = (const char*) &j; }
609           break;
610         case 's':
611           str_arg = va_arg(ap, const char *);
612           if (!str_arg) str_arg_l = 0;
613        /* make sure not to address string beyond the specified precision !!! */
614           else if (!precision_specified) str_arg_l = strlen(str_arg);
615        /* truncate string if necessary as requested by precision */
616           else if (precision <= 0) str_arg_l = 0;
617           else {
618             const char *q = memchr(str_arg,'\0',(size_t)precision);
619             str_arg_l = !q ? precision : (q-str_arg);
620           }
621           break;
622         default: break;
623         }
624         break;
625       case 'd': case 'o': case 'u': case 'x': case 'X': case 'p':
626         long_arg = 0; int_arg = 0; ptr_arg = NULL;
627         if (fmt_spec == 'p') {
628         /* HPUX 10: An l, h, ll or L before any other conversion character
629          *   (other than d, i, o, u, x, or X) is ignored.
630          * Digital Unix:
631          *   not specified, but seems to behave as HPUX does.
632          * Solaris: If an h, l, or L appears before any other conversion
633          *   specifier (other than d, i, o, u, x, or X), the behavior
634          *   is undefined. (Actually %hp converts only 16-bits of address
635          *   and %llp treats address as 64-bit data which is incompatible
636          *   with (void *) argument on a 32-bit system).
637          */
638 #ifdef SOLARIS_COMPATIBLE
639 #  ifdef SOLARIS_BUG_COMPATIBLE
640           /* keep data type modifiers even if it represents 'll' */
641 #  else
642           if (data_type_modifier == '2') data_type_modifier = '\0';
643 #  endif
644 #else
645           data_type_modifier = '\0';
646 #endif
647           ptr_arg = va_arg(ap, void *); long_arg = !ptr_arg ? 0 : 1;
648         } else {
649           switch (data_type_modifier) {
650           case '\0':
651           case 'h':
652          /* It is non-portable to specify a second argument of char or short
653           * to va_arg, because arguments seen by the called function
654           * are not char or short.  C converts char and short arguments
655           * to int before passing them to a function.
656           */
657             int_arg = va_arg(ap, int); long_arg = int_arg; break;
658           case 'l':
659             long_arg = va_arg(ap, long int); break;
660 #ifdef SNPRINTF_LONGLONG_SUPPORT
661           case '2':
662             long_long_arg = va_arg(ap, long long int);
663             /* only the sign of long_arg is guaranteed */
664             if      (long_long_arg > 0) long_arg = +1;
665             else if (long_long_arg < 0) long_arg = -1;
666             else long_arg = 0;
667             break;
668 #endif
669           }
670         }
671         str_arg = tmp; str_arg_l = 0;
672      /* NOTE:
673       *   For d, i, o, u, x, and X conversions, if precision is specified,
674       *   the '0' flag should be ignored. This is so with Solaris 2.6,
675       *   Digital UNIX 4.0 and HPUX 10;  but not with Perl.
676       */
677 #ifndef PERL_COMPATIBLE
678         if (precision_specified) zero_padding = 0;
679 #endif
680         if (fmt_spec == 'd') {
681           if (force_sign && long_arg >= 0)
682             tmp[str_arg_l++] = space_for_positive ? ' ' : '+';
683          /* leave negative numbers for sprintf to handle,
684             to avoid handling tricky cases like (short int)(-32768) */
685         } else if (alternative_form) {
686           if (long_arg != 0 && (fmt_spec == 'x' || fmt_spec == 'X') )
687             { tmp[str_arg_l++] = '0'; tmp[str_arg_l++] = fmt_spec; }
688 #ifdef HPUX_COMPATIBLE
689           else if (fmt_spec == 'p'
690          /* HPUX 10: for an alternative form of p conversion,
691           *          a nonzero result is prefixed by 0x. */
692 #ifndef HPUX_BUG_COMPATIBLE
693          /* Actually it uses 0x prefix even for a zero value. */
694                    && long_arg != 0
695 #endif
696                  ) { tmp[str_arg_l++] = '0'; tmp[str_arg_l++] = 'x'; }
697 #endif
698         }
699         zero_padding_insertion_ind = str_arg_l;
700         if (!precision_specified) precision = 1;   /* default precision is 1 */
701         if (precision == 0 && long_arg == 0
702 #ifdef HPUX_BUG_COMPATIBLE
703             && fmt_spec != 'p'
704          /* HPUX 10 man page claims: With conversion character p the result of
705           * converting a zero value with a precision of zero is a null string.
706           * Actually it returns all zeroes. */
707 #endif
708        ) {  /* converted to null string */  }
709         else {
710           char f[5]; int f_l = 0;
711           f[f_l++] = '%';
712           if (!data_type_modifier) { }
713           else if (data_type_modifier=='2') { f[f_l++] = 'l'; f[f_l++] = 'l'; }
714           else f[f_l++] = data_type_modifier;
715           f[f_l++] = fmt_spec; f[f_l++] = '\0';
716           if (fmt_spec == 'p') str_arg_l+=sprintf(tmp+str_arg_l, f, ptr_arg);
717           else {
718             switch (data_type_modifier) {
719             case '\0':
720             case 'h': str_arg_l+=sprintf(tmp+str_arg_l, f, int_arg);  break;
721             case 'l': str_arg_l+=sprintf(tmp+str_arg_l, f, long_arg); break;
722 #ifdef SNPRINTF_LONGLONG_SUPPORT
723             case '2': str_arg_l+=sprintf(tmp+str_arg_l,f,long_long_arg); break;
724 #endif
725             }
726           }
727           if (zero_padding_insertion_ind < str_arg_l &&
728               tmp[zero_padding_insertion_ind] == '-')
729             zero_padding_insertion_ind++;
730         }
731         { int num_of_digits = str_arg_l - zero_padding_insertion_ind;
732           if (alternative_form && fmt_spec == 'o'
733 #ifdef HPUX_COMPATIBLE                                  /* ("%#.o",0) -> ""  */
734               && (str_arg_l > 0)
735 #endif
736 #ifdef DIGITAL_UNIX_BUG_COMPATIBLE                      /* ("%#o",0) -> "00" */
737 #else
738               && !(zero_padding_insertion_ind < str_arg_l
739                    && tmp[zero_padding_insertion_ind] == '0')
740 #endif
741          ) {      /* assure leading zero for alternative-form octal numbers */
742             if (!precision_specified || precision < num_of_digits+1)
743               { precision = num_of_digits+1; precision_specified = 1; }
744           }
745        /* zero padding to specified precision? */
746           if (num_of_digits < precision) 
747             number_of_zeros_to_pad = precision - num_of_digits;
748         }
749      /* zero padding to specified minimal field width? */
750         if (!justify_left && zero_padding) {
751           int n = min_field_width - (str_arg_l+number_of_zeros_to_pad);
752           if (n > 0) number_of_zeros_to_pad += n;
753         }
754         break;
755       default:  /* unrecognized format, keep format string unchanged */
756         zero_padding = 0;   /* turn zero padding off for non-numeric formats */
757 #ifndef DIGITAL_UNIX_COMPATIBLE
758         justify_left = 1; min_field_width = 0;                /* reset flags */
759 #endif
760 #ifdef PERL_COMPATIBLE
761      /* keep the entire format string unchanged */
762         str_arg = starting_p; str_arg_l = p - starting_p;
763 #else
764      /* discard the unrecognized format, just keep the unrecognized fmt char */
765         str_arg = p; str_arg_l = 0;
766 #endif
767         if (*p) str_arg_l++;  /* include invalid fmt specifier if not at EOS */
768         break;
769       }
770       if (*p) p++;          /* step over the just processed format specifier */
771    /* insert padding to the left as requested by min_field_width */
772       if (!justify_left) {                /* left padding with blank or zero */
773         int n = min_field_width - (str_arg_l+number_of_zeros_to_pad);
774         if (n > 0) {
775           int avail = (int)(str_m-str_l);
776           if (avail > 0) {      /* memset(str+str_l, zp, (n>avail?avail:n)); */
777             const char zp = (zero_padding ? '0' : ' ');
778             register int k; register char *r;
779             for (r=str+str_l, k=(n>avail?avail:n); k>0; k--) *r++ = zp;
780           }
781           str_l += n;
782         }
783       }
784    /* zero padding as requested by the precision for numeric formats requred?*/
785       if (number_of_zeros_to_pad <= 0) {
786      /* will not copy first part of numeric here,   *
787       * force it to be copied later in its entirety */
788         zero_padding_insertion_ind = 0;
789       } else {
790      /* insert first part of numerics (sign or '0x') before zero padding */
791         int n = zero_padding_insertion_ind;
792         if (n > 0) {
793           int avail = (int)(str_m-str_l);
794           if (avail > 0) memcpy(str+str_l, str_arg, (size_t)(n>avail?avail:n));
795           str_l += n;
796         }
797      /* insert zero padding as requested by the precision */
798         n = number_of_zeros_to_pad;
799         if (n > 0) {
800           int avail = (int)(str_m-str_l);
801           if (avail > 0) {     /* memset(str+str_l, '0', (n>avail?avail:n)); */
802             register int k; register char *r;
803             for (r=str+str_l, k=(n>avail?avail:n); k>0; k--) *r++ = '0';
804           }
805           str_l += n;
806         }
807       }
808    /* insert formatted string (or unmodified format for unknown formats) */
809       { int n = str_arg_l - zero_padding_insertion_ind;
810         if (n > 0) {
811           int avail = (int)(str_m-str_l);
812           if (avail > 0) memcpy(str+str_l, str_arg+zero_padding_insertion_ind,
813                                 (size_t)(n>avail ? avail : n) );
814           str_l += n;
815         }
816       }
817    /* insert right padding */
818       if (justify_left) {          /* right blank padding to the field width */
819         int n = min_field_width - (str_arg_l+number_of_zeros_to_pad);
820         if (n > 0) {
821           int avail = (int)(str_m-str_l);
822           if (avail > 0) {     /* memset(str+str_l, ' ', (n>avail?avail:n)); */
823             register int k; register char *r;
824             for (r=str+str_l, k=(n>avail?avail:n); k>0; k--) *r++ = ' ';
825           }
826           str_l += n;
827         }
828       }
829     }
830   }
831 #if defined(NEED_SNPRINTF_ONLY)
832   va_end(ap);
833 #endif
834   if (str_m > 0) { /* make sure the string is null-terminated
835                       even at the expense of overwriting the last character */
836     str[str_l <= str_m-1 ? str_l : str_m-1] = '\0';
837   }
838
839   return str_l;    /* return the number of characters formatted
840                       (excluding trailing null character),
841                       that is, the number of characters that would have been
842                       written to the buffer if it were large enough */
843 }
844 #endif