/** * @file mprPrintf.c * @brief Printf routines safe for embedded programming * @overview This module provides safe replacements for the standard * printf formatting routines. * @remarks Most routines in this file are not thread-safe. It is the callers * responsibility to perform all thread synchronization. */ /* * @copy default * * Copyright (c) Mbedthis Software LLC, 2003-2006. All Rights Reserved. * * This software is distributed under commercial and open source licenses. * You may use the GPL open source license described below or you may acquire * a commercial license from Mbedthis Software. You agree to be fully bound * by the terms of either license. Consult the LICENSE.TXT distributed with * this software for full details. * * This software is open source; 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 the GNU General Public License for more * details at: http://www.mbedthis.com/downloads/gplLicense.html * * This program is distributed WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * This GPL license does NOT permit incorporating this software into * proprietary programs. If you are unable to comply with the GPL, you must * acquire a commercial license to use this software. Commercial licenses * for this software and support services are available from Mbedthis * Software at http://www.mbedthis.com * * @end */ /********************************** Includes **********************************/ /* * We need to use the underlying str(cpy) routines to implement our safe * alternatives */ #if !DOXYGEN #define UNSAFE_FUNCTIONS_OK 1 #endif #include "mpr.h" /*********************************** Defines **********************************/ /* * Class definitions */ #define CLASS_NORMAL 0 /* [All other] Normal characters */ #define CLASS_PERCENT 1 /* [%] Begin format */ #define CLASS_MODIFIER 2 /* [-+ #,] Modifiers */ #define CLASS_ZERO 3 /* [0] Special modifier */ #define CLASS_STAR 4 /* [*] Width supplied by arg */ #define CLASS_DIGIT 5 /* [1-9] Field widths */ #define CLASS_DOT 6 /* [.] Introduce precision */ #define CLASS_BITS 7 /* [hlL] Length bits */ #define CLASS_TYPE 8 /* [cdfinopsSuxX] Type specifiers */ #define STATE_NORMAL 0 /* Normal chars in format string */ #define STATE_PERCENT 1 /* "%" */ #define STATE_MODIFIER 2 /* Read flag */ #define STATE_WIDTH 3 /* Width spec */ #define STATE_DOT 4 /* "." */ #define STATE_PRECISION 5 /* Precision spec */ #define STATE_BITS 6 /* Size spec */ #define STATE_TYPE 7 /* Data type */ #define STATE_COUNT 8 /* * Format: %[modifier][width][precision][bits][type] * * #define CLASS_MODIFIER 2 [-+ #,] Modifiers * #define CLASS_BITS 7 [hlL] Length bits */ /* * Flags */ #define SPRINTF_LEFT 0x1 /* Left align */ #define SPRINTF_SIGN 0x2 /* Always sign the result */ #define SPRINTF_LEAD_SPACE 0x4 /* put leading space for +ve numbers */ #define SPRINTF_ALTERNATE 0x8 /* Alternate format */ #define SPRINTF_LEAD_ZERO 0x10 /* Zero pad */ #define SPRINTF_SHORT 0x20 /* 16-bit */ #define SPRINTF_LONG 0x40 /* 32-bit */ #if BLD_FEATURE_INT64 #define SPRINTF_LONGLONG 0x80 /* 64-bit */ #endif #define SPRINTF_COMMA 0x100 /* Thousand comma separators */ #define SPRINTF_UPPER_CASE 0x200 /* As the name says for numbers */ typedef struct Format { uchar *buf; uchar *endbuf; uchar *start; uchar *end; int growBy; int maxsize; int precision; int radix; int width; int flags; int len; } Format; static int growBuf(MPR_LOC_DEC(ctx, loc), Format *fmt); #define BPUT(ctx, loc, fmt, c) \ if (1) { \ /* Less one to allow room for the null */ \ if ((fmt)->end >= ((fmt)->endbuf - sizeof(char))) { \ if (growBuf(MPR_LOC_PASS(ctx, loc), fmt)) { \ *(fmt)->end++ = (c); \ } \ } else { \ *(fmt)->end++ = (c); \ } \ } else #define BPUTNULL(ctx, loc, fmt) \ if (1) { \ if ((fmt)->end > (fmt)->endbuf) { \ if (growBuf(MPR_LOC_PASS(ctx, loc), fmt)) { \ *(fmt)->end = '\0'; \ } \ } else { \ *(fmt)->end = '\0'; \ } \ } else /******************************************************************************/ #if BLD_FEATURE_INT64 #define unum uint64 #define num int64 #else #define unum uint #define num int #endif /***************************** Forward Declarations ***************************/ #ifdef __cplusplus extern "C" { #endif static int getState(char c, int state); static int mprSprintfCore(MPR_LOC_DEC(ctx, loc), char **s, int maxsize, const char *fmt, va_list arg); static void outNum(MPR_LOC_DEC(ctx, loc), Format *fmt, const char *prefix, unum val); #if BLD_FEATURE_FLOATING_POINT static void outFloat(MPR_LOC_DEC(ctx, loc), Format *fmt, char specChar, double value); #endif /******************************************************************************/ int mprPrintf(MprCtx ctx, const char *fmt, ...) { va_list ap; char *buf; int len; MprApp *app; /* No asserts here as this is used as part of assert reporting */ app = mprGetApp(ctx); va_start(ap, fmt); len = mprAllocVsprintf(MPR_LOC_ARGS(ctx), &buf, 0, fmt, ap); va_end(ap); if (len >= 0 && app->console) { len = mprWrite(app->console, buf, len); } mprFree(buf); return len; } /******************************************************************************/ int mprErrorPrintf(MprCtx ctx, const char *fmt, ...) { va_list ap; char *buf; int len; MprApp *app; /* No asserts here as this is used as part of assert reporting */ app = mprGetApp(ctx); va_start(ap, fmt); len = mprAllocVsprintf(MPR_LOC_ARGS(ctx), &buf, 0, fmt, ap); va_end(ap); if (len >= 0 && app->error) { len = mprWrite(app->error, buf, len); } mprFree(buf); return len; } /******************************************************************************/ int mprFprintf(MprFile *file, const char *fmt, ...) { va_list ap; char *buf; int len; if (file == 0) { return MPR_ERR_BAD_HANDLE; } va_start(ap, fmt); len = mprAllocVsprintf(MPR_LOC_ARGS(file), &buf, 0, fmt, ap); va_end(ap); if (len >= 0) { len = mprWrite(file, buf, len); } mprFree(buf); return len; } /******************************************************************************/ /* * Printf with a static buffer. Used internally only. WILL NOT MALLOC. */ int mprStaticPrintf(MprCtx ctx, const char *fmt, ...) { va_list ap; char buf[MPR_MAX_STRING]; char *bufp; int len; MprApp *app; app = mprGetApp(ctx); va_start(ap, fmt); bufp = buf; len = mprSprintfCore(MPR_LOC_ARGS(0), &bufp, MPR_MAX_STRING, fmt, ap); va_end(ap); if (len >= 0) { len = mprWrite(app->console, buf, len); } return len; } /******************************************************************************/ int mprSprintf(char *buf, int n, const char *fmt, ...) { va_list ap; int result; mprAssert(buf); mprAssert(fmt); mprAssert(n > 0); va_start(ap, fmt); result = mprSprintfCore(MPR_LOC_ARGS(0), &buf, n, fmt, ap); va_end(ap); return result; } /******************************************************************************/ int mprVsprintf(char *buf, int n, const char *fmt, va_list arg) { mprAssert(buf); mprAssert(fmt); mprAssert(n > 0); return mprSprintfCore(MPR_LOC_ARGS(0), &buf, n, fmt, arg); } /******************************************************************************/ int mprAllocSprintf(MPR_LOC_DEC(ctx, loc), char **buf, int maxSize, const char *fmt, ...) { va_list ap; int result; mprAssert(buf); mprAssert(fmt); *buf = 0; va_start(ap, fmt); result = mprSprintfCore(MPR_LOC_PASS(ctx, loc), buf, maxSize, fmt, ap); va_end(ap); return result; } /******************************************************************************/ int mprAllocVsprintf(MPR_LOC_DEC(ctx, loc), char **buf, int maxSize, const char *fmt, va_list arg) { mprAssert(buf); mprAssert(fmt); *buf = 0; return mprSprintfCore(MPR_LOC_PASS(ctx, loc), buf, maxSize, fmt, arg); } /******************************************************************************/ static int getState(char c, int state) { /* * Declared here to remove all static / globals * FUTURE OPT -- need to measure this. Could be slow on BREW. */ char stateMap[] = { /* STATES: Normal Percent Modifier Width Dot Prec Bits Type */ /* CLASS 0 1 2 3 4 5 6 7 */ /* Normal 0 */ 0, 0, 0, 0, 0, 0, 0, 0, /* Percent 1 */ 1, 0, 1, 1, 1, 1, 1, 1, /* Modifier 2 */ 0, 2, 2, 0, 0, 0, 0, 0, /* Zero 3 */ 0, 2, 2, 3, 0, 5, 0, 0, /* Star 4 */ 0, 3, 3, 0, 5, 0, 0, 0, /* Digit 5 */ 0, 3, 3, 3, 5, 5, 0, 0, /* Dot 6 */ 0, 4, 4, 4, 0, 0, 0, 0, /* Bits 7 */ 0, 6, 6, 6, 6, 6, 6, 0, /* Types 8 */ 0, 7, 7, 7, 7, 7, 7, 0, }; /* * Format: %[modifier][width][precision][bits][type] */ char classMap[] = { /* 0 ' ' ! " # $ % & ' */ 2, 0, 0, 2, 0, 1, 0, 0, /* 07 ( ) * + , - . / */ 0, 0, 4, 2, 2, 2, 6, 0, /* 10 0 1 2 3 4 5 6 7 */ 3, 5, 5, 5, 5, 5, 5, 5, /* 17 8 9 : ; < = > ? */ 5, 5, 0, 0, 0, 0, 0, 0, /* 20 @ A B C D E F G */ 0, 0, 0, 0, 0, 0, 0, 0, /* 27 H I J K L M N O */ 0, 0, 0, 0, 7, 0, 0, 0, /* 30 P Q R S T U V W */ 0, 0, 0, 8, 0, 0, 0, 0, /* 37 X Y Z [ \ ] ^ _ */ 8, 0, 0, 0, 0, 0, 0, 0, /* 40 ' a b c d e f g */ 0, 0, 0, 8, 8, 0, 8, 0, /* 47 h i j k l m n o */ 7, 8, 0, 0, 7, 0, 8, 8, /* 50 p q r s t u v w */ 8, 0, 0, 8, 0, 8, 0, 0, /* 57 x y z */ 8, 0, 0, }; int chrClass; if (c < ' ' || c > 'z') { chrClass = CLASS_NORMAL; } else { mprAssert((c - ' ') < (int) sizeof(classMap)); chrClass = classMap[(c - ' ')]; } mprAssert((chrClass * STATE_COUNT + state) < (int) sizeof(stateMap)); state = stateMap[chrClass * STATE_COUNT + state]; return state; } /******************************************************************************/ static int mprSprintfCore(MPR_LOC_DEC(ctx, loc), char **bufPtr, int maxsize, const char *spec, va_list arg) { Format fmt; char *cp; char c; char *sValue; num iValue; unum uValue; int count, i, len, state; mprAssert(bufPtr); mprAssert(spec); if (*bufPtr != 0) { mprAssert(maxsize > 0); fmt.buf = (uchar*) *bufPtr; fmt.endbuf = &fmt.buf[maxsize]; fmt.growBy = 0; } else { if (maxsize <= 0) { maxsize = MAXINT; } len = min(MPR_DEFAULT_ALLOC, maxsize); fmt.buf = (uchar*) mprAllocBlock(MPR_LOC_PASS(ctx, loc), len); fmt.endbuf = &fmt.buf[len]; fmt.growBy = MPR_DEFAULT_ALLOC * 2; } fmt.maxsize = maxsize; fmt.start = fmt.buf; fmt.end = fmt.buf; fmt.len = 0; *fmt.start = '\0'; state = STATE_NORMAL; while ((c = *spec++) != '\0') { state = getState(c, state); switch (state) { case STATE_NORMAL: BPUT(ctx, loc, &fmt, c); break; case STATE_PERCENT: fmt.precision = -1; fmt.width = 0; fmt.flags = 0; break; case STATE_MODIFIER: switch (c) { case '+': fmt.flags |= SPRINTF_SIGN; break; case '-': fmt.flags |= SPRINTF_LEFT; break; case '#': fmt.flags |= SPRINTF_ALTERNATE; break; case '0': fmt.flags |= SPRINTF_LEAD_ZERO; break; case ' ': fmt.flags |= SPRINTF_LEAD_SPACE; break; case ',': fmt.flags |= SPRINTF_COMMA; break; } break; case STATE_WIDTH: if (c == '*') { fmt.width = va_arg(arg, int); if (fmt.width < 0) { fmt.width = -fmt.width; fmt.flags |= SPRINTF_LEFT; } } else { while (isdigit((int)c)) { fmt.width = fmt.width * 10 + (c - '0'); c = *spec++; } spec--; } break; case STATE_DOT: fmt.precision = 0; fmt.flags &= ~SPRINTF_LEAD_ZERO; break; case STATE_PRECISION: if (c == '*') { fmt.precision = va_arg(arg, int); } else { while (isdigit((int) c)) { fmt.precision = fmt.precision * 10 + (c - '0'); c = *spec++; } spec--; } break; case STATE_BITS: switch (c) { #if BLD_FEATURE_INT64 case 'L': fmt.flags |= SPRINTF_LONGLONG; /* 64 bit */ break; #endif case 'l': fmt.flags |= SPRINTF_LONG; break; case 'h': fmt.flags |= SPRINTF_SHORT; break; } break; case STATE_TYPE: switch (c) { #if BLD_FEATURE_FLOATING_POINT case 'e': case 'g': case 'f': fmt.radix = 10; outFloat(MPR_LOC_PASS(ctx, loc), &fmt, c, (double) va_arg(arg, double)); break; #endif case 'c': BPUT(ctx, loc, &fmt, (char) va_arg(arg, int)); break; case 's': case 'S': sValue = va_arg(arg, char*); if (sValue == 0) { sValue = "null"; len = strlen(sValue); } else if (fmt.flags & SPRINTF_ALTERNATE) { sValue++; len = (int) *sValue; } else if (fmt.precision >= 0) { /* * Can't use strlen(), the string may not have a null */ cp = sValue; for (len = 0; len < fmt.precision; len++) { if (*cp++ == '\0') { break; } } } else { len = strlen(sValue); } if (!(fmt.flags & SPRINTF_LEFT)) { for (i = len; i < fmt.width; i++) { BPUT(ctx, loc, &fmt, (char) ' '); } } for (i = 0; i < len && *sValue; i++) { BPUT(ctx, loc, &fmt, *sValue++); } if (fmt.flags & SPRINTF_LEFT) { for (i = len; i < fmt.width; i++) { BPUT(ctx, loc, &fmt, (char) ' '); } } break; case 'i': ; case 'd': fmt.radix = 10; if (fmt.flags & SPRINTF_SHORT) { iValue = (short) va_arg(arg, int); } else if (fmt.flags & SPRINTF_LONG) { iValue = va_arg(arg, long); #if BLD_FEATURE_INT64 } else if (fmt.flags & SPRINTF_LONGLONG) { iValue = va_arg(arg, num); #endif } else { iValue = va_arg(arg, int); } if (iValue >= 0) { if (fmt.flags & SPRINTF_LEAD_SPACE) { outNum(MPR_LOC_PASS(ctx, loc), &fmt, " ", iValue); } else if (fmt.flags & SPRINTF_SIGN) { outNum(MPR_LOC_PASS(ctx, loc), &fmt, "+", iValue); } else { outNum(MPR_LOC_PASS(ctx, loc), &fmt, 0, iValue); } } else { outNum(MPR_LOC_PASS(ctx, loc), &fmt, "-", -iValue); } break; case 'X': fmt.flags |= SPRINTF_UPPER_CASE; /* Fall through */ case 'o': case 'x': case 'u': if (fmt.flags & SPRINTF_SHORT) { uValue = (ushort) va_arg(arg, uint); } else if (fmt.flags & SPRINTF_LONG) { uValue = va_arg(arg, ulong); #if BLD_FEATURE_INT64 } else if (fmt.flags & SPRINTF_LONGLONG) { uValue = va_arg(arg, unum); #endif } else { uValue = va_arg(arg, uint); } if (c == 'u') { fmt.radix = 10; outNum(MPR_LOC_PASS(ctx, loc), &fmt, 0, uValue); } else if (c == 'o') { fmt.radix = 8; if (fmt.flags & SPRINTF_ALTERNATE && uValue != 0) { outNum(MPR_LOC_PASS(ctx, loc), &fmt, "0", uValue); } else { outNum(MPR_LOC_PASS(ctx, loc), &fmt, 0, uValue); } } else { fmt.radix = 16; if (fmt.flags & SPRINTF_ALTERNATE && uValue != 0) { if (c == 'X') { outNum(MPR_LOC_PASS(ctx, loc), &fmt, "0X", uValue); } else { outNum(MPR_LOC_PASS(ctx, loc), &fmt, "0x", uValue); } } else { outNum(MPR_LOC_PASS(ctx, loc), &fmt, 0, uValue); } } break; case 'n': /* Count of chars seen thus far */ if (fmt.flags & SPRINTF_SHORT) { short *count = va_arg(arg, short*); *count = fmt.end - fmt.start; } else if (fmt.flags & SPRINTF_LONG) { long *count = va_arg(arg, long*); *count = fmt.end - fmt.start; } else { int *count = va_arg(arg, int *); *count = fmt.end - fmt.start; } break; case 'p': /* Pointer */ #if __WORDSIZE == 64 && BLD_FEATURE_INT64 uValue = (unum) va_arg(arg, void*); #else uValue = (uint) (int) va_arg(arg, void*); #endif fmt.radix = 16; outNum(MPR_LOC_PASS(ctx, loc), &fmt, "0x", uValue); break; default: BPUT(ctx, loc, &fmt, c); } } } BPUTNULL(ctx, loc, &fmt); count = fmt.end - fmt.start; if (*bufPtr == 0) { *bufPtr = (char*) fmt.buf; } return count; } /******************************************************************************/ /* * Output a number according to the given format. If BLD_FEATURE_INT64 is * defined, then uses 64 bits universally. Slower but smaller code. */ static void outNum(MPR_LOC_DEC(ctx, loc), Format *fmt, const char *prefix, unum value) { char numBuf[64]; char *cp; char *endp; char c; int letter, len, leadingZeros, i, fill; endp = &numBuf[sizeof(numBuf) - 1]; *endp = '\0'; cp = endp; /* * Convert to ascii */ if (fmt->radix == 16) { do { letter = (int) (value % fmt->radix); if (letter > 9) { if (fmt->flags & SPRINTF_UPPER_CASE) { letter = 'A' + letter - 10; } else { letter = 'a' + letter - 10; } } else { letter += '0'; } *--cp = letter; value /= fmt->radix; } while (value > 0); } else if (fmt->flags & SPRINTF_COMMA) { i = 1; do { *--cp = '0' + (int) (value % fmt->radix); value /= fmt->radix; if ((i++ % 3) == 0 && value > 0) { *--cp = ','; } } while (value > 0); } else { do { *--cp = '0' + (int) (value % fmt->radix); value /= fmt->radix; } while (value > 0); } len = endp - cp; fill = fmt->width - len; if (prefix != 0) { fill -= strlen(prefix); } leadingZeros = (fmt->precision > len) ? fmt->precision - len : 0; fill -= leadingZeros; if (!(fmt->flags & SPRINTF_LEFT)) { c = (fmt->flags & SPRINTF_LEAD_ZERO) ? '0': ' '; for (i = 0; i < fill; i++) { BPUT(ctx, loc, fmt, c); } } if (prefix != 0) { while (*prefix) { BPUT(ctx, loc, fmt, *prefix++); } } for (i = 0; i < leadingZeros; i++) { BPUT(ctx, loc, fmt, '0'); } while (*cp) { BPUT(ctx, loc, fmt, *cp); cp++; } if (fmt->flags & SPRINTF_LEFT) { for (i = 0; i < fill; i++) { BPUT(ctx, loc, fmt, ' '); } } } /******************************************************************************/ #if BLD_FEATURE_FLOATING_POINT /* * Output a floating point number */ static void outFloat(MPR_LOC_DEC(ctx, loc), Format *fmt, char specChar, double value) { char *cp; #if FUTURE char numBuf[64]; char *endp; char c; int letter, len, leadingZeros, i, fill, width, precision; endp = &numBuf[sizeof(numBuf) - 1]; *endp = '\0'; precision = fmt->precision; if (precision < 0) { precision = 6; } else if (precision > (sizeof(numBuf) - 1)) { precision = (sizeof(numBuf) - 1); } width = min(fmt->width, sizeof(numBuf) - 1); if (__isnanl(value)) { "nan" } else if (__isinfl(value)) { "infinity" } else if (value < 0) { prefix = "-"; } else if (fmt.flags & SPRINTF_LEAD_SPACE) { prefix = " "; } else if (fmt.flags & SPRINTF_SIGN) { prefix = "+"; } /* * Do the exponent part */ cp = &numBuf[sizeof(numBuf) - precision]; for (i = 0; i < precision; i++) { *cp++ = '0' + (int) (value % fmt->radix); value /= fmt->radix; } /* * Do the decimal part */ if (fmt->flags & SPRINTF_COMMA) { i = 1; do { *--cp = '0' + (int) (value % fmt->radix); value /= fmt->radix; if ((i++ % 3) == 0 && value > 0) { *--cp = ','; } } while (value >= 1.0); } else { do { *--cp = '0' + (int) (value % fmt->radix); value /= fmt->radix; } while (value > 1.0); } len = endp - cp; fill = fmt->width - len; if (prefix != 0) { fill -= strlen(prefix); } leadingZeros = (fmt->precision > len) ? fmt->precision - len : 0; fill -= leadingZeros; if (!(fmt->flags & SPRINTF_LEFT)) { c = (fmt->flags & SPRINTF_LEAD_ZERO) ? '0': ' '; for (i = 0; i < fill; i++) { BPUT(ctx, loc, fmt, c); } } if (prefix != 0) { BPUT(ctx, loc, fmt, prefix); } for (i = 0; i < leadingZeros; i++) { BPUT(ctx, loc, fmt, '0'); } BPUT(ctx, loc, fmt, cp); if (fmt->flags & SPRINTF_LEFT) { for (i = 0; i < fill; i++) { BPUT(ctx, loc, fmt, ' '); } } #else char numBuf[64]; if (specChar == 'f') { sprintf(numBuf, "%*.*f", fmt->width, fmt->precision, value); } else if (specChar == 'g') { sprintf(numBuf, "%*.*g", fmt->width, fmt->precision, value); } else if (specChar == 'e') { sprintf(numBuf, "%*.*e", fmt->width, fmt->precision, value); } for (cp = numBuf; *cp; cp++) { BPUT(ctx, loc, fmt, *cp); } #endif } #endif /* BLD_FEATURE_FLOATING_POINT */ /******************************************************************************/ /* * Grow the buffer to fit new data. Return 1 if the buffer can grow. * Grow using the growBy size specified when creating the buffer. */ static int growBuf(MPR_LOC_DEC(ctx, loc), Format *fmt) { uchar *newbuf; int buflen; buflen = fmt->endbuf - fmt->buf; if (fmt->maxsize >= 0 && buflen >= fmt->maxsize) { return 0; } if (fmt->growBy < 0) { /* * User supplied buffer */ return 0; } newbuf = (uchar*) mprAlloc(ctx, buflen + fmt->growBy); if (fmt->buf) { memcpy(newbuf, fmt->buf, buflen); mprFree(fmt->buf); } buflen += fmt->growBy; fmt->end = newbuf + (fmt->end - fmt->buf); fmt->start = newbuf + (fmt->start - fmt->buf); fmt->buf = newbuf; fmt->endbuf = &fmt->buf[buflen]; /* * Increase growBy to reduce overhead */ if ((buflen + (fmt->growBy * 2)) < fmt->maxsize) { fmt->growBy *= 2; } return 1; } /******************************************************************************/ /* * Local variables: * tab-width: 4 * c-basic-offset: 4 * End: * vim:tw=78 * vim600: sw=4 ts=4 fdm=marker * vim<600: sw=4 ts=4 */