/* * Modified and hacked into libratbox by Aaron Sethman * The original headers are below.. * Note that this implementation does not process floating point numbers so * you will likely need to fall back to using sprintf yourself to do those... */ /* * linux/lib/vsprintf.c * * Copyright (C) 1991, 1992 Linus Torvalds */ /* vsprintf.c -- Lars Wirzenius & Linus Torvalds. */ /* * Wirzenius wrote this portably, Torvalds fucked it up :-) */ /* * Fri Jul 13 2001 Crutcher Dunnavant * - changed to provide snprintf and vsnprintf functions * So Feb 1 16:51:32 CET 2004 Juergen Quade * - scnprintf and vscnprintf */ #include #include static int skip_atoi(const char **s) { int i = 0; while(isdigit(**s)) i = i * 10 + *((*s)++) - '0'; return i; } /* Decimal conversion is by far the most typical, and is used * for /proc and /sys data. This directly impacts e.g. top performance * with many processes running. We optimize it for speed * using code from * http://www.cs.uiowa.edu/~jones/bcd/decimal.html * (with permission from the author, Douglas W. Jones). */ /* Formats correctly any integer in [0,99999]. * Outputs from one to five digits depending on input. * On i386 gcc 4.1.2 -O2: ~250 bytes of code. */ static char * put_dec_trunc(char *buf, unsigned q) { unsigned d3, d2, d1, d0; d1 = (q >> 4) & 0xf; d2 = (q >> 8) & 0xf; d3 = (q >> 12); d0 = 6 * (d3 + d2 + d1) + (q & 0xf); q = (d0 * 0xcd) >> 11; d0 = d0 - 10 * q; *buf++ = d0 + '0'; /* least significant digit */ d1 = q + 9 * d3 + 5 * d2 + d1; if(d1 != 0) { q = (d1 * 0xcd) >> 11; d1 = d1 - 10 * q; *buf++ = d1 + '0'; /* next digit */ d2 = q + 2 * d2; if((d2 != 0) || (d3 != 0)) { q = (d2 * 0xd) >> 7; d2 = d2 - 10 * q; *buf++ = d2 + '0'; /* next digit */ d3 = q + 4 * d3; if(d3 != 0) { q = (d3 * 0xcd) >> 11; d3 = d3 - 10 * q; *buf++ = d3 + '0'; /* next digit */ if(q != 0) *buf++ = q + '0'; /* most sign. digit */ } } } return buf; } /* Same with if's removed. Always emits five digits */ static char * put_dec_full(char *buf, unsigned q) { /* BTW, if q is in [0,9999], 8-bit ints will be enough, */ /* but anyway, gcc produces better code with full-sized ints */ unsigned d3, d2, d1, d0; d1 = (q >> 4) & 0xf; d2 = (q >> 8) & 0xf; d3 = (q >> 12); /* Possible ways to approx. divide by 10 */ /* gcc -O2 replaces multiply with shifts and adds */ // (x * 0xcd) >> 11: 11001101 - shorter code than * 0x67 (on i386) // (x * 0x67) >> 10: 1100111 // (x * 0x34) >> 9: 110100 - same // (x * 0x1a) >> 8: 11010 - same // (x * 0x0d) >> 7: 1101 - same, shortest code (on i386) d0 = 6 * (d3 + d2 + d1) + (q & 0xf); q = (d0 * 0xcd) >> 11; d0 = d0 - 10 * q; *buf++ = d0 + '0'; d1 = q + 9 * d3 + 5 * d2 + d1; q = (d1 * 0xcd) >> 11; d1 = d1 - 10 * q; *buf++ = d1 + '0'; d2 = q + 2 * d2; q = (d2 * 0xd) >> 7; d2 = d2 - 10 * q; *buf++ = d2 + '0'; d3 = q + 4 * d3; q = (d3 * 0xcd) >> 11; /* - shorter code */ /* q = (d3 * 0x67) >> 10; - would also work */ d3 = d3 - 10 * q; *buf++ = d3 + '0'; *buf++ = q + '0'; return buf; } static char * put_dec(char *buf, unsigned long long int num) { while(1) { unsigned rem; if(num < 100000) return put_dec_trunc(buf, num); rem = num % 100000; num = num / 100000; buf = put_dec_full(buf, rem); } } #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 size_t number(char *const buf, const size_t size, size_t idx, unsigned long long int num, int base, int field_width, int precision, int type) { char sign, tmp[66]; const char *digits; /* we are called with base 8, 10 or 16, only, thus don't need "g..." */ static const char small_digits[] = "0123456789abcdefx"; /* "ghijklmnopqrstuvwxyz"; */ static const char large_digits[] = "0123456789ABCDEFX"; /* "GHIJKLMNOPQRSTUVWXYZ"; */ int need_pfx = ((type & SPECIAL) && base != 10); int i; digits = (type & LARGE) ? large_digits : small_digits; if(type & LEFT) type &= ~ZEROPAD; if(base < 2 || base > 36) return idx; sign = 0; if(type & SIGN) { if((signed long long int)num < 0) { sign = '-'; num = -(signed long long int)num; field_width--; } else if(type & PLUS) { sign = '+'; field_width--; } else if(type & SPACE) { sign = ' '; field_width--; } } if(need_pfx) { field_width--; if(base == 16) field_width--; } /* generate full string in tmp[], in reverse order */ i = 0; if(num == 0) tmp[i++] = '0'; /* Generic code, for any base: else do { tmp[i++] = digits[do_div(num,base)]; } while (num != 0); */ else if(base != 10) { /* 8 or 16 */ int mask = base - 1; int shift = 3; if(base == 16) shift = 4; do { tmp[i++] = digits[((unsigned char)num) & mask]; num >>= shift; } while(num); } else { /* base 10 */ i = put_dec(tmp, num) - tmp; } /* printing 100 using %2d gives "100", not "00" */ if(i > precision) precision = i; /* leading space padding */ field_width -= precision; if(!(type & (ZEROPAD + LEFT))) { while(--field_width >= 0) { if(idx < size) buf[idx] = ' '; ++idx; } } /* sign */ if(sign) { if(idx < size) buf[idx] = sign; ++idx; } /* "0x" / "0" prefix */ if(need_pfx) { if(idx < size) buf[idx] = '0'; ++idx; if(base == 16) { if(idx < size) buf[idx] = digits[16]; /* for arbitrary base: digits[33]; */ ++idx; } } /* zero or space padding */ if(!(type & LEFT)) { char c = (type & ZEROPAD) ? '0' : ' '; while(--field_width >= 0) { if(idx < size) buf[idx] = c; ++idx; } } /* hmm even more zero padding? */ while(i <= --precision) { if(idx < size) buf[idx] = '0'; ++idx; } /* actual digits of result */ while(--i >= 0) { if(idx < size) buf[idx] = tmp[i]; ++idx; } /* trailing space padding */ while(--field_width >= 0) { if(idx < size) buf[idx] = ' '; ++idx; } return idx; } /** * vsnprintf - Format a string and place it in a buffer * @buf: The buffer to place the result into * @size: The size of the buffer, including the trailing null space * @fmt: The format string to use * @args: Arguments for the format string * * The return value is the number of characters which would * be generated for the given input, excluding the trailing * '\0', as per ISO C99. If you want to have the exact * number of characters written into @buf as return value * (not including the trailing '\0'), use vscnprintf(). If the * return is greater than or equal to @size, the resulting * string is truncated. * * Call this function if you are already dealing with a va_list. * You probably want snprintf() instead. */ int rb_vsnprintf(char *buf, size_t size, const char *fmt, va_list args) { int len; unsigned long long int num; int i, base; char c; size_t idx; const char *s; 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', 'l', or 'L' for integer fields */ /* 'z' support added 23/7/1999 S.H. */ /* 'z' changed to 'Z' --davidm 1/25/99 */ /* 't' added for ptrdiff_t */ /* Reject out-of-range values early. Large positive sizes are used for unknown buffer sizes. */ if(rb_unlikely(size > INT_MAX)) { return 0; } idx = 0; for(; *fmt; ++fmt) { if(*fmt != '%') { if(idx < size) buf[idx] = *fmt; ++idx; 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(isdigit(*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(isdigit(*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' || *fmt == 'Z' || *fmt == 'z' || *fmt == 't') { qualifier = *fmt; ++fmt; if(qualifier == 'l' && *fmt == 'l') { qualifier = 'L'; ++fmt; } } /* default base */ base = 10; switch (*fmt) { case 'c': if(!(flags & LEFT)) { while(--field_width > 0) { if(idx < size) buf[idx] = ' '; ++idx; } } c = (unsigned char)va_arg(args, int); if(idx < size) buf[idx] = c; ++idx; while(--field_width > 0) { if(idx < size) buf[idx] = ' '; ++idx; } continue; case 's': s = va_arg(args, char *); if(s == NULL) { abort(); /* prefer blowing up vs corrupt data */ } len = rb_strnlen(s, precision); if(!(flags & LEFT)) { while(len < field_width--) { if(idx < size) buf[idx] = ' '; ++idx; } } for(i = 0; i < len; ++i) { if(idx < size) buf[idx] = *s; ++idx; ++s; } while(len < field_width--) { if(idx < size) buf[idx] = ' '; ++idx; } continue; case 'p': if(field_width == -1) { field_width = 2 * sizeof(void *); flags |= ZEROPAD; } idx = number(buf, size, idx, (unsigned long)va_arg(args, void *), 16, field_width, precision, flags); continue; case 'n': /* FIXME: * What does C99 say about the overflow case here? */ if(qualifier == 'l') { long *ip = va_arg(args, long *); *ip = idx; } else if(qualifier == 'Z' || qualifier == 'z') { size_t *ip = va_arg(args, size_t *); *ip = idx; } else { int *ip = va_arg(args, int *); *ip = idx; } continue; case '%': if(idx < size) buf[idx] = '%'; ++idx; continue; /* integer number formats - set up the flags and "break" */ case 'o': base = 8; break; case 'X': flags |= LARGE; case 'x': base = 16; break; case 'd': case 'i': flags |= SIGN; case 'u': break; default: if(idx < size) buf[idx] = '%'; ++idx; if(*fmt) { if(idx < size) buf[idx] = *fmt; ++idx; } else { --fmt; } continue; } if(qualifier == 'L') num = va_arg(args, long long int); else if(qualifier == 'l') { num = va_arg(args, unsigned long); if(flags & SIGN) num = (signed long)num; } else if(qualifier == 'Z' || qualifier == 'z') { num = va_arg(args, size_t); } else if(qualifier == 't') { num = va_arg(args, ptrdiff_t); } else if(qualifier == 'h') { num = (unsigned short)va_arg(args, int); if(flags & SIGN) num = (signed short)num; } else { num = va_arg(args, unsigned int); if(flags & SIGN) num = (signed int)num; } idx = number(buf, size, idx, num, base, field_width, precision, flags); } if(size > 0) { if(idx < size) buf[idx] = '\0'; else buf[size - 1] = '\0'; } /* the trailing null byte doesn't count towards the total */ return idx; } /** * snprintf - Format a string and place it in a buffer * @buf: The buffer to place the result into * @size: The size of the buffer, including the trailing null space * @fmt: The format string to use * @...: Arguments for the format string * * The return value is the number of characters which would be * generated for the given input, excluding the trailing null, * as per ISO C99. If the return is greater than or equal to * @size, the resulting string is truncated. */ int rb_snprintf(char *buf, size_t size, const char *fmt, ...) { va_list args; int i; va_start(args, fmt); i = rb_vsnprintf(buf, size, fmt, args); va_end(args); return i; } /** * vsprintf - Format a string and place it in a buffer * @buf: The buffer to place the result into * @fmt: The format string to use * @args: Arguments for the format string * * The function returns the number of characters written * into @buf. Use vsnprintf() or vscnprintf() in order to avoid * buffer overflows. * * Call this function if you are already dealing with a va_list. * You probably want sprintf() instead. */ int rb_vsprintf(char *buf, const char *fmt, va_list args) { return rb_vsnprintf(buf, INT_MAX, fmt, args); } /** * sprintf - Format a string and place it in a buffer * @buf: The buffer to place the result into * @fmt: The format string to use * @...: Arguments for the format string * * The function returns the number of characters written * into @buf. Use snprintf() or scnprintf() in order to avoid * buffer overflows. */ int rb_sprintf(char *buf, const char *fmt, ...) { va_list args; int i; va_start(args, fmt); i = rb_vsnprintf(buf, INT_MAX, fmt, args); va_end(args); return i; } /* * rb_vsprintf_append() * appends sprintf formatted string to the end of the buffer */ int rb_vsprintf_append(char *str, const char *format, va_list ap) { size_t x = strlen(str); return (rb_vsprintf(str + x, format, ap) + x); } /* * rb_sprintf_append() * appends sprintf formatted string to the end of the buffer */ int rb_sprintf_append(char *str, const char *format, ...) { int x; va_list ap; va_start(ap, format); x = rb_vsprintf_append(str, format, ap); va_end(ap); return (x); } /* * rb_vsnprintf_append() * appends sprintf formatted string to the end of the buffer but not * exceeding len */ int rb_vsnprintf_append(char *str, size_t len, const char *format, va_list ap) { size_t x; if(len == 0) return 0; x = strlen(str); if(len < x) { str[len - 1] = '\0'; return len - 1; } return (rb_vsnprintf(str + x, len - x, format, ap) + x); } /* * rb_snprintf_append() * appends snprintf formatted string to the end of the buffer but not * exceeding len */ int rb_snprintf_append(char *str, size_t len, const char *format, ...) { int x; va_list ap; va_start(ap, format); x = rb_vsnprintf_append(str, len, format, ap); va_end(ap); return (x); }