1004 lines · 23.2 KB · C
RawDownload
/* 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.

Lines 1–500 of 1004.