mirror of
https://github.com/torvalds/linux.git
synced 2026-04-18 14:53:58 -04:00
Add support for dynamically allocating formatted strings through asprintf() and vasprintf(). Signed-off-by: Thomas Weißschuh <linux@weissschuh.net> Acked-by: Willy Tarreau <w@1wt.eu> Link: https://patch.msgid.link/20260401-nolibc-asprintf-v1-3-46292313439f@weissschuh.net
1005 lines
23 KiB
C
1005 lines
23 KiB
C
/* 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.
|
|
*/
|
|
out = outbuf + 2 + 31;
|
|
|
|
if (v == 0) {
|
|
/* There are special rules for zero. */
|
|
if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'p')) {
|
|
/* "%p" match glibc, precision is ignored */
|
|
outstr = "(nil)";
|
|
len = 5;
|
|
goto do_output;
|
|
}
|
|
if (!precision) {
|
|
/* Explicit %nn.0d, no digits output (except for %#.0o) */
|
|
len = 0;
|
|
goto prepend_sign;
|
|
}
|
|
/* All other formats (including "%#x") just output "0". */
|
|
out[0] = '0';
|
|
len = 1;
|
|
} else {
|
|
/* Convert the number to ascii in the required base. */
|
|
unsigned long long recip;
|
|
unsigned int base;
|
|
if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'd', 'i', 'u')) {
|
|
base = 10;
|
|
recip = _NOLIBC_U64TOA_RECIP(10);
|
|
} else if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'o')) {
|
|
base = 8;
|
|
recip = _NOLIBC_U64TOA_RECIP(8);
|
|
} else {
|
|
base = 16;
|
|
recip = _NOLIBC_U64TOA_RECIP(16);
|
|
if (_NOLIBC_PF_FLAGS_CONTAIN(ch_flag, 'p', '#' - 1)) {
|
|
/* "%p" and "%#x" need "0x" prepending. */
|
|
sign_prefix = '0' << 8 | 'x';
|
|
}
|
|
}
|
|
len = _nolibc_u64toa_base(v, out, base, recip);
|
|
}
|
|
|
|
/* Add zero padding */
|
|
if (precision < 0) {
|
|
/* No explicit precision (or negative from "%.*s"). */
|
|
if (!_NOLIBC_PF_FLAGS_CONTAIN(flags, '0'))
|
|
goto no_zero_padding;
|
|
if (_NOLIBC_PF_FLAGS_CONTAIN(flags, '-'))
|
|
/* Left justify overrides zero pad */
|
|
goto no_zero_padding;
|
|
/* eg "%05d", Zero pad to field width less sign.
|
|
* Note that precision can end up negative so all
|
|
* the variables have to be 'signed int'.
|
|
*/
|
|
precision = width;
|
|
if (sign_prefix) {
|
|
precision--;
|
|
if (sign_prefix >= 256)
|
|
precision--;
|
|
}
|
|
}
|
|
if (precision > 31)
|
|
/* Don't run off the start of outbuf[], arbitrary limit
|
|
* longer than the longest number field. */
|
|
precision = 31;
|
|
for (; len < precision; len++) {
|
|
/* Stop gcc generating horrid code and memset(). */
|
|
_NOLIBC_OPTIMIZER_HIDE_VAR(len);
|
|
*--out = '0';
|
|
}
|
|
no_zero_padding:
|
|
|
|
/* %#o has set sign_prefix to '0', but we don't want so add an extra
|
|
* leading zero here.
|
|
* Since the only other byte values of sign_prefix are ' ', '+' and '-'
|
|
* it is enough to check that out[] doesn't already start with sign_prefix.
|
|
*/
|
|
if (sign_prefix - *out) {
|
|
prepend_sign:
|
|
/* Add the 0, 1 or 2 ("0x") sign/prefix characters at the front. */
|
|
for (; sign_prefix; sign_prefix >>= 8) {
|
|
/* Force gcc to increment len inside the loop. */
|
|
_NOLIBC_OPTIMIZER_HIDE_VAR(len);
|
|
len++;
|
|
*--out = sign_prefix;
|
|
}
|
|
}
|
|
outstr = out;
|
|
goto do_output;
|
|
}
|
|
|
|
if (ch == 'm') {
|
|
#ifdef NOLIBC_IGNORE_ERRNO
|
|
outstr = "unknown error";
|
|
#else
|
|
outstr = strerror(errno);
|
|
#endif /* NOLIBC_IGNORE_ERRNO */
|
|
goto do_strlen_output;
|
|
}
|
|
|
|
if (ch != '%') {
|
|
/* Invalid format: back up to output the format characters */
|
|
fmt = outstr + 1;
|
|
/* and output a '%' now. */
|
|
}
|
|
/* %% is documented as a 'conversion specifier'.
|
|
* Any flags, precision or length modifier are ignored.
|
|
*/
|
|
len = 1;
|
|
width = 0;
|
|
outstr = fmt - 1;
|
|
goto do_output;
|
|
|
|
do_strlen_output:
|
|
/* Open coded strnlen() (slightly smaller). */
|
|
for (len = 0; precision < 0 || len < precision; len++)
|
|
if (!outstr[len])
|
|
break;
|
|
|
|
do_output:
|
|
written += len;
|
|
|
|
/* Stop gcc back-merging this code into one of the conditionals above. */
|
|
_NOLIBC_OPTIMIZER_HIDE_VAR(len);
|
|
|
|
/* Output the characters on the required side of any padding. */
|
|
width -= len;
|
|
flags = _NOLIBC_PF_FLAGS_CONTAIN(flags, '-');
|
|
if (flags && cb(state, outstr, len) != 0)
|
|
return -1;
|
|
while (width > 0) {
|
|
/* Output pad in 16 byte blocks with the small block first. */
|
|
int pad_len = ((width - 1) & 15) + 1;
|
|
width -= pad_len;
|
|
written += pad_len;
|
|
if (cb(state, " ", pad_len) != 0)
|
|
return -1;
|
|
}
|
|
if (!flags && cb(state, outstr, len) != 0)
|
|
return -1;
|
|
}
|
|
|
|
/* 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, format(printf, 2, 0)))
|
|
int __nolibc_vasprintf(char **strp, const char *fmt, va_list args1, va_list args2)
|
|
{
|
|
int len1, len2;
|
|
char *buf;
|
|
|
|
len1 = vsnprintf(NULL, 0, fmt, args1);
|
|
if (len1 < 0)
|
|
return -1;
|
|
|
|
buf = malloc(len1 + 1);
|
|
if (!buf)
|
|
return -1;
|
|
|
|
len2 = vsnprintf(buf, len1 + 1, fmt, args2);
|
|
if (len2 < 0) {
|
|
free(buf);
|
|
return -1;
|
|
}
|
|
|
|
*strp = buf;
|
|
return len1;
|
|
}
|
|
|
|
static __attribute__((unused, format(printf, 2, 0)))
|
|
int vasprintf(char **strp, const char *fmt, va_list args)
|
|
{
|
|
va_list args2;
|
|
int ret;
|
|
|
|
va_copy(args2, args);
|
|
ret = __nolibc_vasprintf(strp, fmt, args, args2);
|
|
va_end(args2);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static __attribute__((unused, format(printf, 2, 3)))
|
|
int asprintf(char **strp, const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
int ret;
|
|
|
|
va_start(args, fmt);
|
|
ret = vasprintf(strp, 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))
|
|
int strerror_r(int errnum, char *buf, size_t buflen)
|
|
{
|
|
if (buflen < 18)
|
|
return ERANGE;
|
|
|
|
__builtin_memcpy(buf, "errno=", 6);
|
|
i64toa_r(errnum, buf + 6);
|
|
return 0;
|
|
}
|
|
|
|
static __attribute__((unused))
|
|
const char *strerror(int errnum)
|
|
{
|
|
static char buf[18];
|
|
char *b = buf;
|
|
|
|
/* Force gcc to use 'register offset' to access buf[]. */
|
|
_NOLIBC_OPTIMIZER_HIDE_VAR(b);
|
|
|
|
/* Use strerror_r() to avoid having the only .data in small programs. */
|
|
strerror_r(errnum, b, sizeof(buf));
|
|
|
|
return b;
|
|
}
|
|
|
|
#endif /* _NOLIBC_STDIO_H */
|