| /* |
| * 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 <http://www.fsf.org/copyleft/gpl.txt>. |
| * |
| * 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 <utils/utils.h> |
| #include <utils/debug.h> |
| #include <collections/hashtable.h> |
| |
| #include <inttypes.h> |
| #include <limits.h> |
| #include <stdio.h> |
| #include <stdarg.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <math.h> |
| |
| #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<n){*q++ = (x);} o++; }) |
| |
| static const char lcdigits[] = "0123456789abcdef"; |
| static const char ucdigits[] = "0123456789ABCDEF"; |
| |
| /** |
| * Write an integer argument to q, using flags, base, width and precision |
| */ |
| static size_t format_int(char *q, size_t n, uintmax_t 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; |
| int tickskip, b4tick; |
| |
| /* Select type of digits */ |
| digits = (flags & FL_UPPER) ? ucdigits : lcdigits; |
| |
| /* If signed, separate out the minus */ |
| if (flags & FL_SIGNED && (intmax_t) val < 0) |
| { |
| minus = 1; |
| val = (uintmax_t) (-(intmax_t) val); |
| } |
| |
| /* Count the number of digits needed. This returns zero for 0. */ |
| tmpval = val; |
| while (tmpval) |
| { |
| tmpval /= base; |
| ndigits++; |
| } |
| |
| /* Adjust ndigits for size of output */ |
| if (flags & FL_HASH && base == 8) |
| { |
| if (prec < ndigits + 1) |
| { |
| prec = ndigits + 1; |
| } |
| } |
| |
| if (ndigits < prec) |
| { |
| /* Mandatory number padding */ |
| ndigits = prec; |
| } |
| else if (val == 0) |
| { |
| /* Zero still requires space */ |
| ndigits = 1; |
| } |
| |
| /* For ', figure out what the skip should be */ |
| if (flags & FL_TICK) |
| { |
| if (base == 16) |
| { |
| tickskip = 4; |
| } |
| else |
| { |
| tickskip = 3; |
| } |
| } |
| else |
| { |
| /* No tick marks */ |
| tickskip = ndigits; |
| } |
| |
| /* Tick marks aren't digits, but generated by the number converter */ |
| ndigits += (ndigits - 1) / tickskip; |
| |
| /* Now compute the number of nondigits */ |
| nchars = ndigits; |
| |
| 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; |
| |
| 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; |
| } |