From 9ef0f1b6cbc30d8c4cc7ca71d532a751b95d4419 Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Mon, 6 Mar 2023 14:39:26 +0100 Subject: [PATCH] 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 --- repos/libports/include/format/console.h | 60 +++++ repos/libports/include/format/snprintf.h | 99 +++++++ repos/libports/lib/mk/format.mk | 6 + repos/libports/recipes/api/format/content.mk | 17 ++ repos/libports/recipes/api/format/hash | 1 + repos/libports/src/lib/format/console.cc | 256 +++++++++++++++++++ 6 files changed, 439 insertions(+) create mode 100644 repos/libports/include/format/console.h create mode 100644 repos/libports/include/format/snprintf.h create mode 100644 repos/libports/lib/mk/format.mk create mode 100644 repos/libports/recipes/api/format/content.mk create mode 100644 repos/libports/recipes/api/format/hash create mode 100644 repos/libports/src/lib/format/console.cc diff --git a/repos/libports/include/format/console.h b/repos/libports/include/format/console.h new file mode 100644 index 0000000000..3a58bb7aa7 --- /dev/null +++ b/repos/libports/include/format/console.h @@ -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 + +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 void _out_unsigned(T value, unsigned base = 10, int pad = 0); + template void _out_signed(T value, unsigned base = 10); +}; + +#endif /* _INCLUDE__FORMAT__CONSOLE_H_ */ diff --git a/repos/libports/include/format/snprintf.h b/repos/libports/include/format/snprintf.h new file mode 100644 index 0000000000..fa09381369 --- /dev/null +++ b/repos/libports/include/format/snprintf.h @@ -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 + +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_ */ diff --git a/repos/libports/lib/mk/format.mk b/repos/libports/lib/mk/format.mk new file mode 100644 index 0000000000..2b51767219 --- /dev/null +++ b/repos/libports/lib/mk/format.mk @@ -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 diff --git a/repos/libports/recipes/api/format/content.mk b/repos/libports/recipes/api/format/content.mk new file mode 100644 index 0000000000..ea066e64a3 --- /dev/null +++ b/repos/libports/recipes/api/format/content.mk @@ -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 $@ diff --git a/repos/libports/recipes/api/format/hash b/repos/libports/recipes/api/format/hash new file mode 100644 index 0000000000..499966045d --- /dev/null +++ b/repos/libports/recipes/api/format/hash @@ -0,0 +1 @@ +2023-03-06 bcd738392c9ae1d3f10b17e0b412051d07e650fb diff --git a/repos/libports/src/lib/format/console.cc b/repos/libports/src/lib/format/console.cc new file mode 100644 index 0000000000..53f624e409 --- /dev/null +++ b/repos/libports/src/lib/format/console.cc @@ -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 + +/* Genode includes */ +#include +#include /* 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(""); + 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(numeric_arg, cmd.base, + [&] (char c) { _out_char(c); }); + else + out_signed((long)numeric_arg, cmd.base, + [&] (char c) { _out_char(c); }); + break; + + case Format_command::UINT: + + if (cmd.length == Format_command::LONG_LONG) { + out_unsigned(numeric_arg, cmd.base, cmd.padding, + [&] (char c) { _out_char(c); }); + break; + } + + /* fall through */ + + case Format_command::PTR: + + out_unsigned((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(""); + /* consume the argument of the unsupported command */ + va_arg(list, long); + break; + } + + /* proceed with format string after command */ + format += cmd.consumed; + } +}