/* sb_printf.c * Minimal printf implementation that is designed to work without calling back * into the C library or libsandbox. Goes straight to kernel so that this * should be usable from anywhere. All state is on stack, so also avoids * signal, threaded, and other fun async behavior. * * The following conversion specifiers are supported: * c - character * s - string * d - integer * i - integer * u - unsigned integer * X - HEX number * x - hex number * p - pointer * * The following modifiers are supported: * z - size_t * l - long * ll - long long * * - width is an argument * # - for %x, we always do this, so it's kind of ignored * * Copyright 1999-2012 Gentoo Foundation * Licensed under the GPL-2 */ #include "headers.h" #include "sbutil.h" #define MOD_STAR (1 << 0) #define MOD_SIZE_T (1 << 1) #define MOD_LONG_T (1 << 2) #define MOD_LONG_LONG_T (1 << 3) void sb_vfdprintf(int fd, const char *format, va_list args) { const char *fmt = format; const char *format_end = format + strlen(format); const char *conv = fmt; while (fmt < format_end) { conv = memchr(fmt, '%', format_end - fmt); if (conv != fmt) { size_t out_bytes = (conv ? conv : format_end) - fmt; sb_write(fd, fmt, out_bytes); if (!conv) return; } char buf[50]; size_t idx; unsigned long long int u, x; char hex_base; size_t padding = 0; unsigned int modifiers = 0; eat_more: switch (conv[1]) { default: sb_fdprintf(fd, "{invalid conversion specifier in string: %s}", format); break; case '%': if (modifiers) { inv_modifier: sb_fdprintf(fd, "{invalid modifier in string: %s}", format); } sb_write(fd, conv, 1); break; case 'l': ++conv; if (modifiers & MOD_LONG_T) { if (modifiers & MOD_LONG_LONG_T) goto inv_modifier; modifiers = (modifiers & ~MOD_LONG_T) | MOD_LONG_LONG_T; } else modifiers |= MOD_LONG_T; goto eat_more; case 'z': ++conv; if (modifiers & MOD_SIZE_T) goto inv_modifier; modifiers |= MOD_SIZE_T; goto eat_more; case '*': ++conv; if (modifiers & MOD_STAR) goto inv_modifier; modifiers |= MOD_STAR; padding = va_arg(args, int); goto eat_more; case '#': /* ignore */ ++conv; goto eat_more; case 'c': { char c = va_arg(args, int); sb_write(fd, &c, 1); break; } case 's': { char *s = va_arg(args, char *); if (!s) s = "(null)"; size_t len = strlen(s); while (len < padding--) sb_write(fd, " ", 1); sb_write(fd, s, len); break; } case 'd': case 'i': { long long i; if (modifiers & MOD_SIZE_T) i = va_arg(args, ssize_t); else if (modifiers & MOD_LONG_T) i = va_arg(args, long); else if (modifiers & MOD_LONG_LONG_T) i = va_arg(args, long long); else i = va_arg(args, int); u = i; if (i < 0) { sb_write(fd, "-", 1); u = i * -1; } out_uint: idx = 0; do { buf[idx++] = '0' + (u % 10); } while (u /= 10); while (idx--) sb_write(fd, buf+idx, 1); break; } case 'u': { if (modifiers & MOD_SIZE_T) u = va_arg(args, size_t); else if (modifiers & MOD_LONG_T) u = va_arg(args, unsigned long); else if (modifiers & MOD_LONG_LONG_T) u = va_arg(args, unsigned long long); else u = va_arg(args, unsigned int); goto out_uint; break; } case 'X': { hex_base = 'A'; goto out_hex; } case 'x': { hex_base = 'a'; out_hex: if (modifiers & MOD_SIZE_T) x = va_arg(args, size_t); else if (modifiers & MOD_LONG_T) x = va_arg(args, unsigned long); else if (modifiers & MOD_LONG_LONG_T) x = va_arg(args, unsigned long long); else x = va_arg(args, unsigned int); out_ptr: idx = 0; do { u = x % 16; buf[idx++] = (u < 10 ? '0' + u : hex_base + u - 10); } while (x /= 16); while (idx < padding) buf[idx++] = '0'; buf[idx++] = 'x'; buf[idx++] = '0'; while (idx--) sb_write(fd, buf+idx, 1); break; } case 'p': { void *p = va_arg(args, void *); hex_base = 'a'; padding = sizeof(p) * 2; x = (unsigned long)p; goto out_ptr; } } fmt = conv + 2; } } void sb_fdprintf(int fd, const char *format, ...) { va_list args; va_start(args, format); sb_vfdprintf(fd, format, args); va_end(args); } /* All output goes to stderr as any unexpected interjection into stdout * could easily break shell scripts and such that are piping or evaluating * or some such thing. */ void sb_printf(const char *format, ...) { va_list args; va_start(args, format); sb_vfdprintf(STDERR_FILENO, format, args); va_end(args); }