/* * Copyright (C) 2013 Martin Willi * Copyright (C) 2013 revosec AG * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ /** * Copyright (C) 2002-2006 H. Peter Anvin * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "printf_hook.h" #include #include #include #include #include #include #include #include #include #include #define PRINTF_BUF_LEN 8192 #define ARGS_MAX 3 typedef struct private_printf_hook_t private_printf_hook_t; typedef struct printf_hook_handler_t printf_hook_handler_t; /** * private data of printf_hook */ struct private_printf_hook_t { /** * public functions */ printf_hook_t public; }; /** * struct with information about a registered handler */ struct printf_hook_handler_t { /** * callback function */ printf_hook_function_t hook; /** * number of arguments */ int numargs; /** * types of the arguments */ int argtypes[ARGS_MAX]; }; /** * Data to pass to a printf hook. */ struct printf_hook_data_t { /** * Output buffer */ char *q; /** * Remaining bytes in q */ size_t n; }; /** * Registered hooks (char => printf_hook_handler_t) */ static hashtable_t *hooks; /** * builtin-printf variant of print_in_hook() */ size_t print_in_hook(printf_hook_data_t *data, char *fmt, ...) { int written; va_list args; va_start(args, fmt); written = builtin_vsnprintf(data->q, data->n, fmt, args); va_end(args); if (written > data->n) { data->q += data->n; data->n = 0; } else { data->q += written; data->n -= written; } return written; } METHOD(printf_hook_t, add_handler, void, private_printf_hook_t *this, char spec, printf_hook_function_t hook, ...) { int i = -1; bool failed = FALSE; printf_hook_handler_t *handler; printf_hook_argtype_t argtype; va_list args; INIT(handler, .hook = hook, ); va_start(args, hook); while (!failed) { argtype = va_arg(args, printf_hook_argtype_t); if (argtype == PRINTF_HOOK_ARGTYPE_END) { break; } if (++i >= countof(handler->argtypes)) { DBG1(DBG_LIB, "Too many arguments for printf hook with " "specifier '%c', not registered!", spec); failed = TRUE; break; } handler->argtypes[i] = argtype; } va_end(args); handler->numargs = i + 1; if (!failed && handler->numargs > 0) { free(hooks->put(hooks, (void*)(uintptr_t)spec, handler)); } else { free(handler); } } /** * Printf format modifier flags */ typedef enum { FL_ZERO = 0x01, FL_MINUS = 0x02, FL_PLUS = 0x04, FL_TICK = 0x08, FL_SPACE = 0x10, FL_HASH = 0x20, FL_SIGNED = 0x40, FL_UPPER = 0x80, } bpf_flag_t; /** * Size of format string arguments */ typedef enum { RNK_CHAR = -2, RNK_SHORT = -1, RNK_INT = 0, RNK_LONG = 1, RNK_LONGLONG = 2, RNK_INTMAX = RNK_LONGLONG, RNK_SIZE_T = RNK_LONG, RNK_PTRDIFF_T = RNK_LONG, RNK_MIN = RNK_CHAR, RNK_MAX = RNK_LONGLONG, } bpf_rank_t; /** * Printf specifier Parser state */ typedef enum { /* Ground state */ ST_NORMAL, /* Special flags */ ST_FLAGS, /* Field width */ ST_WIDTH, /* Field precision */ ST_PREC, /* Length or conversion modifiers */ ST_MODIFIERS, } bpf_state_t; #define EMIT(x) ({ if (o nchars) { while (width > nchars) { EMIT(' '); width--; } } /* Emit nondigits */ if (minus) { EMIT('-'); } else if (flags & FL_PLUS) { EMIT('+'); } else if (flags & FL_SPACE) { EMIT(' '); } if ((flags & FL_HASH) && base == 16) { EMIT('0'); EMIT((flags & FL_UPPER) ? 'X' : 'x'); } /* Emit zero padding */ if ((flags & (FL_MINUS | FL_ZERO)) == FL_ZERO && width > ndigits) { while (width > nchars) { EMIT('0'); width--; } } /* Generate the number. This is done from right to left. */ /* Advance the pointer to end of number */ q += ndigits; o += ndigits; /* Temporary values */ qq = q; oo = o; b4tick = tickskip; while (ndigits > 0) { if (!b4tick--) { qq--; oo--; ndigits--; if (oo < n) { *qq = '_'; } b4tick = tickskip - 1; } qq--; oo--; ndigits--; if (oo < n) { *qq = digits[val % base]; } val /= base; } /* Emit late space padding */ while ((flags & FL_MINUS) && width > nchars) { EMIT(' '); width--; } return o; } /** * Write an double argument to q, using flags, base, width and precision */ static size_t format_double(char *q, size_t n, double val, bpf_flag_t flags, int base, int width, int prec) { char *qq; size_t o = 0, oo; const char *digits; uintmax_t tmpval; int minus = 0; int ndigits = 0, nchars; /* Select type of digits */ digits = (flags & FL_UPPER) ? ucdigits : lcdigits; if (prec < 0) { /* default precision */ prec = 6; } if (val < 0) { minus = 1; } tmpval = (uintmax_t)fabs(val); while (tmpval) { tmpval /= base; ndigits++; } if (val == 0) { ndigits++; } /* Now compute the number of nondigits */ nchars = ndigits; if (prec) { /* Space for decimal-point and digits after that */ nchars += prec + 1; } if (minus || (flags & (FL_PLUS | FL_SPACE))) { /* Need space for sign */ nchars++; } if ((flags & FL_HASH) && base == 16) { /* Add 0x for hex */ nchars += 2; } /* Emit early space padding */ if (!(flags & (FL_MINUS | FL_ZERO)) && width > nchars) { while (width > nchars) { EMIT(' '); width--; } } /* Emit nondigits */ if (minus) { EMIT('-'); } else if (flags & FL_PLUS) { EMIT('+'); } else if (flags & FL_SPACE) { EMIT(' '); } if ((flags & FL_HASH) && base == 16) { EMIT('0'); EMIT((flags & FL_UPPER) ? 'X' : 'x'); } /* Emit zero padding */ if ((flags & (FL_MINUS | FL_ZERO)) == FL_ZERO && width > ndigits) { while (width > nchars) { EMIT('0'); width--; } } /* Generate the number. This is done from right to left. */ /* Advance the pointer to end of number */ q += ndigits; o += ndigits; /* Temporary values */ qq = q; oo = o; tmpval = (uintmax_t)fabs(val); if (!prec) { /* round up if no additional digits */ if (fabs(val) - tmpval >= 0.5) { tmpval++; } } while (ndigits > 0) { qq--; oo--; ndigits--; if (oo < n) { *qq = digits[tmpval % base]; } tmpval /= base; } if (prec) { EMIT('.'); q += prec; o += prec; qq = q; oo = o; tmpval = (uintmax_t)(fabs(val) * pow(base, prec)); /* round up if required */ if (fabs(val) * pow(base, prec) - tmpval >= 0.5) { tmpval++; } while (prec > 0) { qq--; oo--; prec--; if (oo < n) { *qq = digits[tmpval % base]; } tmpval /= base; } } /* Emit late space padding */ while ((flags & FL_MINUS) && width > nchars) { EMIT(' '); width--; } return o; } int builtin_vsnprintf(char *buffer, size_t n, const char *format, va_list ap) { const char *p = format; char ch; char *q = buffer; /* Number of characters output */ size_t o = 0; uintmax_t val = 0; /* Default rank */ int rank = RNK_INT; int width = 0; int prec = -1; int base; size_t sz; bpf_flag_t flags = 0; bpf_state_t state = ST_NORMAL; /* %s string argument */ const char *sarg; /* %c char argument */ char carg; /* String length */ int slen; while ((ch = *p++)) { switch (state) { case ST_NORMAL: { if (ch == '%') { state = ST_FLAGS; flags = 0; rank = RNK_INT; width = 0; prec = -1; } else { EMIT(ch); } break; } case ST_FLAGS: { switch (ch) { case '-': flags |= FL_MINUS; break; case '+': flags |= FL_PLUS; break; case '\'': flags |= FL_TICK; break; case ' ': flags |= FL_SPACE; break; case '#': flags |= FL_HASH; break; case '0': flags |= FL_ZERO; break; default: state = ST_WIDTH; /* Process this character again */ p--; break; } break; } case ST_WIDTH: { if (ch >= '0' && ch <= '9') { width = width * 10 + (ch - '0'); } else if (ch == '*') { width = va_arg(ap, int); if (width < 0) { width = -width; flags |= FL_MINUS; } } else if (ch == '.') { /* Precision given */ prec = 0; state = ST_PREC; } else { state = ST_MODIFIERS; /* Process this character again */ p--; } break; } case ST_PREC: { if (ch >= '0' && ch <= '9') { prec = prec * 10 + (ch - '0'); } else if (ch == '*') { prec = va_arg(ap, int); if (prec < 0) { prec = -1; } } else { state = ST_MODIFIERS; /* Process this character again */ p--; } break; } case ST_MODIFIERS: { switch (ch) { /* Length modifiers - nonterminal sequences */ case 'h': rank--; break; case 'l': rank++; break; case 'j': rank = RNK_INTMAX; break; case 'z': rank = RNK_SIZE_T; break; case 't': rank = RNK_PTRDIFF_T; break; case 'L': case 'q': rank += 2; break; default: { /* Output modifiers - terminal sequences */ /* Next state will be normal */ state = ST_NORMAL; /* Canonicalize rank */ if (rank < RNK_MIN) { rank = RNK_MIN; } else if (rank > RNK_MAX) { rank = RNK_MAX; } switch (ch) { case 'p': { /* Pointer */ base = 16; prec = (CHAR_BIT*sizeof(void *)+3)/4; flags |= FL_HASH; val = (uintmax_t)(uintptr_t) va_arg(ap, void *); goto is_integer; } case 'd': case 'i': { /* Signed decimal output */ base = 10; flags |= FL_SIGNED; switch (rank) { case RNK_CHAR: /* Yes, all these casts are needed... */ val = (uintmax_t)(intmax_t)(signed char) va_arg(ap, signed int); break; case RNK_SHORT: val = (uintmax_t)(intmax_t)(signed short) va_arg(ap, signed int); break; case RNK_INT: val = (uintmax_t)(intmax_t) va_arg(ap, signed int); break; case RNK_LONG: val = (uintmax_t)(intmax_t) va_arg(ap, signed long); break; case RNK_LONGLONG: val = (uintmax_t)(intmax_t) va_arg(ap, signed long long); break; } goto is_integer; } case 'o': { /* Octal */ base = 8; goto is_unsigned; } case 'u': { /* Unsigned decimal */ base = 10; goto is_unsigned; } case 'X': { /* Upper case hexadecimal */ flags |= FL_UPPER; /* fall through */ } case 'x': { /* Hexadecimal */ base = 16; goto is_unsigned; } is_unsigned: { switch (rank) { case RNK_CHAR: val = (uintmax_t)(unsigned char) va_arg(ap, unsigned int); break; case RNK_SHORT: val = (uintmax_t)(unsigned short) va_arg(ap, unsigned int); break; case RNK_INT: val = (uintmax_t) va_arg(ap, unsigned int); break; case RNK_LONG: val = (uintmax_t) va_arg(ap, unsigned long); break; case RNK_LONGLONG: val = (uintmax_t) va_arg(ap, unsigned long long); break; } goto is_integer; } is_integer: { sz = format_int(q, (o < n) ? n - o : 0, val, flags, base, width, prec); q += sz; o += sz; break; } case 'c': { /* Character */ carg = (char)va_arg(ap, int); sarg = &carg; slen = 1; goto is_string; } case 's': { /* String */ sarg = va_arg(ap, const char *); sarg = sarg ? sarg : "(null)"; slen = prec != -1 ? strnlen(sarg, prec) : strlen(sarg); goto is_string; } case 'm': { /* glibc error string */ sarg = strerror(errno); slen = strlen(sarg); goto is_string; } is_string: { char sch; int i; if (prec != -1 && slen > prec) { slen = prec; } if (width > slen && !(flags & FL_MINUS)) { char pad = (flags & FL_ZERO) ? '0' : ' '; while (width > slen) { EMIT(pad); width--; } } for (i = slen; i; i--) { sch = *sarg++; EMIT(sch); } if (width > slen && (flags & FL_MINUS)) { while (width > slen) { EMIT(' '); width--; } } break; } case 'A': { base = 16; flags |= FL_UPPER; goto is_double; } case 'E': case 'G': { /* currently not supported, fall */ } case 'F': { base = 10; flags |= FL_UPPER; goto is_double; } case 'a': { base = 16; goto is_double; } case 'e': case 'g': { /* currently not supported, fall */ } case 'f': { base = 10; goto is_double; } is_double: { double dval; dval = va_arg(ap, double); if (isinf(dval)) { if (isgreater(dval, 0.0)) { sarg = flags & FL_UPPER ? "INF" : "inf"; } else { sarg = flags & FL_UPPER ? "-INF" : "-inf"; } slen = strlen(sarg); goto is_string; } if (isnan(dval)) { sarg = flags & FL_UPPER ? "NAN" : "nan"; slen = strlen(sarg); goto is_string; } sz = format_double(q, (o < n) ? n - o : 0, dval, flags, base, width, prec); q += sz; o += sz; break; } case 'n': { /* Output the number of characters written */ switch (rank) { case RNK_CHAR: *va_arg(ap, signed char *) = o; break; case RNK_SHORT: *va_arg(ap, signed short *) = o; break; case RNK_INT: *va_arg(ap, signed int *) = o; break; case RNK_LONG: *va_arg(ap, signed long *) = o; break; case RNK_LONGLONG: *va_arg(ap, signed long long *) = o; break; } break; } default: { printf_hook_handler_t *handler; handler = hooks->get(hooks, (void*)(uintptr_t)ch); if (handler) { const void *args[ARGS_MAX]; int i, iargs[ARGS_MAX]; void *pargs[ARGS_MAX]; printf_hook_spec_t spec = { .hash = flags & FL_HASH, .plus = flags & FL_PLUS, .minus = flags & FL_MINUS, .width = width, }; printf_hook_data_t data = { .q = q, .n = (o < n) ? n - o : 0, }; for (i = 0; i < handler->numargs; i++) { switch (handler->argtypes[i]) { case PRINTF_HOOK_ARGTYPE_INT: iargs[i] = va_arg(ap, int); args[i] = &iargs[i]; break; case PRINTF_HOOK_ARGTYPE_POINTER: pargs[i] = va_arg(ap, void*); args[i] = &pargs[i]; break; } } sz = handler->hook(&data, &spec, args); q += sz; o += sz; } else { /* Anything else, including % */ EMIT(ch); } break; } } } } } } } /* Null-terminate the string */ if (o < n) { /* No overflow */ *q = '\0'; } else if (n > 0) { /* Overflow - terminate at end of buffer */ buffer[n - 1] = '\0'; } return o; } int builtin_printf(const char *format, ...) { int written; va_list args; va_start(args, format); written = builtin_vprintf(format, args); va_end(args); return written; } int builtin_fprintf(FILE *stream, const char *format, ...) { int written; va_list args; va_start(args, format); written = builtin_vfprintf(stream, format, args); va_end(args); return written; } int builtin_sprintf(char *str, const char *format, ...) { int written; va_list args; va_start(args, format); written = builtin_vsnprintf(str, ~(size_t)0, format, args); va_end(args); return written; } int builtin_snprintf(char *str, size_t size, const char *format, ...) { int written; va_list args; va_start(args, format); written = builtin_vsnprintf(str, size, format, args); va_end(args); return written; } int builtin_asprintf(char **str, const char *format, ...) { int written; va_list args; va_start(args, format); written = builtin_vasprintf(str, format, args); va_end(args); return written; } int builtin_vprintf(const char *format, va_list ap) { return builtin_vfprintf(stdout, format, ap); } #ifdef WIN32 /** * Set TTY color on Windows consoles */ static void set_console_color(HANDLE handle, int color) { CONSOLE_SCREEN_BUFFER_INFO info; struct { /* escape code */ int color; /* windows console color combination */ WORD attributes; } maps[] = { { 30, 0 }, { 31, FOREGROUND_RED }, { 32, FOREGROUND_GREEN }, { 33, FOREGROUND_GREEN | FOREGROUND_RED }, { 34, FOREGROUND_BLUE | FOREGROUND_INTENSITY }, { 35, FOREGROUND_RED | FOREGROUND_BLUE }, { 36, FOREGROUND_GREEN | FOREGROUND_BLUE }, { 37, FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED }, { 39, FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED }, { 40, 0 }, { 41, BACKGROUND_RED }, { 42, BACKGROUND_GREEN }, { 43, BACKGROUND_GREEN | BACKGROUND_RED }, { 44, BACKGROUND_BLUE }, { 45, BACKGROUND_RED | BACKGROUND_BLUE }, { 46, BACKGROUND_GREEN | BACKGROUND_BLUE }, { 47, BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_RED }, { 49, 0 }, }; int i; if (GetConsoleScreenBufferInfo(handle, &info)) { if (color < 40) { info.wAttributes &= ~(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY); } else { info.wAttributes &= ~(BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY); } for (i = 0; i < countof(maps); i++) { if (maps[i].color == color) { info.wAttributes |= maps[i].attributes; SetConsoleTextAttribute(handle, info.wAttributes); break; } } } } int builtin_vfprintf(FILE *stream, const char *format, va_list ap) { char buf[PRINTF_BUF_LEN], *pos, *stop; HANDLE handle; int len, total; DWORD clen, mode; total = len = builtin_vsnprintf(buf, sizeof(buf), format, ap); switch (fileno(stream)) { case 1: handle = GetStdHandle(STD_OUTPUT_HANDLE); break; case 2: handle = GetStdHandle(STD_ERROR_HANDLE); break; default: handle = INVALID_HANDLE_VALUE; break; } /* GetConsoleMode fails if output redirected */ if (handle == INVALID_HANDLE_VALUE || !GetConsoleMode(handle, &mode)) { return fwrite(buf, 1, len, stream); } while (len) { pos = &buf[total - len]; if (len > 4) { if (pos[0] == '\e' && pos[1] == '[' && pos[4] == 'm') { if (isdigit(pos[3])) { if (pos[2] == '3' || pos[2] == '4') { set_console_color(handle, (pos[2] - '0') * 10 + pos[3] - '0'); len -= 5; continue; } } } } stop = memchr(pos + 1, '\e', len); if (stop) { clen = stop - pos; } else { clen = len; } if (clen && !WriteConsole(handle, pos, clen, &clen, NULL)) { break; } len -= clen; } return total - len; } #else /* !WIN32 */ int builtin_vfprintf(FILE *stream, const char *format, va_list ap) { char buf[PRINTF_BUF_LEN]; int len; len = builtin_vsnprintf(buf, sizeof(buf), format, ap); return fwrite(buf, 1, len, stream); } #endif /* !WIN32 */ int builtin_vsprintf(char *str, const char *format, va_list ap) { return builtin_vsnprintf(str, ~(size_t)0, format, ap); } int builtin_vasprintf(char **str, const char *format, va_list ap) { char buf[PRINTF_BUF_LEN]; int len; len = builtin_vsnprintf(buf, sizeof(buf), format, ap); *str = strdup(buf); return len; } METHOD(printf_hook_t, destroy, void, private_printf_hook_t *this) { enumerator_t *enumerator; printf_hook_handler_t *handler; enumerator = hooks->create_enumerator(hooks); while (enumerator->enumerate(enumerator, NULL, &handler)) { free(handler); } enumerator->destroy(enumerator); hooks->destroy(hooks); free(this); } /* * see header file */ printf_hook_t *printf_hook_create() { private_printf_hook_t *this; INIT(this, .public = { .add_handler = _add_handler, .destroy = _destroy, }, ); hooks = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 8); return &this->public; }