diff options
Diffstat (limited to 'tools/include/nolibc/stdio.h')
| -rw-r--r-- | tools/include/nolibc/stdio.h | 803 |
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 */ |
