summaryrefslogtreecommitdiff
path: root/tools/include/nolibc/stdio.h
diff options
context:
space:
mode:
Diffstat (limited to 'tools/include/nolibc/stdio.h')
-rw-r--r--tools/include/nolibc/stdio.h803
1 files changed, 707 insertions, 96 deletions
diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
index 3892034198dd..ebdd413d13ec 100644
--- a/tools/include/nolibc/stdio.h
+++ b/tools/include/nolibc/stdio.h
@@ -4,12 +4,16 @@
* Copyright (C) 2017-2021 Willy Tarreau <w@1wt.eu>
*/
+/* make sure to include all global symbols */
+#include "nolibc.h"
+
#ifndef _NOLIBC_STDIO_H
#define _NOLIBC_STDIO_H
#include "std.h"
#include "arch.h"
#include "errno.h"
+#include "fcntl.h"
#include "types.h"
#include "sys.h"
#include "stdarg.h"
@@ -17,6 +21,8 @@
#include "string.h"
#include "compiler.h"
+static const char *strerror(int errnum);
+
#ifndef EOF
#define EOF (-1)
#endif
@@ -50,6 +56,32 @@ FILE *fdopen(int fd, const char *mode __attribute__((unused)))
return (FILE*)(intptr_t)~fd;
}
+static __attribute__((unused))
+FILE *fopen(const char *pathname, const char *mode)
+{
+ int flags, fd;
+
+ switch (*mode) {
+ case 'r':
+ flags = O_RDONLY;
+ break;
+ case 'w':
+ flags = O_WRONLY | O_CREAT | O_TRUNC;
+ break;
+ case 'a':
+ flags = O_WRONLY | O_CREAT | O_APPEND;
+ break;
+ default:
+ SET_ERRNO(EINVAL); return NULL;
+ }
+
+ if (mode[1] == '+')
+ flags = (flags & ~(O_RDONLY | O_WRONLY)) | O_RDWR;
+
+ fd = open(pathname, flags, 0666);
+ return fdopen(fd, mode);
+}
+
/* provides the fd of stream. */
static __attribute__((unused))
int fileno(FILE *stream)
@@ -138,7 +170,7 @@ int putchar(int c)
}
-/* fwrite(), puts(), fputs(). Note that puts() emits '\n' but not fputs(). */
+/* fwrite(), fread(), puts(), fputs(). Note that puts() emits '\n' but not fputs(). */
/* internal fwrite()-like function which only takes a size and returns 0 on
* success or EOF on error. It automatically retries on short writes.
@@ -172,6 +204,38 @@ size_t fwrite(const void *s, size_t size, size_t nmemb, FILE *stream)
return written;
}
+/* internal fread()-like function which only takes a size and returns 0 on
+ * success or EOF on error. It automatically retries on short reads.
+ */
+static __attribute__((unused))
+int _fread(void *buf, size_t size, FILE *stream)
+{
+ int fd = fileno(stream);
+ ssize_t ret;
+
+ while (size) {
+ ret = read(fd, buf, size);
+ if (ret <= 0)
+ return EOF;
+ size -= ret;
+ buf += ret;
+ }
+ return 0;
+}
+
+static __attribute__((unused))
+size_t fread(void *s, size_t size, size_t nmemb, FILE *stream)
+{
+ size_t nread;
+
+ for (nread = 0; nread < nmemb; nread++) {
+ if (_fread(s, size, stream) != 0)
+ break;
+ s += size;
+ }
+ return nread;
+}
+
static __attribute__((unused))
int fputs(const char *s, FILE *stream)
{
@@ -208,117 +272,392 @@ char *fgets(char *s, int size, FILE *stream)
}
-/* minimal vfprintf(). It supports the following formats:
- * - %[l*]{d,u,c,x,p}
- * - %s
- * - unknown modifiers are ignored.
+/* fseek */
+static __attribute__((unused))
+int fseek(FILE *stream, long offset, int whence)
+{
+ int fd = fileno(stream);
+ off_t ret;
+
+ ret = lseek(fd, offset, whence);
+
+ /* lseek() and fseek() differ in that lseek returns the new
+ * position or -1, fseek() returns either 0 or -1.
+ */
+ if (ret >= 0)
+ return 0;
+
+ return -1;
+}
+
+
+/* printf(). Supports most of the normal integer and string formats.
+ * - %[#0-+ ][width|*[.precision|*}][{l,t,z,ll,L,j,q}]{c,d,i,u,o,x,X,p,s,m,%}
+ * - %% generates a single %
+ * - %m outputs strerror(errno).
+ * - %X outputs a..f the same as %x.
+ * - No support for floating point or wide characters.
+ * - Invalid formats are copied to the output buffer.
+ *
+ * Called by vfprintf() and snprintf() to do the actual formatting.
+ * The callers provide a callback function to save the formatted data.
+ * The callback function is called multiple times:
+ * - for each group of literal characters in the format string.
+ * - for field padding.
+ * - for each conversion specifier.
+ * - with (NULL, 0) at the end of the __nolibc_printf.
+ * If the callback returns non-zero __nolibc_printf() immediately returns -1.
*/
-static __attribute__((unused, format(printf, 2, 0)))
-int vfprintf(FILE *stream, const char *fmt, va_list args)
+
+typedef int (*__nolibc_printf_cb)(void *state, const char *buf, size_t size);
+
+/* This code uses 'flag' variables that are indexed by the low 6 bits
+ * of characters to optimise checks for multiple characters.
+ *
+ * _NOLIBC_PF_FLAGS_CONTAIN(flags, 'a', 'b'. ...)
+ * returns non-zero if the bit for any of the specified characters is set.
+ *
+ * _NOLIBC_PF_CHAR_IS_ONE_OF(ch, 'a', 'b'. ...)
+ * returns the flag bit for ch if it is one of the specified characters.
+ * All the characters must be in the same 32 character block (non-alphabetic,
+ * upper case, or lower case) of the ASCII character set.
+ */
+#define _NOLIBC_PF_FLAG(ch) (1u << ((ch) & 0x1f))
+#define _NOLIBC_PF_FLAG_NZ(ch) ((ch) ? _NOLIBC_PF_FLAG(ch) : 0)
+#define _NOLIBC_PF_FLAG8(cmp_1, cmp_2, cmp_3, cmp_4, cmp_5, cmp_6, cmp_7, cmp_8, ...) \
+ (_NOLIBC_PF_FLAG_NZ(cmp_1) | _NOLIBC_PF_FLAG_NZ(cmp_2) | \
+ _NOLIBC_PF_FLAG_NZ(cmp_3) | _NOLIBC_PF_FLAG_NZ(cmp_4) | \
+ _NOLIBC_PF_FLAG_NZ(cmp_5) | _NOLIBC_PF_FLAG_NZ(cmp_6) | \
+ _NOLIBC_PF_FLAG_NZ(cmp_7) | _NOLIBC_PF_FLAG_NZ(cmp_8))
+#define _NOLIBC_PF_FLAGS_CONTAIN(flags, ...) \
+ ((flags) & _NOLIBC_PF_FLAG8(__VA_ARGS__, 0, 0, 0, 0, 0, 0, 0))
+#define _NOLIBC_PF_CHAR_IS_ONE_OF(ch, cmp_1, ...) \
+ ((unsigned int)(ch) - (cmp_1 & 0xe0) > 0x1f ? 0 : \
+ _NOLIBC_PF_FLAGS_CONTAIN(_NOLIBC_PF_FLAG(ch), cmp_1, __VA_ARGS__))
+
+static __attribute__((unused, format(printf, 3, 0)))
+int __nolibc_printf(__nolibc_printf_cb cb, void *state, const char *fmt, va_list args)
{
- char escape, lpref, c;
+ char ch;
unsigned long long v;
- unsigned int written;
- size_t len, ofs;
- char tmpbuf[21];
+ long long signed_v;
+ int written, width, precision, len;
+ unsigned int flags, ch_flag;
+ char outbuf[2 + 31 + 22 + 1];
+ char *out;
const char *outstr;
+ unsigned int sign_prefix;
+ int got_width;
- written = ofs = escape = lpref = 0;
+ written = 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' || c == 'p') {
- char *out = tmpbuf;
-
- if (c == 'p')
- v = va_arg(args, unsigned long);
- else 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;
+ outstr = fmt;
+ ch = *fmt++;
+ if (!ch)
+ break;
+
+ width = 0;
+ flags = 0;
+ if (ch != '%') {
+ while (*fmt && *fmt != '%')
+ fmt++;
+ /* Output characters from the format string. */
+ len = fmt - outstr;
+ goto do_output;
+ }
+
+ /* we're in a format sequence */
+
+ /* Conversion flag characters */
+ while (1) {
+ ch = *fmt++;
+ ch_flag = _NOLIBC_PF_CHAR_IS_ONE_OF(ch, ' ', '#', '+', '-', '0');
+ if (!ch_flag)
+ break;
+ flags |= ch_flag;
+ }
+
+ /* Width and precision */
+ for (got_width = 0;; ch = *fmt++) {
+ if (ch == '*') {
+ precision = va_arg(args, int);
+ ch = *fmt++;
+ } else {
+ for (precision = 0; ch >= '0' && ch <= '9'; ch = *fmt++)
+ precision = precision * 10 + (ch - '0');
+ }
+ if (got_width)
+ break;
+ width = precision;
+ if (ch != '.') {
+ /* Default precision for strings */
+ precision = -1;
+ break;
+ }
+ got_width = 1;
+ }
+ /* A negative width (e.g. from "%*s") requests left justify. */
+ if (width < 0) {
+ width = -width;
+ flags |= _NOLIBC_PF_FLAG('-');
+ }
+
+ /* Length modifier.
+ * They miss the conversion flags characters " #+-0" so can go into flags.
+ * Change both L and ll to j (all always 64bit).
+ */
+ if (ch == 'L')
+ ch = 'j';
+ ch_flag = _NOLIBC_PF_CHAR_IS_ONE_OF(ch, 'l', 't', 'z', 'j', 'q');
+ if (ch_flag != 0) {
+ if (ch == 'l' && fmt[0] == 'l') {
+ fmt++;
+ ch_flag = _NOLIBC_PF_FLAG('j');
+ }
+ flags |= ch_flag;
+ ch = *fmt++;
+ }
+
+ /* Conversion specifiers. */
+
+ /* Numeric and pointer conversion specifiers.
+ *
+ * Use an explicit bound check (rather than _NOLIBC_PF_CHAR_IS_ONE_OF())
+ * so that 'X' can be allowed through.
+ * 'X' gets treated and 'x' because _NOLIBC_PF_FLAG() returns the same
+ * value for both.
+ *
+ * We need to check for "%p" or "%#x" later, merging here gives better code.
+ * But '#' collides with 'c' so shift right.
+ */
+ ch_flag = _NOLIBC_PF_FLAG(ch) | (flags & _NOLIBC_PF_FLAG('#')) >> 1;
+ if (((ch >= 'a' && ch <= 'z') || ch == 'X') &&
+ _NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'c', 'd', 'i', 'u', 'o', 'x', 'p', 's')) {
+ /* 'long' is needed for pointer/string conversions and ltz lengths.
+ * A single test can be used provided 'p' (the same bit as '0')
+ * is masked from flags.
+ */
+ if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag | (flags & ~_NOLIBC_PF_FLAG('p')),
+ 'p', 's', 'l', 't', 'z')) {
+ v = va_arg(args, unsigned long);
+ signed_v = (long)v;
+ } else if (_NOLIBC_PF_FLAGS_CONTAIN(flags, 'j', 'q')) {
+ v = va_arg(args, unsigned long long);
+ signed_v = v;
+ } else {
+ v = va_arg(args, unsigned int);
+ signed_v = (int)v;
+ }
+
+ if (ch == 'c') {
+ /* "%c" - single character. */
+ outbuf[0] = v;
+ len = 1;
+ outstr = outbuf;
+ goto do_output;
+ }
+
+ if (ch == 's') {
+ /* "%s" - character string. */
+ outstr = (const char *)(uintptr_t)v;
+ if (!outstr) {
+ outstr = "(null)";
+ /* Match glibc, nothing output if precision too small */
+ len = precision < 0 || precision >= 6 ? 6 : 0;
+ goto do_output;
+ }
+ goto do_strlen_output;
+ }
+
+ /* The 'sign_prefix' can be zero, one or two ("0x") characters.
+ * Prepended least significant byte first stopping on a zero byte.
+ */
+ sign_prefix = 0;
+
+ if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'd', 'i')) {
+ /* "%d" and "%i" - signed decimal numbers. */
+ if (signed_v < 0) {
+ sign_prefix = '-';
+ v = -(signed_v + 1);
+ v++;
+ } else if (_NOLIBC_PF_FLAGS_CONTAIN(flags, '+')) {
+ sign_prefix = '+';
+ } else if (_NOLIBC_PF_FLAGS_CONTAIN(flags, ' ')) {
+ sign_prefix = ' ';
}
+ } else {
+ /* "#o" requires that the output always starts with a '0'.
+ * This needs another check after any zero padding to avoid
+ * adding an extra leading '0'.
+ */
+ if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'o') &&
+ _NOLIBC_PF_FLAGS_CONTAIN(ch_flag, '#' - 1))
+ sign_prefix = '0';
+ }
- switch (c) {
- case 'c':
- out[0] = v;
- out[1] = 0;
- break;
- case 'd':
- i64toa_r(v, out);
- break;
- case 'u':
- u64toa_r(v, out);
- break;
- case 'p':
- *(out++) = '0';
- *(out++) = 'x';
- __nolibc_fallthrough;
- default: /* 'x' and 'p' above */
- u64toh_r(v, out);
- break;
+ /* The value is converted offset into the buffer so that
+ * 31 zero pad characters and the sign/prefix can be added in front.
+ * The longest digit string is 22 + 1 for octal conversions.
+ */
+ out = outbuf + 2 + 31;
+
+ if (v == 0) {
+ /* There are special rules for zero. */
+ if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'p')) {
+ /* "%p" match glibc, precision is ignored */
+ outstr = "(nil)";
+ len = 5;
+ goto do_output;
+ }
+ if (!precision) {
+ /* Explicit %nn.0d, no digits output (except for %#.0o) */
+ len = 0;
+ goto prepend_sign;
}
- outstr = tmpbuf;
+ /* All other formats (including "%#x") just output "0". */
+ out[0] = '0';
+ len = 1;
+ } else {
+ /* Convert the number to ascii in the required base. */
+ unsigned long long recip;
+ unsigned int base;
+ if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'd', 'i', 'u')) {
+ base = 10;
+ recip = _NOLIBC_U64TOA_RECIP(10);
+ } else if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'o')) {
+ base = 8;
+ recip = _NOLIBC_U64TOA_RECIP(8);
+ } else {
+ base = 16;
+ recip = _NOLIBC_U64TOA_RECIP(16);
+ if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'p', '#' - 1)) {
+ /* "%p" and "%#x" need "0x" prepending. */
+ sign_prefix = '0' << 8 | 'x';
+ }
+ }
+ len = _nolibc_u64toa_base(v, out, base, recip);
}
- else if (c == 's') {
- outstr = va_arg(args, char *);
- if (!outstr)
- outstr="(null)";
+
+ /* Add zero padding */
+ if (precision < 0) {
+ /* No explicit precision (or negative from "%.*s"). */
+ if (!_NOLIBC_PF_FLAGS_CONTAIN(flags, '0'))
+ goto no_zero_padding;
+ if (_NOLIBC_PF_FLAGS_CONTAIN(flags, '-'))
+ /* Left justify overrides zero pad */
+ goto no_zero_padding;
+ /* eg "%05d", Zero pad to field width less sign.
+ * Note that precision can end up negative so all
+ * the variables have to be 'signed int'.
+ */
+ precision = width;
+ if (sign_prefix) {
+ precision--;
+ if (sign_prefix >= 256)
+ precision--;
+ }
}
- else if (c == '%') {
- /* queue it verbatim */
- continue;
+ if (precision > 31)
+ /* Don't run off the start of outbuf[], arbitrary limit
+ * longer than the longest number field. */
+ precision = 31;
+ for (; len < precision; len++) {
+ /* Stop gcc generating horrid code and memset(). */
+ _NOLIBC_OPTIMIZER_HIDE_VAR(len);
+ *--out = '0';
}
- else {
- /* modifiers or final 0 */
- if (c == 'l') {
- /* long format prefix, maintain the escape */
- lpref++;
+no_zero_padding:
+
+ /* %#o has set sign_prefix to '0', but we don't want so add an extra
+ * leading zero here.
+ * Since the only other byte values of sign_prefix are ' ', '+' and '-'
+ * it is enough to check that out[] doesn't already start with sign_prefix.
+ */
+ if (sign_prefix - *out) {
+prepend_sign:
+ /* Add the 0, 1 or 2 ("0x") sign/prefix characters at the front. */
+ for (; sign_prefix; sign_prefix >>= 8) {
+ /* Force gcc to increment len inside the loop. */
+ _NOLIBC_OPTIMIZER_HIDE_VAR(len);
+ len++;
+ *--out = sign_prefix;
}
- escape = 1;
- goto do_escape;
}
- len = strlen(outstr);
- goto flush_str;
+ outstr = out;
+ goto do_output;
}
- /* 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;
+ if (ch == 'm') {
+#ifdef NOLIBC_IGNORE_ERRNO
+ outstr = "unknown error";
+#else
+ outstr = strerror(errno);
+#endif /* NOLIBC_IGNORE_ERRNO */
+ goto do_strlen_output;
+ }
- written += len;
- do_escape:
- if (c == 0)
- break;
- fmt += ofs;
- ofs = 0;
- continue;
+ if (ch != '%') {
+ /* Invalid format: back up to output the format characters */
+ fmt = outstr + 1;
+ /* and output a '%' now. */
}
+ /* %% is documented as a 'conversion specifier'.
+ * Any flags, precision or length modifier are ignored.
+ */
+ len = 1;
+ width = 0;
+ outstr = fmt - 1;
+ goto do_output;
+
+do_strlen_output:
+ /* Open coded strnlen() (slightly smaller). */
+ for (len = 0; precision < 0 || len < precision; len++)
+ if (!outstr[len])
+ break;
- /* literal char, just queue it */
+do_output:
+ written += len;
+
+ /* Stop gcc back-merging this code into one of the conditionals above. */
+ _NOLIBC_OPTIMIZER_HIDE_VAR(len);
+
+ /* Output the characters on the required side of any padding. */
+ width -= len;
+ flags = _NOLIBC_PF_FLAGS_CONTAIN(flags, '-');
+ if (flags && cb(state, outstr, len) != 0)
+ return -1;
+ while (width > 0) {
+ /* Output pad in 16 byte blocks with the small block first. */
+ int pad_len = ((width - 1) & 15) + 1;
+ width -= pad_len;
+ written += pad_len;
+ if (cb(state, " ", pad_len) != 0)
+ return -1;
+ }
+ if (!flags && cb(state, outstr, len) != 0)
+ return -1;
}
+
+ /* Request a final '\0' be added to the snprintf() output.
+ * This may be the only call of the cb() function.
+ */
+ if (cb(state, NULL, 0) != 0)
+ return -1;
+
return written;
}
+static int __nolibc_fprintf_cb(void *stream, const char *buf, size_t size)
+{
+ return _fwrite(buf, size, stream);
+}
+
+static __attribute__((unused, format(printf, 2, 0)))
+int vfprintf(FILE *stream, const char *fmt, va_list args)
+{
+ return __nolibc_printf(__nolibc_fprintf_cb, stream, fmt, args);
+}
+
static __attribute__((unused, format(printf, 1, 0)))
int vprintf(const char *fmt, va_list args)
{
@@ -349,10 +688,269 @@ int printf(const char *fmt, ...)
return ret;
}
+static __attribute__((unused, format(printf, 2, 0)))
+int vdprintf(int fd, const char *fmt, va_list args)
+{
+ FILE *stream;
+
+ stream = fdopen(fd, NULL);
+ if (!stream)
+ return -1;
+ /* Technically 'stream' is leaked, but as it's only a wrapper around 'fd' that is fine */
+ return vfprintf(stream, fmt, args);
+}
+
+static __attribute__((unused, format(printf, 2, 3)))
+int dprintf(int fd, const char *fmt, ...)
+{
+ va_list args;
+ int ret;
+
+ va_start(args, fmt);
+ ret = vdprintf(fd, fmt, args);
+ va_end(args);
+
+ return ret;
+}
+
+struct __nolibc_sprintf_cb_state {
+ char *buf;
+ size_t space;
+};
+
+static int __nolibc_sprintf_cb(void *v_state, const char *buf, size_t size)
+{
+ struct __nolibc_sprintf_cb_state *state = v_state;
+ size_t space = state->space;
+ char *tgt;
+
+ /* Truncate the request to fit in the output buffer space.
+ * The last byte is reserved for the terminating '\0'.
+ * state->space can only be zero for snprintf(NULL, 0, fmt, args)
+ * so this normally lets through calls with 'size == 0'.
+ */
+ if (size >= space) {
+ if (space <= 1)
+ return 0;
+ size = space - 1;
+ }
+ tgt = state->buf;
+
+ /* __nolibc_printf() ends with cb(state, NULL, 0) to request the output
+ * buffer be '\0' terminated.
+ * That will be the only cb() call for, eg, snprintf(buf, sz, "").
+ * Zero lengths can occur at other times (eg "%s" for an empty string).
+ * Unconditionally write the '\0' byte to reduce code size, it is
+ * normally overwritten by the data being output.
+ * There is no point adding a '\0' after copied data - there is always
+ * another call.
+ */
+ *tgt = '\0';
+ if (size) {
+ state->space = space - size;
+ state->buf = tgt + size;
+ memcpy(tgt, buf, size);
+ }
+
+ return 0;
+}
+
+static __attribute__((unused, format(printf, 3, 0)))
+int vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
+{
+ struct __nolibc_sprintf_cb_state state = { .buf = buf, .space = size };
+
+ return __nolibc_printf(__nolibc_sprintf_cb, &state, fmt, args);
+}
+
+static __attribute__((unused, format(printf, 3, 4)))
+int snprintf(char *buf, size_t size, const char *fmt, ...)
+{
+ va_list args;
+ int ret;
+
+ va_start(args, fmt);
+ ret = vsnprintf(buf, size, fmt, args);
+ va_end(args);
+
+ return ret;
+}
+
+static __attribute__((unused, format(printf, 2, 0)))
+int vsprintf(char *buf, const char *fmt, va_list args)
+{
+ return vsnprintf(buf, SIZE_MAX, fmt, args);
+}
+
+static __attribute__((unused, format(printf, 2, 3)))
+int sprintf(char *buf, const char *fmt, ...)
+{
+ va_list args;
+ int ret;
+
+ va_start(args, fmt);
+ ret = vsprintf(buf, fmt, args);
+ va_end(args);
+
+ return ret;
+}
+
+static __attribute__((unused, format(printf, 2, 0)))
+int __nolibc_vasprintf(char **strp, const char *fmt, va_list args1, va_list args2)
+{
+ int len1, len2;
+ char *buf;
+
+ len1 = vsnprintf(NULL, 0, fmt, args1);
+ if (len1 < 0)
+ return -1;
+
+ buf = malloc(len1 + 1);
+ if (!buf)
+ return -1;
+
+ len2 = vsnprintf(buf, len1 + 1, fmt, args2);
+ if (len2 < 0) {
+ free(buf);
+ return -1;
+ }
+
+ *strp = buf;
+ return len1;
+}
+
+static __attribute__((unused, format(printf, 2, 0)))
+int vasprintf(char **strp, const char *fmt, va_list args)
+{
+ va_list args2;
+ int ret;
+
+ va_copy(args2, args);
+ ret = __nolibc_vasprintf(strp, fmt, args, args2);
+ va_end(args2);
+
+ return ret;
+}
+
+static __attribute__((unused, format(printf, 2, 3)))
+int asprintf(char **strp, const char *fmt, ...)
+{
+ va_list args;
+ int ret;
+
+ va_start(args, fmt);
+ ret = vasprintf(strp, fmt, args);
+ va_end(args);
+
+ return ret;
+}
+
+static __attribute__((unused))
+int vsscanf(const char *str, const char *format, va_list args)
+{
+ uintmax_t uval;
+ intmax_t ival;
+ int base;
+ char *endptr;
+ int matches;
+ int lpref;
+
+ matches = 0;
+
+ while (1) {
+ if (*format == '%') {
+ /* start of pattern */
+ lpref = 0;
+ format++;
+
+ if (*format == 'l') {
+ /* same as in printf() */
+ lpref = 1;
+ format++;
+ if (*format == 'l') {
+ lpref = 2;
+ format++;
+ }
+ }
+
+ if (*format == '%') {
+ /* literal % */
+ if ('%' != *str)
+ goto done;
+ str++;
+ format++;
+ continue;
+ } else if (*format == 'd') {
+ ival = strtoll(str, &endptr, 10);
+ if (lpref == 0)
+ *va_arg(args, int *) = ival;
+ else if (lpref == 1)
+ *va_arg(args, long *) = ival;
+ else if (lpref == 2)
+ *va_arg(args, long long *) = ival;
+ } else if (*format == 'u' || *format == 'x' || *format == 'X') {
+ base = *format == 'u' ? 10 : 16;
+ uval = strtoull(str, &endptr, base);
+ if (lpref == 0)
+ *va_arg(args, unsigned int *) = uval;
+ else if (lpref == 1)
+ *va_arg(args, unsigned long *) = uval;
+ else if (lpref == 2)
+ *va_arg(args, unsigned long long *) = uval;
+ } else if (*format == 'p') {
+ *va_arg(args, void **) = (void *)strtoul(str, &endptr, 16);
+ } else {
+ SET_ERRNO(EILSEQ);
+ goto done;
+ }
+
+ format++;
+ str = endptr;
+ matches++;
+
+ } else if (*format == '\0') {
+ goto done;
+ } else if (isspace(*format)) {
+ /* skip spaces in format and str */
+ while (isspace(*format))
+ format++;
+ while (isspace(*str))
+ str++;
+ } else if (*format == *str) {
+ /* literal match */
+ format++;
+ str++;
+ } else {
+ if (!matches)
+ matches = EOF;
+ goto done;
+ }
+ }
+
+done:
+ return matches;
+}
+
+static __attribute__((unused, format(scanf, 2, 3)))
+int sscanf(const char *str, const char *format, ...)
+{
+ va_list args;
+ int ret;
+
+ va_start(args, format);
+ ret = vsscanf(str, format, args);
+ va_end(args);
+ return ret;
+}
+
static __attribute__((unused))
void perror(const char *msg)
{
+#ifdef NOLIBC_IGNORE_ERRNO
+ fprintf(stderr, "%s%sunknown error\n", (msg && *msg) ? msg : "", (msg && *msg) ? ": " : "");
+#else
fprintf(stderr, "%s%serrno=%d\n", (msg && *msg) ? msg : "", (msg && *msg) ? ": " : "", errno);
+#endif
}
static __attribute__((unused))
@@ -378,16 +976,29 @@ int setvbuf(FILE *stream __attribute__((unused)),
}
static __attribute__((unused))
-const char *strerror(int errno)
+int strerror_r(int errnum, char *buf, size_t buflen)
{
- static char buf[18] = "errno=";
+ if (buflen < 18)
+ return ERANGE;
- i64toa_r(errno, &buf[6]);
-
- return buf;
+ __builtin_memcpy(buf, "errno=", 6);
+ i64toa_r(errnum, buf + 6);
+ return 0;
}
-/* make sure to include all global symbols */
-#include "nolibc.h"
+static __attribute__((unused))
+const char *strerror(int errnum)
+{
+ static char buf[18];
+ char *b = buf;
+
+ /* Force gcc to use 'register offset' to access buf[]. */
+ _NOLIBC_OPTIMIZER_HIDE_VAR(b);
+
+ /* Use strerror_r() to avoid having the only .data in small programs. */
+ strerror_r(errnum, b, sizeof(buf));
+
+ return b;
+}
#endif /* _NOLIBC_STDIO_H */