tools/nolibc/stdio: add a minimal [vf]printf() implementation
authorWilly Tarreau <w@1wt.eu>
Mon, 7 Feb 2022 16:23:33 +0000 (17:23 +0100)
committerPaul E. McKenney <paulmck@kernel.org>
Thu, 21 Apr 2022 00:05:44 +0000 (17:05 -0700)
This adds a minimal vfprintf() implementation as well as the commonly
used fprintf() and printf() that rely on it.

For now the function supports:
  - formats: %s, %c, %u, %d, %x
  - modifiers: %l and %ll
  - unknown chars are considered as modifiers and are ignored

It is designed to remain minimalist, despite this printf() is 549 bytes
on x86_64. It would be wise not to add too many formats.

Signed-off-by: Willy Tarreau <w@1wt.eu>
Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
tools/include/nolibc/stdio.h

index 996bf89a30d25cbe980acb2389090ab9b2c7be36..a73cf24cb68d03db25f47a7133bb83e45ce0fbf1 100644 (file)
@@ -7,6 +7,8 @@
 #ifndef _NOLIBC_STDIO_H
 #define _NOLIBC_STDIO_H
 
+#include <stdarg.h>
+
 #include "std.h"
 #include "arch.h"
 #include "types.h"
@@ -158,4 +160,130 @@ char *fgets(char *s, int size, FILE *stream)
        return ofs ? s : NULL;
 }
 
+
+/* minimal vfprintf(). It supports the following formats:
+ *  - %[l*]{d,u,c,x}
+ *  - %s
+ *  - unknown modifiers are ignored.
+ */
+static __attribute__((unused))
+int vfprintf(FILE *stream, const char *fmt, va_list args)
+{
+       char escape, lpref, c;
+       unsigned long long v;
+       unsigned int written;
+       size_t len, ofs;
+       char tmpbuf[21];
+       const char *outstr;
+
+       written = ofs = escape = lpref = 0;
+       while (1) {
+               c = fmt[ofs++];
+
+               if (escape) {
+                       /* we're in an escape sequence, ofs == 1 */
+                       escape = 0;
+                       if (c == 'c' || c == 'd' || c == 'u' || c == 'x') {
+                               if (lpref) {
+                                       if (lpref > 1)
+                                               v = va_arg(args, unsigned long long);
+                                       else
+                                               v = va_arg(args, unsigned long);
+                               } else
+                                       v = va_arg(args, unsigned int);
+
+                               if (c == 'd') {
+                                       /* sign-extend the value */
+                                       if (lpref == 0)
+                                               v = (long long)(int)v;
+                                       else if (lpref == 1)
+                                               v = (long long)(long)v;
+                               }
+
+                               switch (c) {
+                               case 'd':
+                                       i64toa_r(v, tmpbuf);
+                                       break;
+                               case 'u':
+                                       u64toa_r(v, tmpbuf);
+                                       break;
+                               case 'x':
+                                       u64toh_r(v, tmpbuf);
+                                       break;
+                               default: /* 'c' */
+                                       tmpbuf[0] = v;
+                                       tmpbuf[1] = 0;
+                                       break;
+                               }
+                               outstr = tmpbuf;
+                       }
+                       else if (c == 's') {
+                               outstr = va_arg(args, char *);
+                       }
+                       else if (c == '%') {
+                               /* queue it verbatim */
+                               continue;
+                       }
+                       else {
+                               /* modifiers or final 0 */
+                               if (c == 'l') {
+                                       /* long format prefix, maintain the escape */
+                                       lpref++;
+                               }
+                               escape = 1;
+                               goto do_escape;
+                       }
+                       len = strlen(outstr);
+                       goto flush_str;
+               }
+
+               /* not an escape sequence */
+               if (c == 0 || c == '%') {
+                       /* flush pending data on escape or end */
+                       escape = 1;
+                       lpref = 0;
+                       outstr = fmt;
+                       len = ofs - 1;
+               flush_str:
+                       if (_fwrite(outstr, len, stream) != 0)
+                               break;
+
+                       written += len;
+               do_escape:
+                       if (c == 0)
+                               break;
+                       fmt += ofs;
+                       ofs = 0;
+                       continue;
+               }
+
+               /* literal char, just queue it */
+       }
+       return written;
+}
+
+static __attribute__((unused))
+int fprintf(FILE *stream, const char *fmt, ...)
+{
+       va_list args;
+       int ret;
+
+       va_start(args, fmt);
+       ret = vfprintf(stream, fmt, args);
+       va_end(args);
+       return ret;
+}
+
+static __attribute__((unused))
+int printf(const char *fmt, ...)
+{
+       va_list args;
+       int ret;
+
+       va_start(args, fmt);
+       ret = vfprintf(stdout, fmt, args);
+       va_end(args);
+       return ret;
+}
+
 #endif /* _NOLIBC_STDIO_H */