root/tools/include/nolibc/stdio.h
/* 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;
}


/* 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)";
                        }
                        else if (c == 'm') {
#ifdef NOLIBC_IGNORE_ERRNO
                                outstr = "unknown error";
#else
                                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)
{
#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 */