libports: freestanding 'format' string library

This little library implements rudimentary format-string support. It is
useful for porting 3rd-party code that ought not depend on a full libc.

Issue #2064
This commit is contained in:
Norman Feske 2023-03-06 14:39:26 +01:00 committed by Christian Helmuth
parent 50ee8dfaf8
commit 9ef0f1b6cb
6 changed files with 439 additions and 0 deletions

View File

@ -0,0 +1,60 @@
/*
* \brief Simple console for debug output
* \author Norman Feske
* \date 2006-04-07
*/
/*
* Copyright (C) 2006-2017 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _INCLUDE__FORMAT__CONSOLE_H_
#define _INCLUDE__FORMAT__CONSOLE_H_
#include <stdarg.h>
namespace Format { class Console; }
class Format::Console
{
public:
virtual ~Console() {}
/**
* Print format string
*/
void printf(const char *format, ...) __attribute__((format(printf, 2, 3)));
void vprintf(const char *format, va_list) __attribute__((format(printf, 2, 0)));
protected:
/**
* Back end used for the output of a single character
*/
virtual void _out_char(char c) = 0;
/**
* Back end for the output of a null-terminated string
*
* The default implementation uses _out_char. This method may
* be overridden by the backend for improving efficiency.
*
* This method is virtual to enable the use an optimized
* string-output operation on some target platforms, e.g.,
* a kernel debugger that offers a string-output syscall. The
* default implementation calls '_out_char' for each character.
*/
virtual void _out_string(const char *str);
private:
template <typename T> void _out_unsigned(T value, unsigned base = 10, int pad = 0);
template <typename T> void _out_signed(T value, unsigned base = 10);
};
#endif /* _INCLUDE__FORMAT__CONSOLE_H_ */

View File

@ -0,0 +1,99 @@
/*
* \brief Facility to write format string into character buffer
* \author Norman Feske
* \date 2006-07-17
*/
/*
* Copyright (C) 2006-2017 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _INCLUDE__FORMAT__SNPRINTF_H_
#define _INCLUDE__FORMAT__SNPRINTF_H_
#include <format/console.h>
namespace Format {
class String_console;
using size_t = __SIZE_TYPE__;
/**
* Print format string into character buffer
*
* \param dst destination buffer
* \param dst_len size of 'dst' in bytes
* \param format format string followed by the list of arguments
*
* \return number of characters written to destination buffer
*/
inline size_t snprintf(char *dst, size_t dst_size, const char *format, ...)
__attribute__((format(printf, 3, 4)));
}
class Format::String_console : public Console
{
private:
char *_dst;
size_t _dst_len;
size_t _w_offset { 0 };
/*
* Noncopyable
*/
String_console &operator = (String_console const &);
String_console(String_console const &);
public:
/**
* Constructor
*
* \param dst destination character buffer
* \param dst_len size of 'dst'
*/
String_console(char *dst, size_t dst_len)
: _dst(dst), _dst_len(dst_len)
{ _dst[0] = 0; }
/**
* Return number of characters in destination buffer
*/
size_t len() { return _w_offset; }
/***********************
** Console interface **
***********************/
void _out_char(char c) override
{
/* ensure to leave space for null-termination */
if (_w_offset + 2 > _dst_len)
return;
_dst[_w_offset++] = c;
_dst[_w_offset] = 0;
}
};
inline Format::size_t Format::snprintf(char *dst, size_t dst_len, const char *format, ...)
{
va_list list;
va_start(list, format);
String_console sc(dst, dst_len);
sc.vprintf(format, list);
va_end(list);
return sc.len();
}
#endif /* _INCLUDE__FORMAT__SNPRINTF_H_ */

View File

@ -0,0 +1,6 @@
SRC_CC += console.cc
vpath %.cc $(REP_DIR)/src/lib/format
# for reusing output.h
REP_INC_DIR += src/include/base/internal

View File

@ -0,0 +1,17 @@
MIRROR_FROM_REP_DIR := include/format src/lib/format lib/mk/format.mk
content: $(MIRROR_FROM_REP_DIR)
$(MIRROR_FROM_REP_DIR):
$(mirror_from_rep_dir)
MIRROR_FROM_BASE_DIR := src/include/base/internal/output.h
content: $(MIRROR_FROM_BASE_DIR)
$(MIRROR_FROM_BASE_DIR):
mkdir -p $(dir $@)
cp -r $(addprefix $(GENODE_DIR)/repos/base/,$@) $(dir $@)
LICENSE:
cp $(GENODE_DIR)/LICENSE $@

View File

@ -0,0 +1 @@
2023-03-06 bcd738392c9ae1d3f10b17e0b412051d07e650fb

View File

@ -0,0 +1,256 @@
/*
* \brief Output of format strings
* \author Norman Feske
* \date 2006-04-07
*/
/*
* Copyright (C) 2006-2017 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* format library interface */
#include <format/console.h>
/* Genode includes */
#include <base/stdint.h>
#include <output.h> /* base/internal/output.h */
using namespace Format;
using namespace Genode;
/**
* Format string command representation
*/
class Format_command
{
public:
enum Type { INT, UINT, STRING, CHAR, PTR, PERCENT, INVALID };
enum Length { DEFAULT, LONG, SIZE_T, LONG_LONG };
private:
/**
* Read decimal value from string
*/
int decode_decimal(const char *str, int *consumed)
{
int res = 0;
while (1) {
char c = str[*consumed];
if (!c || c < '0' || c > '0' + 9)
return res;
res = (res * 10) + c - '0';
(*consumed)++;
}
}
public:
Type type = INVALID; /* format argument type */
Length length = DEFAULT; /* format argument length */
int padding = 0; /* min number of characters to print */
int base = 10; /* base of numeric arguments */
bool zeropad = false; /* pad with zero instead of space */
bool uppercase = false; /* use upper case for hex numbers */
int consumed = 0; /* nb of consumed format string chars */
/**
* Constructor
*
* \param format begin of command in format string
*/
explicit Format_command(const char *format)
{
/* check for command begin and eat the character */
if (format[consumed] != '%') return;
if (!format[++consumed]) return;
/* heading zero indicates zero-padding */
zeropad = (format[consumed] == '0');
/* read decimal padding value */
padding = decode_decimal(format, &consumed);
if (!format[consumed]) return;
/* decode length */
switch (format[consumed]) {
case 'l':
{
/* long long ints are marked by a subsequenting 'l' character */
bool is_long_long = (format[consumed + 1] == 'l');
length = is_long_long ? LONG_LONG : LONG;
consumed += is_long_long ? 2 : 1;
break;
}
case 'z':
length = SIZE_T;
consumed++;
break;
case 'p':
length = LONG;
break;
default: break;
}
if (!format[consumed]) return;
/* decode type */
switch (format[consumed]) {
case 'd':
case 'i': type = INT; base = 10; break;
case 'o': type = UINT; base = 8; break;
case 'u': type = UINT; base = 10; break;
case 'x': type = UINT; base = 16; break;
case 'X': type = UINT; base = 16; uppercase = 1; break;
case 'p': type = PTR; base = 16; break;
case 'c': type = CHAR; break;
case 's': type = STRING; break;
case '%': type = PERCENT; break;
case 0: return;
default: break;
}
/* eat type character */
consumed++;
}
int numeric()
{
return (type == INT || type == UINT || type == PTR);
}
};
void Console::_out_string(const char *str)
{
if (!str)
_out_string("<NULL>");
else
while (*str) _out_char(*str++);
}
void Console::printf(const char *format, ...)
{
va_list list;
va_start(list, format);
vprintf(format, list);
va_end(list);
}
void Console::vprintf(const char *format, va_list list)
{
while (*format) {
/* eat and output plain characters */
if (*format != '%') {
_out_char(*format++);
continue;
}
/* parse format argument descriptor */
Format_command cmd(format);
/* read numeric argument from va_list */
long long numeric_arg = 0;
if (cmd.numeric()) {
switch (cmd.length) {
case Format_command::LONG_LONG:
numeric_arg = va_arg(list, long long);
break;
case Format_command::LONG:
numeric_arg = (cmd.type == Format_command::UINT) ?
(long long)va_arg(list, unsigned long) : va_arg(list, long);
break;
case Format_command::SIZE_T:
numeric_arg = va_arg(list, size_t);
break;
case Format_command::DEFAULT:
numeric_arg = (cmd.type == Format_command::UINT) ?
(long long)va_arg(list, unsigned int) : va_arg(list, int);
break;
}
}
/* call type-specific output routines */
switch (cmd.type) {
case Format_command::INT:
if (cmd.length == Format_command::LONG_LONG)
out_signed<long long>(numeric_arg, cmd.base,
[&] (char c) { _out_char(c); });
else
out_signed<long>((long)numeric_arg, cmd.base,
[&] (char c) { _out_char(c); });
break;
case Format_command::UINT:
if (cmd.length == Format_command::LONG_LONG) {
out_unsigned<unsigned long long>(numeric_arg, cmd.base, cmd.padding,
[&] (char c) { _out_char(c); });
break;
}
/* fall through */
case Format_command::PTR:
out_unsigned<unsigned long>((long)numeric_arg, cmd.base, cmd.padding,
[&] (char c) { _out_char(c); });
break;
case Format_command::CHAR:
_out_char((char)va_arg(list, int));
break;
case Format_command::STRING:
_out_string(va_arg(list, const char *));
break;
case Format_command::PERCENT:
_out_char('%');
break;
case Format_command::INVALID:
_out_string("<warning: unsupported format string argument>");
/* consume the argument of the unsupported command */
va_arg(list, long);
break;
}
/* proceed with format string after command */
format += cmd.consumed;
}
}