backport a recent version of vsprintf to linux 2.6.28 to fix mac80211 wifi interface...
authorFelix Fietkau <nbd@openwrt.org>
Mon, 9 Nov 2009 00:58:44 +0000 (00:58 +0000)
committerFelix Fietkau <nbd@openwrt.org>
Mon, 9 Nov 2009 00:58:44 +0000 (00:58 +0000)
SVN-Revision: 18352

target/linux/generic-2.6/patches-2.6.28/981-vsprintf_backport.patch [new file with mode: 0644]

diff --git a/target/linux/generic-2.6/patches-2.6.28/981-vsprintf_backport.patch b/target/linux/generic-2.6/patches-2.6.28/981-vsprintf_backport.patch
new file mode 100644 (file)
index 0000000..37589b1
--- /dev/null
@@ -0,0 +1,888 @@
+--- a/lib/vsprintf.c
++++ b/lib/vsprintf.c
+@@ -170,6 +170,8 @@ int strict_strtoul(const char *cp, unsig
+               return -EINVAL;
+       val = simple_strtoul(cp, &tail, base);
++      if (tail == cp)
++              return -EINVAL;
+       if ((*tail == '\0') ||
+               ((len == (size_t)(tail - cp) + 1) && (*tail == '\n'))) {
+               *res = val;
+@@ -241,6 +243,8 @@ int strict_strtoull(const char *cp, unsi
+               return -EINVAL;
+       val = simple_strtoull(cp, &tail, base);
++      if (tail == cp)
++              return -EINVAL;
+       if ((*tail == '\0') ||
+               ((len == (size_t)(tail - cp) + 1) && (*tail == '\n'))) {
+               *res = val;
+@@ -392,7 +396,38 @@ static noinline char* put_dec(char *buf,
+ #define SMALL 32              /* Must be 32 == 0x20 */
+ #define SPECIAL       64              /* 0x */
+-static char *number(char *buf, char *end, unsigned long long num, int base, int size, int precision, int type)
++enum format_type {
++      FORMAT_TYPE_NONE, /* Just a string part */
++      FORMAT_TYPE_WIDTH,
++      FORMAT_TYPE_PRECISION,
++      FORMAT_TYPE_CHAR,
++      FORMAT_TYPE_STR,
++      FORMAT_TYPE_PTR,
++      FORMAT_TYPE_PERCENT_CHAR,
++      FORMAT_TYPE_INVALID,
++      FORMAT_TYPE_LONG_LONG,
++      FORMAT_TYPE_ULONG,
++      FORMAT_TYPE_LONG,
++      FORMAT_TYPE_USHORT,
++      FORMAT_TYPE_SHORT,
++      FORMAT_TYPE_UINT,
++      FORMAT_TYPE_INT,
++      FORMAT_TYPE_NRCHARS,
++      FORMAT_TYPE_SIZE_T,
++      FORMAT_TYPE_PTRDIFF
++};
++
++struct printf_spec {
++      enum format_type        type;
++      int                     flags;          /* flags to number() */
++      int                     field_width;    /* width of output field */
++      int                     base;
++      int                     precision;      /* # of digits/chars */
++      int                     qualifier;
++};
++
++static char *number(char *buf, char *end, unsigned long long num,
++                      struct printf_spec spec)
+ {
+       /* we are called with base 8, 10 or 16, only, thus don't need "G..."  */
+       static const char digits[16] = "0123456789ABCDEF"; /* "GHIJKLMNOPQRSTUVWXYZ"; */
+@@ -400,32 +435,32 @@ static char *number(char *buf, char *end
+       char tmp[66];
+       char sign;
+       char locase;
+-      int need_pfx = ((type & SPECIAL) && base != 10);
++      int need_pfx = ((spec.flags & SPECIAL) && spec.base != 10);
+       int i;
+       /* locase = 0 or 0x20. ORing digits or letters with 'locase'
+        * produces same digits or (maybe lowercased) letters */
+-      locase = (type & SMALL);
+-      if (type & LEFT)
+-              type &= ~ZEROPAD;
++      locase = (spec.flags & SMALL);
++      if (spec.flags & LEFT)
++              spec.flags &= ~ZEROPAD;
+       sign = 0;
+-      if (type & SIGN) {
++      if (spec.flags & SIGN) {
+               if ((signed long long) num < 0) {
+                       sign = '-';
+                       num = - (signed long long) num;
+-                      size--;
+-              } else if (type & PLUS) {
++                      spec.field_width--;
++              } else if (spec.flags & PLUS) {
+                       sign = '+';
+-                      size--;
+-              } else if (type & SPACE) {
++                      spec.field_width--;
++              } else if (spec.flags & SPACE) {
+                       sign = ' ';
+-                      size--;
++                      spec.field_width--;
+               }
+       }
+       if (need_pfx) {
+-              size--;
+-              if (base == 16)
+-                      size--;
++              spec.field_width--;
++              if (spec.base == 16)
++                      spec.field_width--;
+       }
+       /* generate full string in tmp[], in reverse order */
+@@ -437,10 +472,10 @@ static char *number(char *buf, char *end
+               tmp[i++] = (digits[do_div(num,base)] | locase);
+       } while (num != 0);
+       */
+-      else if (base != 10) { /* 8 or 16 */
+-              int mask = base - 1;
++      else if (spec.base != 10) { /* 8 or 16 */
++              int mask = spec.base - 1;
+               int shift = 3;
+-              if (base == 16) shift = 4;
++              if (spec.base == 16) shift = 4;
+               do {
+                       tmp[i++] = (digits[((unsigned char)num) & mask] | locase);
+                       num >>= shift;
+@@ -450,12 +485,12 @@ static char *number(char *buf, char *end
+       }
+       /* printing 100 using %2d gives "100", not "00" */
+-      if (i > precision)
+-              precision = i;
++      if (i > spec.precision)
++              spec.precision = i;
+       /* leading space padding */
+-      size -= precision;
+-      if (!(type & (ZEROPAD+LEFT))) {
+-              while(--size >= 0) {
++      spec.field_width -= spec.precision;
++      if (!(spec.flags & (ZEROPAD+LEFT))) {
++              while(--spec.field_width >= 0) {
+                       if (buf < end)
+                               *buf = ' ';
+                       ++buf;
+@@ -472,23 +507,23 @@ static char *number(char *buf, char *end
+               if (buf < end)
+                       *buf = '0';
+               ++buf;
+-              if (base == 16) {
++              if (spec.base == 16) {
+                       if (buf < end)
+                               *buf = ('X' | locase);
+                       ++buf;
+               }
+       }
+       /* zero or space padding */
+-      if (!(type & LEFT)) {
+-              char c = (type & ZEROPAD) ? '0' : ' ';
+-              while (--size >= 0) {
++      if (!(spec.flags & LEFT)) {
++              char c = (spec.flags & ZEROPAD) ? '0' : ' ';
++              while (--spec.field_width >= 0) {
+                       if (buf < end)
+                               *buf = c;
+                       ++buf;
+               }
+       }
+       /* hmm even more zero padding? */
+-      while (i <= --precision) {
++      while (i <= --spec.precision) {
+               if (buf < end)
+                       *buf = '0';
+               ++buf;
+@@ -500,7 +535,7 @@ static char *number(char *buf, char *end
+               ++buf;
+       }
+       /* trailing space padding */
+-      while (--size >= 0) {
++      while (--spec.field_width >= 0) {
+               if (buf < end)
+                       *buf = ' ';
+               ++buf;
+@@ -508,17 +543,17 @@ static char *number(char *buf, char *end
+       return buf;
+ }
+-static char *string(char *buf, char *end, char *s, int field_width, int precision, int flags)
++static char *string(char *buf, char *end, char *s, struct printf_spec spec)
+ {
+       int len, i;
+       if ((unsigned long)s < PAGE_SIZE)
+               s = "<NULL>";
+-      len = strnlen(s, precision);
++      len = strnlen(s, spec.precision);
+-      if (!(flags & LEFT)) {
+-              while (len < field_width--) {
++      if (!(spec.flags & LEFT)) {
++              while (len < spec.field_width--) {
+                       if (buf < end)
+                               *buf = ' ';
+                       ++buf;
+@@ -529,7 +564,7 @@ static char *string(char *buf, char *end
+                       *buf = *s;
+               ++buf; ++s;
+       }
+-      while (len < field_width--) {
++      while (len < spec.field_width--) {
+               if (buf < end)
+                       *buf = ' ';
+               ++buf;
+@@ -537,21 +572,24 @@ static char *string(char *buf, char *end
+       return buf;
+ }
+-static char *symbol_string(char *buf, char *end, void *ptr, int field_width, int precision, int flags)
++static char *symbol_string(char *buf, char *end, void *ptr,
++                              struct printf_spec spec)
+ {
+       unsigned long value = (unsigned long) ptr;
+ #ifdef CONFIG_KALLSYMS
+       char sym[KSYM_SYMBOL_LEN];
+       sprint_symbol(sym, value);
+-      return string(buf, end, sym, field_width, precision, flags);
++      return string(buf, end, sym, spec);
+ #else
+-      field_width = 2*sizeof(void *);
+-      flags |= SPECIAL | SMALL | ZEROPAD;
+-      return number(buf, end, value, 16, field_width, precision, flags);
++      spec.field_width = 2*sizeof(void *);
++      spec.flags |= SPECIAL | SMALL | ZEROPAD;
++      spec.base = 16;
++      return number(buf, end, value, spec);
+ #endif
+ }
+-static char *resource_string(char *buf, char *end, struct resource *res, int field_width, int precision, int flags)
++static char *resource_string(char *buf, char *end, struct resource *res,
++                              struct printf_spec spec)
+ {
+ #ifndef IO_RSRC_PRINTK_SIZE
+ #define IO_RSRC_PRINTK_SIZE   4
+@@ -560,7 +598,11 @@ static char *resource_string(char *buf, 
+ #ifndef MEM_RSRC_PRINTK_SIZE
+ #define MEM_RSRC_PRINTK_SIZE  8
+ #endif
+-
++      struct printf_spec num_spec = {
++              .base = 16,
++              .precision = -1,
++              .flags = SPECIAL | SMALL | ZEROPAD,
++      };
+       /* room for the actual numbers, the two "0x", -, [, ] and the final zero */
+       char sym[4*sizeof(resource_size_t) + 8];
+       char *p = sym, *pend = sym + sizeof(sym);
+@@ -572,13 +614,73 @@ static char *resource_string(char *buf, 
+               size = MEM_RSRC_PRINTK_SIZE;
+       *p++ = '[';
+-      p = number(p, pend, res->start, 16, size, -1, SPECIAL | SMALL | ZEROPAD);
++      num_spec.field_width = size;
++      p = number(p, pend, res->start, num_spec);
+       *p++ = '-';
+-      p = number(p, pend, res->end, 16, size, -1, SPECIAL | SMALL | ZEROPAD);
++      p = number(p, pend, res->end, num_spec);
+       *p++ = ']';
+       *p = 0;
+-      return string(buf, end, sym, field_width, precision, flags);
++      return string(buf, end, sym, spec);
++}
++
++static char *mac_address_string(char *buf, char *end, u8 *addr,
++                              struct printf_spec spec)
++{
++      char mac_addr[6 * 3]; /* (6 * 2 hex digits), 5 colons and trailing zero */
++      char *p = mac_addr;
++      int i;
++
++      for (i = 0; i < 6; i++) {
++              p = pack_hex_byte(p, addr[i]);
++              if (!(spec.flags & SPECIAL) && i != 5)
++                      *p++ = ':';
++      }
++      *p = '\0';
++      spec.flags &= ~SPECIAL;
++
++      return string(buf, end, mac_addr, spec);
++}
++
++static char *ip6_addr_string(char *buf, char *end, u8 *addr,
++                              struct printf_spec spec)
++{
++      char ip6_addr[8 * 5]; /* (8 * 4 hex digits), 7 colons and trailing zero */
++      char *p = ip6_addr;
++      int i;
++
++      for (i = 0; i < 8; i++) {
++              p = pack_hex_byte(p, addr[2 * i]);
++              p = pack_hex_byte(p, addr[2 * i + 1]);
++              if (!(spec.flags & SPECIAL) && i != 7)
++                      *p++ = ':';
++      }
++      *p = '\0';
++      spec.flags &= ~SPECIAL;
++
++      return string(buf, end, ip6_addr, spec);
++}
++
++static char *ip4_addr_string(char *buf, char *end, u8 *addr,
++                              struct printf_spec spec)
++{
++      char ip4_addr[4 * 4]; /* (4 * 3 decimal digits), 3 dots and trailing zero */
++      char temp[3];   /* hold each IP quad in reverse order */
++      char *p = ip4_addr;
++      int i, digits;
++
++      for (i = 0; i < 4; i++) {
++              digits = put_dec_trunc(temp, addr[i]) - temp;
++              /* reverse the digits in the quad */
++              while (digits--)
++                      *p++ = temp[digits];
++              if (i != 3)
++                      *p++ = '.';
++      }
++      *p = '\0';
++      spec.flags &= ~SPECIAL;
++
++      return string(buf, end, ip4_addr, spec);
+ }
+ /*
+@@ -592,28 +694,244 @@ static char *resource_string(char *buf, 
+  * - 'S' For symbolic direct pointers
+  * - 'R' For a struct resource pointer, it prints the range of
+  *       addresses (not the name nor the flags)
++ * - 'M' For a 6-byte MAC address, it prints the address in the
++ *       usual colon-separated hex notation
++ * - 'I' [46] for IPv4/IPv6 addresses printed in the usual way (dot-separated
++ *       decimal for v4 and colon separated network-order 16 bit hex for v6)
++ * - 'i' [46] for 'raw' IPv4/IPv6 addresses, IPv6 omits the colons, IPv4 is
++ *       currently the same
+  *
+  * Note: The difference between 'S' and 'F' is that on ia64 and ppc64
+  * function pointers are really function descriptors, which contain a
+  * pointer to the real address.
+  */
+-static char *pointer(const char *fmt, char *buf, char *end, void *ptr, int field_width, int precision, int flags)
++static char *pointer(const char *fmt, char *buf, char *end, void *ptr,
++                      struct printf_spec spec)
+ {
++      if (!ptr)
++              return string(buf, end, "(null)", spec);
++
+       switch (*fmt) {
+       case 'F':
+               ptr = dereference_function_descriptor(ptr);
+               /* Fallthrough */
+       case 'S':
+-              return symbol_string(buf, end, ptr, field_width, precision, flags);
++              return symbol_string(buf, end, ptr, spec);
+       case 'R':
+-              return resource_string(buf, end, ptr, field_width, precision, flags);
++              return resource_string(buf, end, ptr, spec);
++      case 'm':
++              spec.flags |= SPECIAL;
++              /* Fallthrough */
++      case 'M':
++              return mac_address_string(buf, end, ptr, spec);
++      case 'i':
++              spec.flags |= SPECIAL;
++              /* Fallthrough */
++      case 'I':
++              if (fmt[1] == '6')
++                      return ip6_addr_string(buf, end, ptr, spec);
++              if (fmt[1] == '4')
++                      return ip4_addr_string(buf, end, ptr, spec);
++              spec.flags &= ~SPECIAL;
++              break;
+       }
+-      flags |= SMALL;
+-      if (field_width == -1) {
+-              field_width = 2*sizeof(void *);
+-              flags |= ZEROPAD;
++      spec.flags |= SMALL;
++      if (spec.field_width == -1) {
++              spec.field_width = 2*sizeof(void *);
++              spec.flags |= ZEROPAD;
+       }
+-      return number(buf, end, (unsigned long) ptr, 16, field_width, precision, flags);
++      spec.base = 16;
++
++      return number(buf, end, (unsigned long) ptr, spec);
++}
++
++/*
++ * Helper function to decode printf style format.
++ * Each call decode a token from the format and return the
++ * number of characters read (or likely the delta where it wants
++ * to go on the next call).
++ * The decoded token is returned through the parameters
++ *
++ * '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
++ *
++ * @fmt: the format string
++ * @type of the token returned
++ * @flags: various flags such as +, -, # tokens..
++ * @field_width: overwritten width
++ * @base: base of the number (octal, hex, ...)
++ * @precision: precision of a number
++ * @qualifier: qualifier of a number (long, size_t, ...)
++ */
++static int format_decode(const char *fmt, struct printf_spec *spec)
++{
++      const char *start = fmt;
++
++      /* we finished early by reading the field width */
++      if (spec->type == FORMAT_TYPE_WIDTH) {
++              if (spec->field_width < 0) {
++                      spec->field_width = -spec->field_width;
++                      spec->flags |= LEFT;
++              }
++              spec->type = FORMAT_TYPE_NONE;
++              goto precision;
++      }
++
++      /* we finished early by reading the precision */
++      if (spec->type == FORMAT_TYPE_PRECISION) {
++              if (spec->precision < 0)
++                      spec->precision = 0;
++
++              spec->type = FORMAT_TYPE_NONE;
++              goto qualifier;
++      }
++
++      /* By default */
++      spec->type = FORMAT_TYPE_NONE;
++
++      for (; *fmt ; ++fmt) {
++              if (*fmt == '%')
++                      break;
++      }
++
++      /* Return the current non-format string */
++      if (fmt != start || !*fmt)
++              return fmt - start;
++
++      /* Process flags */
++      spec->flags = 0;
++
++      while (1) { /* this also skips first '%' */
++              bool found = true;
++
++              ++fmt;
++
++              switch (*fmt) {
++              case '-': spec->flags |= LEFT;    break;
++              case '+': spec->flags |= PLUS;    break;
++              case ' ': spec->flags |= SPACE;   break;
++              case '#': spec->flags |= SPECIAL; break;
++              case '0': spec->flags |= ZEROPAD; break;
++              default:  found = false;
++              }
++
++              if (!found)
++                      break;
++      }
++
++      /* get field width */
++      spec->field_width = -1;
++
++      if (isdigit(*fmt))
++              spec->field_width = skip_atoi(&fmt);
++      else if (*fmt == '*') {
++              /* it's the next argument */
++              spec->type = FORMAT_TYPE_WIDTH;
++              return ++fmt - start;
++      }
++
++precision:
++      /* get the precision */
++      spec->precision = -1;
++      if (*fmt == '.') {
++              ++fmt;
++              if (isdigit(*fmt)) {
++                      spec->precision = skip_atoi(&fmt);
++                      if (spec->precision < 0)
++                              spec->precision = 0;
++              } else if (*fmt == '*') {
++                      /* it's the next argument */
++                      spec->type = FORMAT_TYPE_PRECISION;
++                      return ++fmt - start;
++              }
++      }
++
++qualifier:
++      /* get the conversion qualifier */
++      spec->qualifier = -1;
++      if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L' ||
++          *fmt == 'Z' || *fmt == 'z' || *fmt == 't') {
++              spec->qualifier = *fmt;
++              ++fmt;
++              if (spec->qualifier == 'l' && *fmt == 'l') {
++                      spec->qualifier = 'L';
++                      ++fmt;
++              }
++      }
++
++      /* default base */
++      spec->base = 10;
++      switch (*fmt) {
++      case 'c':
++              spec->type = FORMAT_TYPE_CHAR;
++              return ++fmt - start;
++
++      case 's':
++              spec->type = FORMAT_TYPE_STR;
++              return ++fmt - start;
++
++      case 'p':
++              spec->type = FORMAT_TYPE_PTR;
++              return fmt - start;
++              /* skip alnum */
++
++      case 'n':
++              spec->type = FORMAT_TYPE_NRCHARS;
++              return ++fmt - start;
++
++      case '%':
++              spec->type = FORMAT_TYPE_PERCENT_CHAR;
++              return ++fmt - start;
++
++      /* integer number formats - set up the flags and "break" */
++      case 'o':
++              spec->base = 8;
++              break;
++
++      case 'x':
++              spec->flags |= SMALL;
++
++      case 'X':
++              spec->base = 16;
++              break;
++
++      case 'd':
++      case 'i':
++              spec->flags |= SIGN;
++      case 'u':
++              break;
++
++      default:
++              spec->type = FORMAT_TYPE_INVALID;
++              return fmt - start;
++      }
++
++      if (spec->qualifier == 'L')
++              spec->type = FORMAT_TYPE_LONG_LONG;
++      else if (spec->qualifier == 'l') {
++              if (spec->flags & SIGN)
++                      spec->type = FORMAT_TYPE_LONG;
++              else
++                      spec->type = FORMAT_TYPE_ULONG;
++      } else if (spec->qualifier == 'Z' || spec->qualifier == 'z') {
++              spec->type = FORMAT_TYPE_SIZE_T;
++      } else if (spec->qualifier == 't') {
++              spec->type = FORMAT_TYPE_PTRDIFF;
++      } else if (spec->qualifier == 'h') {
++              if (spec->flags & SIGN)
++                      spec->type = FORMAT_TYPE_SHORT;
++              else
++                      spec->type = FORMAT_TYPE_USHORT;
++      } else {
++              if (spec->flags & SIGN)
++                      spec->type = FORMAT_TYPE_INT;
++              else
++                      spec->type = FORMAT_TYPE_UINT;
++      }
++
++      return ++fmt - start;
+ }
+ /**
+@@ -642,18 +960,9 @@ static char *pointer(const char *fmt, ch
+ int vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
+ {
+       unsigned long long num;
+-      int base;
+       char *str, *end, c;
+-
+-      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 */
++      int read;
++      struct printf_spec spec = {0};
+       /* Reject out-of-range values early.  Large positive sizes are
+          used for unknown buffer sizes. */
+@@ -674,184 +983,137 @@ int vsnprintf(char *buf, size_t size, co
+               size = end - buf;
+       }
+-      for (; *fmt ; ++fmt) {
+-              if (*fmt != '%') {
+-                      if (str < end)
+-                              *str = *fmt;
+-                      ++str;
+-                      continue;
+-              }
++      while (*fmt) {
++              const char *old_fmt = fmt;
+-              /* 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;
+-                      }
++              read = format_decode(fmt, &spec);
+-              /* 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;
+-                      }
+-              }
++              fmt += read;
+-              /* 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);
++              switch (spec.type) {
++              case FORMAT_TYPE_NONE: {
++                      int copy = read;
++                      if (str < end) {
++                              if (copy > end - str)
++                                      copy = end - str;
++                              memcpy(str, old_fmt, copy);
+                       }
+-                      if (precision < 0)
+-                              precision = 0;
++                      str += read;
++                      break;
+               }
+-              /* 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;
+-                      }
+-              }
++              case FORMAT_TYPE_WIDTH:
++                      spec.field_width = va_arg(args, int);
++                      break;
+-              /* default base */
+-              base = 10;
++              case FORMAT_TYPE_PRECISION:
++                      spec.precision = va_arg(args, int);
++                      break;
+-              switch (*fmt) {
+-                      case 'c':
+-                              if (!(flags & LEFT)) {
+-                                      while (--field_width > 0) {
+-                                              if (str < end)
+-                                                      *str = ' ';
+-                                              ++str;
+-                                      }
+-                              }
+-                              c = (unsigned char) va_arg(args, int);
+-                              if (str < end)
+-                                      *str = c;
+-                              ++str;
+-                              while (--field_width > 0) {
++              case FORMAT_TYPE_CHAR:
++                      if (!(spec.flags & LEFT)) {
++                              while (--spec.field_width > 0) {
+                                       if (str < end)
+                                               *str = ' ';
+                                       ++str;
+-                              }
+-                              continue;
+-
+-                      case 's':
+-                              str = string(str, end, va_arg(args, char *), field_width, precision, flags);
+-                              continue;
+-
+-                      case 'p':
+-                              str = pointer(fmt+1, str, end,
+-                                              va_arg(args, void *),
+-                                              field_width, precision, flags);
+-                              /* Skip all alphanumeric pointer suffixes */
+-                              while (isalnum(fmt[1]))
+-                                      fmt++;
+-                              continue;
+-                      case 'n':
+-                              /* FIXME:
+-                              * What does C99 say about the overflow case here? */
+-                              if (qualifier == 'l') {
+-                                      long * ip = va_arg(args, long *);
+-                                      *ip = (str - buf);
+-                              } else if (qualifier == 'Z' || qualifier == 'z') {
+-                                      size_t * ip = va_arg(args, size_t *);
+-                                      *ip = (str - buf);
+-                              } else {
+-                                      int * ip = va_arg(args, int *);
+-                                      *ip = (str - buf);
+                               }
+-                              continue;
+-
+-                      case '%':
++                      }
++                      c = (unsigned char) va_arg(args, int);
++                      if (str < end)
++                              *str = c;
++                      ++str;
++                      while (--spec.field_width > 0) {
+                               if (str < end)
+-                                      *str = '%';
++                                      *str = ' ';
+                               ++str;
+-                              continue;
++                      }
++                      break;
+-                              /* integer number formats - set up the flags and "break" */
+-                      case 'o':
+-                              base = 8;
+-                              break;
++              case FORMAT_TYPE_STR:
++                      str = string(str, end, va_arg(args, char *), spec);
++                      break;
+-                      case 'x':
+-                              flags |= SMALL;
+-                      case 'X':
+-                              base = 16;
+-                              break;
++              case FORMAT_TYPE_PTR:
++                      str = pointer(fmt+1, str, end, va_arg(args, void *),
++                                    spec);
++                      while (isalnum(*fmt))
++                              fmt++;
++                      break;
+-                      case 'd':
+-                      case 'i':
+-                              flags |= SIGN;
+-                      case 'u':
+-                              break;
++              case FORMAT_TYPE_PERCENT_CHAR:
++                      if (str < end)
++                              *str = '%';
++                      ++str;
++                      break;
+-                      default:
+-                              if (str < end)
+-                                      *str = '%';
+-                              ++str;
+-                              if (*fmt) {
+-                                      if (str < end)
+-                                              *str = *fmt;
+-                                      ++str;
+-                              } else {
+-                                      --fmt;
+-                              }
+-                              continue;
++              case FORMAT_TYPE_INVALID:
++                      if (str < end)
++                              *str = '%';
++                      ++str;
++                      break;
++
++              case FORMAT_TYPE_NRCHARS: {
++                      int qualifier = spec.qualifier;
++
++                      if (qualifier == 'l') {
++                              long *ip = va_arg(args, long *);
++                              *ip = (str - buf);
++                      } else if (qualifier == 'Z' ||
++                                      qualifier == 'z') {
++                              size_t *ip = va_arg(args, size_t *);
++                              *ip = (str - buf);
++                      } else {
++                              int *ip = va_arg(args, int *);
++                              *ip = (str - buf);
++                      }
++                      break;
+               }
+-              if (qualifier == 'L')
+-                      num = va_arg(args, long long);
+-              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;
++
++              default:
++                      switch (spec.type) {
++                      case FORMAT_TYPE_LONG_LONG:
++                              num = va_arg(args, long long);
++                              break;
++                      case FORMAT_TYPE_ULONG:
++                              num = va_arg(args, unsigned long);
++                              break;
++                      case FORMAT_TYPE_LONG:
++                              num = va_arg(args, long);
++                              break;
++                      case FORMAT_TYPE_SIZE_T:
++                              num = va_arg(args, size_t);
++                              break;
++                      case FORMAT_TYPE_PTRDIFF:
++                              num = va_arg(args, ptrdiff_t);
++                              break;
++                      case FORMAT_TYPE_USHORT:
++                              num = (unsigned short) va_arg(args, int);
++                              break;
++                      case FORMAT_TYPE_SHORT:
++                              num = (short) va_arg(args, int);
++                              break;
++                      case FORMAT_TYPE_INT:
++                              num = (int) va_arg(args, int);
++                              break;
++                      default:
++                              num = va_arg(args, unsigned int);
++                      }
++
++                      str = number(str, end, num, spec);
+               }
+-              str = number(str, end, num, base,
+-                              field_width, precision, flags);
+       }
++
+       if (size > 0) {
+               if (str < end)
+                       *str = '\0';
+               else
+                       end[-1] = '\0';
+       }
++
+       /* the trailing null byte doesn't count towards the total */
+       return str-buf;
++
+ }
+ EXPORT_SYMBOL(vsnprintf);