1 // Copyright 2016 The Fuchsia Authors
2 // Copyright (c) 2008-2014 Travis Geiselbrecht
3 //
4 // Use of this source code is governed by a MIT-style
5 // license that can be found in the LICENSE file or at
6 // https://opensource.org/licenses/MIT
7 
8 #include <debug.h>
9 #include <assert.h>
10 #include <limits.h>
11 #include <printf.h>
12 #include <stdarg.h>
13 #include <sys/types.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <platform/debug.h>
18 
19 struct _output_args {
20     char *outstr;
21     size_t len;
22     size_t pos;
23 };
24 
_vsnprintf_output(const char * str,size_t len,void * state)25 static int _vsnprintf_output(const char *str, size_t len, void *state)
26 {
27     struct _output_args *args = (struct _output_args*)state;
28 
29     size_t count = 0;
30     while (count < len) {
31         if (args->pos < args->len) {
32             args->outstr[args->pos++] = *str;
33         }
34 
35         str++;
36         count++;
37     }
38 
39     return (int)count;
40 }
41 
PRINTF_DECL(vsnprintf)42 PRINTF_DECL(vsnprintf)(char *str, size_t len, const char *fmt, va_list ap)
43 {
44     struct _output_args args;
45     int wlen;
46 
47     args.outstr = str;
48     args.len = len;
49     args.pos = 0;
50 
51     wlen = PRINTF_CALL(_printf_engine)(&_vsnprintf_output, (void *)&args,
52                                        fmt, ap);
53     if (args.pos >= len)
54         str[len-1] = '\0';
55     else
56         str[wlen] = '\0';
57     return wlen;
58 }
59 
PRINTF_DECL(snprintf)60 PRINTF_DECL(snprintf)(char *str, size_t len, const char *fmt, ...)
61 {
62     int err;
63 
64     va_list ap;
65     va_start(ap, fmt);
66     err = PRINTF_CALL(vsnprintf)(str, len, fmt, ap);
67     va_end(ap);
68 
69     return err;
70 }
71 
72 #define LONGFLAG       0x00000001
73 #define LONGLONGFLAG   0x00000002
74 #define HALFFLAG       0x00000004
75 #define HALFHALFFLAG   0x00000008
76 #define SIZETFLAG      0x00000010
77 #define INTMAXFLAG     0x00000020
78 #define PTRDIFFFLAG    0x00000040
79 #define ALTFLAG        0x00000080
80 #define CAPSFLAG       0x00000100
81 #define SHOWSIGNFLAG   0x00000200
82 #define SIGNEDFLAG     0x00000400
83 #define LEFTFORMATFLAG 0x00000800
84 #define LEADZEROFLAG   0x00001000
85 #define BLANKPOSFLAG   0x00002000
86 #define FIELDWIDTHFLAG 0x00004000
87 
longlong_to_string(char * buf,unsigned long long n,size_t len,uint flag,char * signchar)88 __NO_INLINE static char *longlong_to_string(char *buf, unsigned long long n, size_t len, uint flag, char *signchar)
89 {
90     size_t pos = len;
91     int negative = 0;
92 
93     if ((flag & SIGNEDFLAG) && (long long)n < 0) {
94         negative = 1;
95         n = -n;
96     }
97 
98     buf[--pos] = 0;
99 
100     /* only do the math if the number is >= 10 */
101     while (n >= 10) {
102         int digit = (int)(n % 10);
103 
104         n /= 10;
105 
106         buf[--pos] = (char)(digit + '0');
107     }
108     buf[--pos] = (char)(n + '0');
109 
110     if (negative)
111         *signchar = '-';
112     else if ((flag & SHOWSIGNFLAG))
113         *signchar = '+';
114     else if ((flag & BLANKPOSFLAG))
115         *signchar = ' ';
116     else
117         *signchar = '\0';
118 
119     return &buf[pos];
120 }
121 
122 static const char hextable[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
123 static const char hextable_caps[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
124 
longlong_to_hexstring(char * buf,unsigned long long u,size_t len,uint flag)125 __NO_INLINE static const char *longlong_to_hexstring(char *buf, unsigned long long u, size_t len, uint flag)
126 {
127     size_t pos = len;
128     const char *table = (flag & CAPSFLAG) ? hextable_caps : hextable;
129 
130     // Special case because ALTFLAG does not prepend 0x to 0.
131     if (u == 0)
132         return "0";
133 
134     buf[--pos] = 0;
135 
136     do {
137         unsigned int digit = u % 16;
138         u /= 16;
139 
140         buf[--pos] = table[digit];
141     } while (u != 0);
142 
143     if (flag & ALTFLAG) {
144         buf[--pos] = (flag & CAPSFLAG) ? 'X' : 'x';
145         buf[--pos] = '0';
146     }
147 
148     return &buf[pos];
149 }
150 
PRINTF_DECL(_printf_engine)151 PRINTF_DECL(_printf_engine)(_printf_engine_output_func out, void *state, const char *fmt, va_list ap)
152 {
153     int err = 0;
154     char c;
155     unsigned char uc;
156     const char *s;
157     size_t string_len;
158     unsigned long long n;
159     void *ptr;
160     int flags;
161     unsigned int format_num;
162     char signchar;
163     size_t chars_written = 0;
164     char num_buffer[32];
165 
166 #define OUTPUT_STRING(str, len) do { err = out(str, len, state); if (err < 0) { goto exit; } else { chars_written += err; } } while(0)
167 #define OUTPUT_CHAR(c) do { char __temp[1] = { c }; OUTPUT_STRING(__temp, 1); } while (0)
168 
169     for (;;) {
170         /* reset the format state */
171         flags = 0;
172         format_num = 0;
173         signchar = '\0';
174 
175         /* handle regular chars that aren't format related */
176         s = fmt;
177         string_len = 0;
178         while ((c = *fmt++) != 0) {
179             if (c == '%')
180                 break; /* we saw a '%', break and start parsing format */
181             string_len++;
182         }
183 
184         /* output the string we've accumulated */
185         OUTPUT_STRING(s, string_len);
186 
187         /* make sure we haven't just hit the end of the string */
188         if (c == 0)
189             break;
190 
191 next_format:
192         /* grab the next format character */
193         c = *fmt++;
194         if (c == 0)
195             break;
196 
197         switch (c) {
198             case '0'...'9':
199                 if (c == '0' && format_num == 0)
200                     flags |= LEADZEROFLAG;
201                 format_num *= 10;
202                 format_num += c - '0';
203                 goto next_format;
204             case '*': {
205                 int width = va_arg(ap, int);
206                 if (width < 0) {
207                     flags |= LEFTFORMATFLAG;
208                     width = -width;
209                 }
210                 format_num = width;
211                 goto next_format;
212             }
213             case '.':
214                 // Check the next character. It either should be * (if valid)
215                 // or something else (if invalid) that we consume as invalid.
216                 c = *fmt;
217                 if (c == '*') {
218                     fmt++;
219                     flags |= FIELDWIDTHFLAG;
220                     format_num = va_arg(ap, intmax_t);
221                 } else if (c == 's') {
222                     // %.s is invalid, and testing glibc printf it
223                     // results in no output so force skipping the 's'
224                     fmt++;
225                 }
226                 goto next_format;
227             case '%':
228                 OUTPUT_CHAR('%');
229                 break;
230             case 'c':
231                 uc = (unsigned char)va_arg(ap, unsigned int);
232                 OUTPUT_CHAR(uc);
233                 break;
234             case 's':
235                 s = va_arg(ap, const char *);
236                 if (s == 0)
237                     s = "<null>";
238                 flags &= ~LEADZEROFLAG; /* doesn't make sense for strings */
239                 goto _output_string;
240             case '-':
241                 flags |= LEFTFORMATFLAG;
242                 goto next_format;
243             case '+':
244                 flags |= SHOWSIGNFLAG;
245                 goto next_format;
246             case ' ':
247                 flags |= BLANKPOSFLAG;
248                 goto next_format;
249             case '#':
250                 flags |= ALTFLAG;
251                 goto next_format;
252             case 'l':
253                 if (flags & LONGFLAG)
254                     flags |= LONGLONGFLAG;
255                 flags |= LONGFLAG;
256                 goto next_format;
257             case 'h':
258                 if (flags & HALFFLAG)
259                     flags |= HALFHALFFLAG;
260                 flags |= HALFFLAG;
261                 goto next_format;
262             case 'z':
263                 flags |= SIZETFLAG;
264                 goto next_format;
265             case 'j':
266                 flags |= INTMAXFLAG;
267                 goto next_format;
268             case 't':
269                 flags |= PTRDIFFFLAG;
270                 goto next_format;
271             case 'i':
272             case 'd':
273                 n = (flags & LONGLONGFLAG) ? va_arg(ap, long long) :
274                     (flags & LONGFLAG) ? va_arg(ap, long) :
275                     (flags & HALFHALFFLAG) ? (signed char)va_arg(ap, int) :
276                     (flags & HALFFLAG) ? (short)va_arg(ap, int) :
277                     (flags & SIZETFLAG) ? va_arg(ap, ssize_t) :
278                     (flags & INTMAXFLAG) ? va_arg(ap, intmax_t) :
279                     (flags & PTRDIFFFLAG) ? va_arg(ap, ptrdiff_t) :
280                     va_arg(ap, int);
281                 flags |= SIGNEDFLAG;
282                 s = longlong_to_string(num_buffer, n, sizeof(num_buffer), flags, &signchar);
283                 goto _output_string;
284             case 'u':
285                 n = (flags & LONGLONGFLAG) ? va_arg(ap, unsigned long long) :
286                     (flags & LONGFLAG) ? va_arg(ap, unsigned long) :
287                     (flags & HALFHALFFLAG) ? (unsigned char)va_arg(ap, unsigned int) :
288                     (flags & HALFFLAG) ? (unsigned short)va_arg(ap, unsigned int) :
289                     (flags & SIZETFLAG) ? va_arg(ap, size_t) :
290                     (flags & INTMAXFLAG) ? va_arg(ap, uintmax_t) :
291                     (flags & PTRDIFFFLAG) ? (uintptr_t)va_arg(ap, ptrdiff_t) :
292                     va_arg(ap, unsigned int);
293                 s = longlong_to_string(num_buffer, n, sizeof(num_buffer), flags, &signchar);
294                 goto _output_string;
295             case 'p':
296                 flags |= LONGFLAG | ALTFLAG;
297                 goto hex;
298             case 'X':
299                 flags |= CAPSFLAG;
300                 /* fallthrough */
301 hex:
302             case 'x':
303                 n = (flags & LONGLONGFLAG) ? va_arg(ap, unsigned long long) :
304                     (flags & LONGFLAG) ? va_arg(ap, unsigned long) :
305                     (flags & HALFHALFFLAG) ? (unsigned char)va_arg(ap, unsigned int) :
306                     (flags & HALFFLAG) ? (unsigned short)va_arg(ap, unsigned int) :
307                     (flags & SIZETFLAG) ? va_arg(ap, size_t) :
308                     (flags & INTMAXFLAG) ? va_arg(ap, uintmax_t) :
309                     (flags & PTRDIFFFLAG) ? (uintptr_t)va_arg(ap, ptrdiff_t) :
310                     va_arg(ap, unsigned int);
311                 s = longlong_to_hexstring(num_buffer, n, sizeof(num_buffer), flags);
312                 goto _output_string;
313             case 'n':
314                 ptr = va_arg(ap, void *);
315                 if (flags & LONGLONGFLAG)
316                     *(long long *)ptr = chars_written;
317                 else if (flags & LONGFLAG)
318                     *(long *)ptr = chars_written;
319                 else if (flags & HALFHALFFLAG)
320                     *(signed char *)ptr = (signed char)chars_written;
321                 else if (flags & HALFFLAG)
322                     *(short *)ptr = (short)chars_written;
323                 else if (flags & SIZETFLAG)
324                     *(size_t *)ptr = chars_written;
325                 else
326                     *(int *)ptr = (int)chars_written;
327                 break;
328             default:
329                 OUTPUT_CHAR('%');
330                 OUTPUT_CHAR(c);
331                 break;
332         }
333 
334         /* move on to the next field */
335         continue;
336 
337         /* shared output code */
338 _output_string:
339         string_len = strlen(s);
340 
341         // In the event of a field width smaller than the length, we need to
342         // truncate the width to fit. This only applies to %s.
343         if (flags & FIELDWIDTHFLAG) {
344             string_len = MIN(string_len, format_num);
345         }
346 
347         if (flags & LEFTFORMATFLAG) {
348             /* left justify the text */
349             OUTPUT_STRING(s, string_len);
350             uint written = err;
351 
352             /* pad to the right (if necessary) */
353             for (; format_num > written; format_num--)
354                 OUTPUT_CHAR(' ');
355         } else {
356             /* right justify the text (digits) */
357 
358             /* if we're going to print a sign digit,
359                it'll chew up one byte of the format size */
360             if (signchar != '\0' && format_num > 0)
361                 format_num--;
362 
363             /* output the sign char before the leading zeros */
364             if (flags & LEADZEROFLAG && signchar != '\0')
365                 OUTPUT_CHAR(signchar);
366 
367             /* pad according to the format string */
368             for (; format_num > string_len; format_num--)
369                 OUTPUT_CHAR(flags & LEADZEROFLAG ? '0' : ' ');
370 
371             /* if not leading zeros, output the sign char just before the number */
372             if (!(flags & LEADZEROFLAG) && signchar != '\0')
373                 OUTPUT_CHAR(signchar);
374 
375             /* output the string */
376             OUTPUT_STRING(s, string_len);
377         }
378         continue;
379     }
380 
381 #undef OUTPUT_STRING
382 #undef OUTPUT_CHAR
383 
384 exit:
385     return (err < 0) ? err : (int)chars_written;
386 }
387