|  | /* 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(), 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; | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* minimal printf(). It supports the following formats: | 
|  | *  - %[l*]{d,u,c,x,p} | 
|  | *  - %s | 
|  | *  - unknown modifiers are ignored. | 
|  | */ | 
|  | typedef int (*__nolibc_printf_cb)(intptr_t state, const char *buf, size_t size); | 
|  |  | 
|  | static __attribute__((unused, format(printf, 4, 0))) | 
|  | int __nolibc_printf(__nolibc_printf_cb cb, intptr_t state, size_t n, const char *fmt, va_list args) | 
|  | { | 
|  | char escape, lpref, c; | 
|  | unsigned long long v; | 
|  | unsigned int written, width; | 
|  | size_t len, ofs, w; | 
|  | char tmpbuf[21]; | 
|  | const char *outstr; | 
|  |  | 
|  | written = ofs = escape = lpref = 0; | 
|  | while (1) { | 
|  | c = fmt[ofs++]; | 
|  | width = 0; | 
|  |  | 
|  | if (escape) { | 
|  | /* we're in an escape sequence, ofs == 1 */ | 
|  | escape = 0; | 
|  |  | 
|  | /* width */ | 
|  | while (c >= '0' && c <= '9') { | 
|  | width *= 10; | 
|  | width += c - '0'; | 
|  |  | 
|  | c = fmt[ofs++]; | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } | 
|  | outstr = tmpbuf; | 
|  | } | 
|  | else if (c == 's') { | 
|  | outstr = va_arg(args, char *); | 
|  | if (!outstr) | 
|  | outstr="(null)"; | 
|  | } | 
|  | #ifndef NOLIBC_IGNORE_ERRNO | 
|  | else if (c == 'm') { | 
|  | outstr = strerror(errno); | 
|  | } | 
|  | #endif /* NOLIBC_IGNORE_ERRNO */ | 
|  | else if (c == '%') { | 
|  | /* queue it verbatim */ | 
|  | continue; | 
|  | } | 
|  | else { | 
|  | /* modifiers or final 0 */ | 
|  | if (c == 'l') { | 
|  | /* long format prefix, maintain the escape */ | 
|  | lpref++; | 
|  | } else if (c == 'j') { | 
|  | lpref = 2; | 
|  | } | 
|  | 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 (n) { | 
|  | w = len < n ? len : n; | 
|  | n -= w; | 
|  | while (width-- > w) { | 
|  | if (cb(state, " ", 1) != 0) | 
|  | return -1; | 
|  | written += 1; | 
|  | } | 
|  | if (cb(state, outstr, w) != 0) | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | written += len; | 
|  | do_escape: | 
|  | if (c == 0) | 
|  | break; | 
|  | fmt += ofs; | 
|  | ofs = 0; | 
|  | continue; | 
|  | } | 
|  |  | 
|  | /* literal char, just queue it */ | 
|  | } | 
|  | return written; | 
|  | } | 
|  |  | 
|  | static int __nolibc_fprintf_cb(intptr_t state, const char *buf, size_t size) | 
|  | { | 
|  | return _fwrite(buf, size, (FILE *)state); | 
|  | } | 
|  |  | 
|  | static __attribute__((unused, format(printf, 2, 0))) | 
|  | int vfprintf(FILE *stream, const char *fmt, va_list args) | 
|  | { | 
|  | return __nolibc_printf(__nolibc_fprintf_cb, (intptr_t)stream, SIZE_MAX, 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; | 
|  | } | 
|  |  | 
|  | static int __nolibc_sprintf_cb(intptr_t _state, const char *buf, size_t size) | 
|  | { | 
|  | char **state = (char **)_state; | 
|  |  | 
|  | memcpy(*state, buf, size); | 
|  | *state += size; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static __attribute__((unused, format(printf, 3, 0))) | 
|  | int vsnprintf(char *buf, size_t size, const char *fmt, va_list args) | 
|  | { | 
|  | char *state = buf; | 
|  | int ret; | 
|  |  | 
|  | ret = __nolibc_printf(__nolibc_sprintf_cb, (intptr_t)&state, size, fmt, args); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  | buf[(size_t)ret < size ? (size_t)ret : size - 1] = '\0'; | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | 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)) | 
|  | 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) | 
|  | { | 
|  | fprintf(stderr, "%s%serrno=%d\n", (msg && *msg) ? msg : "", (msg && *msg) ? ": " : "", errno); | 
|  | } | 
|  |  | 
|  | 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)) | 
|  | const char *strerror(int errno) | 
|  | { | 
|  | static char buf[18] = "errno="; | 
|  |  | 
|  | i64toa_r(errno, &buf[6]); | 
|  |  | 
|  | return buf; | 
|  | } | 
|  |  | 
|  | #endif /* _NOLIBC_STDIO_H */ |