diff --git a/firmware/SOURCES b/firmware/SOURCES index 9337c14234..1310fa0434 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -204,7 +204,7 @@ common/fileobj_mgr.c common/dircache.c #endif /* HAVE_DIRCACHE */ common/pathfuncs.c -common/format.c +common/fdprintf.c common/linked_list.c common/strcasecmp.c common/strcasestr.c @@ -214,6 +214,7 @@ common/strlcpy.c common/structec.c common/timefuncs.c common/unicode.c +common/vuprintf.c /* Display */ scroll_engine.c diff --git a/firmware/common/fdprintf.c b/firmware/common/fdprintf.c new file mode 100644 index 0000000000..21d0a72e58 --- /dev/null +++ b/firmware/common/fdprintf.c @@ -0,0 +1,56 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2002 by Felix Arends + * + * 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. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include +#include "file.h" +#include "vuprintf.h" + +struct for_fprintf { + int fd; /* where to store it */ + int rem; /* amount remaining */ +}; + +static int fprfunc(void *pr, int letter) +{ + struct for_fprintf *fpr = (struct for_fprintf *)pr; + + /* TODO: add a small buffer to reduce write() calls */ + if (write(fpr->fd, &(char){ letter }, 1) > 0) { + return --fpr->rem; + } + + return -1; +} + +int fdprintf(int fd, const char *fmt, ...) +{ + int bytes; + struct for_fprintf fpr; + va_list ap; + + fpr.fd = fd; + fpr.rem = INT_MAX; + + va_start(ap, fmt); + bytes = vuprintf(fprfunc, &fpr, fmt, ap); + va_end(ap); + + return bytes; +} diff --git a/firmware/common/format.c b/firmware/common/format.c deleted file mode 100644 index 60c50ccd89..0000000000 --- a/firmware/common/format.c +++ /dev/null @@ -1,267 +0,0 @@ -/*************************************************************************** - * __________ __ ___. - * Open \______ \ ____ ____ | | _\_ |__ _______ ___ - * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / - * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < - * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ - * \/ \/ \/ \/ \/ - * $Id$ - * - * Copyright (C) 2002 by Gary Czvitkovicz - * - * 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. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - ****************************************************************************/ - - -#include -#include -#include -#include -#include "file.h" -#include "format.h" - -static const char hexdigit[] = "0123456789ABCDEF"; - -void format( - /* call 'push()' for each output letter */ - int (*push)(void *userp, unsigned char data), - void *userp, - const char *fmt, - va_list ap) -{ - char *str; - char tmpbuf[12], pad; - int ch, width, val, sign, precision; - long lval, lsign; - unsigned int uval; - unsigned long ulval; - size_t uszval; - ssize_t szval, szsign; - bool ok = true; - - tmpbuf[sizeof tmpbuf - 1] = '\0'; - - while ((ch = *fmt++) != '\0' && ok) - { - if (ch == '%') - { - ch = *fmt++; - pad = ' '; - if (ch == '0') - pad = '0'; - - width = 0; - while (ch >= '0' && ch <= '9') - { - width = 10*width + ch - '0'; - ch = *fmt++; - } - - precision = 0; - if(ch == '.') - { - ch = *fmt++; - while (ch >= '0' && ch <= '9') - { - precision = 10*precision + ch - '0'; - ch = *fmt++; - } - } else { - precision = INT_MAX; - } - - str = tmpbuf + sizeof tmpbuf - 1; - switch (ch) - { - case 'c': - *--str = va_arg (ap, int); - break; - - case 's': - str = va_arg (ap, char*); - break; - - case 'd': - val = sign = va_arg (ap, int); - if (val < 0) - val = -val; - do - { - *--str = (val % 10) + '0'; - val /= 10; - } - while (val > 0); - if (sign < 0) - *--str = '-'; - break; - - case 'u': - uval = va_arg(ap, unsigned int); - do - { - *--str = (uval % 10) + '0'; - uval /= 10; - } - while (uval > 0); - break; - case 'p': - case 'P': - /* for pointers prepend 0x and act like 'X' */ - push(userp, '0'); - push(userp, 'x'); - /* fall through */ - case 'x': - case 'X': - pad='0'; - uval = va_arg (ap, int); - do - { - *--str = hexdigit[uval & 0xf]; - uval >>= 4; - } - while (uval); - break; - - case 'l': - ch = *fmt++; - switch(ch) { - case 'x': - case 'X': - pad='0'; - ulval = va_arg (ap, long); - do - { - *--str = hexdigit[ulval & 0xf]; - ulval >>= 4; - } - while (ulval); - break; - case 'd': - lval = lsign = va_arg (ap, long); - if (lval < 0) - lval = -lval; - do - { - *--str = (lval % 10) + '0'; - lval /= 10; - } - while (lval > 0); - if (lsign < 0) - *--str = '-'; - break; - - case 'u': - ulval = va_arg(ap, unsigned long); - do - { - *--str = (ulval % 10) + '0'; - ulval /= 10; - } - while (ulval > 0); - break; - - default: - *--str = 'l'; - *--str = ch; - } - - break; - - case 'z': - ch = *fmt++; - switch(ch) { - case 'd': - szval = szsign = va_arg (ap, ssize_t); - if (szval < 0) - szval = -szval; - do - { - *--str = (szval % 10) + '0'; - szval /= 10; - } - while (szval > 0); - if (szsign < 0) - *--str = '-'; - break; - - case 'u': - uszval = va_arg(ap, size_t); - do - { - *--str = (uszval % 10) + '0'; - uszval /= 10; - } - while (uszval > 0); - break; - - default: - *--str = 'z'; - *--str = ch; - } - - break; - - default: - *--str = ch; - break; - } - - if (width > 0) - { - width -= strlen (str); - while (width-- > 0 && ok) - ok=push(userp, pad); - } - while (*str != '\0' && ok && precision--) - ok=push(userp, *str++); - } - else - ok=push(userp, ch); - } -} - -struct for_fprintf { - int fd; /* where to store it */ - int bytes; /* amount stored */ -}; - -static int fprfunc(void *pr, unsigned char letter) -{ - struct for_fprintf *fpr = (struct for_fprintf *)pr; - int rc = write(fpr->fd, &letter, 1); - - if(rc > 0) { - fpr->bytes++; /* count them */ - return true; /* we are ok */ - } - - return false; /* failure */ -} - - -int fdprintf(int fd, const char *fmt, ...) -{ - va_list ap; - struct for_fprintf fpr; - - fpr.fd=fd; - fpr.bytes=0; - - va_start(ap, fmt); - format(fprfunc, &fpr, fmt, ap); - va_end(ap); - - return fpr.bytes; /* return 0 on error */ -} - -void vuprintf(int (*push)(void *userp, unsigned char data), void *userp, const char *fmt, va_list ap) -{ - format(push, userp, fmt, ap); -} diff --git a/firmware/common/vuprintf.c b/firmware/common/vuprintf.c new file mode 100644 index 0000000000..c32b690cf8 --- /dev/null +++ b/firmware/common/vuprintf.c @@ -0,0 +1,974 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2002 by Gary Czvitkovicz + * Copyright (C) 2017 by Michael A. Sevakis + * + * 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. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include +#include +#include +#include "system.h" +#include "vuprintf.h" + +#if 0 +/* turn everything on */ +#define FMT_LENMOD (0xffffffff) +#define FMT_RADIX (0xffffffff) +#endif + +/* these are the defaults if no other preference is given */ +#ifndef FMT_LENMOD +#define FMT_LENMOD (FMT_LENMOD_l | \ + FMT_LENMOD_z) +#endif /* FMT_LENMOD */ + +#ifndef FMT_RADIX +#define FMT_RADIX (FMT_RADIX_c | \ + FMT_RADIX_d | \ + FMT_RADIX_p | \ + FMT_RADIX_s | \ + FMT_RADIX_u | \ + FMT_RADIX_x) +#endif /* FMT_RADIX */ + +/** Length modifier and radix flags **/ + +/* compulsory length modifiers: NONE + * however a compatible 'l' or 'll' must be defined if another requires it */ +#define FMT_LENMOD_h 0x001 /* signed/unsigned short (%h) */ +#define FMT_LENMOD_hh 0x002 /* signed/unsigned char (%hh) */ +#define FMT_LENMOD_j 0x004 /* intmax_t/uintmax_t (%j) */ +#define FMT_LENMOD_l 0x008 /* signed/unsigned long (%l) */ +#define FMT_LENMOD_ll 0x010 /* signed/unsigned long long (%ll) */ +#define FMT_LENMOD_t 0x020 /* signed/unsigned ptrdiff_t (%t) */ +#define FMT_LENMOD_z 0x040 /* size_t/ssize_t (%z) */ +#define FMT_LENMOD_L 0x080 /* long double (instead of double) */ + +/* compulsory radixes: c, d, i, u, s */ +#define FMT_RADIX_c 0x001 /* single character (%c) */ +#define FMT_RADIX_d 0x002 /* signed integer type, decimal (%d %i) */ +#define FMT_RADIX_n 0x004 /* bytes output so far (%n) */ +#define FMT_RADIX_o 0x008 /* unsigned integer type, octal (%o) */ +#define FMT_RADIX_p 0x010 /* pointer (%p %P) */ +#define FMT_RADIX_s 0x020 /* string (%s) */ +#define FMT_RADIX_u 0x040 /* unsigned integer type, decimal (%u) */ +#define FMT_RADIX_x 0x080 /* unsigned integer type, hex (%x %X) */ +#define FMT_RADIX_a 0x100 /* hex floating point "[-]0xh.hhhhp±d" */ +#define FMT_RADIX_e 0x200 /* floating point with exponent "[-]d.ddde±dd" */ +#define FMT_RADIX_f 0x400 /* floating point "[-]ddd.ddd" */ +#define FMT_RADIX_g 0x800 /* floating point exponent or decimal depending + upon value and precision */ + +/* avoid defining redundant functions if two or more types can use the same + * something not getting a macro means it gets assigned its own value and + * formatter */ + +/* l */ +#if LONG_MIN == INT_MIN && LONG_MAX == INT_MAX +#define val_ld val_d +#define format_ld format_d +#define branch_fmt_ld branch_fmt_d +#elif !(FMT_LENMOD & FMT_LENMOD_l) /* unique */ +#define val_ld +#endif /* LONG_ */ + +#if ULONG_MAX == UINT_MAX +#define val_lu val_u +#define format_lu format_u +#define branch_fmt_lu branch_fmt_u +#elif !(FMT_LENMOD & FMT_LENMOD_l) /* unique */ +#define val_lu +#endif /* ULONG_ */ + +/* ll */ +#if LLONG_MIN == INT_MIN && LLONG_MAX == INT_MAX +#define val_lld val_d +#define format_lld format_d +#define branch_fmt_lld branch_fmt_d +#elif LLONG_MIN == LONG_MIN && LLONG_MAX == LONG_MAX +#define val_lld val_ld +#define format_lld format_ld +#define branch_fmt_lld branch_fmt_ld +#elif !(FMT_LENMOD & FMT_LENMOD_ll) /* unique */ +#define val_lld +#endif /* LLONG_ */ + +#if ULLONG_MAX == UINT_MAX +#define val_llu val_u +#define format_llu format_u +#define branch_fmt_llu branch_fmt_u +#elif ULLONG_MAX == ULONG_MAX +#define val_llu val_lu +#define format_llu format_lu +#define branch_fmt_llu branch_fmt_lu +#elif !(FMT_LENMOD & FMT_LENMOD_ll) /* unique */ +#define val_llu +#endif /* ULLONG_ */ + +/* char/short parameter type promotions */ +#define SCHAR_INT_ARG int +#define UCHAR_INT_ARG int +#define SSHRT_INT_ARG int +#if USHRT_MAX == UINT_MAX +#define USHRT_INT_ARG unsigned int +#else +#define USHRT_INT_ARG int +#endif + +/* some macros to have conditional work inside macros */ +#if (FMT_LENMOD & FMT_LENMOD_l) +#define IF_FMT_LENMOD_l(...) __VA_ARGS__ +#else +#define IF_FMT_LENMOD_l(...) +#endif + +#if (FMT_LENMOD & FMT_LENMOD_ll) +#define IF_FMT_LENMOD_ll(...) __VA_ARGS__ +#else +#define IF_FMT_LENMOD_ll(...) +#endif + +#if (FMT_RADIX & FMT_RADIX_o) +#define IF_FMT_RADIX_o(...) __VA_ARGS__ +#else +#define IF_FMT_RADIX_o(...) +#endif + +#if (FMT_RADIX & FMT_RADIX_x) +#define IF_FMT_RADIX_x(...) __VA_ARGS__ +#else +#define IF_FMT_RADIX_x(...) +#endif + +/* synthesize multicharacter constant */ +#define LENMOD2(cv, ch) \ + (((cv) << CHAR_BIT) | (ch)) + +#define LENMOD_NONE 0 + +#if (FMT_LENMOD & FMT_LENMOD_h) +#define LENMOD_h 'h' +#endif +#if (FMT_LENMOD & FMT_LENMOD_hh) +#define LENMOD_hh LENMOD2('h', 'h') /* 'hh' */ +#endif +#if (FMT_LENMOD & FMT_LENMOD_j) +#define LENMOD_j 'j' +#endif +#if (FMT_LENMOD & FMT_LENMOD_l) +#define LENMOD_l 'l' +#endif +#if (FMT_LENMOD & FMT_LENMOD_ll) +#undef FMT_MAX_L +#define LENMOD_ll LENMOD2('l', 'l') /* 'll' */ +#endif +#if (FMT_LENMOD & FMT_LENMOD_t) +#define LENMOD_t 't' +#endif +#if (FMT_LENMOD & FMT_LENMOD_z) +#define LENMOD_z 'z' +#endif + +/* select type-compatible length modifier + * (a bit hacky; it should be range-based) */ +#define LENMOD_INTCOMPAT_SEL(type, signd) \ + ({ int __lenmod; \ + size_t __size = sizeof (type); \ + if (__size == ((signd) ? sizeof (int) : \ + sizeof (unsigned int))) { \ + __lenmod = LENMOD_NONE; \ + } \ + else if (__size == ((signd) ? sizeof (long) : \ + sizeof (unsigned long))) { \ + IF_FMT_LENMOD_l(__lenmod = LENMOD_l;) \ + } \ + else if (__size == ((signd) ? sizeof (long long) : \ + sizeof (unsigned long long))) { \ + IF_FMT_LENMOD_ll(__lenmod = LENMOD_ll;) \ + } \ + __lenmod; }) + +/* call formatting function for the compatible integer type */ +#define LENMOD_INTCOMPAT_CALL(inteqv, val, fmt_buf, x, signd) \ + ({ const char *__buf; \ + switch (inteqv) { \ + case LENMOD_NONE: \ + __buf = (signd) ? \ + format_d((val), (fmt_buf), (x)) : \ + format_u((val), (fmt_buf), (x)); \ + break; \ + IF_FMT_LENMOD_l( \ + case LENMOD_l: \ + __buf = (signd) ? \ + format_ld((val), (fmt_buf), (x)) : \ + format_lu((val), (fmt_buf), (x)); \ + break; \ + ) \ + IF_FMT_LENMOD_ll( \ + case LENMOD_ll: \ + __buf = (signd) ? \ + format_lld((val), (fmt_buf), (x)) : \ + format_llu((val), (fmt_buf), (x)); \ + break; \ + ) \ + } \ + __buf; \ + }) + +/* execute formatting branch for the compatible integer type */ +#define LENMOD_INTCOMPAT_BRANCH(inteqv, val, signd) \ + ({ switch (inteqv) { \ + case LENMOD_NONE: \ + if (signd) { \ + val_d = (val); \ + goto branch_fmt_d; \ + } \ + else { \ + val_u = (val); \ + goto branch_fmt_u; \ + } \ + IF_FMT_LENMOD_l( \ + case LENMOD_l: \ + if (signd) { \ + val_ld = (val); \ + goto branch_fmt_ld; \ + } \ + else { \ + val_lu = (val); \ + goto branch_fmt_lu; \ + } \ + ) \ + IF_FMT_LENMOD_ll( \ + case LENMOD_ll: \ + if (signd) { \ + val_lld = (val); \ + goto branch_fmt_lld; \ + } \ + else { \ + val_llu = (val); \ + goto branch_fmt_llu; \ + } \ + ) \ + } \ + }) + +#define CONVERT_RADIX_10_SIGN(val, fmt_buf, p, signchar, type) \ + do { \ + if (val) { \ + unsigned type v; \ + \ + if (val < 0) { \ + v = (typeof (v))-(val + 1) + 1; \ + signchar = '-'; \ + } \ + else { \ + v = val; \ + } \ + \ + do { \ + *--p = (v % 10) + '0'; \ + v /= 10; \ + } while (v); \ + } \ + \ + if (signchar) { \ + p[-1] = signchar; \ + fmt_buf->length = 1; \ + break; \ + } \ + \ + fmt_buf->length = 0; \ + } while (0) + +#define CONVERT_RADIX_8(val, fmt_buf, p) \ + do { \ + if (val) { \ + typeof (val) v = val; \ + \ + do { \ + *--p = (v % 010) + '0'; \ + v /= 010; \ + } while (v); \ + } \ + \ + if (fmt_buf->length) { \ + *--p = '0'; \ + fmt_buf->length = 0; \ + } \ + } while (0) + +#define CONVERT_RADIX_10(val, fmt_buf, p) \ + do { \ + if (val) { \ + typeof (val) v = val; \ + \ + do { \ + *--p = (v % 10) + '0'; \ + v /= 10; \ + } while (v); \ + } \ + \ + fmt_buf->length = 0; \ + } while (0) + +#define CONVERT_RADIX_16(val, fmt_buf, p, x) \ + do { \ + if (val) { \ + const int h = x - 'X' - 0xA \ + + 'A' - '0'; \ + typeof (val) v = val; \ + \ + do { \ + unsigned int d = v % 0x10; \ + if (d >= 0xA) { \ + d += h; \ + } \ + *--p = d + '0'; \ + v /= 0x10; \ + } while (v); \ + \ + if (fmt_buf->length) { \ + p[-1] = x; \ + p[-2] = '0'; \ + fmt_buf->length = 2; \ + break; \ + } \ + } \ + \ + fmt_buf->length = 0; \ + } while (0) + +#define CONVERT_RADIX_NOSIGN(val, fmt_buf, p, x) \ + switch (x) \ + { \ + IF_FMT_RADIX_o( case 'o': \ + CONVERT_RADIX_8(val, fmt_buf, p); \ + break; ) \ + case 'u': \ + CONVERT_RADIX_10(val, fmt_buf, p); \ + break; \ + IF_FMT_RADIX_x( default: \ + CONVERT_RADIX_16(val, fmt_buf, p, x); \ + break; ) \ + } + +struct fmt_buf { + const char *fmt_start; /* second character of formatter after '%' */ + size_t length; /* length of formatted text (non-numeric) + or prefix (numeric) */ + char buf[24]; /* work buffer */ + char bufend[1]; /* buffer end marker and guard '0' */ +}; + +/* %d %i */ +static inline const char * format_d(int val, + struct fmt_buf *fmt_buf, + int signchar) +{ + char *p = fmt_buf->bufend; + CONVERT_RADIX_10_SIGN(val, fmt_buf, p, signchar, int); + return p; +} + +/* %o %u %x %X */ +static inline const char * format_u(unsigned int val, + struct fmt_buf *fmt_buf, + int radixchar) +{ + char *p = fmt_buf->bufend; + CONVERT_RADIX_NOSIGN(val, fmt_buf, p, radixchar); + return p; +} + +#if (FMT_LENMOD & FMT_LENMOD_l) +#ifndef format_ld +/* %ld %li */ +static inline const char * format_ld(long val, + struct fmt_buf *fmt_buf, + int signchar) +{ + char *p = fmt_buf->bufend; + CONVERT_RADIX_10_SIGN(val, fmt_buf, p, signchar, long); + return p; +} +#endif /* format_ld */ + +#ifndef format_lu +/* %lo %lu %lx %lX */ +static inline const char * format_lu(unsigned long val, + struct fmt_buf *fmt_buf, + int radixchar) +{ + char *p = fmt_buf->bufend; + CONVERT_RADIX_NOSIGN(val, fmt_buf, p, radixchar); + return p; +} +#endif /* format_lu */ +#endif /* FMT_LENMOD_l */ + +#if (FMT_LENMOD & FMT_LENMOD_ll) +#ifndef format_lld +/* %lld %lli */ +static inline const char * format_lld(long long val, + struct fmt_buf *fmt_buf, + int signchar) +{ + char *p = fmt_buf->bufend; + CONVERT_RADIX_10_SIGN(val, fmt_buf, p, signchar, long long); + return p; +} +#endif /* format_lld */ + +#ifndef format_llu +/* %llo %llu %llx %llX */ +static inline const char * format_llu(unsigned long long val, + struct fmt_buf *fmt_buf, + int radixchar) +{ + char *p = fmt_buf->bufend; + CONVERT_RADIX_NOSIGN(val, fmt_buf, p, radixchar); + return p; +} +#endif /* format_llu */ +#endif /* FMT_LENMOD_ll */ + +/* %c */ +static inline const char * format_c(int c, + struct fmt_buf *fmt_buf, + int lenmod) +{ + if (lenmod != LENMOD_NONE) { + return NULL; /* wchar_t support for now */ + } + + char *p = fmt_buf->bufend; + fmt_buf->length = 1; + *--p = (unsigned char)c; + return p; +} + +/* %s */ +static inline const char * format_s(const void *str, + struct fmt_buf *fmt_buf, + int precision, + int lenmod) +{ + if (lenmod != LENMOD_NONE) { + return NULL; /* wchar_t support for now */ + } + + /* string length may be specified by precision instead of \0- + terminated; however, don't go past a \0 if one is there */ + const char *s = str; + size_t len = precision >= 0 ? precision : -1; + + const char *nil = memchr(s, '\0', len); + if (nil) { + len = nil - s; + } + + fmt_buf->length = len; + return s; +} + +#if (FMT_RADIX & FMT_RADIX_n) +/* %n */ +static inline bool format_n(void *np, + int count, + int lenmod) +{ + if (lenmod != LENMOD_NONE) { + return false; /* int only for now */ + } + + *(int *)np = count; + return true; +} +#endif /* FMT_RADIX_n */ + +#if (FMT_RADIX & FMT_RADIX_p) +/* %p %P */ +static inline const char * format_p(const void *p, + struct fmt_buf *fmt_buf, + int radixchar, + bool *numericp) +{ + if (p) { + /* format as %#x or %#X */ + *numericp = true; + radixchar -= 'P' - 'X'; + fmt_buf->length = 2; + return LENMOD_INTCOMPAT_CALL(LENMOD_INTCOMPAT_SEL(uintptr_t, false), + (uintptr_t)p, fmt_buf, radixchar, false); + } + else { + /* format as %s */ + fmt_buf->length = 5; + return "(nil)"; + } +} +#endif /* FMT_RADIX_p */ + +/* parse fixed width or precision field */ +static const char * parse_number_spec(const char *fmt, + int ch, + int *out) +{ + int i = ch - '0'; + + while (1) { + ch = *fmt - '0'; + + if (ch < 0 || ch > 9 || i > INT_MAX / 10 || + (i == INT_MAX / 10 && ch > INT_MAX % 10)) { + break; + } + + i = i * 10 + ch; + fmt++; + } + + *out = i; + return fmt; +} + +int vuprintf(vuprintf_push_cb push, /* call 'push()' for each output letter */ + void *userp, + const char *fmt, + va_list ap) +{ + #define PUSHCHAR(ch) \ + do { \ + int __ch = (ch); \ + int __rc = push(userp, __ch); \ + count += __rc >= 0; \ + if (__rc <= 0) { \ + goto done; \ + } \ + } while (0) + + int count = 0; + int ch; + + /* macrofied identifiers share a variable with another */ + unsigned int val_d; + unsigned int val_u; + #ifndef val_ld + unsigned long val_ld; + #endif + #ifndef val_lu + unsigned long val_lu; + #endif + #ifndef val_lld + unsigned long long val_lld; + #endif + #ifndef val_llu + unsigned long long val_llu; + #endif + + struct fmt_buf fmt_buf; + fmt_buf.bufend[0] = '0'; + + while (1) { + while (1) { + if ((ch = *fmt++) == '\0') { + goto done; + } + + if (ch == '%' && (ch = *fmt++) != '%') { + break; + } + + PUSHCHAR(ch); + } + + /* set to defaults */ + fmt_buf.fmt_start = fmt; + + int signchar = 0; + unsigned int width = 0; + int lenmod = LENMOD_NONE; + size_t length = 0; + size_t pfxlen = 0; + bool numeric = false; + int alignchar = '0' + 1; + int precision = -1; + const char *buf = NULL; + + /*** flags ***/ + while (1) { + switch (ch) + { + case ' ': /* before non-negative value (signed conversion) */ + case '+': /* '+' before non-negative value (signed conversion) */ + /* '+' overrides ' ' */ + if (ch > signchar) { + signchar = ch; + } + break; + case '-': /* left-justify in field */ + case '0': /* zero-pad to fill field */ + /* '-' overrides '0' */ + if (ch < alignchar) { + alignchar = ch; + } + break; + case '#': /* number prefix (nonzero %o:'0' %x/%X:'0x') */ + /* indicate; formatter updates with actual length */ + pfxlen = 1; + break; + #if 0 + case '\'': /* digit grouping (non-monetary) */ + break; + #endif + default: + goto flags_done; + } + + ch = *fmt++; + } + flags_done: + + /*** width ***/ + if (ch == '*') { + /* variable width */ + int w = va_arg(ap, int); + if (w < 0) { + /* negative width is width with implied '-' */ + width = (unsigned int)-(w + 1) + 1; + alignchar = '-'; + } + else { + width = w; + } + + ch = *fmt++; + } + else if (ch >= '1' && ch <= '9') { + /* fixed width */ + fmt = parse_number_spec(fmt, ch, &width); + ch = *fmt++; + } + + /*** precision ***/ + if (ch == '.') { + ch = *fmt++; + + if (ch == '*') { + /* variable precision; negative precision is ignored */ + precision = va_arg (ap, int); + ch = *fmt++; + } + else if (ch >= '0' && ch <= '9') { + /* fixed precision */ + fmt = parse_number_spec(fmt, ch, &precision); + ch = *fmt++; + } + } + + /*** length modifier ***/ + #if FMT_LENMOD + switch (ch) + { + #if (FMT_LENMOD & (FMT_LENMOD_h | FMT_LENMOD_hh)) + case 'h': + #endif + #if (FMT_LENMOD & FMT_LENMOD_j) + case 'j': + #endif + #if (FMT_LENMOD & (FMT_LENMOD_l | FMT_LENMOD_ll)) + case 'l': + #endif + #if (FMT_LENMOD & FMT_LENMOD_t) + case 't': + #endif + #if (FMT_LENMOD & FMT_LENMOD_z) + case 'z': + #endif + lenmod = ch; + ch = *fmt++; + #if (FMT_LENMOD & (FMT_LENMOD_hh | FMT_LENMOD_ll)) + /* doesn't matter if jj, tt or zz happen; they will be rejected + by the radix handler */ + if (ch == lenmod) { + lenmod = LENMOD2(lenmod, ch); + ch = *fmt++; + } + #endif + } + #endif /* FMT_LENMOD */ + + /*** radix ***/ + switch (ch) + { + /** non-numeric **/ + case 'c': + buf = format_c(va_arg(ap, int), &fmt_buf, lenmod); + break; + #if (FMT_RADIX & FMT_RADIX_n) + case 'n': + if (format_n(va_arg(ap, void *), count, lenmod)) { + continue; /* no output */ + } + break; + #endif + case 's': + buf = format_s(va_arg(ap, const void *), &fmt_buf, + precision, lenmod); + break; + + /** non-integer **/ + #if (FMT_RADIX & FMT_RADIX_p) + case 'p': + case 'P': + buf = format_p(va_arg(ap, void *), &fmt_buf, ch, + &numeric); + break; + #endif + + /** signed integer **/ + case 'd': + case 'i': + fmt_buf.length = pfxlen; + + switch (lenmod) + { + case LENMOD_NONE: + val_d = va_arg(ap, signed int); + goto branch_fmt_d; + #if (FMT_LENMOD & FMT_LENMOD_h) + case LENMOD_h: + val_d = (signed short)va_arg(ap, SSHRT_INT_ARG); + goto branch_fmt_d; + #endif + #if (FMT_LENMOD & FMT_LENMOD_hh) + case LENMOD_hh: + val_d = (signed char)va_arg(ap, SCHAR_INT_ARG); + goto branch_fmt_d; + #endif + #if (FMT_LENMOD & FMT_LENMOD_j) + case LENMOD_j: + LENMOD_INTCOMPAT_BRANCH(LENMOD_INTCOMPAT_SEL(intmax_t, true), + va_arg(ap, intmax_t), true); + #endif + #if (FMT_LENMOD & FMT_LENMOD_l) + case LENMOD_l: + val_ld = va_arg(ap, signed long); + goto branch_fmt_ld; + #endif + #if (FMT_LENMOD & FMT_LENMOD_ll) + case LENMOD_ll: + val_lld = va_arg(ap, signed long long); + goto branch_fmt_lld; + #endif + #if (FMT_LENMOD & FMT_LENMOD_t) + case LENMOD_t: + LENMOD_INTCOMPAT_BRANCH(LENMOD_INTCOMPAT_SEL(ptrdiff_t, true), + va_arg(ap, ptrdiff_t), true); + #endif + #if (FMT_LENMOD & FMT_LENMOD_z) + case LENMOD_z: + LENMOD_INTCOMPAT_BRANCH(LENMOD_INTCOMPAT_SEL(ssize_t, true), + va_arg(ap, ssize_t), true); + #endif + } + + /* macrofied labels share a formatter with another */ + if (0) { + branch_fmt_d: + buf = format_d(val_d, &fmt_buf, signchar); + } else if (0) { + #ifndef val_ld + branch_fmt_ld: + buf = format_ld(val_ld, &fmt_buf, signchar); + #endif + } else if (0) { + #ifndef val_lld + branch_fmt_lld: + buf = format_lld(val_lld, &fmt_buf, signchar); + #endif + } + + numeric = true; + break; + + /** unsigned integer **/ + #if (FMT_RADIX & FMT_RADIX_o) + case 'o': + #endif + case 'u': + #if (FMT_RADIX & FMT_RADIX_x) + case 'x': + case 'X': + #endif + fmt_buf.length = pfxlen; + + switch (lenmod) + { + case LENMOD_NONE: + val_u = va_arg(ap, unsigned int); + goto branch_fmt_u; + #if (FMT_LENMOD & FMT_LENMOD_h) + case LENMOD_h: + val_u = (unsigned short)va_arg(ap, USHRT_INT_ARG); + goto branch_fmt_u; + #endif + #if (FMT_LENMOD & FMT_LENMOD_hh) + case LENMOD_hh: + val_u = (unsigned char)va_arg(ap, UCHAR_INT_ARG); + goto branch_fmt_u; + #endif + #if (FMT_LENMOD & FMT_LENMOD_j) + case LENMOD_j: + LENMOD_INTCOMPAT_BRANCH(LENMOD_INTCOMPAT_SEL(uintmax_t, false), + va_arg(ap, uintmax_t), false); + #endif + #if (FMT_LENMOD & FMT_LENMOD_l) + case LENMOD_l: + val_lu = va_arg(ap, unsigned long); + goto branch_fmt_lu; + #endif + #if (FMT_LENMOD & FMT_LENMOD_ll) + case LENMOD_ll: + val_llu = va_arg(ap, unsigned long long); + goto branch_fmt_llu; + #endif + #if (FMT_LENMOD & (FMT_LENMOD_t | FMT_LENMOD_z)) + /* format "uptrdiff_t" as size_t (unless it becomes standard) */ + #if (FMT_LENMOD & FMT_LENMOD_t) + case LENMOD_t: + #endif + #if (FMT_LENMOD & FMT_LENMOD_z) + case LENMOD_z: + #endif + LENMOD_INTCOMPAT_BRANCH(LENMOD_INTCOMPAT_SEL(size_t, false), + va_arg(ap, size_t), false); + #endif + } + + /* macrofied labels share a formatter with another */ + if (0) { + branch_fmt_u: + buf = format_u(val_u, &fmt_buf, ch); + } else if (0) { + #ifndef val_lu + branch_fmt_lu: + buf = format_lu(val_lu, &fmt_buf, ch); + #endif + } else if (0) { + #ifndef val_llu + branch_fmt_llu: + buf = format_llu(val_llu, &fmt_buf, ch); + #endif + } + + numeric = true; + break; + } + + if (buf) { + /** padding **/ + if (numeric) { + /* numeric formats into fmt_buf.buf */ + pfxlen = fmt_buf.length; + length = fmt_buf.bufend - buf; + + size_t size = pfxlen + length; + + if (precision >= 0) { + /* explicit precision */ + precision -= (int)length; + + if (precision > 0) { + size += precision; + } + + width -= MIN(width, size); + } + else { + /* default precision */ + if (!length) { + length = 1; + size++; + } + + width -= MIN(width, size); + + if (alignchar == '0') { + /* width zero-fill */ + precision = width; + width = 0; + } + } + } + else { + /* non-numeric: supress prefix and precision; keep length and + width */ + pfxlen = 0; + precision = 0; + length = fmt_buf.length; + width -= MIN(width, length); + } + } + else { + /* format not accepted; print it literally */ + buf = fmt_buf.fmt_start - 2; + length = fmt - buf; + width = 0; + pfxlen = 0; + precision = 0; + } + + /** push all the stuff **/ + + if (alignchar != '-') { + /* left padding */ + while (width > 0) { + PUSHCHAR(' '); + width--; + } + } + + /* prefix */ + while (pfxlen > 0) { + PUSHCHAR(buf[-pfxlen]); + pfxlen--; + } + + /* 0-padding */ + while (precision > 0) { + PUSHCHAR('0'); + precision--; + } + + /* field */ + while (length > 0) { + PUSHCHAR(*buf++); + length--; + } + + /* right padding */ + while (width > 0) { + PUSHCHAR(' '); + width--; + } + } + +done: + return count; +} diff --git a/firmware/include/format.h b/firmware/include/vuprintf.h similarity index 59% rename from firmware/include/format.h rename to firmware/include/vuprintf.h index 30a072aca8..3876482fb2 100644 --- a/firmware/include/format.h +++ b/firmware/include/vuprintf.h @@ -19,19 +19,27 @@ * ****************************************************************************/ -#ifndef __FORMAT_H__ -#define __FORMAT_H__ +#ifndef __VUPRINTF_H__ +#define __VUPRINTF_H__ -void format( - /* call 'push()' for each output letter */ - int (*push)(void *userp, unsigned char data), - void *userp, - const char *fmt, - va_list ap); +#include -/* callback function is called for every output character (byte) with userp and - * should return 0 when ch is a char other than '\0' that should stop printing */ -void vuprintf(int (*push)(void *userp, unsigned char data), - void *userp, const char *fmt, va_list ap); +/* callback function is called for every output character (byte) in the + * output with userp + * + * it must return > 0 to continue (increments counter) + * it may return 0 to stop (increments counter) + * it may return < 0 to stop (does not increment counter) + * a zero in the format string stops (does not increment counter) + * + * caller is reponsible for stopping formatting in order to keep the return + * value from overflowing (assuming they have a reason to care) + */ +typedef int (* vuprintf_push_cb)(void *userp, int c); -#endif /* __FORMAT_H__ */ +/* + * returns the number of times push() was called and returned >= 0 + */ +int vuprintf(vuprintf_push_cb push, void *userp, const char *fmt, va_list ap); + +#endif /* __VUPRINTF_H__ */ diff --git a/firmware/libc/sprintf.c b/firmware/libc/sprintf.c index 18e2ce6fd2..a56f454c34 100644 --- a/firmware/libc/sprintf.c +++ b/firmware/libc/sprintf.c @@ -18,74 +18,94 @@ * KIND, either express or implied. * ****************************************************************************/ - -/* - * Minimal printf and snprintf formatting functions - * - * These support %c %s %d and %x - * Field width and zero-padding flag only - */ - #include -#include -#include #include -#include "format.h" +#include +#include "vuprintf.h" /* ALSA library requires a more advanced snprintf, so let's not override it in simulator for Linux. Note that Cygwin requires our snprintf or it produces garbled output after a while. */ struct for_snprintf { - unsigned char *ptr; /* where to store it */ - size_t bytes; /* amount already stored */ - size_t max; /* max amount to store */ + char *ptr; /* where to store it */ + int rem; /* unwritten buffer remaining */ }; -static int sprfunc(void *ptr, unsigned char letter) +static int sprfunc(void *ptr, int letter) { struct for_snprintf *pr = (struct for_snprintf *)ptr; - if(pr->bytes < pr->max) { - *pr->ptr = letter; - pr->ptr++; - pr->bytes++; - return true; + + if (pr->rem > 0) { + if (--pr->rem == 0) { + return 0; + } + + *pr->ptr++ = letter; + return 1; } - return false; /* filled buffer */ -} + else { + if (pr->rem == -INT_MAX) { + pr->rem = 1; + return -1; + } - -int snprintf(char *buf, size_t size, const char *fmt, ...) -{ - va_list ap; - struct for_snprintf pr; - - pr.ptr = (unsigned char *)buf; - pr.bytes = 0; - pr.max = size; - - va_start(ap, fmt); - format(sprfunc, &pr, fmt, ap); - va_end(ap); - - /* make sure it ends with a trailing zero */ - pr.ptr[(pr.bytes < pr.max) ? 0 : -1] = '\0'; - - return pr.bytes; + --pr->rem; + return 1; + } } int vsnprintf(char *buf, size_t size, const char *fmt, va_list ap) { - struct for_snprintf pr; + if (size <= INT_MAX) { + int bytes; + struct for_snprintf pr; - pr.ptr = (unsigned char *)buf; - pr.bytes = 0; - pr.max = size; + pr.ptr = buf; + pr.rem = size; - format(sprfunc, &pr, fmt, ap); + bytes = vuprintf(sprfunc, &pr, fmt, ap); - /* make sure it ends with a trailing zero */ - pr.ptr[(pr.bytes < pr.max) ? 0 : -1] = '\0'; - - return pr.bytes; + if (size) { + *pr.ptr = '\0'; + } + else if (pr.rem > 0) { + goto overflow; + } + + return bytes; + } + +overflow: + errno = EOVERFLOW; + return -1; +} + +int snprintf(char *buf, size_t size, const char *fmt, ...) +{ + if (size <= INT_MAX) { + int bytes; + struct for_snprintf pr; + va_list ap; + + pr.ptr = buf; + pr.rem = size; + + va_start(ap, fmt); + bytes = vuprintf(sprfunc, &pr, fmt, ap); + va_end(ap); + + if (size) { + *pr.ptr = '\0'; + } + else if (pr.rem > 0) { + goto overflow; + } + + return bytes; + } + +overflow: + errno = EOVERFLOW; + return -1; } diff --git a/firmware/logf.c b/firmware/logf.c index bdc5ad9cc0..3f9bd36112 100644 --- a/firmware/logf.c +++ b/firmware/logf.c @@ -38,7 +38,7 @@ #endif #include "logf.h" #include "serial.h" -#include "format.h" +#include "vuprintf.h" #ifdef HAVE_USBSTACK #include "usb_core.h" @@ -193,7 +193,7 @@ static void check_logfindex(void) } } -static int logf_push(void *userp, unsigned char c) +static int logf_push(void *userp, int c) { (void)userp; @@ -210,7 +210,7 @@ static int logf_push(void *userp, unsigned char c) } #endif - return true; + return 1; } void _logf(const char *fmt, ...) @@ -310,7 +310,7 @@ void logf_panic_dump(int *y) #endif #ifdef ROCKBOX_HAS_LOGDISKF -static int logdiskf_push(void *userp, unsigned char c) +static int logdiskf_push(void *userp, int c) { (void)userp; @@ -319,11 +319,11 @@ static int logdiskf_push(void *userp, unsigned char c) { strcpy(&logdiskfbuffer[logdiskfindex-8], "LOGFULL"); logdiskfindex=MAX_LOGDISKF_SIZE; - return false; + return 0; } logdiskfbuffer[logdiskfindex++] = c; - return true; + return 1; } static void flush_buffer(void); diff --git a/firmware/target/arm/s3c2440/uart-s3c2440.c b/firmware/target/arm/s3c2440/uart-s3c2440.c index e9c9345103..47f11a92b6 100644 --- a/firmware/target/arm/s3c2440/uart-s3c2440.c +++ b/firmware/target/arm/s3c2440/uart-s3c2440.c @@ -60,7 +60,7 @@ void tx_writec(unsigned char c) } -static int uart_push(void *user_data, unsigned char ch) +static int uart_push(void *user_data, int ch) { (void)user_data;