/* SPDX-License-Identifier: LGPL-2.1 OR MIT */ /* * minimal stdio function definitions for NOLIBC * Copyright (C) 2017-2021 Willy Tarreau */ /* 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. * * 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); 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, ch; unsigned long long v; unsigned int written, width; size_t len, ofs; char outbuf[21]; const char *outstr; written = ofs = escape = lpref = 0; while (1) { ch = fmt[ofs++]; width = 0; if (escape) { /* we're in an escape sequence, ofs == 1 */ escape = 0; /* width */ while (ch >= '0' && ch <= '9') { width *= 10; width += ch - '0'; ch = fmt[ofs++]; } if (ch == 'c' || ch == 'd' || ch == 'u' || ch == 'x' || ch == 'p') { char *out = outbuf; if (ch == '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 (ch == 'd') { /* sign-extend the value */ if (lpref == 0) v = (long long)(int)v; else if (lpref == 1) v = (long long)(long)v; } switch (ch) { 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 = outbuf; } else if (ch == 's') { outstr = va_arg(args, char *); if (!outstr) outstr="(null)"; } else if (ch == 'm') { #ifdef NOLIBC_IGNORE_ERRNO outstr = "unknown error"; #else outstr = strerror(errno); #endif /* NOLIBC_IGNORE_ERRNO */ } else if (ch == '%') { /* queue it verbatim */ continue; } else { /* modifiers or final 0 */ if (ch == 'l') { /* long format prefix, maintain the escape */ lpref++; } else if (ch == 'j') { lpref = 2; } escape = 1; goto do_escape; } len = strlen(outstr); goto flush_str; } /* not an escape sequence */ if (ch == 0 || ch == '%') { /* flush pending data on escape or end */ escape = 1; lpref = 0; outstr = fmt; len = ofs - 1; flush_str: while (width-- > len) { if (cb(state, " ", 1) != 0) return -1; written += 1; } if (cb(state, outstr, len) != 0) return -1; written += len; do_escape: if (ch == 0) break; fmt += ofs; ofs = 0; continue; } /* literal char, just queue it */ } /* 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)) 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)) const char *strerror(int errno) { static char buf[18] = "errno="; i64toa_r(errno, &buf[6]); return buf; } #endif /* _NOLIBC_STDIO_H */