Commit | Line | Data |
---|---|---|
d42e7319 JB |
1 | /* |
2 | * Copyright (C) 2010 The Android Open Source Project | |
3 | * All rights reserved. | |
4 | * | |
5 | * Redistribution and use in source and binary forms, with or without | |
6 | * modification, are permitted provided that the following conditions | |
7 | * are met: | |
8 | * * Redistributions of source code must retain the above copyright | |
9 | * notice, this list of conditions and the following disclaimer. | |
10 | * * Redistributions in binary form must reproduce the above copyright | |
11 | * notice, this list of conditions and the following disclaimer in | |
12 | * the documentation and/or other materials provided with the | |
13 | * distribution. | |
14 | * | |
15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
18 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
19 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
20 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
21 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS | |
22 | * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED | |
23 | * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT | |
25 | * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
26 | * SUCH DAMAGE. | |
27 | */ | |
28 | ||
29 | #include <stdarg.h> | |
30 | #include <string.h> | |
31 | #include <errno.h> | |
32 | #include <unistd.h> | |
33 | #include <stdint.h> | |
34 | #include <stddef.h> | |
35 | #include "linker_format.h" | |
36 | #include "linker_debug.h" | |
37 | ||
38 | /* define UNIT_TESTS to build this file as a single executable that runs | |
39 | * the formatter's unit tests | |
40 | */ | |
41 | #define xxUNIT_TESTS | |
42 | ||
43 | /*** Generic output sink | |
44 | ***/ | |
45 | ||
46 | typedef struct { | |
47 | void *opaque; | |
48 | void (*send)(void *opaque, const char *data, int len); | |
49 | } Out; | |
50 | ||
51 | static void | |
52 | out_send(Out *o, const void *data, size_t len) | |
53 | { | |
54 | o->send(o->opaque, data, (int)len); | |
55 | } | |
56 | ||
57 | static void | |
58 | out_send_repeat(Out *o, char ch, int count) | |
59 | { | |
60 | char pad[8]; | |
61 | const int padSize = (int)sizeof(pad); | |
62 | ||
63 | memset(pad, ch, sizeof(pad)); | |
64 | while (count > 0) { | |
65 | int avail = count; | |
66 | if (avail > padSize) { | |
67 | avail = padSize; | |
68 | } | |
69 | o->send(o->opaque, pad, avail); | |
70 | count -= avail; | |
71 | } | |
72 | } | |
73 | ||
74 | /* forward declaration */ | |
75 | static void | |
76 | out_vformat(Out *o, const char *format, va_list args); | |
77 | ||
78 | /*** Bounded buffer output | |
79 | ***/ | |
80 | ||
81 | typedef struct { | |
82 | Out out[1]; | |
83 | char *buffer; | |
84 | char *pos; | |
85 | char *end; | |
86 | int total; | |
87 | } BufOut; | |
88 | ||
89 | static void | |
90 | buf_out_send(void *opaque, const char *data, int len) | |
91 | { | |
92 | BufOut *bo = opaque; | |
93 | ||
94 | if (len < 0) | |
95 | len = strlen(data); | |
96 | ||
97 | bo->total += len; | |
98 | ||
99 | while (len > 0) { | |
100 | int avail = bo->end - bo->pos; | |
101 | if (avail == 0) | |
102 | break; | |
103 | if (avail > len) | |
104 | avail = len; | |
105 | memcpy(bo->pos, data, avail); | |
106 | bo->pos += avail; | |
107 | bo->pos[0] = '\0'; | |
108 | len -= avail; | |
109 | } | |
110 | } | |
111 | ||
112 | static Out* | |
113 | buf_out_init(BufOut *bo, char *buffer, size_t size) | |
114 | { | |
115 | if (size == 0) | |
116 | return NULL; | |
117 | ||
118 | bo->out->opaque = bo; | |
119 | bo->out->send = buf_out_send; | |
120 | bo->buffer = buffer; | |
121 | bo->end = buffer + size - 1; | |
122 | bo->pos = bo->buffer; | |
123 | bo->pos[0] = '\0'; | |
124 | bo->total = 0; | |
125 | ||
126 | return bo->out; | |
127 | } | |
128 | ||
129 | static int | |
130 | buf_out_length(BufOut *bo) | |
131 | { | |
132 | return bo->total; | |
133 | } | |
134 | ||
135 | static int | |
136 | vformat_buffer(char *buff, size_t buffsize, const char *format, va_list args) | |
137 | { | |
138 | BufOut bo; | |
139 | Out *out; | |
140 | ||
141 | out = buf_out_init(&bo, buff, buffsize); | |
142 | if (out == NULL) | |
143 | return 0; | |
144 | ||
145 | out_vformat(out, format, args); | |
146 | ||
147 | return buf_out_length(&bo); | |
148 | } | |
149 | ||
150 | int | |
151 | format_buffer(char *buff, size_t buffsize, const char *format, ...) | |
152 | { | |
153 | va_list args; | |
154 | int ret; | |
155 | ||
156 | va_start(args, format); | |
157 | ret = vformat_buffer(buff, buffsize, format, args); | |
158 | va_end(args); | |
159 | ||
160 | return ret; | |
161 | } | |
162 | ||
163 | /* The __stack_chk_fail() function calls __libc_android_log_print() | |
164 | * which calls vsnprintf(). | |
165 | * | |
166 | * We define our version of the function here to avoid dragging | |
167 | * about 25 KB of C library routines related to formatting. | |
168 | */ | |
169 | #if 0 | |
170 | int | |
171 | vsnprintf(char *buff, size_t bufsize, const char *format, va_list args) | |
172 | { | |
173 | return format_buffer(buff, bufsize, format, args); | |
174 | } | |
175 | #endif | |
176 | ||
177 | #if LINKER_DEBUG | |
178 | ||
179 | /*** File descriptor output | |
180 | ***/ | |
181 | ||
182 | typedef struct { | |
183 | Out out[1]; | |
184 | int fd; | |
185 | int total; | |
186 | } FdOut; | |
187 | ||
188 | static void | |
189 | fd_out_send(void *opaque, const char *data, int len) | |
190 | { | |
191 | FdOut *fdo = opaque; | |
192 | ||
193 | if (len < 0) | |
194 | len = strlen(data); | |
195 | ||
196 | while (len > 0) { | |
197 | int ret = write(fdo->fd, data, len); | |
198 | if (ret < 0) { | |
199 | if (errno == EINTR) | |
200 | continue; | |
201 | break; | |
202 | } | |
203 | data += ret; | |
204 | len -= ret; | |
205 | fdo->total += ret; | |
206 | } | |
207 | } | |
208 | ||
209 | static Out* | |
210 | fd_out_init(FdOut *fdo, int fd) | |
211 | { | |
212 | fdo->out->opaque = fdo; | |
213 | fdo->out->send = fd_out_send; | |
214 | fdo->fd = fd; | |
215 | fdo->total = 0; | |
216 | ||
217 | return fdo->out; | |
218 | } | |
219 | ||
220 | static int | |
221 | fd_out_length(FdOut *fdo) | |
222 | { | |
223 | return fdo->total; | |
224 | } | |
225 | ||
226 | ||
227 | int | |
228 | format_fd(int fd, const char *format, ...) | |
229 | { | |
230 | FdOut fdo; | |
231 | Out* out; | |
232 | va_list args; | |
233 | ||
234 | out = fd_out_init(&fdo, fd); | |
235 | if (out == NULL) | |
236 | return 0; | |
237 | ||
238 | va_start(args, format); | |
239 | out_vformat(out, format, args); | |
240 | va_end(args); | |
241 | ||
242 | return fd_out_length(&fdo); | |
243 | } | |
244 | ||
245 | /*** Log output | |
246 | ***/ | |
247 | ||
248 | /* We need our own version of __libc_android_log_vprint, otherwise | |
249 | * the log output is completely broken. Probably due to the fact | |
250 | * that the C library is not initialized yet. | |
251 | * | |
252 | * You can test that by setting CUSTOM_LOG_VPRINT to 0 | |
253 | */ | |
254 | #define CUSTOM_LOG_VPRINT 1 | |
255 | ||
256 | #if CUSTOM_LOG_VPRINT | |
257 | ||
258 | #include <unistd.h> | |
259 | #include <fcntl.h> | |
260 | #include <sys/uio.h> | |
261 | ||
262 | static int log_vprint(int prio, const char *tag, const char *fmt, va_list args) | |
263 | { | |
264 | char buf[1024]; | |
265 | int result; | |
266 | static int log_fd = -1; | |
267 | ||
268 | result = vformat_buffer(buf, sizeof buf, fmt, args); | |
269 | ||
270 | if (log_fd < 0) { | |
271 | log_fd = open("/dev/log/main", O_WRONLY); | |
272 | if (log_fd < 0) { | |
273 | log_fd = fileno(stdout); // kernel doesn't have android log | |
274 | return result; | |
275 | } | |
276 | } | |
277 | ||
278 | { | |
279 | ssize_t ret; | |
280 | struct iovec vec[3]; | |
281 | ||
282 | vec[0].iov_base = (unsigned char *) &prio; | |
283 | vec[0].iov_len = 1; | |
284 | vec[1].iov_base = (void *) tag; | |
285 | vec[1].iov_len = strlen(tag) + 1; | |
286 | vec[2].iov_base = (void *) buf; | |
287 | vec[2].iov_len = strlen(buf) + 1; | |
288 | ||
289 | do { | |
290 | ret = writev(log_fd, vec, 3); | |
291 | } while ((ret < 0) && (errno == EINTR)); | |
292 | } | |
293 | return result; | |
294 | } | |
295 | ||
296 | #define __libc_android_log_vprint log_vprint | |
297 | ||
298 | #else /* !CUSTOM_LOG_VPRINT */ | |
299 | ||
300 | extern int __libc_android_log_vprint(int prio, const char* tag, const char* format, va_list ap); | |
301 | ||
302 | #endif /* !CUSTOM_LOG_VPRINT */ | |
303 | ||
304 | int | |
305 | format_log(int prio, const char *tag, const char *format, ...) | |
306 | { | |
307 | int ret; | |
308 | va_list args; | |
309 | va_start(args, format); | |
310 | ret = __libc_android_log_vprint(prio, tag, format, args); | |
311 | va_end(args); | |
312 | return ret; | |
313 | } | |
314 | ||
315 | #endif /* LINKER_DEBUG */ | |
316 | ||
317 | /*** formatted output implementation | |
318 | ***/ | |
319 | ||
320 | /* Parse a decimal string from 'format + *ppos', | |
321 | * return the value, and writes the new position past | |
322 | * the decimal string in '*ppos' on exit. | |
323 | * | |
324 | * NOTE: Does *not* handle a sign prefix. | |
325 | */ | |
326 | static unsigned | |
327 | parse_decimal(const char *format, int *ppos) | |
328 | { | |
329 | const char* p = format + *ppos; | |
330 | unsigned result = 0; | |
331 | ||
332 | for (;;) { | |
333 | int ch = *p; | |
334 | unsigned d = (unsigned)(ch - '0'); | |
335 | ||
336 | if (d >= 10U) | |
337 | break; | |
338 | ||
339 | result = result*10 + d; | |
340 | p++; | |
341 | } | |
342 | *ppos = p - format; | |
343 | return result; | |
344 | } | |
345 | ||
346 | /* write an octal/decimal/number into a bounded buffer. | |
347 | * assumes that bufsize > 0, and 'digits' is a string of | |
348 | * digits of at least 'base' values. | |
349 | */ | |
350 | static void | |
351 | format_number(char *buffer, size_t bufsize, uint64_t value, int base, const char *digits) | |
352 | { | |
353 | char *pos = buffer; | |
354 | char *end = buffer + bufsize - 1; | |
355 | ||
356 | /* generate digit string in reverse order */ | |
357 | while (value) { | |
358 | unsigned d = value % base; | |
359 | value /= base; | |
360 | if (pos < end) { | |
361 | *pos++ = digits[d]; | |
362 | } | |
363 | } | |
364 | ||
365 | /* special case for 0 */ | |
366 | if (pos == buffer) { | |
367 | if (pos < end) { | |
368 | *pos++ = '0'; | |
369 | } | |
370 | } | |
371 | pos[0] = '\0'; | |
372 | ||
373 | /* now reverse digit string in-place */ | |
374 | end = pos - 1; | |
375 | pos = buffer; | |
376 | while (pos < end) { | |
377 | int ch = pos[0]; | |
378 | pos[0] = end[0]; | |
379 | end[0] = (char) ch; | |
380 | pos++; | |
381 | end--; | |
382 | } | |
383 | } | |
384 | ||
385 | /* Write an integer (octal or decimal) into a buffer, assumes buffsize > 2 */ | |
386 | static void | |
387 | format_integer(char *buffer, size_t buffsize, uint64_t value, int base, int isSigned) | |
388 | { | |
389 | if (isSigned && (int64_t)value < 0) { | |
390 | buffer[0] = '-'; | |
391 | buffer += 1; | |
392 | buffsize -= 1; | |
393 | value = (uint64_t)(-(int64_t)value); | |
394 | } | |
395 | ||
396 | format_number(buffer, buffsize, value, base, "0123456789"); | |
397 | } | |
398 | ||
399 | /* Write an octal into a buffer, assumes buffsize > 2 */ | |
400 | static void | |
401 | format_octal(char *buffer, size_t buffsize, uint64_t value, int isSigned) | |
402 | { | |
403 | format_integer(buffer, buffsize, value, 8, isSigned); | |
404 | } | |
405 | ||
406 | /* Write a decimal into a buffer, assumes buffsize > 2 */ | |
407 | static void | |
408 | format_decimal(char *buffer, size_t buffsize, uint64_t value, int isSigned) | |
409 | { | |
410 | format_integer(buffer, buffsize, value, 10, isSigned); | |
411 | } | |
412 | ||
413 | /* Write an hexadecimal into a buffer, isCap is true for capital alphas. | |
414 | * Assumes bufsize > 2 */ | |
415 | static void | |
416 | format_hex(char *buffer, size_t buffsize, uint64_t value, int isCap) | |
417 | { | |
418 | const char *digits = isCap ? "0123456789ABCDEF" : "0123456789abcdef"; | |
419 | ||
420 | format_number(buffer, buffsize, value, 16, digits); | |
421 | } | |
422 | ||
423 | ||
424 | /* Perform formatted output to an output target 'o' */ | |
425 | static void | |
426 | out_vformat(Out *o, const char *format, va_list args) | |
427 | { | |
428 | int nn = 0; | |
429 | ||
430 | for (;;) { | |
431 | int mm; | |
432 | int padZero = 0; | |
433 | int padLeft = 0; | |
434 | char sign = '\0'; | |
435 | int width = -1; | |
436 | int prec = -1; | |
437 | size_t bytelen = sizeof(int); | |
438 | const char* str; | |
439 | int slen; | |
440 | char buffer[32]; /* temporary buffer used to format numbers */ | |
441 | ||
442 | char c; | |
443 | ||
444 | /* first, find all characters that are not 0 or '%' */ | |
445 | /* then send them to the output directly */ | |
446 | mm = nn; | |
447 | do { | |
448 | c = format[mm]; | |
449 | if (c == '\0' || c == '%') | |
450 | break; | |
451 | mm++; | |
452 | } while (1); | |
453 | ||
454 | if (mm > nn) { | |
455 | out_send(o, format+nn, mm-nn); | |
456 | nn = mm; | |
457 | } | |
458 | ||
459 | /* is this it ? then exit */ | |
460 | if (c == '\0') | |
461 | break; | |
462 | ||
463 | /* nope, we are at a '%' modifier */ | |
464 | nn++; // skip it | |
465 | ||
466 | /* parse flags */ | |
467 | for (;;) { | |
468 | c = format[nn++]; | |
469 | if (c == '\0') { /* single trailing '%' ? */ | |
470 | c = '%'; | |
471 | out_send(o, &c, 1); | |
472 | return; | |
473 | } | |
474 | else if (c == '0') { | |
475 | padZero = 1; | |
476 | continue; | |
477 | } | |
478 | else if (c == '-') { | |
479 | padLeft = 1; | |
480 | continue; | |
481 | } | |
482 | else if (c == ' ' || c == '+') { | |
483 | sign = c; | |
484 | continue; | |
485 | } | |
486 | break; | |
487 | } | |
488 | ||
489 | /* parse field width */ | |
490 | if ((c >= '0' && c <= '9')) { | |
491 | nn --; | |
492 | width = (int)parse_decimal(format, &nn); | |
493 | c = format[nn++]; | |
494 | } | |
495 | ||
496 | /* parse precision */ | |
497 | if (c == '.') { | |
498 | prec = (int)parse_decimal(format, &nn); | |
499 | c = format[nn++]; | |
500 | } | |
501 | ||
502 | /* length modifier */ | |
503 | switch (c) { | |
504 | case 'h': | |
505 | bytelen = sizeof(short); | |
506 | if (format[nn] == 'h') { | |
507 | bytelen = sizeof(char); | |
508 | nn += 1; | |
509 | } | |
510 | c = format[nn++]; | |
511 | break; | |
512 | case 'l': | |
513 | bytelen = sizeof(long); | |
514 | if (format[nn] == 'l') { | |
515 | bytelen = sizeof(long long); | |
516 | nn += 1; | |
517 | } | |
518 | c = format[nn++]; | |
519 | break; | |
520 | case 'z': | |
521 | bytelen = sizeof(size_t); | |
522 | c = format[nn++]; | |
523 | break; | |
524 | case 't': | |
525 | bytelen = sizeof(ptrdiff_t); | |
526 | c = format[nn++]; | |
527 | break; | |
528 | default: | |
529 | ; | |
530 | } | |
531 | ||
532 | /* conversion specifier */ | |
533 | if (c == 's') { | |
534 | /* string */ | |
535 | str = va_arg(args, const char*); | |
536 | } else if (c == 'c') { | |
537 | /* character */ | |
538 | /* NOTE: char is promoted to int when passed through the stack */ | |
539 | buffer[0] = (char) va_arg(args, int); | |
540 | buffer[1] = '\0'; | |
541 | str = buffer; | |
542 | } else if (c == 'p') { | |
543 | uint64_t value = (uintptr_t) va_arg(args, void*); | |
544 | buffer[0] = '0'; | |
545 | buffer[1] = 'x'; | |
546 | format_hex(buffer + 2, sizeof buffer-2, value, 0); | |
547 | str = buffer; | |
548 | } else { | |
549 | /* integers - first read value from stack */ | |
550 | uint64_t value; | |
551 | int isSigned = (c == 'd' || c == 'i' || c == 'o'); | |
552 | ||
553 | /* NOTE: int8_t and int16_t are promoted to int when passed | |
554 | * through the stack | |
555 | */ | |
556 | switch (bytelen) { | |
557 | case 1: value = (uint8_t) va_arg(args, int); break; | |
558 | case 2: value = (uint16_t) va_arg(args, int); break; | |
559 | case 4: value = va_arg(args, uint32_t); break; | |
560 | case 8: value = va_arg(args, uint64_t); break; | |
561 | default: return; /* should not happen */ | |
562 | } | |
563 | ||
564 | /* sign extension, if needed */ | |
565 | if (isSigned) { | |
566 | int shift = 64 - 8*bytelen; | |
567 | value = (uint64_t)(((int64_t)(value << shift)) >> shift); | |
568 | } | |
569 | ||
570 | /* format the number properly into our buffer */ | |
571 | switch (c) { | |
572 | case 'i': case 'd': | |
573 | format_integer(buffer, sizeof buffer, value, 10, isSigned); | |
574 | break; | |
575 | case 'o': | |
576 | format_integer(buffer, sizeof buffer, value, 8, isSigned); | |
577 | break; | |
578 | case 'x': case 'X': | |
579 | format_hex(buffer, sizeof buffer, value, (c == 'X')); | |
580 | break; | |
581 | default: | |
582 | buffer[0] = '\0'; | |
583 | } | |
584 | /* then point to it */ | |
585 | str = buffer; | |
586 | } | |
587 | ||
588 | /* if we are here, 'str' points to the content that must be | |
589 | * outputted. handle padding and alignment now */ | |
590 | ||
591 | slen = strlen(str); | |
592 | ||
593 | if (slen < width && !padLeft) { | |
594 | char padChar = padZero ? '0' : ' '; | |
595 | out_send_repeat(o, padChar, width - slen); | |
596 | } | |
597 | ||
598 | out_send(o, str, slen); | |
599 | ||
600 | if (slen < width && padLeft) { | |
601 | char padChar = padZero ? '0' : ' '; | |
602 | out_send_repeat(o, padChar, width - slen); | |
603 | } | |
604 | } | |
605 | } | |
606 | ||
607 | ||
608 | #ifdef UNIT_TESTS | |
609 | ||
610 | #include <stdio.h> | |
611 | ||
612 | static int gFails = 0; | |
613 | ||
614 | #define MARGIN 40 | |
615 | ||
616 | #define UTEST_CHECK(condition,message) \ | |
617 | printf("Checking %-*s: ", MARGIN, message); fflush(stdout); \ | |
618 | if (!(condition)) { \ | |
619 | printf("KO\n"); \ | |
620 | gFails += 1; \ | |
621 | } else { \ | |
622 | printf("ok\n"); \ | |
623 | } | |
624 | ||
625 | static void | |
626 | utest_BufOut(void) | |
627 | { | |
628 | char buffer[16]; | |
629 | BufOut bo[1]; | |
630 | Out* out; | |
631 | int ret; | |
632 | ||
633 | buffer[0] = '1'; | |
634 | out = buf_out_init(bo, buffer, sizeof buffer); | |
635 | UTEST_CHECK(buffer[0] == '\0', "buf_out_init clears initial byte"); | |
636 | out_send(out, "abc", 3); | |
637 | UTEST_CHECK(!memcmp(buffer, "abc", 4), "out_send() works with BufOut"); | |
638 | out_send_repeat(out, 'X', 4); | |
639 | UTEST_CHECK(!memcmp(buffer, "abcXXXX", 8), "out_send_repeat() works with BufOut"); | |
640 | buffer[sizeof buffer-1] = 'x'; | |
641 | out_send_repeat(out, 'Y', 2*sizeof(buffer)); | |
642 | UTEST_CHECK(buffer[sizeof buffer-1] == '\0', "overflows always zero-terminates"); | |
643 | ||
644 | out = buf_out_init(bo, buffer, sizeof buffer); | |
645 | out_send_repeat(out, 'X', 2*sizeof(buffer)); | |
646 | ret = buf_out_length(bo); | |
647 | UTEST_CHECK(ret == 2*sizeof(buffer), "correct size returned on overflow"); | |
648 | } | |
649 | ||
650 | static void | |
651 | utest_expect(const char* result, const char* format, ...) | |
652 | { | |
653 | va_list args; | |
654 | BufOut bo[1]; | |
655 | char buffer[256]; | |
656 | Out* out = buf_out_init(bo, buffer, sizeof buffer); | |
657 | ||
658 | printf("Checking %-*s: ", MARGIN, format); fflush(stdout); | |
659 | va_start(args, format); | |
660 | out_vformat(out, format, args); | |
661 | va_end(args); | |
662 | ||
663 | if (strcmp(result, buffer)) { | |
664 | printf("KO. got '%s' expecting '%s'\n", buffer, result); | |
665 | gFails += 1; | |
666 | } else { | |
667 | printf("ok. got '%s'\n", result); | |
668 | } | |
669 | } | |
670 | ||
671 | int main(void) | |
672 | { | |
673 | utest_BufOut(); | |
674 | utest_expect("", ""); | |
675 | utest_expect("a", "a"); | |
676 | utest_expect("01234", "01234", ""); | |
677 | utest_expect("01234", "%s", "01234"); | |
678 | utest_expect("aabbcc", "aa%scc", "bb"); | |
679 | utest_expect("a", "%c", 'a'); | |
680 | utest_expect("1234", "%d", 1234); | |
681 | utest_expect("-8123", "%d", -8123); | |
682 | utest_expect("16", "%hd", 0x7fff0010); | |
683 | utest_expect("16", "%hhd", 0x7fffff10); | |
684 | utest_expect("68719476736", "%lld", 0x1000000000LL); | |
685 | utest_expect("70000", "%ld", 70000); | |
686 | utest_expect("0xb0001234", "%p", (void*)0xb0001234); | |
687 | utest_expect("12ab", "%x", 0x12ab); | |
688 | utest_expect("12AB", "%X", 0x12ab); | |
689 | utest_expect("00123456", "%08x", 0x123456); | |
690 | utest_expect("01234", "0%d", 1234); | |
691 | utest_expect(" 1234", "%5d", 1234); | |
692 | utest_expect("01234", "%05d", 1234); | |
693 | utest_expect(" 1234", "%8d", 1234); | |
694 | utest_expect("1234 ", "%-8d", 1234); | |
695 | utest_expect("abcdef ", "%-11s", "abcdef"); | |
696 | utest_expect("something:1234", "%s:%d", "something", 1234); | |
697 | utest_expect("005:5:05", "%03d:%d:%02d", 5, 5, 5); | |
698 | utest_expect("5,0x0", "%d,%p", 5, NULL); | |
699 | utest_expect("68719476736,6,7,8", "%lld,%d,%d,%d", 0x1000000000LL, 6, 7, 8); | |
700 | return gFails != 0; | |
701 | } | |
702 | ||
703 | #endif /* UNIT_TESTS */ |