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