]> git.corax.cc Git - corax/commitdiff
Add snprintf() implementation to klibc
authorMatthias Kruk <m@m10k.eu>
Tue, 31 Dec 2019 04:49:07 +0000 (13:49 +0900)
committerMatthias Kruk <m@m10k.eu>
Tue, 31 Dec 2019 04:49:07 +0000 (13:49 +0900)
config.h
include/stdio.h [new file with mode: 0644]
kernel/klibc/stdio.c [new file with mode: 0644]

index 830468816dc92023bdfd5f2db9a5991e437ad7ff..923be4fcd8ed8b0d9280517beeeb85ea68b20dc8 100644 (file)
--- a/config.h
+++ b/config.h
 #define CONFIG_PERM_USER_NAME_LEN  32
 #define CONFIG_PERM_GROUP_NAME_LEN 32
 
+#define CONFIG_IO_MAXPROCFDS 16
 #define CONFIG_ELF 0
 
+/* klibc configuration */
+#define CONFIG_SNPRINTF_FLAG_MINUS 0
+#define CONFIG_SNPRINTF_FLAG_APOS  0 /* not implemented */
+
+#define CONFIG_SNPRINTF_CONV_INT   1
+#define CONFIG_SNPRINTF_CONV_HEX   1
+#define CONFIG_SNPRINTF_CONV_BIN   1
+#define CONFIG_SNPRINTF_CONV_STR   1
+#define CONFIG_SNPRINTF_CONV_CHAR  1
+#define CONFIG_SNPRINTF_CONV_OCT   1
+
 /* sanity checks */
 
 #if !defined(CONFIG_SMP_CPUS) || CONFIG_SMP_CPUS == 0
diff --git a/include/stdio.h b/include/stdio.h
new file mode 100644 (file)
index 0000000..8c60ca9
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef STDIO_H
+#define STDIO_H
+
+#include <sys/types.h>
+
+extern int snprintf(char*, size_t, const char*, ...);
+
+#endif /* STDIO_H */
diff --git a/kernel/klibc/stdio.c b/kernel/klibc/stdio.c
new file mode 100644 (file)
index 0000000..74c2b3d
--- /dev/null
@@ -0,0 +1,809 @@
+#include <config.h>
+#include <corax/types.h>
+#include <sys/types.h>
+#include <string.h>
+
+#define STATE_NORMAL 0
+#define STATE_FLAG   1
+#define STATE_WIDTH  2
+#define STATE_PREC   3
+#define STATE_LENGTH 4
+#define STATE_CONV   5
+#define STATE_ERR    6
+
+#define FLAG_POUND   (1 << 0)
+#define FLAG_ZERO    (1 << 1)
+#define FLAG_MINUS   (1 << 2)
+#define FLAG_SPACE   (1 << 3)
+#define FLAG_PLUS    (1 << 4)
+#define FLAG_APOS    (1 << 5)
+#define FLAG_LCHEX   (1 << 6)
+#define FLAG_USIGN   (1 << 7)
+
+#if FEATURE(SNPRINTF_CONV_OCT)
+int _convert_oct(char *dst, int flags, int precision, int width, int padlen, void *data)
+{
+       union {
+               u64_t v64;
+               u32_t v32[2];
+               u16_t v16[4];
+               u8_t v8[8];
+       } val;
+
+       char str[22];
+       int ret_val;
+       int olen;
+       char pad;
+
+       ret_val = 0;
+       pad = flags & FLAG_ZERO ? '0' : ' ';
+       val.v64 = 0LL;
+       olen = 0;
+
+       switch(width) {
+       case 8:
+               val.v64 = *(u64_t*)data;
+               break;
+
+       default:
+       case 4:
+               val.v32[0] = *(u32_t*)data;
+               break;
+
+       case 2:
+               val.v16[0] = *(u16_t*)data;
+               break;
+
+       case 1:
+               val.v8[0] = *(u8_t*)data;
+               break;
+       }
+
+       memset(str, '0', sizeof(str));
+
+       while(val.v64) {
+               olen++;
+               str[sizeof(str) - olen] = (char)(val.v64 & 0x7) + '0';
+               val.v64 >>= 3;
+       }
+
+       /* prepend the unit symbol, if # was found in the conversion specification */
+       if(flags & FLAG_POUND) {
+               str[sizeof(str) - 1 - olen] = '0';
+               olen++;
+       }
+
+       /* padlen - olen bytes need to be padded */
+       padlen -= olen;
+
+       while(padlen > 0 && precision > 0) {
+               dst[ret_val++] = pad;
+               padlen--;
+               precision--;
+       }
+
+       memcpy(dst + ret_val, str + sizeof(str) - olen, olen);
+       ret_val += olen;
+
+       return(ret_val);
+}
+#endif /* FEATURE(SNPRINTF_CONV_OCT) */
+
+#if FEATURE(SNPRINTF_CONV_HEX)
+int _convert_hex(char *dst, int flags, int precision, int width, int padlen, void *data)
+{
+       static const char hex_lc[] = "0123456789abcdef";
+       static const char hex_uc[] = "0123456789ABCDEF";
+       static const char hexp_lc[] = "0x";
+       static const char hexp_uc[] = "0X";
+
+       union {
+               u64_t v64;
+               u32_t v32[2];
+               u16_t v16[4];
+               u8_t v8[8];
+       } val;
+
+       const char *charset;
+       char str[16];
+       char pad;
+       int ret_val;
+       int len;
+       int i;
+
+       pad = flags & FLAG_ZERO ? '0' : ' ';
+       charset = flags & FLAG_LCHEX ? hex_lc : hex_uc;
+       ret_val = 0;
+       val.v64 = 0LL;
+
+       switch(width) {
+       case 8:
+               val.v64 = *(u64_t*)data;
+               break;
+
+       default:
+       case 4:
+               val.v32[0] = *(u32_t*)data;
+               break;
+
+       case 2:
+               val.v16[0] = *(u16_t*)data;
+               break;
+
+       case 1:
+               val.v8[0] = *(u8_t*)data;
+               break;
+       }
+
+       for(i = 0; i < 8; i++) {
+               str[(i * 2) + 0] = charset[(val.v8[7 - i] >> 4) & 0xf];
+               str[(i * 2) + 1] = charset[(val.v8[7 - i] >> 0) & 0xf];
+       }
+
+       if(flags & FLAG_POUND) {
+               /* we're supposed to print the unit prefix */
+
+               const char *prefix;
+
+               prefix = flags & FLAG_LCHEX ? hexp_lc : hexp_uc;
+
+               for(i = 0; i < strlen(prefix) && precision > 0; i++, precision--) {
+                       dst[ret_val++] = prefix[i];
+                       padlen--;
+               }
+       }
+
+       /* count number of leading zeroes */
+       for(i = 0; i < 16 && str[i] == '0'; i++);
+       len = 16 - i;
+
+       /* we're supposed to print (padlen - len) bytes of padding */
+       for(padlen -= len; padlen > 0 && precision > 0; padlen--, precision--) {
+               dst[ret_val++] = pad;
+       }
+
+       if(len > precision) {
+               len = precision;
+       }
+
+       memcpy(dst + ret_val, str + i, len);
+       ret_val += len;
+
+       return(ret_val);
+}
+#endif /* FEATURE(SNPRINTF_CONV_HEX) */
+
+#if FEATURE(SNPRINTF_CONV_INT)
+int _convert_int(char *dst, int flags, int precision, int width, int padlen, void *data)
+{
+       char rev[24];
+       i64_t val;
+       u64_t uval;
+       int ret_val;
+       char sign;
+       int rlen;
+       char pad;
+
+       ret_val = 0;
+       sign = 0;
+       rlen = 0;
+       pad = flags & FLAG_SPACE ? ' ' : '0';
+
+       if(flags & FLAG_USIGN) {
+               switch(width) {
+               case 8:
+                       uval = *(u64_t*)data;
+                       break;
+
+               default:
+               case 4:
+                       uval = *(u32_t*)data;
+                       break;
+
+               case 2:
+                       uval = *(u16_t*)data;
+                       break;
+
+               case 1:
+                       uval = *(u8_t*)data;
+                       break;
+               }
+
+               val = 0;
+       } else {
+               switch(width) {
+               case 8:
+                       val = *(i64_t*)data;
+                       break;
+
+               default:
+               case 4:
+                       val = *(i32_t*)data;
+                       break;
+
+               case 2:
+                       val = *(i16_t*)data;
+                       break;
+
+               case 1:
+                       val = *(i8_t*)data;
+                       break;
+               }
+
+               uval = val;
+
+               /* print sign only for signed integer conversion */
+               if(val < 0) {
+                       uval = -val;
+                       sign = '-';
+               } else if(flags & FLAG_PLUS) {
+                       sign = flags & FLAG_SPACE ? ' ' : '+';
+               }
+
+               if(sign && precision > 0) {
+                       dst[ret_val++] = sign;
+                       precision--;
+                       padlen--;
+               }
+       }
+
+       do {
+               rev[rlen++] = '0' + (uval % 10);
+               uval /= 10;
+       } while(uval > 0);
+
+       padlen -= rlen;
+
+       while(padlen-- > 0 && precision-- > 0) {
+               dst[ret_val++] = pad;
+       }
+
+       while(rlen > 0 && precision > 0) {
+               dst[ret_val++] = rev[--rlen];
+               precision--;
+       }
+
+       return(ret_val);
+}
+#endif /* FEATURE(SNPRINTF_CONV_INT) */
+
+#if FEATURE(SNPRINTF_CONV_BIN)
+int _convert_bin(char *dst, int flags, int precision, int width, int length, void *data)
+{
+       char str[64];
+       union {
+               u64_t v64;
+               u32_t v32[2];
+               u16_t v16[4];
+               u8_t v8[8];
+       } val;
+    u64_t mask;
+       int ret_val;
+       int i;
+       int blen;
+       char pad;
+
+       ret_val = 0;
+       val.v64 = 0LL;
+       pad = flags & FLAG_ZERO ? '0' : ' ';
+
+       switch(width) {
+       case 8:
+               val.v64 = *(u64_t*)data;
+               break;
+
+       default:
+       case 4:
+               val.v32[0] = *(u32_t*)data;
+               break;
+
+       case 2:
+               val.v16[0] = *(u16_t*)data;
+               break;
+
+       case 1:
+               val.v8[0] = *(u8_t*)data;
+               break;
+       }
+
+       for(mask = 0x8000000000000000LL, i = 0; mask; mask >>= 1, i++) {
+               str[i] = val.v64 & mask ? '1' : '0';
+       }
+
+       for(i = 0; i < 64 && str[i] == '0'; i++);
+       blen = 64 - i;
+
+       /* print the prefix, if desired */
+       if(flags & FLAG_POUND && precision > 0) {
+               dst[ret_val++] = 'b';
+               width--;
+       }
+
+       /* width - blen is the amount of padding we still need */
+       width -= blen;
+
+       while(width > 0 && precision > 0) {
+               dst[ret_val++] = pad;
+               width--;
+               precision--;
+       }
+
+       memcpy(dst + ret_val, str + i, blen);
+       ret_val += blen;
+
+       return(ret_val);
+}
+#endif /* FEATURE(SNPRINTF_CONV_BIN) */
+
+#if FEATURE(SNPRINTF_CONV_STR)
+int _convert_str(char *dst, int precision, int padlen, const char *src)
+{
+       static const char nilstr[] = "(null)";
+       int slen;
+       int ret_val;
+
+       ret_val = 0;
+
+       if(!src) {
+               src = nilstr;
+       }
+
+       /*
+     * We're supposed to write at least padlen bytes, and at most precision bytes.
+     * That means, `padlen - strlen(src)' is the number of spaces we have to insert
+     * in the front.
+     */
+
+       slen = strlen(src);
+       padlen -= slen;
+
+       while(precision-- > 0 && padlen-- > 0) {
+               dst[ret_val++] = ' ';
+       }
+
+       /* precision may still be larger than slen */
+       if(slen < precision) {
+               precision = slen;
+       }
+
+       /* copy the actual string, if there is space left */
+       if(precision > 0) {
+               memcpy(dst + ret_val, src, precision);
+               ret_val += precision;
+       }
+
+       return(ret_val);
+}
+#endif /* FEATURE(SNPRINTF_CONV_STR) */
+
+int snprintf(char *str, size_t size, const char *format, ...)
+{
+       int offset;
+       int state;
+       int flags;
+       int width;
+       int precision;
+       int length;
+       const char *fmt;
+       void *arg;
+       char c;
+
+#define RESET() do {                                                   \
+               flags = 0;                                                              \
+               width = 0;                                                              \
+               precision = 0;                                                  \
+               length = 0;                                                             \
+       } while(0)
+#define OUTPUT(_c) do {                                                        \
+               if(offset < size) {                                             \
+                       str[offset++] = c;                                      \
+               } else {                                                                \
+                       goto gtfo;                                                      \
+               }                                                                               \
+       } while(0)
+#define NEXT() fmt++
+#define NEXTARG() (arg + (length == 8 ? 8 : sizeof(arg)))
+#define ADD_LENGTH(_l,_c) do {                                 \
+               switch(_c) {                                                    \
+               case 'l':                                                               \
+                       (_l) = !(_l) ? 4 : ((_l) << 1);         \
+                       break;                                                          \
+               case 'h':                                                               \
+                       (_l) = !(_l) ? 2 : ((_l) >> 1);         \
+                       break;                                                          \
+               default:                                                                \
+                       break;                                                          \
+               }                                                                               \
+       } while(0)
+#define ADD_WIDTH(_l,_c) (_l) = (_l) * 10 + ((_c) - '0')
+#define ADD_PRECISION ADD_WIDTH
+#define ADD_FLAG(_f,_c) do {                                   \
+               switch(_c) {                                                    \
+               case '#':                                                               \
+                       (_f) |= FLAG_POUND;                                     \
+                       break;                                                          \
+               case '0':                                                               \
+                       (_f) |= FLAG_ZERO;                                      \
+                       break;                                                          \
+               case '-':                                                               \
+                       (_f) |= FLAG_MINUS;                                     \
+                       break;                                                          \
+               case ' ':                                                               \
+                       (_f) |= FLAG_SPACE | FLAG_PLUS;         \
+                       break;                                                          \
+               case '+':                                                               \
+                       (_f) |= FLAG_PLUS;                                      \
+                       break;                                                          \
+               case '\'':                                                              \
+                       (_f) |= FLAG_APOS;                                      \
+                       break;                                                          \
+               default:                                                                \
+                       break;                                                          \
+               }                                                                               \
+       } while(0)
+#define SET_STATE(_s) (state = (_s))
+
+       offset = 0;
+       state = STATE_NORMAL;
+       fmt = format;
+       arg = (void*)&format + sizeof(format);
+
+       while((c = *fmt)) {
+               switch(state) {
+               case STATE_NORMAL:
+                       switch(c) {
+                       case '%':
+                               SET_STATE(STATE_FLAG);
+                               RESET();
+                               break;
+
+                       default:
+                               OUTPUT(c);
+                               break;
+                       }
+
+                       NEXT();
+                       break;
+
+               case STATE_FLAG:
+                       switch(c) {
+                       case '#':
+                       case '0':
+#if FEATURE(SNPRINTF_FLAG_MINUS)
+                       case '-':
+#endif /* FEATURE(SNPRINTF_FLAG_MINUS) */
+                       case ' ':
+                       case '+':
+#if FEATURE(SNPRINTF_FLAG_APOS)
+                       case '\'':
+#endif /* FEATURE(SNPRINTF_FLAG_APOS) */
+                               ADD_FLAG(flags, c);
+                               NEXT();
+                               break;
+
+                       case '1':
+                       case '2':
+                       case '3':
+                       case '4':
+                       case '5':
+                       case '6':
+                       case '7':
+                       case '8':
+                       case '9':
+                           SET_STATE(STATE_WIDTH);
+                               /* parse again in STATE_WIDTH */
+                               break;
+
+                       case '.':
+                               SET_STATE(STATE_PREC);
+                               NEXT();
+                               break;
+
+                       case 'h':
+                       case 'l':
+                           SET_STATE(STATE_LENGTH);
+                               /* parse again in STATE_LENGTH */
+                               break;
+
+#if FEATURE(SNPRINTF_CONV_INT)
+                       case 'd':
+                       case 'i':
+                       case 'u':
+#endif /* FEATURE(SNPRINTF_CONV_INT) */
+#if FEATURE(SNPRINTF_CONV_OCT)
+                       case 'o':
+#endif /* FEATURE(SNPRINTF_CONV_OCT) */
+#if FEATURE(SNPRINTF_CONV_HEX)
+                       case 'p':
+                       case 'x':
+                       case 'X':
+#endif /* FEATURE(SNPRINTF_CONV_HEX) */
+#if FEATURE(SNPRINTF_CONV_CHAR)
+                       case 'c':
+#endif /* FEATURE(SNPRINT_CONV_CHAR) */
+#if FEATURE(SNPRINTF_CONV_STR)
+                       case 's':
+#endif /* FEATURE(SNPRINTF_CONV_STR) */
+#if FEATURE(SNPRINTF_CONV_BIN)
+                       case 'b':
+#endif /* FEATURE(SNPRINTF_CONV_BIN) */
+                               /* parse again in STATE_CONV */
+                           SET_STATE(STATE_CONV);
+                               break;
+
+                       default:
+                               SET_STATE(STATE_NORMAL);
+                               break;
+                       }
+                       break;
+
+               case STATE_WIDTH:
+                       switch(c) {
+                       case '0':
+                       case '1':
+                       case '2':
+                       case '3':
+                       case '4':
+                       case '5':
+                       case '6':
+                       case '7':
+                       case '8':
+                       case '9':
+                               ADD_WIDTH(width, c);
+                               NEXT();
+                               break;
+
+                       case '.':
+                               SET_STATE(STATE_PREC);
+                               NEXT();
+                               break;
+
+                       case 'l':
+                       case 'h':
+                               SET_STATE(STATE_LENGTH);
+                               break;
+
+#if FEATURE(SNPRINTF_CONV_INT)
+                       case 'd':
+                       case 'i':
+                       case 'u':
+#endif /* FEATURE(SNPRINTF_CONV_INT) */
+#if FEATURE(SNPRINTF_CONV_OCT)
+                       case 'o':
+#endif /* FEATURE(SNPRINTF_CONV_OCT) */
+#if FEATURE(SNPRINTF_CONV_HEX)
+                       case 'p':
+                       case 'x':
+                       case 'X':
+#endif /* FEATURE(SNPRINTF_CONV_HEX) */
+#if FEATURE(SNPRINTF_CONV_CHAR)
+                       case 'c':
+#endif /* FEATURE(SNPRINT_CONV_CHAR) */
+#if FEATURE(SNPRINTF_CONV_STR)
+                       case 's':
+#endif /* FEATURE(SNPRINTF_CONV_STR) */
+#if FEATURE(SNPRINTF_CONV_BIN)
+                       case 'b':
+#endif /* FEATURE(SNPRINTF_CONV_BIN) */
+                               SET_STATE(STATE_CONV);
+                               break;
+
+                       default:
+                               SET_STATE(STATE_ERR);
+                               break;
+                       }
+                       break;
+
+               case STATE_PREC:
+                       switch(c) {
+                       case '0':
+                       case '1':
+                       case '2':
+                       case '3':
+                       case '4':
+                       case '5':
+                       case '6':
+                       case '7':
+                       case '8':
+                       case '9':
+                               ADD_PRECISION(precision, c);
+                               NEXT();
+                               break;
+
+                       case 'h':
+                       case 'l':
+                               SET_STATE(STATE_LENGTH);
+                               break;
+
+#if FEATURE(SNPRINTF_CONV_INT)
+                       case 'd':
+                       case 'i':
+                       case 'u':
+#endif /* FEATURE(SNPRINTF_CONV_INT) */
+#if FEATURE(SNPRINTF_CONV_OCT)
+                       case 'o':
+#endif /* FEATURE(SNPRINTF_CONV_OCT) */
+#if FEATURE(SNPRINTF_CONV_HEX)
+                       case 'p':
+                       case 'x':
+                       case 'X':
+#endif /* FEATURE(SNPRINTF_CONV_HEX) */
+#if FEATURE(SNPRINTF_CONV_CHAR)
+                       case 'c':
+#endif /* FEATURE(SNPRINT_CONV_CHAR) */
+#if FEATURE(SNPRINTF_CONV_STR)
+                       case 's':
+#endif /* FEATURE(SNPRINTF_CONV_STR) */
+#if FEATURE(SNPRINTF_CONV_BIN)
+                       case 'b':
+#endif /* FEATURE(SNPRINTF_CONV_BIN) */
+                               SET_STATE(STATE_CONV);
+                               break;
+
+                       default:
+                               SET_STATE(STATE_ERR);
+                               break;
+                       }
+                       break;
+
+               case STATE_LENGTH:
+                       switch(c) {
+                       case 'h':
+                       case 'l':
+                               ADD_LENGTH(length, c);
+                               NEXT();
+                               break;
+
+#if FEATURE(SNPRINTF_CONV_INT)
+                       case 'd':
+                       case 'i':
+                       case 'u':
+#endif /* FEATURE(SNPRINTF_CONV_INT) */
+#if FEATURE(SNPRINTF_CONV_OCT)
+                       case 'o':
+#endif /* FEATURE(SNPRINTF_CONV_OCT) */
+#if FEATURE(SNPRINTF_CONV_HEX)
+                       case 'p':
+                       case 'x':
+                       case 'X':
+#endif /* FEATURE(SNPRINTF_CONV_HEX) */
+#if FEATURE(SNPRINTF_CONV_CHAR)
+                       case 'c':
+#endif /* FEATURE(SNPRINT_CONV_CHAR) */
+#if FEATURE(SNPRINTF_CONV_STR)
+                       case 's':
+#endif /* FEATURE(SNPRINTF_CONV_STR) */
+#if FEATURE(SNPRINTF_CONV_BIN)
+                       case 'b':
+#endif /* FEATURE(SNPRINTF_CONV_BIN) */
+                               SET_STATE(STATE_CONV);
+                               break;
+
+                       default:
+                               SET_STATE(STATE_ERR);
+                               break;
+                       }
+                       break;
+
+               case STATE_CONV:
+                       switch(c) {
+#if FEATURE(SNPRINTF_CONV_INT)
+                       case 'u':
+                               flags |= FLAG_USIGN;
+                               /* fall-through */
+                       case 'd':
+                       case 'i':
+                               if(!precision || (size - offset) < precision) {
+                                       precision = size - offset;
+                               }
+
+                               offset += _convert_int(str + offset, flags, precision,
+                                                                          length, width, arg);
+                               arg = NEXTARG();
+                               break;
+#endif /* FEATURE(SNPRINTF_CONV_INT) */
+
+#if FEATURE(SNPRINTF_CONV_OCT)
+                       case 'o':
+                               if(!precision || (size - offset) < precision) {
+                                       precision = size - offset;
+                               }
+
+                               offset += _convert_oct(str + offset, flags, precision,
+                                                                          length, width, arg);
+                               arg = NEXTARG();
+                               break;
+#endif /* FEATURE(SNPRINTF_CONV_OCT) */
+
+#if FEATURE(SNPRINTF_CONV_HEX)
+                       case 'x':
+                               flags |= FLAG_LCHEX;
+                               /* fall through */
+                       case 'X':
+                               if(!precision || (size - offset) < precision) {
+                                       precision = size - offset;
+                               }
+
+                               offset += _convert_hex(str + offset, flags, precision,
+                                                                          length, width, arg);
+                               arg = NEXTARG();
+                               break;
+
+                       case 'p':
+                               offset += _convert_hex(str + offset, FLAG_POUND | FLAG_LCHEX,
+                                                                          size - offset, sizeof(void*), 0, arg);
+                               arg = NEXTARG();
+                               break;
+#endif /* FEATURE(SNPRINTF_CONV_HEX) */
+
+#if FEATURE(SNPRINTF_CONV_CHAR)
+                       case 'c':
+                               OUTPUT(*(char*)arg);
+                               arg = NEXTARG();
+                               break;
+#endif /* FEATURE(SNPRINTF_CONV_CHAR) */
+
+#if FEATURE(SNPRINTF_CONV_STR)
+                       case 's':
+                               if(!precision || (size - offset) < precision) {
+                                       precision = size - offset;
+                               }
+
+                               offset += _convert_str(str + offset, precision, width, *(const char**)arg);
+                               arg = NEXTARG();
+                               break;
+#endif /* FEATURE(SNPRINTF_CONV_STR) */
+
+#if FEATURE(SNPRINTF_CONV_BIN)
+                       case 'b':
+                               if(!precision || (size - offset) < precision) {
+                                       precision = size - offset;
+                               }
+
+                               offset += _convert_bin(str + offset, flags, precision, width, length, arg);
+                               arg = NEXTARG();
+                               break;
+#endif /* FEATURE(SNPRINTF_CONV_BIN) */
+
+                       case '%':
+                               OUTPUT('%');
+                               break;
+
+                       default:
+                               break;
+                       }
+
+                       SET_STATE(STATE_NORMAL);
+                       NEXT();
+                       break;
+
+               case STATE_ERR:
+                       SET_STATE(STATE_NORMAL);
+                       OUTPUT(c);
+                       NEXT();
+                       break;
+
+               default:
+                       SET_STATE(STATE_ERR);
+                       break;
+               }
+       }
+
+       /* add trailing '\0', if there is space left for it */
+       if(offset < size) {
+               str[offset] = 0;
+       }
+
+gtfo:
+       return(offset);
+
+#undef RESET
+#undef OUTPUT
+#undef NEXT
+#undef NEXTARG
+#undef ADD_LENGTH
+#undef ADD_WIDTH
+#undef ADD_PRECISION
+#undef ADD_FLAG
+#undef SET_STATE
+}