| /* |
| * libc printf and friends |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU Library General Public License version 2. |
| */ |
| |
| #include "libcflat.h" |
| #include "ctype.h" |
| |
| #define BUFSZ 2000 |
| |
| typedef struct pstream { |
| char *buffer; |
| int remain; |
| int added; |
| } pstream_t; |
| |
| typedef struct strprops { |
| char pad; |
| int npad; |
| bool alternate; |
| int precision; |
| } strprops_t; |
| |
| static void addchar(pstream_t *p, char c) |
| { |
| if (p->remain) { |
| *p->buffer++ = c; |
| --p->remain; |
| } |
| ++p->added; |
| } |
| |
| static void print_str(pstream_t *p, const char *s, strprops_t props) |
| { |
| const char *s_orig = s; |
| int npad = props.npad; |
| |
| if (npad > 0) { |
| npad -= strlen(s_orig); |
| while (npad > 0) { |
| addchar(p, props.pad); |
| --npad; |
| } |
| } |
| |
| while (*s && props.precision--) |
| addchar(p, *s++); |
| |
| if (npad < 0) { |
| props.pad = ' '; /* ignore '0' flag with '-' flag */ |
| npad += strlen(s_orig); |
| while (npad < 0) { |
| addchar(p, props.pad); |
| ++npad; |
| } |
| } |
| } |
| |
| /* |
| * Adapted from drivers/firmware/efi/libstub/vsprintf.c |
| */ |
| static u32 utf16_to_utf32(const u16 **s16) |
| { |
| u16 c0, c1; |
| |
| c0 = *(*s16)++; |
| /* not a surrogate */ |
| if ((c0 & 0xf800) != 0xd800) |
| return c0; |
| /* invalid: low surrogate instead of high */ |
| if (c0 & 0x0400) |
| return 0xfffd; |
| c1 = **s16; |
| /* invalid: missing low surrogate */ |
| if ((c1 & 0xfc00) != 0xdc00) |
| return 0xfffd; |
| /* valid surrogate pair */ |
| ++(*s16); |
| return (0x10000 - (0xd800 << 10) - 0xdc00) + (c0 << 10) + c1; |
| } |
| |
| /* |
| * Adapted from drivers/firmware/efi/libstub/vsprintf.c |
| */ |
| static size_t utf16s_utf8nlen(const u16 *s16, size_t maxlen) |
| { |
| size_t len, clen; |
| |
| for (len = 0; len < maxlen && *s16; len += clen) { |
| u16 c0 = *s16++; |
| |
| /* First, get the length for a BMP character */ |
| clen = 1 + (c0 >= 0x80) + (c0 >= 0x800); |
| if (len + clen > maxlen) |
| break; |
| /* |
| * If this is a high surrogate, and we're already at maxlen, we |
| * can't include the character if it's a valid surrogate pair. |
| * Avoid accessing one extra word just to check if it's valid |
| * or not. |
| */ |
| if ((c0 & 0xfc00) == 0xd800) { |
| if (len + clen == maxlen) |
| break; |
| if ((*s16 & 0xfc00) == 0xdc00) { |
| ++s16; |
| ++clen; |
| } |
| } |
| } |
| |
| return len; |
| } |
| |
| /* |
| * Adapted from drivers/firmware/efi/libstub/vsprintf.c |
| */ |
| static void print_wstring(pstream_t *p, const u16 *s, strprops_t props) |
| { |
| const u16 *ws = (const u16 *)s; |
| size_t pos = 0, size = p->remain + 1, len = utf16s_utf8nlen(ws, props.precision); |
| |
| while (len-- > 0) { |
| u32 c32 = utf16_to_utf32(&ws); |
| u8 *s8; |
| size_t clen; |
| |
| if (c32 < 0x80) { |
| addchar(p, c32); |
| continue; |
| } |
| |
| /* Number of trailing octets */ |
| clen = 1 + (c32 >= 0x800) + (c32 >= 0x10000); |
| |
| len -= clen; |
| s8 = (u8 *)(p->buffer - p->added + pos); |
| |
| /* Avoid writing partial character */ |
| addchar(p, '\0'); |
| pos += clen; |
| if (pos >= size) |
| continue; |
| |
| /* Set high bits of leading octet */ |
| *s8 = (0xf00 >> 1) >> clen; |
| /* Write trailing octets in reverse order */ |
| for (s8 += clen; clen; --clen, c32 >>= 6) |
| *s8-- = 0x80 | (c32 & 0x3f); |
| /* Set low bits of leading octet */ |
| *s8 |= c32; |
| } |
| } |
| |
| static char digits[16] = "0123456789abcdef"; |
| |
| static void print_int(pstream_t *ps, long long n, int base, strprops_t props) |
| { |
| char buf[sizeof(long) * 3 + 2], *p = buf; |
| int s = 0, i; |
| |
| if (n < 0) { |
| n = -n; |
| s = 1; |
| } |
| |
| while (n) { |
| *p++ = digits[n % base]; |
| n /= base; |
| } |
| |
| while (p == buf || (p - buf < props.precision)) |
| *p++ = '0'; |
| props.precision = -1; |
| |
| if (s) |
| *p++ = '-'; |
| |
| for (i = 0; i < (p - buf) / 2; ++i) { |
| char tmp; |
| |
| tmp = buf[i]; |
| buf[i] = p[-1 - i]; |
| p[-1 - i] = tmp; |
| } |
| |
| *p = 0; |
| |
| print_str(ps, buf, props); |
| } |
| |
| static void print_unsigned(pstream_t *ps, unsigned long long n, int base, |
| strprops_t props) |
| { |
| char buf[sizeof(long) * 3 + 3], *p = buf; |
| int i; |
| |
| while (n) { |
| *p++ = digits[n % base]; |
| n /= base; |
| } |
| |
| if (p == buf) |
| props.alternate = false; |
| |
| while (p == buf || (p - buf < props.precision)) |
| *p++ = '0'; |
| props.precision = -1; |
| |
| if (props.alternate && base == 16) { |
| if (props.pad == '0') { |
| addchar(ps, '0'); |
| addchar(ps, 'x'); |
| |
| if (props.npad > 0) |
| props.npad = MAX(props.npad - 2, 0); |
| } else { |
| *p++ = 'x'; |
| *p++ = '0'; |
| } |
| } |
| |
| for (i = 0; i < (p - buf) / 2; ++i) { |
| char tmp; |
| |
| tmp = buf[i]; |
| buf[i] = p[-1 - i]; |
| p[-1 - i] = tmp; |
| } |
| |
| *p = 0; |
| |
| print_str(ps, buf, props); |
| } |
| |
| static int fmtnum(const char **fmt) |
| { |
| const char *f = *fmt; |
| int len = 0, num; |
| |
| if (*f == '-') |
| ++f, ++len; |
| |
| while (*f >= '0' && *f <= '9') |
| ++f, ++len; |
| |
| num = atol(*fmt); |
| *fmt += len; |
| return num; |
| } |
| |
| /* |
| * Adapted from drivers/firmware/efi/libstub/vsprintf.c |
| */ |
| static int skip_atoi(const char **s) |
| { |
| int i = 0; |
| |
| do { |
| i = i*10 + *((*s)++) - '0'; |
| } while (isdigit(**s)); |
| |
| return i; |
| } |
| |
| /* |
| * Adapted from drivers/firmware/efi/libstub/vsprintf.c |
| */ |
| static int get_int(const char **fmt, va_list *ap) |
| { |
| if (isdigit(**fmt)) |
| return skip_atoi(fmt); |
| |
| if (**fmt == '*') { |
| ++(*fmt); |
| /* it's the next argument */ |
| return va_arg(*ap, int); |
| } |
| return 0; |
| } |
| |
| int vsnprintf(char *buf, int size, const char *fmt, va_list va) |
| { |
| pstream_t s; |
| va_list args; |
| |
| /* |
| * We want to pass our input va_list to helper functions by reference, |
| * but there's an annoying edge case. If va_list was originally passed |
| * to us by value, we could just pass &ap down to the helpers. This is |
| * the case on, for example, X86_32. |
| * However, on X86_64 (and possibly others), va_list is actually a |
| * size-1 array containing a structure. Our function parameter ap has |
| * decayed from T[1] to T*, and &ap has type T** rather than T(*)[1], |
| * which is what will be expected by a function taking a va_list * |
| * parameter. |
| * One standard way to solve this mess is by creating a copy in a local |
| * variable of type va_list and then passing a pointer to that local |
| * copy instead, which is what we do here. |
| */ |
| va_copy(args, va); |
| |
| s.buffer = buf; |
| s.remain = size - 1; |
| s.added = 0; |
| while (*fmt) { |
| char f = *fmt++; |
| int nlong = 0; |
| strprops_t props; |
| memset(&props, 0, sizeof(props)); |
| props.pad = ' '; |
| props.precision = -1; |
| |
| if (f != '%') { |
| addchar(&s, f); |
| continue; |
| } |
| morefmt: |
| f = *fmt++; |
| switch (f) { |
| case '%': |
| addchar(&s, '%'); |
| break; |
| case 'c': |
| addchar(&s, va_arg(args, int)); |
| break; |
| case '\0': |
| --fmt; |
| break; |
| case '.': |
| props.pad = ' '; |
| props.precision = get_int(&fmt, &args); |
| goto morefmt; |
| case '#': |
| props.alternate = true; |
| goto morefmt; |
| case '0': |
| props.pad = '0'; |
| ++fmt; |
| /* fall through */ |
| case '1' ... '9': |
| case '-': |
| --fmt; |
| props.npad = fmtnum(&fmt); |
| goto morefmt; |
| case 'l': |
| ++nlong; |
| goto morefmt; |
| case 't': |
| case 'z': |
| /* Here we only care that sizeof(size_t) == sizeof(long). |
| * On a 32-bit platform it doesn't matter that size_t is |
| * typedef'ed to int or long; va_arg will work either way. |
| * Same for ptrdiff_t (%td). |
| */ |
| nlong = 1; |
| goto morefmt; |
| case 'd': |
| switch (nlong) { |
| case 0: |
| print_int(&s, va_arg(args, int), 10, props); |
| break; |
| case 1: |
| print_int(&s, va_arg(args, long), 10, props); |
| break; |
| default: |
| print_int(&s, va_arg(args, long long), 10, props); |
| break; |
| } |
| break; |
| case 'u': |
| switch (nlong) { |
| case 0: |
| print_unsigned(&s, va_arg(args, unsigned int), 10, props); |
| break; |
| case 1: |
| print_unsigned(&s, va_arg(args, unsigned long), 10, props); |
| break; |
| default: |
| print_unsigned(&s, va_arg(args, unsigned long long), 10, props); |
| break; |
| } |
| break; |
| case 'x': |
| switch (nlong) { |
| case 0: |
| print_unsigned(&s, va_arg(args, unsigned int), 16, props); |
| break; |
| case 1: |
| print_unsigned(&s, va_arg(args, unsigned long), 16, props); |
| break; |
| default: |
| print_unsigned(&s, va_arg(args, unsigned long long), 16, props); |
| break; |
| } |
| break; |
| case 'p': |
| props.alternate = true; |
| print_unsigned(&s, (unsigned long)va_arg(args, void *), 16, props); |
| break; |
| case 's': |
| if (nlong) |
| print_wstring(&s, va_arg(args, const u16 *), props); |
| else |
| print_str(&s, va_arg(args, const char *), props); |
| break; |
| default: |
| addchar(&s, f); |
| break; |
| } |
| } |
| va_end(args); |
| *s.buffer = 0; |
| return s.added; |
| } |
| |
| int snprintf(char *buf, int size, const char *fmt, ...) |
| { |
| va_list va; |
| int r; |
| |
| va_start(va, fmt); |
| r = vsnprintf(buf, size, fmt, va); |
| va_end(va); |
| return r; |
| } |
| |
| int vprintf(const char *fmt, va_list va) |
| { |
| char buf[BUFSZ]; |
| int r; |
| |
| r = vsnprintf(buf, sizeof(buf), fmt, va); |
| puts(buf); |
| return r; |
| } |
| |
| int printf(const char *fmt, ...) |
| { |
| va_list va; |
| char buf[BUFSZ]; |
| int r; |
| |
| va_start(va, fmt); |
| r = vsnprintf(buf, sizeof buf, fmt, va); |
| va_end(va); |
| puts(buf); |
| return r; |
| } |
| |
| void binstr(unsigned long x, char out[BINSTR_SZ]) |
| { |
| int i; |
| char *c; |
| int n; |
| |
| n = sizeof(unsigned long) * 8; |
| i = 0; |
| c = &out[0]; |
| for (;;) { |
| *c++ = (x & (1ul << (n - i - 1))) ? '1' : '0'; |
| i++; |
| |
| if (i == n) { |
| *c = '\0'; |
| break; |
| } |
| if (i % 4 == 0) |
| *c++ = '\''; |
| } |
| assert(c + 1 - &out[0] == BINSTR_SZ); |
| } |
| |
| void print_binstr(unsigned long x) |
| { |
| char out[BINSTR_SZ]; |
| binstr(x, out); |
| printf("%s", out); |
| } |