/* * BIRD Library -- Formatted Output * * (c) 1991, 1992 Lars Wirzenius & Linus Torvalds * * Hacked up for BIRD by Martin Mares * Buffer size limitation implemented by Martin Mares. */ #include "nest/bird.h" #include "lib/macro.h" #include "lib/string.h" #include #include "nest/iface.h" /* we use this so that we can do without the ctype library */ #define is_digit(c) ((c) >= '0' && (c) <= '9') static int skip_atoi(const char **s) { int i=0; while (is_digit(**s)) i = i*10 + *((*s)++) - '0'; return i; } #define ZEROPAD 1 /* pad with zero */ #define SIGN 2 /* unsigned/signed long */ #define PLUS 4 /* show plus */ #define SPACE 8 /* space if plus */ #define LEFT 16 /* left justified */ #define SPECIAL 32 /* 0x */ #define LARGE 64 /* use 'ABCDEF' instead of 'abcdef' */ static char * number(char * str, u64 num, uint base, int size, int precision, int type, int remains) { char c,sign,tmp[66]; const char *digits="0123456789abcdefghijklmnopqrstuvwxyz"; int i; if (size >= 0 && (remains -= size) < 0) return NULL; if (type & LARGE) digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; if (type & LEFT) type &= ~ZEROPAD; if (base < 2 || base > 36) return 0; c = (type & ZEROPAD) ? '0' : ' '; sign = 0; if (type & SIGN) { if (num > (u64) INT64_MAX) { sign = '-'; num = -num; size--; } else if (type & PLUS) { sign = '+'; size--; } else if (type & SPACE) { sign = ' '; size--; } } if (type & SPECIAL) { if (base == 16) size -= 2; else if (base == 8) size--; } i = 0; if (num == 0) tmp[i++]='0'; else if (base == 10) { /* Separate cases to have fixed divisors */ while (num != 0) { uint res = num % 10; num = num / 10; tmp[i++] = digits[res]; } } else if (base == 16) { while (num != 0) { uint res = num % 16; num = num / 16; tmp[i++] = digits[res]; } } else { while (num != 0) { uint res = num % base; num = num / base; tmp[i++] = digits[res]; } } if (i > precision) precision = i; size -= precision; if (size < 0 && -size > remains) return NULL; if (!(type&(ZEROPAD+LEFT))) while(size-->0) *str++ = ' '; if (sign) *str++ = sign; if (type & SPECIAL) { if (base==8) *str++ = '0'; else if (base==16) { *str++ = '0'; *str++ = digits[33]; } } if (!(type & LEFT)) while (size-- > 0) *str++ = c; while (i < precision--) *str++ = '0'; while (i-- > 0) *str++ = tmp[i]; while (size-- > 0) *str++ = ' '; return str; } /** * bvsnprintf - BIRD's vsnprintf() * @buf: destination buffer * @size: size of the buffer * @fmt: format string * @args: a list of arguments to be formatted * * This functions acts like ordinary sprintf() except that it checks available * space to avoid buffer overflows and it allows some more format specifiers: * |%I| for formatting of IP addresses (width of 1 is automatically replaced by * standard IP address width which depends on whether we use IPv4 or IPv6; |%I4| * or |%I6| can be used for explicit ip4_addr / ip6_addr arguments, |%N| for * generic network addresses (net_addr *), |%R| for Router / Network ID (u32 * value printed as IPv4 address), |%lR| for 64bit Router / Network ID (u64 * value printed as eight :-separated octets), |%t| for time values (btime) with * specified subsecond precision, and |%m| resp. |%M| for error messages (uses * strerror() to translate @errno code to message text). On the other hand, it * doesn't support floating point numbers. The bvsnprintf() supports |%h| and * |%l| qualifiers, but |%l| is used for s64/u64 instead of long/ulong. * * Result: number of characters of the output string or -1 if * the buffer space was insufficient. */ int bvsnprintf(char *buf, int size, const char *fmt, va_list args) { int len, i; u64 num; uint base; u32 x; u64 X; btime t; s64 t1, t2; char *str, *start; const char *s; char ipbuf[NET_MAX_TEXT_LENGTH+1]; struct iface *iface; int flags; /* flags to number() */ int field_width; /* width of output field */ int precision; /* min. # of digits for integers; max number of chars for from string */ int qualifier; /* 'h' or 'l' for integer fields */ for (start=str=buf ; *fmt ; ++fmt, size-=(str-start), start=str) { if (!size) return -1; if (*fmt != '%') { *str++ = *fmt; continue; } /* process flags */ flags = 0; repeat: ++fmt; /* this also skips first '%' */ switch (*fmt) { case '-': flags |= LEFT; goto repeat; case '+': flags |= PLUS; goto repeat; case ' ': flags |= SPACE; goto repeat; case '#': flags |= SPECIAL; goto repeat; case '0': flags |= ZEROPAD; goto repeat; } /* get field width */ field_width = -1; if (is_digit(*fmt)) field_width = skip_atoi(&fmt); else if (*fmt == '*') { ++fmt; /* it's the next argument */ field_width = va_arg(args, int); if (field_width < 0) { field_width = -field_width; flags |= LEFT; } } /* get the precision */ precision = -1; if (*fmt == '.') { ++fmt; if (is_digit(*fmt)) precision = skip_atoi(&fmt); else if (*fmt == '*') { ++fmt; /* it's the next argument */ precision = va_arg(args, int); } if (precision < 0) precision = 0; } /* get the conversion qualifier */ qualifier = -1; if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L') { qualifier = *fmt; ++fmt; } /* default base */ base = 10; if (field_width > size) return -1; switch (*fmt) { case 'c': if (!(flags & LEFT)) while (--field_width > 0) *str++ = ' '; *str++ = (byte) va_arg(args, int); while (--field_width > 0) *str++ = ' '; continue; case 'm': if (flags & SPECIAL) { if (!errno) continue; if (size < 2) return -1; *str++ = ':'; *str++ = ' '; start += 2; size -= 2; } s = strerror(errno); goto str; case 'M': s = strerror(va_arg(args, int)); goto str; case 'N': { net_addr *n = va_arg(args, net_addr *); if (field_width == 1) field_width = net_max_text_length[n->type]; net_format(n, ipbuf, sizeof(ipbuf)); s = ipbuf; goto str; } case 's': s = va_arg(args, char *); if (!s) s = ""; str: len = strlen(s); if (precision >= 0 && len > precision) len = precision; if ((len > size) || (field_width > size)) return -1; if (!(flags & LEFT)) while (len < field_width--) *str++ = ' '; for (i = 0; i < len; ++i) *str++ = *s++; while (len < field_width--) *str++ = ' '; continue; case 'V': { const char *vfmt = va_arg(args, const char *); va_list *vargs = va_arg(args, va_list *); int res = bvsnprintf(str, size, vfmt, *vargs); if (res < 0) return -1; str += res; size -= res; continue; } case 'p': if (field_width == -1) { field_width = 2*sizeof(void *); flags |= ZEROPAD; } str = number(str, (uintptr_t) va_arg(args, void *), 16, field_width, precision, flags, size); if (!str) return -1; continue; case 'n': if (qualifier == 'l') { s64 * ip = va_arg(args, s64 *); *ip = (str - buf); } else { int * ip = va_arg(args, int *); *ip = (str - buf); } continue; /* IP address */ case 'I': if (fmt[1] == '4') { /* Explicit IPv4 address */ ip4_addr a = va_arg(args, ip4_addr); ip4_ntop(a, ipbuf); i = IP4_MAX_TEXT_LENGTH; fmt++; } else if (fmt[1] == '6') { /* Explicit IPv6 address */ ip6_addr a = va_arg(args, ip6_addr); ip6_ntop(a, ipbuf); i = IP6_MAX_TEXT_LENGTH; fmt++; } else { /* Just IP address */ ip_addr a = va_arg(args, ip_addr); if (ipa_is_ip4(a)) { ip4_ntop(ipa_to_ip4(a), ipbuf); i = IP4_MAX_TEXT_LENGTH; } else { ip6_ntop(ipa_to_ip6(a), ipbuf); i = IP6_MAX_TEXT_LENGTH; } } s = ipbuf; if (field_width == 1) field_width = i; goto str; /* Interface scope after link-local IP address */ case 'J': iface = va_arg(args, struct iface *); if (!iface) continue; if (!size) return -1; *str++ = '%'; start++; size--; s = iface->name; goto str; /* Router/Network ID - essentially IPv4 address in u32 value */ case 'R': if (qualifier == 'l') { X = va_arg(args, u64); bsprintf(ipbuf, "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", (uint) ((X >> 56) & 0xff), (uint) ((X >> 48) & 0xff), (uint) ((X >> 40) & 0xff), (uint) ((X >> 32) & 0xff), (uint) ((X >> 24) & 0xff), (uint) ((X >> 16) & 0xff), (uint) ((X >> 8) & 0xff), (uint) (X & 0xff)); } else { x = va_arg(args, u32); ip4_ntop(ip4_from_u32(x), ipbuf); } s = ipbuf; goto str; case 't': t = va_arg(args, btime); t1 = t TO_S; t2 = t - t1 S; if (precision < 0) precision = 3; if (precision > 6) precision = 6; /* Compute field_width for second part */ if ((precision > 0) && (field_width > 0)) field_width -= (1 + precision); if (field_width < 0) field_width = 0; /* Print seconds */ flags |= SIGN; str = number(str, (u64) t1, 10, field_width, 0, flags, size); if (!str) return -1; if (precision > 0) { size -= (str-start); start = str; if ((1 + precision) > size) return -1; /* Convert microseconds to requested precision */ for (i = precision; i < 6; i++) t2 /= 10; /* Print sub-seconds */ *str++ = '.'; str = number(str, (u64) t2, 10, precision, 0, ZEROPAD, size - 1); if (!str) return -1; } goto done; /* integer number formats - set up the flags and "break" */ case 'o': base = 8; break; case 'X': flags |= LARGE; /* fallthrough */ case 'x': base = 16; break; case 'd': case 'i': flags |= SIGN; case 'u': break; default: if (size < 2) return -1; if (*fmt != '%') *str++ = '%'; if (*fmt) *str++ = *fmt; else --fmt; continue; } if (flags & SIGN) { /* Conversions valid per ISO C99 6.3.1.3 (2) */ if (qualifier == 'l') num = (u64) va_arg(args, s64); else if (qualifier == 'h') num = (u64) (short) va_arg(args, int); else num = (u64) va_arg(args, int); } else { if (qualifier == 'l') num = va_arg(args, u64); else if (qualifier == 'h') num = (unsigned short) va_arg(args, int); else num = va_arg(args, uint); } str = number(str, num, base, field_width, precision, flags, size); if (!str) return -1; done: ; } if (!size) return -1; *str = '\0'; return str-buf; } /** * bvsprintf - BIRD's vsprintf() * @buf: buffer * @fmt: format string * @args: a list of arguments to be formatted * * This function is equivalent to bvsnprintf() with an infinite * buffer size. Please use carefully only when you are absolutely * sure the buffer won't overflow. */ int bvsprintf(char *buf, const char *fmt, va_list args) { return bvsnprintf(buf, 1000000000, fmt, args); } /** * bsprintf - BIRD's sprintf() * @buf: buffer * @fmt: format string * * This function is equivalent to bvsnprintf() with an infinite * buffer size and variable arguments instead of a &va_list. * Please use carefully only when you are absolutely * sure the buffer won't overflow. */ int bsprintf(char * buf, const char *fmt, ...) { va_list args; int i; va_start(args, fmt); i=bvsnprintf(buf, 1000000000, fmt, args); va_end(args); return i; } /** * bsnprintf - BIRD's snprintf() * @buf: buffer * @size: buffer size * @fmt: format string * * This function is equivalent to bsnprintf() with variable arguments instead of a &va_list. */ int bsnprintf(char * buf, int size, const char *fmt, ...) { va_list args; int i; va_start(args, fmt); i=bvsnprintf(buf, size, fmt, args); va_end(args); return i; } int buffer_vprint(buffer *buf, const char *fmt, va_list args) { int i = bvsnprintf((char *) buf->pos, buf->end - buf->pos, fmt, args); if ((i < 0) && (buf->pos < buf->end)) *buf->pos = 0; buf->pos = (i >= 0) ? (buf->pos + i) : buf->end; return i; } int buffer_print(buffer *buf, const char *fmt, ...) { va_list args; int i; va_start(args, fmt); i = bvsnprintf((char *) buf->pos, buf->end - buf->pos, fmt, args); va_end(args); if ((i < 0) && (buf->pos < buf->end)) *buf->pos = 0; buf->pos = (i >= 0) ? (buf->pos + i) : buf->end; return i; } void buffer_puts(buffer *buf, const char *str) { byte *bp = buf->pos; byte *be = buf->end - 1; while (bp < be && *str) *bp++ = *str++; if (bp <= be) *bp = 0; buf->pos = (bp < be) ? bp : buf->end; } #define POOL_PRINTF_MAXBUF 1024 char *mb_vsprintf(pool *p, const char *fmt, va_list args) { char buf[POOL_PRINTF_MAXBUF]; int count = bvsnprintf(buf, POOL_PRINTF_MAXBUF, fmt, args); if (count < 0) bug("Attempted to mb_vsprintf() a too long string"); char *out = mb_alloc(p, count + 1); memcpy(out, buf, count + 1); return out; } char *mb_sprintf(pool *p, const char *fmt, ...) { va_list args; char *out; va_start(args, fmt); out = mb_vsprintf(p, fmt, args); va_end(args); return out; } char *lp_vsprintf(linpool *p, const char *fmt, va_list args) { char buf[POOL_PRINTF_MAXBUF]; int count = bvsnprintf(buf, POOL_PRINTF_MAXBUF, fmt, args); if (count < 0) bug("Attempted to mb_vsprintf() a too long string"); char *out = lp_alloc(p, count + 1); memcpy(out, buf, count + 1); return out; } char *lp_sprintf(linpool *p, const char *fmt, ...) { va_list args; char *out; va_start(args, fmt); out = lp_vsprintf(p, fmt, args); va_end(args); return out; } static const u64 decadic_multiplier[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, #if 0 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000, 100000000000000, 1000000000000000, 10000000000000000, 100000000000000000, 1000000000000000000, #endif }; static const u64 decmul_limit[] = { ~0ULL / 1, ~0ULL / 10, ~0ULL / 100, ~0ULL / 1000, ~0ULL / 10000, ~0ULL / 100000, ~0ULL / 1000000, ~0ULL / 10000000, #if 0 ~0ULL / 100000000, ~0ULL / 1000000000, ~0ULL / 10000000000, ~0ULL / 100000000000, ~0ULL / 1000000000000, ~0ULL / 10000000000000, ~0ULL / 100000000000000, ~0ULL / 1000000000000000, ~0ULL / 10000000000000000, ~0ULL / 100000000000000000, ~0ULL / 1000000000000000000, #endif }; STATIC_ASSERT(sizeof decadic_multiplier == sizeof decmul_limit); char *fmt_order(u64 value, uint decimals, u64 kb_threshold) { bool too_big = (value + 512 < 512ULL); u64 mv = value; uint magnitude = 0; while (mv > kb_threshold) { magnitude++; mv = (mv + (too_big ? 0 : 512)) / 1024; } uint shift = magnitude * 10; /* The trivial case */ if (magnitude == 0) return tmp_sprintf("%lu ", value); /* Now we can find the suffix and the main divisor */ ASSERT_DIE(magnitude < 7); char suffix = " kMGTPE"[magnitude]; /* The value before the dot is available just by dividing */ u64 before_dot = value >> shift; /* Remainder is more tricky. First we need to know it. */ u64 remainder = value - (before_dot << shift); /* We would like to compute (remainder * decmul) / divisor * in integers but it's tricky because of u64 limits. */ ASSERT_DIE(decimals < ARRAY_SIZE(decadic_multiplier)); u64 decmul = decadic_multiplier[decimals]; u64 product; if (remainder < decmul_limit[decimals]) { /* The easier version: Everything fits into u64 */ product = remainder * decmul; product >>= shift - 1; product++; product >>= 1; } else { /* Harder version: We have to multiply by parts. * Fortunately, decmul always fits into 32 bits. */ /* After this, product = lower + (upper << 32). */ u64 lower = (remainder & ((1ULL << 32) - 1)) * decmul; u64 upper = (remainder >> 32) * decmul; if (shift < 33) { /* Divide lower */ lower >>= shift - 1; /* Add the full upper part, not shifted enough to lose any bits */ lower += upper << (33 - shift); } else { /* First move the shifted-out bits from upper to lower */ lower += (upper & ((1ULL << (shift - 32)) - 1)) << 32; /* Then we can divide */ lower >>= shift - 1; /* And add the shifted upper part */ lower += upper >> (shift - 33); } /* Now we finish the division by rounding */ product = (lower + 1) >> 1; } if (product == decmul) { product = 0; before_dot++; } ASSERT_DIE(product < decmul); /* And now we finally have all the numbers to print! */ if (decimals) return tmp_sprintf("%lu.%0*lu %c", before_dot, decimals, product, suffix ); else return tmp_sprintf("%lu %c", before_dot, suffix); }