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
19struct _output_args {
20    char *outstr;
21    size_t len;
22    size_t pos;
23};
24
25static 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
42PRINTF_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
60PRINTF_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
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
122static const char hextable[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
123static const char hextable_caps[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
124
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
151PRINTF_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
191next_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 */
301hex:
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
384exit:
385    return (err < 0) ? err : (int)chars_written;
386}
387