kprintf: implement "full" format sequence support
So this was painful. kprintf() supports most of the format specifiers from BSD now, except for the $ sequence. It has gotten a general overhaul and is significantly more sophisticated, bloated and slower now. There is also some minor stuff i forgot about, like the 't' length modifier, but that isn't so important right now and will be fixed later(TM).
This commit is contained in:
parent
c31149c6cc
commit
d475429639
6 changed files with 537 additions and 147 deletions
|
|
@ -55,9 +55,9 @@ enum vga_color fb_foreground;
|
||||||
#define cell_at(line, col) ( &framebuffer[(line) * FB_COLS + (col)] )
|
#define cell_at(line, col) ( &framebuffer[(line) * FB_COLS + (col)] )
|
||||||
#define current_cell (cell_at(fb_line, fb_col))
|
#define current_cell (cell_at(fb_line, fb_col))
|
||||||
|
|
||||||
static ssize_t fb_write(struct kprintf_renderer *renderer, const void *buf, size_t size);
|
static ssize_t fb_write(struct kprintf_printer *renderer, const void *buf, size_t size);
|
||||||
static ssize_t fb_flush(struct kprintf_renderer *renderer);
|
static ssize_t fb_flush(struct kprintf_printer *renderer);
|
||||||
static struct kprintf_renderer fb_kprintf_renderer = {
|
static struct kprintf_printer fb_kprintf_renderer = {
|
||||||
.write = fb_write,
|
.write = fb_write,
|
||||||
.flush = fb_flush,
|
.flush = fb_flush,
|
||||||
};
|
};
|
||||||
|
|
@ -76,7 +76,7 @@ extern int main(int argc, char *argv[]);
|
||||||
|
|
||||||
__asmlink void _boot(u32 magic, void *address) /* NOLINT */
|
__asmlink void _boot(u32 magic, void *address) /* NOLINT */
|
||||||
{
|
{
|
||||||
kprintf_set_renderer(&fb_kprintf_renderer);
|
kprintf_set_printer(&fb_kprintf_renderer);
|
||||||
fb_init(VGA_COLOR_LIGHT_GREY, VGA_COLOR_BLACK);
|
fb_init(VGA_COLOR_LIGHT_GREY, VGA_COLOR_BLACK);
|
||||||
|
|
||||||
x86_setup_interrupts();
|
x86_setup_interrupts();
|
||||||
|
|
@ -173,7 +173,7 @@ static void fb_newline(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t fb_write(struct kprintf_renderer *renderer, const void *buf, size_t size)
|
static ssize_t fb_write(struct kprintf_printer *renderer, const void *buf, size_t size)
|
||||||
{
|
{
|
||||||
ssize_t ret = 0;
|
ssize_t ret = 0;
|
||||||
const u8 *s = buf;
|
const u8 *s = buf;
|
||||||
|
|
@ -200,7 +200,7 @@ static ssize_t fb_write(struct kprintf_renderer *renderer, const void *buf, size
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t fb_flush(struct kprintf_renderer *renderer)
|
static ssize_t fb_flush(struct kprintf_printer *renderer)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ int mem_init(uintptr_t start_phys, uintptr_t end_phys)
|
||||||
|
|
||||||
setup_pagemap();
|
setup_pagemap();
|
||||||
|
|
||||||
kprintf("Available memory: %u bytes (%u pages)\n",
|
kprintf("Available memory: %zu bytes (%lu pages)\n",
|
||||||
dynpage_end - dynpage_start, (dynpage_end - dynpage_start) / PAGE_SIZE);
|
dynpage_end - dynpage_start, (dynpage_end - dynpage_start) / PAGE_SIZE);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,9 @@
|
||||||
|
|
||||||
#define __aligned(bytes) __attribute__(( aligned(bytes) ))
|
#define __aligned(bytes) __attribute__(( aligned(bytes) ))
|
||||||
|
|
||||||
|
#ifndef __always_inline
|
||||||
#define __always_inline inline __attribute__(( always_inline ))
|
#define __always_inline inline __attribute__(( always_inline ))
|
||||||
|
#endif
|
||||||
|
|
||||||
#define __packed __attribute__(( packed ))
|
#define __packed __attribute__(( packed ))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,41 +32,43 @@ int kprintf(const char *__restrict fmt, ...);
|
||||||
int kvprintf(const char *__restrict fmt, va_list args);
|
int kvprintf(const char *__restrict fmt, va_list args);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Renderer functions that `kprintf()` and friends call for writing.
|
* @brief Printing functions that `kprintf()` and friends call for writing.
|
||||||
* Depending on the current kernel log output target (which in turn depends
|
* Depending on the current kernel log output target (which in turn depends
|
||||||
* primarily on the current boot stage), this will be implemented by different
|
* primarily on the current runlevel), this will be implemented by different
|
||||||
* subsystems. To change the renderer, call `kprintf_set_renderer()`.
|
* subsystems. To change the printer, call `kprintf_set_printer()`.
|
||||||
*/
|
*/
|
||||||
struct kprintf_renderer {
|
struct kprintf_printer {
|
||||||
/**
|
/**
|
||||||
* @brief Write to the kernel log.
|
* @brief Write to the kernel log.
|
||||||
|
* The data itself may be cached in a buffer rather than written to the
|
||||||
|
* target immediately; `krpintf()` will call `flush()` when needed.
|
||||||
*
|
*
|
||||||
* @param renderer A reference to the original structure
|
* @param printer A reference to the original structure
|
||||||
* @param buf Data to write
|
* @param buf Data to write
|
||||||
* @param len Amount of data in bytes
|
* @param len Length of `buf` in bytes
|
||||||
* @returns The amount of bytes actually written,
|
* @returns The amount of bytes actually written,
|
||||||
* or a negative code from `errno.h` on failure
|
* or a negative code from `errno.h` on failure
|
||||||
*/
|
*/
|
||||||
ssize_t (*write)(struct kprintf_renderer *renderer, const void *buf, size_t len);
|
ssize_t (*write)(struct kprintf_printer *printer, const void *buf, size_t len);
|
||||||
/**
|
/**
|
||||||
* @brief Flush the kernel log buffer.
|
* @brief Flush the kernel log buffer.
|
||||||
* On implementations that don't have a buffer, this can be a no-op.
|
* On implementations that don't have a buffer, this can be a no-op.
|
||||||
* If that is the case, this call should always return 0.
|
* If that is the case, this call should always return 0.
|
||||||
*
|
*
|
||||||
* @param renderer A reference to the original structure
|
* @param printer A reference to the original structure
|
||||||
* @returns The amount of bytes flushed out (0 if none),
|
* @returns The amount of bytes flushed out (0 if none),
|
||||||
* or a negative code from `errno.h` on failure
|
* or a negative code from `errno.h` on failure
|
||||||
*/
|
*/
|
||||||
ssize_t (*flush)(struct kprintf_renderer *renderer);
|
ssize_t (*flush)(struct kprintf_printer *printer);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Set the current renderer for the `fprintf()` family of functions.
|
* @brief Set the current printer for the `kprintf()` family of functions.
|
||||||
*
|
*
|
||||||
* @param renderer Implementation of the write and flush functions
|
* @param new Implementation of the write and flush functions
|
||||||
* @returns 0 on success, or a negative code from `errno.h` on failure
|
* @returns 0 on success, or a negative code from `errno.h` on failure
|
||||||
*/
|
*/
|
||||||
int kprintf_set_renderer(struct kprintf_renderer *renderer);
|
int kprintf_set_printer(struct kprintf_printer *new);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This file is part of GayBSD.
|
* This file is part of GayBSD.
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,11 @@ typedef __SIZE_TYPE__ ssize_t;
|
||||||
typedef ssize_t isize;
|
typedef ssize_t isize;
|
||||||
typedef size_t usize;
|
typedef size_t usize;
|
||||||
|
|
||||||
|
typedef __INTMAX_TYPE__ intmax_t;
|
||||||
|
typedef __UINTMAX_TYPE__ uintmax_t;
|
||||||
|
typedef __WCHAR_TYPE__ wchar_t;
|
||||||
|
typedef __WINT_TYPE__ wint_t;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This file is part of GayBSD.
|
* This file is part of GayBSD.
|
||||||
* Copyright (c) 2021 fef <owo@fef.moe>.
|
* Copyright (c) 2021 fef <owo@fef.moe>.
|
||||||
|
|
|
||||||
637
kernel/kprintf.c
637
kernel/kprintf.c
|
|
@ -1,156 +1,95 @@
|
||||||
/* See the end of this file for copyright and license terms. */
|
/* See the end of this file for copyright and license terms. */
|
||||||
|
|
||||||
#include <stdarg.h> /* from clang */
|
/*
|
||||||
|
* Ok, i'm gonna be honest, this is one of those functions where i wish i was
|
||||||
|
* writing them in Rust rather than C. Anyway, the tricky part of this entire
|
||||||
|
* implementation is that we can't call kmalloc() because that uses kprintf()
|
||||||
|
* internally for debug messages and the such, so it could potentially end in an
|
||||||
|
* infinite loop. But, since format strings allow arbitrary padding, we need to
|
||||||
|
* resort to our good old friend alloca().
|
||||||
|
* Also, this code is probably buggy as shit and i only tested some basic format
|
||||||
|
* sequences, so don't push it too far. Be gentle.
|
||||||
|
* I hope i never have to touch this again.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <alloca.h> /* clang */
|
||||||
|
#include <stdarg.h> /* clang */
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <gay/cdefs.h>
|
#include <gay/cdefs.h>
|
||||||
#include <gay/config.h>
|
|
||||||
#include <gay/errno.h>
|
|
||||||
#include <gay/kprintf.h>
|
#include <gay/kprintf.h>
|
||||||
#include <gay/types.h>
|
#include <gay/types.h>
|
||||||
|
|
||||||
#if __SIZEOF_INT__ == 2
|
static struct kprintf_printer *printer = NULL;
|
||||||
/* 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_printer(struct kprintf_printer *new)
|
||||||
|
|
||||||
int kprintf_set_renderer(struct kprintf_renderer *new)
|
|
||||||
{
|
{
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
if (renderer != NULL)
|
if (printer != NULL)
|
||||||
ret = renderer->flush(renderer);
|
ret = printer->flush(printer);
|
||||||
|
|
||||||
renderer = new;
|
printer = new;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int fmt_handle_ptr(uintptr_t ptr)
|
enum length_modifier {
|
||||||
|
LENGTH_DEFAULT = 0,
|
||||||
|
LENGTH_H,
|
||||||
|
LENGTH_HH,
|
||||||
|
LENGTH_L,
|
||||||
|
LENGTH_LL,
|
||||||
|
LENGTH_J,
|
||||||
|
LENGTH_T,
|
||||||
|
LENGTH_Z,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct fmt_sequence {
|
||||||
|
isize (*render)(const struct fmt_sequence *sequence, va_list *ap);
|
||||||
|
unsigned int min_width;
|
||||||
|
unsigned int max_precision;
|
||||||
|
enum length_modifier length_modifier;
|
||||||
|
struct {
|
||||||
|
bool hash;
|
||||||
|
bool zero;
|
||||||
|
bool minus;
|
||||||
|
bool space;
|
||||||
|
bool plus;
|
||||||
|
bool apos;
|
||||||
|
} flags __packed; /* save some bytes on the stack :) */
|
||||||
|
bool uppercase;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void parse_fmt_sequence(struct fmt_sequence *sequence, const char **restrict pos);
|
||||||
|
|
||||||
|
/** @brief Write a NUL terminated string using the current `printer`. */
|
||||||
|
static isize write_asciz(const char *s);
|
||||||
|
/** @brief Write a specific amount of bytes using the current `printer`. */
|
||||||
|
static isize write_bytes(const void *buf, usize len);
|
||||||
|
|
||||||
|
int kvprintf(const char *fmt, va_list _args)
|
||||||
{
|
{
|
||||||
static const char table[] = {
|
isize ret = 0;
|
||||||
'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;
|
const char *tmp = fmt;
|
||||||
union {
|
va_list args;
|
||||||
char c;
|
va_copy(args, _args);
|
||||||
int d;
|
|
||||||
uintptr_t p;
|
|
||||||
char *s;
|
|
||||||
unsigned int u;
|
|
||||||
} val;
|
|
||||||
|
|
||||||
while (*tmp != '\0') {
|
while (*tmp != '\0') {
|
||||||
if (*tmp++ == '%') {
|
if (*tmp++ == '%') {
|
||||||
/* flush out everything we have so far (minus one char for %) */
|
/* write out everything we have so far (minus one char for %) */
|
||||||
ssize_t write_ret = renderer->write(
|
isize write_ret = write_bytes(fmt, (usize)tmp - (usize)fmt - 1);
|
||||||
renderer,
|
|
||||||
fmt,
|
|
||||||
(size_t)tmp - (size_t)fmt - 1
|
|
||||||
);
|
|
||||||
if (write_ret < 0) {
|
if (write_ret < 0) {
|
||||||
ret = write_ret;
|
ret = write_ret;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
ret += write_ret;
|
ret += write_ret;
|
||||||
|
|
||||||
ssize_t fmt_ret = 0;
|
isize fmt_ret = 0;
|
||||||
switch (*tmp) {
|
struct fmt_sequence sequence;
|
||||||
case '%': /* literal percent sign */
|
parse_fmt_sequence(&sequence, &tmp);
|
||||||
fmt_ret = renderer->write(renderer, tmp, sizeof(*tmp));
|
if (sequence.render != NULL)
|
||||||
break;
|
fmt_ret = sequence.render(&sequence, &args);
|
||||||
|
|
||||||
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
|
* act as if the current position were the beginning in
|
||||||
|
|
@ -165,19 +104,20 @@ int kvprintf(const char *fmt, va_list args)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tmp != fmt && ret >= 0) {
|
if (tmp != fmt && ret >= 0) {
|
||||||
ssize_t render_ret = renderer->write(renderer, fmt, (size_t)tmp - (size_t)fmt);
|
isize render_ret = write_bytes(fmt, (usize)tmp - (usize)fmt);
|
||||||
if (render_ret < 0)
|
if (render_ret < 0)
|
||||||
ret = render_ret;
|
ret = render_ret;
|
||||||
else
|
else
|
||||||
ret += render_ret;
|
ret += render_ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t flush_ret = renderer->flush(renderer);
|
isize flush_ret = printer->flush(printer);
|
||||||
if (flush_ret < 0)
|
if (flush_ret < 0)
|
||||||
ret = flush_ret;
|
ret = flush_ret;
|
||||||
else
|
else
|
||||||
ret += flush_ret;
|
ret += flush_ret;
|
||||||
|
|
||||||
|
va_end(args);
|
||||||
return (int)ret;
|
return (int)ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -193,6 +133,447 @@ int kprintf(const char *fmt, ...)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static isize render_c(const struct fmt_sequence *sequence, va_list *ap);
|
||||||
|
static isize render_d(const struct fmt_sequence *sequence, va_list *ap);
|
||||||
|
static isize render_o(const struct fmt_sequence *sequence, va_list *ap);
|
||||||
|
static isize render_p(const struct fmt_sequence *sequence, va_list *ap);
|
||||||
|
static isize render_s(const struct fmt_sequence *sequence, va_list *ap);
|
||||||
|
static isize render_u(const struct fmt_sequence *sequence, va_list *ap);
|
||||||
|
static isize render_x(const struct fmt_sequence *sequence, va_list *ap);
|
||||||
|
static isize render_percent(const struct fmt_sequence *sequence, va_list *ap);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Oh boi this is gonna be fun.
|
||||||
|
* So, this is basically a step by step implementation of the FreeBSD manpage
|
||||||
|
* for printf(), except that there is no support for all the deprecated
|
||||||
|
* specifiers and the (imho insane) $ directive:
|
||||||
|
* <https://www.freebsd.org/cgi/man.cgi?query=printf&sektion=3&manpath=FreeBSD+13.0-RELEASE>
|
||||||
|
*/
|
||||||
|
void parse_fmt_sequence(struct fmt_sequence *sequence, const char **restrict pos)
|
||||||
|
{
|
||||||
|
memset(sequence, 0, sizeof(*sequence));
|
||||||
|
|
||||||
|
if (**pos == '%') { /* %% */
|
||||||
|
sequence->render = render_percent;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* parse optional flags
|
||||||
|
*/
|
||||||
|
bool continue_parse_flags = true;
|
||||||
|
while (continue_parse_flags) {
|
||||||
|
switch (**pos) {
|
||||||
|
case '#':
|
||||||
|
sequence->flags.hash = true;
|
||||||
|
break;
|
||||||
|
case '0':
|
||||||
|
sequence->flags.zero = true;
|
||||||
|
break;
|
||||||
|
case '-':
|
||||||
|
sequence->flags.minus = true;
|
||||||
|
break;
|
||||||
|
case ' ':
|
||||||
|
/* the FreeBSD manpage says plus overrides space if both are used */
|
||||||
|
if (!sequence->flags.plus)
|
||||||
|
sequence->flags.space = true;
|
||||||
|
break;
|
||||||
|
case '+':
|
||||||
|
sequence->flags.plus = true;
|
||||||
|
sequence->flags.space = false;
|
||||||
|
break;
|
||||||
|
case '\'':
|
||||||
|
sequence->flags.apos = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
continue_parse_flags = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
(*pos)++;
|
||||||
|
}
|
||||||
|
(*pos)--;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* parse optional minimum digits
|
||||||
|
*/
|
||||||
|
while (**pos >= '0' && **pos <= '9') {
|
||||||
|
sequence->min_width *= 10;
|
||||||
|
sequence->min_width += **pos - '0';
|
||||||
|
if (sequence->max_precision > 128)
|
||||||
|
sequence->max_precision = 128;
|
||||||
|
(*pos)++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* parse optional maximum precision
|
||||||
|
*/
|
||||||
|
if (**pos == '.') {
|
||||||
|
(*pos)++;
|
||||||
|
while (**pos >= '0' && **pos <= '9') {
|
||||||
|
sequence->max_precision *= 10;
|
||||||
|
sequence->max_precision += **pos - '0';
|
||||||
|
/* sanitize length (prevents stack overflow) */
|
||||||
|
if (sequence->max_precision > 128)
|
||||||
|
sequence->max_precision = 128;
|
||||||
|
(*pos)++;
|
||||||
|
}
|
||||||
|
(*pos)--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* parse optional length modifier
|
||||||
|
*/
|
||||||
|
switch (**pos) {
|
||||||
|
case 'h':
|
||||||
|
case 'H':
|
||||||
|
if ((*pos)[1] == 'h' || (*pos)[1] == 'H') {
|
||||||
|
sequence->length_modifier = LENGTH_HH;
|
||||||
|
(*pos) += 2;
|
||||||
|
} else {
|
||||||
|
sequence->length_modifier = LENGTH_H;
|
||||||
|
(*pos) += 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'l':
|
||||||
|
case 'L':
|
||||||
|
if ((*pos)[1] == 'l' || (*pos)[1] == 'L') {
|
||||||
|
sequence->length_modifier = LENGTH_LL;
|
||||||
|
(*pos) += 2;
|
||||||
|
} else {
|
||||||
|
sequence->length_modifier = LENGTH_L;
|
||||||
|
(*pos) += 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'j':
|
||||||
|
case 'J':
|
||||||
|
sequence->length_modifier = LENGTH_J;
|
||||||
|
(*pos)++;
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
case 'T':
|
||||||
|
sequence->length_modifier = LENGTH_T;
|
||||||
|
(*pos)++;
|
||||||
|
break;
|
||||||
|
case 'z':
|
||||||
|
case 'Z':
|
||||||
|
sequence->length_modifier = LENGTH_Z;
|
||||||
|
(*pos)++;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* parse type specifier
|
||||||
|
*/
|
||||||
|
switch (**pos) {
|
||||||
|
case 'C':
|
||||||
|
sequence->length_modifier = LENGTH_L;
|
||||||
|
/* fall through */
|
||||||
|
case 'c':
|
||||||
|
sequence->render = render_c;
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
case 'i':
|
||||||
|
sequence->render = render_d;
|
||||||
|
break;
|
||||||
|
case 'o':
|
||||||
|
sequence->render = render_o;
|
||||||
|
break;
|
||||||
|
case 'P':
|
||||||
|
sequence->uppercase = true;
|
||||||
|
/* fall through */
|
||||||
|
case 'p':
|
||||||
|
sequence->render = render_p;
|
||||||
|
break;
|
||||||
|
case 'S':
|
||||||
|
sequence->length_modifier = LENGTH_L;
|
||||||
|
/* fall through */
|
||||||
|
case 's':
|
||||||
|
sequence->render = render_s;
|
||||||
|
break;
|
||||||
|
case 'u':
|
||||||
|
sequence->render = render_u;
|
||||||
|
break;
|
||||||
|
case 'X':
|
||||||
|
sequence->uppercase = true;
|
||||||
|
/* fall through */
|
||||||
|
case 'x':
|
||||||
|
sequence->render = render_x;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sequence->render = NULL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
(*pos)++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t render_c(const struct fmt_sequence *sequence, va_list *ap)
|
||||||
|
{
|
||||||
|
ssize_t ret;
|
||||||
|
|
||||||
|
if (sequence->length_modifier == LENGTH_L) {
|
||||||
|
wchar_t val = (wchar_t)va_arg(*ap, wint_t);
|
||||||
|
ret = printer->write(printer, &val, sizeof(val));
|
||||||
|
} else {
|
||||||
|
char val = (char)va_arg(*ap, int);
|
||||||
|
ret = printer->write(printer, &val, sizeof(val));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t render_s(const struct fmt_sequence *sequence, va_list *ap)
|
||||||
|
{
|
||||||
|
/* yes i know i forgot the wchar_t if the length_modifier is LENGTH_L but idgaf */
|
||||||
|
const char *s = va_arg(*ap, char *);
|
||||||
|
return write_asciz(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void get_arg_signed(intmax_t *dest,
|
||||||
|
const struct fmt_sequence *sequence,
|
||||||
|
va_list *ap)
|
||||||
|
{
|
||||||
|
switch (sequence->length_modifier) {
|
||||||
|
case LENGTH_H:
|
||||||
|
case LENGTH_HH:
|
||||||
|
case LENGTH_DEFAULT:
|
||||||
|
/* short and char will be promoted to int with parameter passing */
|
||||||
|
*dest = va_arg(*ap, int);
|
||||||
|
break;
|
||||||
|
case LENGTH_L:
|
||||||
|
*dest = va_arg(*ap, long);
|
||||||
|
break;
|
||||||
|
case LENGTH_LL:
|
||||||
|
*dest = va_arg(*ap, long long);
|
||||||
|
break;
|
||||||
|
case LENGTH_Z:
|
||||||
|
*dest = va_arg(*ap, isize);
|
||||||
|
break;
|
||||||
|
case LENGTH_J:
|
||||||
|
*dest = va_arg(*ap, intmax_t);
|
||||||
|
break;
|
||||||
|
case LENGTH_T:
|
||||||
|
*dest = va_arg(*ap, intptr_t);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void get_arg_unsigned(uintmax_t *dest,
|
||||||
|
const struct fmt_sequence *sequence,
|
||||||
|
va_list *ap)
|
||||||
|
{
|
||||||
|
switch (sequence->length_modifier) {
|
||||||
|
case LENGTH_H:
|
||||||
|
case LENGTH_HH:
|
||||||
|
case LENGTH_DEFAULT:
|
||||||
|
/* short and char will be promoted to int with parameter passing */
|
||||||
|
*dest = va_arg(*ap, unsigned int);
|
||||||
|
break;
|
||||||
|
case LENGTH_L:
|
||||||
|
*dest = va_arg(*ap, unsigned long);
|
||||||
|
break;
|
||||||
|
case LENGTH_LL:
|
||||||
|
*dest = va_arg(*ap, unsigned long long);
|
||||||
|
break;
|
||||||
|
case LENGTH_Z:
|
||||||
|
*dest = va_arg(*ap, usize);
|
||||||
|
break;
|
||||||
|
case LENGTH_J:
|
||||||
|
*dest = va_arg(*ap, uintmax_t);
|
||||||
|
break;
|
||||||
|
case LENGTH_T:
|
||||||
|
*dest = va_arg(*ap, uintptr_t);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *digit_table_smol = "0123456789abcdef";
|
||||||
|
static const char *digit_table_big = "0123456789ABCDEF";
|
||||||
|
|
||||||
|
static inline void stringify_uint(char **buf, uintmax_t val, const char *digit_table,
|
||||||
|
unsigned int radix, int direction)
|
||||||
|
{
|
||||||
|
do {
|
||||||
|
**buf = digit_table[val % radix];
|
||||||
|
*buf += direction;
|
||||||
|
} while ((val /= radix) != 0);
|
||||||
|
*buf -= direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
static isize render_d(const struct fmt_sequence *sequence, va_list *ap)
|
||||||
|
{
|
||||||
|
isize ret = 0;
|
||||||
|
intmax_t val;
|
||||||
|
get_arg_signed(&val, sequence, ap);
|
||||||
|
|
||||||
|
if (val < 0) {
|
||||||
|
val = -val;
|
||||||
|
ret = write_asciz("-");
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
} else if (sequence->flags.plus) {
|
||||||
|
ret = write_asciz("+");
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
} else if (sequence->flags.space) {
|
||||||
|
ret = write_asciz(" ");
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
usize len = 20; /* 2**64 has 20 decimal digits, let's hope intmax_t isn't 128 bits */
|
||||||
|
if (sequence->min_width > len)
|
||||||
|
len = sequence->min_width;
|
||||||
|
char *buf = alloca(len);
|
||||||
|
char *pos = &buf[len - 1];
|
||||||
|
|
||||||
|
stringify_uint(&pos, val, digit_table_smol, 10, -1);
|
||||||
|
while (sequence->min_width > len - (pos - buf))
|
||||||
|
*--pos = '0';
|
||||||
|
|
||||||
|
isize tmp = write_bytes(pos, len - (pos - buf));
|
||||||
|
if (tmp > 0)
|
||||||
|
ret += tmp;
|
||||||
|
else
|
||||||
|
ret = tmp;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static isize render_o(const struct fmt_sequence *sequence, va_list *ap)
|
||||||
|
{
|
||||||
|
isize ret = 0;
|
||||||
|
|
||||||
|
if (sequence->flags.plus) {
|
||||||
|
ret = write_asciz("+");
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
} else if (sequence->flags.space) {
|
||||||
|
ret = write_asciz(" ");
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
uintmax_t val;
|
||||||
|
get_arg_unsigned(&val, sequence, ap);
|
||||||
|
|
||||||
|
usize len = 22; /* 2**64 has 22 octal digits, let's hope intmax_t isn't 128 bits */
|
||||||
|
if (sequence->min_width > len)
|
||||||
|
len = sequence->min_width;
|
||||||
|
char *buf = alloca(len);
|
||||||
|
char *pos = &buf[len - 1];
|
||||||
|
|
||||||
|
stringify_uint(&pos, val, digit_table_smol, 8, -1);
|
||||||
|
while (sequence->min_width > len - (pos - buf))
|
||||||
|
*--pos = '0';
|
||||||
|
|
||||||
|
isize tmp = write_bytes(pos, len - (pos - buf));
|
||||||
|
if (tmp > 0)
|
||||||
|
ret += tmp;
|
||||||
|
else
|
||||||
|
ret = tmp;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static isize render_p(const struct fmt_sequence *sequence, va_list *ap)
|
||||||
|
{
|
||||||
|
/* 2 hex digits per byte + 2 for 0x prefix */
|
||||||
|
char buf[sizeof(uintptr_t) * 2 + 2];
|
||||||
|
char *pos = &buf[sizeof(uintptr_t) * 2 + 1];
|
||||||
|
const char *digit_table;
|
||||||
|
uintptr_t ptr = va_arg(*ap, uintptr_t);
|
||||||
|
|
||||||
|
buf[0] = '0';
|
||||||
|
buf[1] = 'x';
|
||||||
|
|
||||||
|
if (sequence->uppercase)
|
||||||
|
digit_table = digit_table_big;
|
||||||
|
else
|
||||||
|
digit_table = digit_table_smol;
|
||||||
|
|
||||||
|
while (pos > &buf[1]) {
|
||||||
|
*pos-- = digit_table[ptr % 0x10];
|
||||||
|
ptr >>= 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
return write_bytes(buf, sizeof(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
static isize render_u(const struct fmt_sequence *sequence, va_list *ap)
|
||||||
|
{
|
||||||
|
isize ret = 0;
|
||||||
|
|
||||||
|
if (sequence->flags.plus) {
|
||||||
|
ret = write_asciz("+");
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
} else if (sequence->flags.space) {
|
||||||
|
ret = write_asciz(" ");
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
uintmax_t val;
|
||||||
|
get_arg_unsigned(&val, sequence, ap);
|
||||||
|
|
||||||
|
usize len = 20; /* 2^64 has 20 decimal digits, let's hope intmax_t isn't 128 bits */
|
||||||
|
if (sequence->min_width > len)
|
||||||
|
len = sequence->min_width;
|
||||||
|
char *buf = alloca(len);
|
||||||
|
char *pos = &buf[len - 1];
|
||||||
|
|
||||||
|
stringify_uint(&pos, val, digit_table_smol, 10, -1);
|
||||||
|
while (sequence->min_width > len - (pos - buf))
|
||||||
|
*--pos = '0';
|
||||||
|
|
||||||
|
isize tmp = write_bytes(pos, len - (pos - buf));
|
||||||
|
if (tmp > 0)
|
||||||
|
ret += tmp;
|
||||||
|
else
|
||||||
|
ret = tmp;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static isize render_x(const struct fmt_sequence *sequence, va_list *ap)
|
||||||
|
{
|
||||||
|
char *buf;
|
||||||
|
usize len = sizeof(uintmax_t) * 2; /* 2 hex digits per byte */
|
||||||
|
if (len < sequence->min_width)
|
||||||
|
len = sequence->min_width;
|
||||||
|
buf = alloca(len);
|
||||||
|
char *pos = &buf[len];
|
||||||
|
|
||||||
|
uintmax_t val;
|
||||||
|
get_arg_unsigned(&val, sequence, ap);
|
||||||
|
|
||||||
|
const char *digit_table;
|
||||||
|
if (sequence->uppercase)
|
||||||
|
digit_table = digit_table_big;
|
||||||
|
else
|
||||||
|
digit_table = digit_table_smol;
|
||||||
|
|
||||||
|
stringify_uint(&pos, val, digit_table, 16, -1);
|
||||||
|
while (sequence->min_width > len - (pos - buf))
|
||||||
|
*--pos = '0';
|
||||||
|
|
||||||
|
return write_bytes(pos, len - (pos - buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
static isize render_percent(const struct fmt_sequence *sequence, va_list *ap)
|
||||||
|
{
|
||||||
|
return write_asciz("%");
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline isize write_asciz(const char *s)
|
||||||
|
{
|
||||||
|
return printer->write(printer, s, strlen(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline isize write_bytes(const void *buf, usize len)
|
||||||
|
{
|
||||||
|
return printer->write(printer, buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This file is part of GayBSD.
|
* This file is part of GayBSD.
|
||||||
* Copyright (c) 2021 fef <owo@fef.moe>.
|
* Copyright (c) 2021 fef <owo@fef.moe>.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue