| /* SPDX-License-Identifier: LGPL-2.1 OR MIT */ | |
| /* | |
| * minimal stdio function definitions for NOLIBC | |
| * 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" | |
| #include "stdlib.h" | |
| #include "string.h" | |
| #include "compiler.h" | |
| static const char *strerror(int errnum); | |
| #ifndef EOF | |
| #define EOF (-1) | |
| #endif | |
| /* Buffering mode used by setvbuf. */ | |
| #define _IOFBF 0 /* Fully buffered. */ | |
| #define _IOLBF 1 /* Line buffered. */ | |
| #define _IONBF 2 /* No buffering. */ | |
| /* just define FILE as a non-empty type. The value of the pointer gives | |
| * the FD: FILE=~fd for fd>=0 or NULL for fd<0. This way positive FILE | |
| * are immediately identified as abnormal entries (i.e. possible copies | |
| * of valid pointers to something else). | |
| */ | |
| typedef struct FILE { | |
| char dummy[1]; | |
| } FILE; | |
| static __attribute__((unused)) FILE* const stdin = (FILE*)(intptr_t)~STDIN_FILENO; | |
| static __attribute__((unused)) FILE* const stdout = (FILE*)(intptr_t)~STDOUT_FILENO; | |
| static __attribute__((unused)) FILE* const stderr = (FILE*)(intptr_t)~STDERR_FILENO; | |
| /* provides a FILE* equivalent of fd. The mode is ignored. */ | |
| static __attribute__((unused)) | |
| FILE *fdopen(int fd, const char *mode __attribute__((unused))) | |
| { | |
| if (fd < 0) { | |
| SET_ERRNO(EBADF); | |
| return NULL; | |
| } | |
| 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) | |
| { | |
| intptr_t i = (intptr_t)stream; | |
| if (i >= 0) { | |
| SET_ERRNO(EBADF); | |
| return -1; | |
| } | |
| return ~i; | |
| } | |
| /* flush a stream. */ | |
| static __attribute__((unused)) | |
| int fflush(FILE *stream) | |
| { | |
| intptr_t i = (intptr_t)stream; | |
| /* NULL is valid here. */ | |
| if (i > 0) { | |
| SET_ERRNO(EBADF); | |
| return -1; | |
| } | |
| /* Don't do anything, nolibc does not support buffering. */ | |
| return 0; | |
| } | |
| /* flush a stream. */ | |
| static __attribute__((unused)) | |
| int fclose(FILE *stream) | |
| { | |
| intptr_t i = (intptr_t)stream; | |
| if (i >= 0) { | |
| SET_ERRNO(EBADF); | |
| return -1; | |
| } | |
| if (close(~i)) | |
| return EOF; | |
| return 0; | |
| } | |
| /* getc(), fgetc(), getchar() */ | |
| #define getc(stream) fgetc(stream) | |
| static __attribute__((unused)) | |
| int fgetc(FILE* stream) | |
| { | |
| unsigned char ch; | |
| if (read(fileno(stream), &ch, 1) <= 0) | |
| return EOF; | |
| return ch; | |
| } | |
| static __attribute__((unused)) | |
| int getchar(void) | |
| { | |
| return fgetc(stdin); | |
| } | |
| /* putc(), fputc(), putchar() */ | |
| #define putc(c, stream) fputc(c, stream) | |
| static __attribute__((unused)) | |
| int fputc(int c, FILE* stream) | |
| { | |
| unsigned char ch = c; | |
| if (write(fileno(stream), &ch, 1) <= 0) | |
| return EOF; | |
| return ch; | |
| } | |
| static __attribute__((unused)) | |
| int putchar(int c) | |
| { | |
| return fputc(c, stdout); | |
| } | |
| /* 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. | |
| */ | |
| static __attribute__((unused)) | |
| int _fwrite(const void *buf, size_t size, FILE *stream) | |
| { | |
| ssize_t ret; | |
| int fd = fileno(stream); | |
| while (size) { | |
| ret = write(fd, buf, size); | |
| if (ret <= 0) | |
| return EOF; | |
| size -= ret; | |
| buf += ret; | |
| } | |
| return 0; | |
| } | |
| static __attribute__((unused)) | |
| size_t fwrite(const void *s, size_t size, size_t nmemb, FILE *stream) | |
| { | |
| size_t written; | |
| for (written = 0; written < nmemb; written++) { | |
| if (_fwrite(s, size, stream) != 0) | |
| break; | |
| s += size; | |
| } | |
| 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) | |
| { | |
| return _fwrite(s, strlen(s), stream); | |
| } | |
| static __attribute__((unused)) | |
| int puts(const char *s) | |
| { | |
| if (fputs(s, stdout) == EOF) | |
| return EOF; | |
| return putchar('\n'); | |
| } | |
| /* fgets() */ | |
| static __attribute__((unused)) | |
| char *fgets(char *s, int size, FILE *stream) | |
| { | |
| int ofs; | |
| int c; | |
| for (ofs = 0; ofs + 1 < size;) { | |
| c = fgetc(stream); | |
| if (c == EOF) | |
| break; | |
| s[ofs++] = c; | |
| if (c == '\n') | |
| break; | |
| } | |
| if (ofs < size) | |
| s[ofs] = 0; | |
| return ofs ? s : NULL; | |
| } | |
| /* 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. | |
| */ | |
| 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 ch; | |
| unsigned long long v; | |
| 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 = 0; | |
| while (1) { | |
| 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'; | |
| } | |
| /* 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; | |
| } | |
| /* 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); | |
| } | |
| /* 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--; | |
| } | |
| } | |
| 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'; | |
| } | |
| 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; | |
| } | |
| } | |
| outstr = out; | |
| goto do_output; | |
| } | |
| if (ch == 'm') { | |
| #ifdef NOLIBC_IGNORE_ERRNO | |
| outstr = "unknown error"; | |
| #else | |
| outstr = strerror(errno); | |
| #endif /* NOLIBC_IGNORE_ERRNO */ | |
| goto do_strlen_output; | |
| } | |
| 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; | |
| 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) | |
| { | |
| return vfprintf(stdout, fmt, args); | |
| } | |
| static __attribute__((unused, format(printf, 2, 3))) | |
| 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, format(printf, 1, 2))) | |
| int printf(const char *fmt, ...) | |
| { | |
| va_list args; | |
| int ret; | |
| va_start(args, fmt); | |
| ret = vfprintf(stdout, fmt, args); | |
| va_end(args); | |
| 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)) | |
| int setvbuf(FILE *stream __attribute__((unused)), | |
| char *buf __attribute__((unused)), | |
| int mode, | |
| size_t size __attribute__((unused))) | |
| { | |
| /* | |
| * nolibc does not support buffering so this is a nop. Just check mode | |
| * is valid as required by the spec. | |
| */ | |
| switch (mode) { | |
| case _IOFBF: | |
| case _IOLBF: | |
| case _IONBF: | |
| break; | |
| default: | |
| return EOF; | |
| } | |
| return 0; | |
| } | |
| static __attribute__((unused)) | |
| int strerror_r(int errnum, char *buf, size_t buflen) | |
| { | |
| if (buflen < 18) | |
| return ERANGE; | |
| __builtin_memcpy(buf, "errno=", 6); | |
| i64toa_r(errnum, buf + 6); | |
| return 0; | |
| } | |
| 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 */ |