1 /*        $NetBSD: vsnprintf_ss.c,v 1.14 2022/04/19 20:32:16 rillig Exp $       */
2 
3 /*-
4  * Copyright (c) 1990, 1993
5  *        The Regents of the University of California.  All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * Chris Torek.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. Neither the name of the University nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include <sys/cdefs.h>
36 #if defined(LIBC_SCCS) && !defined(lint)
37 #if 0
38 static char sccsid[] = "@(#)vsnprintf.c 8.1 (Berkeley) 6/4/93";
39 #else
40 __RCSID("$NetBSD: vsnprintf_ss.c,v 1.14 2022/04/19 20:32:16 rillig Exp $");
41 #endif
42 #endif /* LIBC_SCCS and not lint */
43 
44 #include "namespace.h"
45 
46 #include <sys/types.h>
47 #include <inttypes.h>
48 #include <assert.h>
49 #include <stdio.h>
50 #include <errno.h>
51 #include <stdarg.h>
52 #include <string.h>
53 #include "reentrant.h"
54 #include "extern.h"
55 #include "local.h"
56 
57 #ifdef __weak_alias
__weak_alias(vsnprintf_ss,_vsnprintf_ss)58 __weak_alias(vsnprintf_ss,_vsnprintf_ss)
59 #endif
60 
61 /*
62  * vsnprintf_ss: scaled down version of printf(3).
63  *
64  * this version based on vfprintf() from libc which was derived from
65  * software contributed to Berkeley by Chris Torek.
66  *
67  */
68 
69 /*
70  * macros for converting digits to letters and vice versa
71  */
72 #define   to_digit(c)         ((c) - '0')
73 #define is_digit(c) ((unsigned)to_digit(c) <= 9)
74 #define   to_char(n)          (char)((n) + '0')
75 
76 /*
77  * flags used during conversion.
78  */
79 #define   ALT                 0x001               /* alternate form */
80 #define   HEXPREFIX 0x002               /* add 0x or 0X prefix */
81 #define   LADJUST             0x004               /* left adjustment */
82 #define   LONGDBL             0x008               /* long double; unimplemented */
83 #define   LONGINT             0x010               /* long integer */
84 #define   QUADINT             0x020               /* quad integer */
85 #define   SHORTINT  0x040               /* short integer */
86 #define   MAXINT              0x080               /* intmax_t */
87 #define   PTRINT              0x100               /* intptr_t */
88 #define   SIZEINT             0x200               /* size_t */
89 #define   ZEROPAD             0x400               /* zero (as opposed to blank) pad */
90 #define FPT                   0x800               /* Floating point number */
91 
92           /*
93            * To extend shorts properly, we need both signed and unsigned
94            * argument extraction methods.
95            */
96 #define   SARG() \
97           (flags&MAXINT ? va_arg(ap, intmax_t) : \
98               flags&PTRINT ? va_arg(ap, intptr_t) : \
99               flags&SIZEINT ? va_arg(ap, ssize_t) : /* XXX */ \
100               flags&QUADINT ? va_arg(ap, quad_t) : \
101               flags&LONGINT ? va_arg(ap, long) : \
102               flags&SHORTINT ? (long)(short)va_arg(ap, int) : \
103               (long)va_arg(ap, int))
104 #define   UARG() \
105           (flags&MAXINT ? va_arg(ap, uintmax_t) : \
106               flags&PTRINT ? va_arg(ap, uintptr_t) : \
107               flags&SIZEINT ? va_arg(ap, size_t) : \
108               flags&QUADINT ? va_arg(ap, u_quad_t) : \
109               flags&LONGINT ? va_arg(ap, u_long) : \
110               flags&SHORTINT ? (u_long)(u_short)va_arg(ap, int) : \
111               (u_long)va_arg(ap, u_int))
112 
113 #define PUTCHAR(C) do {                                               \
114           if (sbuf < tailp)                                 \
115                     *sbuf++ = (C);                                    \
116 } while (0)
117 
118 int
119 vsnprintf_ss(char *sbuf, size_t slen, const char *fmt0, va_list ap)
120 {
121           const char *fmt;    /* format string */
122           int ch;                       /* character from fmt */
123           int n;                        /* handy integer (short term usage) */
124           char *cp;           /* handy char pointer (short term usage) */
125           int flags;                    /* flags as above */
126           int ret;            /* return value accumulator */
127           int width;                    /* width from format (%8d), or 0 */
128           int prec;           /* precision from format (%.3d), or -1 */
129           char sign;                    /* sign prefix (' ', '+', '-', or \0) */
130 
131           u_quad_t _uquad;    /* integer arguments %[diouxX] */
132           enum { OCT, DEC, HEX } base;/* base for [diouxX] conversion */
133           int dprec;                    /* a copy of prec if [diouxX], 0 otherwise */
134           int realsz;                   /* field size expanded by dprec */
135           int size;           /* size of converted field or string */
136           const char *xdigs;  /* digits for [xX] conversion */
137           char bf[128];                 /* space for %c, %[diouxX] */
138           char *tailp;                  /* tail pointer for snprintf */
139           size_t len;
140 
141           static const char xdigs_lower[16] = "0123456789abcdef";
142           static const char xdigs_upper[16] = "0123456789ABCDEF";
143 
144           _DIAGASSERT(slen == 0 || sbuf != NULL);
145           _DIAGASSERT(fmt0 != NULL);
146 
147           if (slen > INT_MAX) {
148                     errno = EOVERFLOW;
149                     return -1;
150           }
151 
152           tailp = sbuf + slen;
153 
154           cp = NULL;          /* XXX: shutup gcc */
155           size = 0; /* XXX: shutup gcc */
156 
157           fmt = fmt0;
158           ret = 0;
159 
160           xdigs = NULL;                 /* XXX: shut up gcc warning */
161 
162           /*
163            * Scan the format for conversions (`%' character).
164            */
165           for (;;) {
166                     while (*fmt != '%' && *fmt) {
167                               ret++;
168                               PUTCHAR(*fmt);
169                               fmt++;
170                     }
171                     if (*fmt == 0)
172                               goto done;
173 
174                     fmt++;              /* skip over '%' */
175 
176                     flags = 0;
177                     dprec = 0;
178                     width = 0;
179                     prec = -1;
180                     sign = '\0';
181 
182 rflag:              ch = *fmt++;
183 reswitch: switch (ch) {
184                     case ' ':
185                               /*
186                                * ``If the space and + flags both appear, the space
187                                * flag will be ignored.''
188                                *        -- ANSI X3J11
189                                */
190                               if (!sign)
191                                         sign = ' ';
192                               goto rflag;
193                     case '#':
194                               flags |= ALT;
195                               goto rflag;
196                     case '*':
197                               /*
198                                * ``A negative field width argument is taken as a
199                                * - flag followed by a positive field width.''
200                                *        -- ANSI X3J11
201                                * They don't exclude field widths read from args.
202                                */
203                               if ((width = va_arg(ap, int)) >= 0)
204                                         goto rflag;
205                               width = -width;
206                               /* FALLTHROUGH */
207                     case '-':
208                               flags |= LADJUST;
209                               goto rflag;
210                     case '+':
211                               sign = '+';
212                               goto rflag;
213                     case '.':
214                               if ((ch = *fmt++) == '*') {
215                                         n = va_arg(ap, int);
216                                         prec = n < 0 ? -1 : n;
217                                         goto rflag;
218                               }
219                               n = 0;
220                               while (is_digit(ch)) {
221                                         n = 10 * n + to_digit(ch);
222                                         ch = *fmt++;
223                               }
224                               prec = n < 0 ? -1 : n;
225                               goto reswitch;
226                     case '0':
227                               /*
228                                * ``Note that 0 is taken as a flag, not as the
229                                * beginning of a field width.''
230                                *        -- ANSI X3J11
231                                */
232                               flags |= ZEROPAD;
233                               goto rflag;
234                     case '1': case '2': case '3': case '4':
235                     case '5': case '6': case '7': case '8': case '9':
236                               n = 0;
237                               do {
238                                         n = 10 * n + to_digit(ch);
239                                         ch = *fmt++;
240                               } while (is_digit(ch));
241                               width = n;
242                               goto reswitch;
243                     case 'h':
244                               flags |= SHORTINT;
245                               goto rflag;
246                     case 'j':
247                               flags |= MAXINT;
248                               goto rflag;
249                     case 'l':
250                               if (*fmt == 'l') {
251                                         fmt++;
252                                         flags |= QUADINT;
253                               } else {
254                                         flags |= LONGINT;
255                               }
256                               goto rflag;
257                     case 'q':
258                               flags |= QUADINT;
259                               goto rflag;
260                     case 't':
261                               flags |= PTRINT;
262                               goto rflag;
263                     case 'z':
264                               flags |= SIZEINT;
265                               goto rflag;
266                     case 'c':
267                               *(cp = bf) = va_arg(ap, int);
268                               size = 1;
269                               sign = '\0';
270                               break;
271                     case 'D':
272                               flags |= LONGINT;
273                               /*FALLTHROUGH*/
274                     case 'd':
275                     case 'i':
276                               _uquad = SARG();
277                               if ((quad_t)_uquad < 0) {
278                                         _uquad = -_uquad;
279                                         sign = '-';
280                               }
281                               base = DEC;
282                               goto number;
283                     case 'n':
284                               if (flags & MAXINT)
285                                         *va_arg(ap, intmax_t *) = ret;
286                               else if (flags & PTRINT)
287                                         *va_arg(ap, intptr_t *) = ret;
288                               else if (flags & SIZEINT)
289                                         *va_arg(ap, ssize_t *) = ret;
290                               else if (flags & QUADINT)
291                                         *va_arg(ap, quad_t *) = ret;
292                               else if (flags & LONGINT)
293                                         *va_arg(ap, long *) = ret;
294                               else if (flags & SHORTINT)
295                                         *va_arg(ap, short *) = ret;
296                               else
297                                         *va_arg(ap, int *) = ret;
298                               continue; /* no output */
299                     case 'O':
300                               flags |= LONGINT;
301                               /*FALLTHROUGH*/
302                     case 'o':
303                               _uquad = UARG();
304                               base = OCT;
305                               goto nosign;
306                     case 'p':
307                               /*
308                                * ``The argument shall be a pointer to void.  The
309                                * value of the pointer is converted to a sequence
310                                * of printable characters, in an implementation-
311                                * defined manner.''
312                                *        -- ANSI X3J11
313                                */
314                               /* NOSTRICT */
315                               _uquad = (u_long)va_arg(ap, void *);
316                               base = HEX;
317                               xdigs = xdigs_lower;
318                               flags |= HEXPREFIX;
319                               ch = 'x';
320                               goto nosign;
321                     case 's':
322                               if ((cp = va_arg(ap, char *)) == NULL)
323                                         /*XXXUNCONST*/
324                                         cp = __UNCONST("(null)");
325                               if (prec >= 0) {
326                                         /*
327                                          * can't use strlen; can only look for the
328                                          * NUL in the first `prec' characters, and
329                                          * strlen() will go further.
330                                          */
331                                         char *p = memchr(cp, 0, (size_t)prec);
332 
333                                         if (p != NULL) {
334                                                   _DIAGASSERT(__type_fit(int, p - cp));
335                                                   size = (int)(p - cp);
336                                                   if (size > prec)
337                                                             size = prec;
338                                         } else
339                                                   size = prec;
340                               } else {
341                                         len = strlen(cp);
342                                         _DIAGASSERT(__type_fit(int, len));
343                                         size = (int)len;
344                               }
345                               sign = '\0';
346                               break;
347                     case 'U':
348                               flags |= LONGINT;
349                               /*FALLTHROUGH*/
350                     case 'u':
351                               _uquad = UARG();
352                               base = DEC;
353                               goto nosign;
354                     case 'X':
355                               xdigs = xdigs_upper;
356                               goto hex;
357                     case 'x':
358                               xdigs = xdigs_lower;
359 hex:                          _uquad = UARG();
360                               base = HEX;
361                               /* leading 0x/X only if non-zero */
362                               if (flags & ALT && _uquad != 0)
363                                         flags |= HEXPREFIX;
364 
365                               /* unsigned conversions */
366 nosign:                       sign = '\0';
367                               /*
368                                * ``... diouXx conversions ... if a precision is
369                                * specified, the 0 flag will be ignored.''
370                                *        -- ANSI X3J11
371                                */
372 number:                       if ((dprec = prec) >= 0)
373                                         flags &= ~ZEROPAD;
374 
375                               /*
376                                * ``The result of converting a zero value with an
377                                * explicit precision of zero is no characters.''
378                                *        -- ANSI X3J11
379                                */
380                               cp = bf + sizeof(bf);
381                               if (_uquad != 0 || prec != 0) {
382                                         /*
383                                          * Unsigned mod is hard, and unsigned mod
384                                          * by a constant is easier than that by
385                                          * a variable; hence this switch.
386                                          */
387                                         switch (base) {
388                                         case OCT:
389                                                   do {
390                                                             *--cp = to_char(_uquad & 7);
391                                                             _uquad >>= 3;
392                                                   } while (_uquad);
393                                                   /* handle octal leading 0 */
394                                                   if (flags & ALT && *cp != '0')
395                                                             *--cp = '0';
396                                                   break;
397 
398                                         case DEC:
399                                                   /* many numbers are 1 digit */
400                                                   while (_uquad >= 10) {
401                                                             *--cp = to_char(_uquad % 10);
402                                                             _uquad /= 10;
403                                                   }
404                                                   *--cp = to_char(_uquad);
405                                                   break;
406 
407                                         case HEX:
408                                                   do {
409                                                             *--cp = xdigs[(size_t)_uquad & 15];
410                                                             _uquad >>= 4;
411                                                   } while (_uquad);
412                                                   break;
413 
414                                         default:
415                                                   /*XXXUNCONST*/
416                                                   cp = __UNCONST("bug bad base");
417                                                   len = strlen(cp);
418                                                   _DIAGASSERT(__type_fit(int, len));
419                                                   size = (int)len;
420                                                   goto skipsize;
421                                         }
422                               }
423                               _DIAGASSERT(__type_fit(int, bf + sizeof(bf) - cp));
424                               size = (int)(bf + sizeof(bf) - cp);
425                     skipsize:
426                               break;
427                     default:  /* "%?" prints ?, unless ? is NUL */
428                               if (ch == '\0')
429                                         goto done;
430                               /* pretend it was %c with argument ch */
431                               cp = bf;
432                               *cp = ch;
433                               size = 1;
434                               sign = '\0';
435                               break;
436                     }
437 
438                     /*
439                      * All reasonable formats wind up here.  At this point, `cp'
440                      * points to a string which (if not flags&LADJUST) should be
441                      * padded out to `width' places.  If flags&ZEROPAD, it should
442                      * first be prefixed by any sign or other prefix; otherwise,
443                      * it should be blank padded before the prefix is emitted.
444                      * After any left-hand padding and prefixing, emit zeroes
445                      * required by a decimal [diouxX] precision, then print the
446                      * string proper, then emit zeroes required by any leftover
447                      * floating precision; finally, if LADJUST, pad with blanks.
448                      *
449                      * Compute actual size, so we know how much to pad.
450                      * size excludes decimal prec; realsz includes it.
451                      */
452                     realsz = dprec > size ? dprec : size;
453                     if (sign)
454                               realsz++;
455                     else if (flags & HEXPREFIX)
456                               realsz+= 2;
457 
458                     /* adjust ret */
459                     ret += width > realsz ? width : realsz;
460 
461                     /* right-adjusting blank padding */
462                     if ((flags & (LADJUST|ZEROPAD)) == 0) {
463                               n = width - realsz;
464                               while (n-- > 0)
465                                         PUTCHAR(' ');
466                     }
467 
468                     /* prefix */
469                     if (sign) {
470                               PUTCHAR(sign);
471                     } else if (flags & HEXPREFIX) {
472                               PUTCHAR('0');
473                               PUTCHAR(ch);
474                     }
475 
476                     /* right-adjusting zero padding */
477                     if ((flags & (LADJUST|ZEROPAD)) == ZEROPAD) {
478                               n = width - realsz;
479                               while (n-- > 0)
480                                         PUTCHAR('0');
481                     }
482 
483                     /* leading zeroes from decimal precision */
484                     n = dprec - size;
485                     while (n-- > 0)
486                               PUTCHAR('0');
487 
488                     /* the string or number proper */
489                     while (size--)
490                               PUTCHAR(*cp++);
491                     /* left-adjusting padding (always blank) */
492                     if (flags & LADJUST) {
493                               n = width - realsz;
494                               while (n-- > 0)
495                                         PUTCHAR(' ');
496                     }
497           }
498 
499 done:
500           if (sbuf == tailp)
501                     sbuf[-1] = '\0';
502           else
503                     *sbuf = '\0';
504           return ret;
505           /* NOTREACHED */
506 }
507