kern/kernel/kprintf.c
fef 8fb2f7987c
kprintf: fix vararg bug
Turns out you can't pass a va_list to subroutines
as per the C standard, even though it worked
perfectly fine on ARM.  Well then, the entire
kprintf thing needs to be refactored anyway at
some point in the future, so that more formatting
options are supported.
2021-09-20 18:02:13 +02:00

208 lines
4.4 KiB
C

/* See the end of this file for copyright and license terms. */
#include <stdarg.h> /* from clang */
#include <string.h>
#include <gay/cdefs.h>
#include <gay/config.h>
#include <gay/errno.h>
#include <gay/kprintf.h>
#include <gay/types.h>
#if __SIZEOF_INT__ == 2
/* 5 decimal digits of 65535 (2 ** 16 - 1) */
# define PRINTF_UINT_BUFSZ 5
#elif __SIZEOF_INT__ == 4
/* 10 decimal digits of 4294967295 (2 ** 32 - 1) */
# define PRINTF_UINT_BUFSZ 10
#elif __SIZEOF_INT__ == 8
/* 20 decimal digits of 18446744073709551616 (2 ** 64 - 1) */
# define PRINTF_UINT_BUFSZ 20
#else
# error "Unsupported int size"
#endif
static struct kprintf_renderer *renderer = NULL;
int kprintf_set_renderer(struct kprintf_renderer *new)
{
int ret = 0;
if (renderer != NULL)
ret = renderer->flush(renderer);
renderer = new;
return ret;
}
static int fmt_handle_ptr(uintptr_t ptr)
{
static const char table[] = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
};
int ret;
/* 2 chars per byte, plus 2 for the "0x" hex prefix */
char str[2 * sizeof(uintptr_t) + 2];
char *pos = &str[2 * sizeof(uintptr_t) + 1];
str[0] = '0';
str[1] = 'x';
do {
*pos-- = table[ptr & 0xf];
ptr >>= 4;
} while (pos != &str[1]);
ret = renderer->write(renderer, str, 2 * sizeof(uintptr_t) + 2);
return ret;
}
static int fmt_handle_uint(unsigned int u)
{
char str[PRINTF_UINT_BUFSZ];
char *pos = &str[PRINTF_UINT_BUFSZ - 1];
do {
/* stupid big endian humans, forcing us to do the whole thing in reverse */
*pos-- = (char)(u % 10) + '0'; /* convert to ASCII */
u /= 10;
} while (u != 0);
pos++;
return (int)renderer->write(renderer, pos, PRINTF_UINT_BUFSZ - (pos - str));
}
static inline int fmt_handle_int(int i)
{
int ret = 0;
char minus = '-';
if (i < 0) {
ret = renderer->write(renderer, &minus, sizeof(minus));
i = -i;
}
if (ret >= 0) {
int uint_ret = fmt_handle_uint((unsigned int)i);
if (uint_ret < 0)
ret = uint_ret;
else if (ret < 0)
ret += uint_ret;
}
return ret;
}
int kvprintf(const char *fmt, va_list args)
{
ssize_t ret = 0;
const char *tmp = fmt;
union {
char c;
int d;
uintptr_t p;
char *s;
unsigned int u;
} val;
while (*tmp != '\0') {
if (*tmp++ == '%') {
/* flush out everything we have so far (minus one char for %) */
ssize_t write_ret = renderer->write(
renderer,
fmt,
(size_t)tmp - (size_t)fmt - 1
);
if (write_ret < 0) {
ret = write_ret;
break;
}
ret += write_ret;
ssize_t fmt_ret = 0;
switch (*tmp) {
case '%': /* literal percent sign */
fmt_ret = renderer->write(renderer, tmp, sizeof(*tmp));
break;
case 'c': /* char */
val.c = va_arg(args, int); /* POSIX wants an int */
fmt_ret = renderer->write(renderer, &val.c, sizeof(val.c));
break;
case 'd': /* int */
case 'i':
fmt_ret = fmt_handle_int(va_arg(args, int));
break;
case 'p': /* ptr */
fmt_ret = fmt_handle_ptr(va_arg(args, uintptr_t));
break;
case 's': /* string */
val.s = va_arg(args, char *);
fmt_ret = renderer->write(renderer, val.s, strlen(val.s));
break;
case 'u': /* unsigned int */
fmt_ret = fmt_handle_uint(va_arg(args, unsigned int));
break;
}
tmp++;
/*
* act as if the current position were the beginning in
* order to make the first step of this if block easier
*/
fmt = tmp;
if (fmt_ret < 0)
break;
ret += fmt_ret;
}
}
if (tmp != fmt && ret >= 0) {
ssize_t render_ret = renderer->write(renderer, fmt, (size_t)tmp - (size_t)fmt);
if (render_ret < 0)
ret = render_ret;
else
ret += render_ret;
}
ssize_t flush_ret = renderer->flush(renderer);
if (flush_ret < 0)
ret = flush_ret;
else
ret += flush_ret;
return (int)ret;
}
int kprintf(const char *fmt, ...)
{
int ret;
va_list args;
va_start(args, fmt);
ret = kvprintf(fmt, args);
va_end(args);
return ret;
}
/*
* This file is part of GayBSD.
* Copyright (c) 2021 fef <owo@fef.moe>.
*
* GayBSD is nonviolent software: you may only use, redistribute, and/or
* modify it under the terms of the Cooperative Nonviolent Public License
* (CNPL) as found in the LICENSE file in the source code root directory
* or at <https://git.pixie.town/thufie/npl-builder>; either version 7
* of the license, or (at your option) any later version.
*
* GayBSD comes with ABSOLUTELY NO WARRANTY, to the extent
* permitted by applicable law. See the CNPL for details.
*/