/* 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 */
