diff --git a/include/kernel/string.h b/include/kernel/string.h index 7c6e8d588c65660409ac6904f136480432a5e860..7993a091292ff10e7eeba3179260c5e6e845cafb 100644 --- a/include/kernel/string.h +++ b/include/kernel/string.h @@ -8,9 +8,12 @@ #define _KERNEL_STRING_H_ #include <kernel/types.h> +#include <stdarg.h> +#include <limits.h> int sprintf(char *str, const char *format, ...); +int snprintf(char *str, size_t size, const char *format, ...); int strcmp(const char *s1, const char *s2); int strncmp(const char *s1, const char *s2, size_t n); char *strpbrk(const char *s, const char *accept); @@ -25,8 +28,14 @@ void *memcpy(void *dest, const void *src, size_t n); char *strcpy(char *dest, const char *src); void bzero(void *s, size_t n); +int isdigit(int c); int isspace(int c); int atoi(const char *nptr); +int vprintf(const char *format, va_list ap); +int vsprintf(char *str, const char *format, va_list ap); +int vsnprintf(char *str, size_t size, const char *format, va_list ap); + + #endif /* _KERNEL_STRING_H_ */ diff --git a/lib/Makefile b/lib/Makefile index e45e8cf9c0cbb04f602a42586d9345b6dc4dbb7d..317fd22cd67451be44c50f7ef051b0c0140ed6f4 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -4,6 +4,7 @@ lib-$(CONFIG_PAGE_MAP) += page.o lib-$(CONFIG_AR) += ar.o lib-$(CONFIG_CHUNK) += chunk.o lib-y += string.o +lib-y += vsnprintf.o lib-y += elf.o lib-y += data_proc_tracker.o lib-y += data_proc_task.o diff --git a/lib/string.c b/lib/string.c index 4f330e03d7b4c6f3909907edda8c47c98bbdb500..246be440eeeadb298eeccc57ddc7d2ea115fd3ba 100644 --- a/lib/string.c +++ b/lib/string.c @@ -17,6 +17,7 @@ #include <kernel/export.h> #include <kernel/types.h> #include <kernel/string.h> +#include <kernel/printk.h> /** @@ -25,7 +26,6 @@ * @returns <0, 0 or > 0 if s1 is less than, matches or greater than s2 */ -#include <kernel/printk.h> int strcmp(const char *s1, const char *s2) { unsigned char c1, c2; @@ -350,11 +350,6 @@ EXPORT_SYMBOL(bzero); -#include <stdarg.h> -#include <limits.h> - -int vsnprintf(char *str, size_t size, const char *format, va_list ap); - /** * @brief print a string into a buffer * @@ -380,6 +375,66 @@ int sprintf(char *str, const char *format, ...) EXPORT_SYMBOL(sprintf); +/** + * @brief print a string into a buffer of a given maximum size + * + * @param str the destination buffer + * @param size the size of the destination buffer + * @param format the format string buffer + * @param ... arguments to the format string + * + * @return the number of characters written to buf + */ + +int snprintf(char *str, size_t size, const char *format, ...) +{ + int n; + va_list ap; + + + va_start(ap, format); + n = vsnprintf(str, size, format, ap); + va_end(ap); + + return n; +} +EXPORT_SYMBOL(snprintf); + + +/** + * @brief format a string and print it into a buffer + * + * @param str the destination buffer + * @param format the format string buffer + * @param ... arguments to the format string + * + * @return the number of characters written to buf + */ + +int vsprintf(char *str, const char *format, va_list ap) +{ + return vsnprintf(str, INT_MAX, format, ap); +} +EXPORT_SYMBOL(vsprintf); + + + +/** + * @brief format a string and print it to the standard output + * + * @param format the format string buffer + * @param ... arguments to the format string + * + * @return the number of characters written to stdout + */ + +int vprintf(const char *format, va_list ap) +{ + return vsnprintf(NULL, INT_MAX, format, ap); +} +EXPORT_SYMBOL(vprintf); + + /** * @brief check if a character is a white space * @@ -416,6 +471,21 @@ int isspace(int c) } EXPORT_SYMBOL(isspace); + +/** + * @brief check if a character is a digit + * + * @param c the character to test + * + * @returns 0 if not a digit + */ + +int isdigit(int c) +{ + return '0' <= c && c <= '9'; +} +EXPORT_SYMBOL(isdigit); + /** * @brief convert a string to an integer * diff --git a/lib/vsnprintf.c b/lib/vsnprintf.c new file mode 100644 index 0000000000000000000000000000000000000000..2f572dff984d92bbbda862cf44cda2809d3c3b08 --- /dev/null +++ b/lib/vsnprintf.c @@ -0,0 +1,1248 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/*+ + * @file lib/vsnprint.c + * + * @ingroup string + * + * @brief implements vsnprintf() + * + * @note we split this out, otherwise string.c would be way too messy + * + * a format specifier looks like this: + * %[flags][width][.precision][length]type + * + * - we do not support the POSIX parameter field extension + * - we support only a subset of length field flags (l, h, hh) + * - we support a %b base-2 type, because sometimes its nice to print bitfields + * - %f specifiers are forced to %g if the floating-point value is > 2^32-1 + * + * - happy bug hunting, I bet there are lots + */ + +#include <kernel/kmem.h> +#include <kernel/export.h> +#include <kernel/types.h> +#include <kernel/string.h> +#include <kernel/bitops.h> + + +#define STACK_BUF_SIZE 32 + + + +/* leading flags */ +#define VSN_LEFT (1 << 0) /* left-align output */ +#define VSN_PLUS (1 << 1) /* show leading plus */ +#define VSN_SPACE (1 << 2) /* show space if plus */ +#define VSN_ZEROPAD (1 << 3) /* pad with zero */ +#define VSN_HASH (1 << 4) /* trailing zeros, decimal points, 0x */ + +/* precision field flag */ +#define VSN_PRECISION (1 << 5) /* post-comma precision */ + +/* length field flags */ +#define VSN_CHAR (1 << 6) /* arg is int, upcast from char */ +#define VSN_SHORT (1 << 7) /* arg is int, upcast from short */ +#define VSN_LONG (1 << 8) /* arg is long type */ + +/* type flags */ +#define VSN_UPPERCASE (1 << 9) /* %x or %X */ + + +struct fmt_spec { + unsigned int flags; + unsigned int width; + unsigned int base; + unsigned int prec; +}; + + + +#define TREADY 4 + +static volatile int *console = (int *)0x80000100; + +static int putchar(int c) +{ + while (!(console[1] & TREADY)); + + console[0] = 0x0ff & c; + + if (c == '\n') { + while (!(console[1] & TREADY)); + console[0] = (int) '\r'; + } + + return c; +} + + + +/** + * @brief print a single character + * + * @note if str is NULL, this prints to stdout + * + * @note putchar() must be provided by the architecture in some form + * e.g. this could be implemented in a kernel module for more efficient + * use of the 8-byte UART FIFO on the GR712RC and patched after + * the initial boot process + */ + +static void _sprintc(int c, char **str, const char *end) +{ + if (end) { + if ((*str) > end) + return; + } + + if (str) { + (**str) = c; + (*str)++; + } else { + putchar(c); + } +} + + +/** + * @brief print a one or more character to str + * + * @param str the destionation buffer + * @param buf the source buffer + * @param n the number of bytes to write + * + * @returns the number of bytes written + * + * @note if str is NULL, this prints to stdout + * + * @note putchar() must be provided by the architecture in some form + * e.g. this could be implemented in a kernel module for more efficient + * use of the 8-byte UART FIFO on the GR712RC and patched after + * the initial boot process + */ + +static size_t _printn(char *str, const char *buf, size_t n) +{ + size_t i; + + if (str) { + memcpy(str, buf, n); + } else { + for (i = 0; i < n; i++) + putchar(buf[i]); + } + + return n; +} + + +/** + * @brief the result of an incestuous relationship of atoi() and strtol() + * + * @param nptr the pointer to a string (possibly) representing a number + * @param endptr if not NULL, stores the pointer to the first character not part + * of the number + * + * @return the converted number + * + * @note if no number has been found, the function will return 0 and + * nptr == endptr + * + */ + +static int _vsn_strtoi(const char *nptr, char **endptr) +{ + int res = 0; + int neg = 0; + + unsigned int d = -1; + + const char *start = nptr; + + + + for (; isspace(*nptr); ++nptr); + + if (*nptr == '-') { + nptr++; + neg = 1; + } else if (*nptr == '+') { + nptr++; + } + + while (1) { + d = (*nptr) - '0'; + + if (d > 9) + break; + + res = res * 10 + (int) d; + nptr++; + } + + if (neg) + res = -res; + + if (endptr) { + if (d == -1) + (*endptr) = (char *) start; + else + (*endptr) = (char *) nptr; + } + + return res; +} + + +/** + * @brief evaluate any flags in the fmt string + * + * @param fmt a format string buffer + * @param spec a struct fmt_spec + * + * @returns the number of bytes read + */ + +static size_t eval_flags(const char *fmt, struct fmt_spec *spec) +{ + bool done = false; + + const char *begin = fmt; + + + spec->flags = 0; + + do { + switch ((*fmt)) { + case '-': + spec->flags |= VSN_LEFT; + fmt++; + break; + + case '+': + spec->flags |= VSN_PLUS; + fmt++; + break; + + case ' ': + spec->flags |= VSN_SPACE; + fmt++; + break; + + case '0': + spec->flags |= VSN_ZEROPAD; + fmt++; + break; + + case '#': + spec->flags |= VSN_HASH; + fmt++; + break; + + default: + done = true; + break; + } + + } while (!done); + + + return (size_t) (fmt - begin); +} + + +/** + * @brief evaluate the width field + * + * @param fmt a format string buffer + * @param spec a struct fmt_spec + * @param args the arguments to the format string + * + * @returns the number of bytes read + */ + +static size_t eval_width(const char *fmt, struct fmt_spec *spec, va_list *args) +{ + int w; + + char *end; + + const char *begin = fmt; + + + if (isdigit((*fmt))) { + + w = _vsn_strtoi(fmt, &end); + fmt = (const char *) end; + + /* leading zeroes evaluate as zero-pad flag */ + if (!w) + spec->flags |= VSN_ZEROPAD; + + } else if ((*fmt) == '*') { + + /* the width is part of the argument */ + w = (int) va_arg((*args), int); + + if (w < 0) { + spec->flags |= VSN_LEFT; + w = -w; + } + + fmt++; + } + + spec->width = (unsigned int) w; + + return (size_t) (fmt - begin); +} + + +/** + * @brief evaluate the precision field + * + * @param fmt a format string buffer + * @param spec a struct fmt_spec + * @param args the arguments to the format string + * + * @returns the number of bytes read + */ + +static size_t eval_prec(const char *fmt, struct fmt_spec *spec, va_list *args) +{ + char *end; + + const char *begin = fmt; + + + if ((*fmt) != '.') + return 0; + + + spec->flags |= VSN_PRECISION; + fmt++; + + if (isdigit((*fmt))) { + + spec->prec = _vsn_strtoi(fmt, &end); + fmt = (const char *) end; + + } else if ((*fmt) == '*') { + + spec->prec = va_arg((*args), unsigned int); + fmt++; + } + + return (size_t) (fmt - begin); +} + + +/** + * @brief evaluate the length field + * + * @param fmt a format string buffer + * @param spec a struct fmt_spec + * + * @returns the number of bytes read + */ + +static size_t eval_length(const char *fmt, struct fmt_spec *spec) +{ + const char *begin = fmt; + + + switch ((*fmt)) { + case 'h': + + fmt++; + if ((*fmt) == 'h') { + spec->flags |= VSN_CHAR; + fmt++; + } else { + spec->flags |= VSN_SHORT; + } + + break; + + case 'l': + /* we don't support long long specifiers */ + spec->flags |= VSN_LONG; + fmt++; + break; + + default: + break; + } + + return (size_t) (fmt - begin); +} + + +/** + * @brief configure flags and set base for specifier + * + * @param fmt a format string buffer + */ + +static void config_specifier(const char *fmt, struct fmt_spec *spec) +{ + switch ((*fmt)) { + + case 'd': + spec->flags &= ~(VSN_HASH); + case 'i': + spec->base = 10; + break; + + case 'u': + spec->base = 10; + spec->flags &= ~(VSN_PLUS | VSN_SPACE | VSN_HASH); + break; + + case 'p': + case 'x': + spec->base = 16; + spec->flags &= ~(VSN_PLUS | VSN_SPACE); + break; + + case 'X': + spec->base = 16; + spec->flags |= VSN_UPPERCASE; + spec->flags &= ~(VSN_PLUS | VSN_SPACE); + break; + + case 'o': + spec->base = 8; + spec->flags &= ~(VSN_PLUS | VSN_SPACE); + break; + + case 'b': + spec->base = 2; + spec->flags &= ~(VSN_PLUS | VSN_SPACE); + break; + + default: + spec->base = 0; + break; + } +} + + +/** + * @brief + * + * @param str the destination buffer, NULL to print to stdout + * @param fmt a format string buffer + * @param spec a struct fmt_spec + * @param args the arguments to the format string + * + * @return the number of bytes written + */ + +static size_t render_final(char *str, const char *end, bool sign, + char *buf, size_t n, struct fmt_spec *spec) +{ + size_t i; + size_t len = 0; + + /* first, pad */ + if (!(spec->flags & VSN_LEFT)) { + for (; n < STACK_BUF_SIZE && n < spec->prec; n++) + buf[n] = '0'; + + if (spec->flags & VSN_ZEROPAD) { + for (; n < STACK_BUF_SIZE && n < spec->width; n++) + buf[n] = '0'; + } + } + + + /* now treat the hash */ + if (spec->flags & VSN_HASH) { + + if (n) { + if ((n == spec->prec) || (n == spec->width)) { + + n--; + + if (n && (spec->base == 16)) + n--; + } + } + + if (n < STACK_BUF_SIZE) { + if (spec->base == 16) { + if (spec->flags & VSN_UPPERCASE) + buf[n++] = 'x'; + else + buf[n++] = 'X'; + } + } + + if (n < STACK_BUF_SIZE) + buf[n++] = '0'; + } + + + /* now the sign */ + if (sign || (spec->flags & VSN_PLUS) || (spec->flags & VSN_SPACE)) { + if (n == spec->width) + n--; + } + + if (n < STACK_BUF_SIZE) { + if (sign) + buf[n++] = '-'; + else if (spec->flags & VSN_PLUS) + buf[n++] = '+'; + else if (spec->flags & VSN_SPACE) + buf[n++] = ' '; + } + + /* pad spaces to given width */ + if (!(spec->flags & (VSN_LEFT | VSN_ZEROPAD))) { + for (i = n; i < spec->width; i++) { + _sprintc(' ', (char **) str, end); + len++; + } + } + + /* reverse buffer */ + for (i = 0; i < n; i++) { + _sprintc(buf[n-i-1], (char **) str, end); + len++; + } + + // append pad spaces up to given width + if (spec->flags & VSN_LEFT) { + while (len < spec->width) { + _sprintc(' ', (char **) str, end); + len++; + } + } + + return len; +} + + +/** + * @brief + * + * @param usign whether the value is treated as signed or unsigned + * @param str the destination buffer, NULL to print to stdout + * @param spec a struct fmt_spec + * @param args the arguments to the format string + * + * @return the number of bytes written + */ + +static size_t render_xlong_to_ascii(bool usign, long value, char *str, + const char *end, struct fmt_spec *spec) +{ + size_t n = 0; + + bool sign = false; + + char digit; + char buf[STACK_BUF_SIZE]; + + + if (!usign) { + if (value < 0) { + sign = true; + value = -value; + } + } + + while (value && (n < STACK_BUF_SIZE)) { + + digit = (char) (value % spec->base); + + if (digit < 10) { + + buf[n] = '0' + digit; + + } else { /* base 16 hex (we assume) */ + if (spec->flags & VSN_UPPERCASE) + buf[n] = 'A' + digit - 10; + else + buf[n] = 'a' + digit - 10; + } + + n++; + value /= spec->base; + } + + return render_final(str, end, sign, buf, n, spec); +} + + +/** + * @brief render signed integer types + * + * @param usign whether the value is treated as signed or unsigned + * @param str the destination buffer, NULL to print to stdout + * @param spec a struct fmt_spec + * @param args the arguments to the format string + * + * @return the number of bytes written + */ + +static size_t render_xsigned_integer(bool usign, char *str, const char *end, + struct fmt_spec *spec, va_list *args) +{ + int i; + long l; + + + /* argument is a long, will turn into a long */ + if (spec->flags & VSN_LONG) { + l = va_arg((*args), long); + return render_xlong_to_ascii(usign, l, str, end, spec); + } + + /* argument is an int, which has been upcast from short */ + if (spec->flags & VSN_SHORT) { + i = (short int) va_arg((*args), int); + return render_xlong_to_ascii(usign, (long) i, str, end, spec); + } + + /* argument is an int, which has been upcast from char */ + if (spec->flags & VSN_CHAR) { + i = (char) va_arg((*args), int); + return render_xlong_to_ascii(usign, (long) i, str, end, spec); + } + + + /* argument is an int */ + i = (int) va_arg((*args), int); + return render_xlong_to_ascii(usign, (long) i, str, end, spec); +} + + +/** + * @brief render a pointer + * + * @param str the destination buffer, NULL to print to stdout + * @param spec a struct fmt_spec + * @param args the arguments to the format string + * + * @return the number of bytes written + */ + +static size_t render_pointer(char *str, const char *end, + struct fmt_spec *spec, va_list *args) +{ + long l; + + + l = (long) ((uintptr_t) va_arg((*args), void *)); + + return render_xlong_to_ascii(true, l, str, end, spec); +} + + +/** + * @brief render integer types + * + * @param str the destination buffer, NULL to print to stdout + * @param fmt a format string buffer + * @param spec a struct fmt_spec + * @param args the arguments to the format string + * + * @return the number of bytes written + */ + +static size_t render_integer(char *str, const char *end, const char *fmt, + struct fmt_spec *spec, va_list *args) +{ + unsigned int n = 0; + + + switch ((*fmt)) { + case 'd': + case 'i': + n = render_xsigned_integer(false, str, end, spec, args); + break; + case 'u': + case 'x': + case 'X': + case 'o': + case 'b': + case 'p': + n = render_pointer(str, end, spec, args); + break; + default: + break; + } + + return n; +} + + +/** + * @brief renders a mantissa into a buffer + * + * @param buf the destination buffer + * @param n the offset into the buffer + * @param size the size of the buffer + * + * @return the new offset into the buffer + */ + +static size_t render_mantissa(char *buf, size_t n, const size_t size, + unsigned long mantissa, struct fmt_spec *spec) +{ + int digits = (int) spec->prec; + + + while (n < size) { + + buf[n++] = (char)('0' + (mantissa % 10)); + mantissa /= 10; + digits--; + + if (!mantissa) + break; + } + + /* pad with zeroes up to precision */ + while (n < size) { + + if (digits < 1) + break; + + digits--; + buf[n++] = '0'; + } + + /* add decimal point */ + if (n < size) + buf[n++] = '.'; + + + return n; +} + + +/** + * @brief renders a float exponent into a buffer + * + * @param buf the destination buffer + * @param n the offset into the buffer + * @param size the size of the buffer + * @param pow the value of the exponent + * + * @return the new offset into the buffer + */ + +static size_t render_exponent(char *buf, size_t n, const size_t size, + int exp) +{ + int d; + + bool sign = false; + + + + if (exp < 0) { + sign = true; + exp = -exp; + } + + d = exp; + + while (n < size) { + buf[n++] = (char)('0' + (d % 10)); + d /= 10; + + if (!d) + break; + } + + if (exp < 10) + if (n < size) + buf[n++] = '0'; + + if (n < size) { + if (sign) + buf[n++] = '-'; + else + buf[n++] = '+'; + } + + if (n < size) + buf[n++] = 'e'; + + + + return n; +} + + +/** + * @brief renders a float characteristic into a buffer + * + * @param buf the destination buffer + * @param n the offset into the buffer + * @param size the size of the buffer + * @param characteristic the value of the characteristic + * + * @return the new offset into the buffer + */ + +static size_t render_characteristic(char *buf, size_t n, const size_t size, + unsigned long characteristic) +{ + while (n < size) { + buf[n++] = (char)('0' + (characteristic % 10)); + characteristic /= 10; + + if (!characteristic) + break; + } + + return n; +} + + +/** + * @brief adjust a float value and parameters for "%g" format + * + * @param value the value to adjust + * @param[out] exp the value of the exponent + * @param pow_max the maximum power-of-10 + * @param prec_max the maximum precision + * @param spec a struct fmt_spec + * + * @return the adjusted float + */ + +static double get_exp_float_val_param(double value, int *exp, + const unsigned int pow_max, + const unsigned int prec_max, + struct fmt_spec *spec) +{ + int e = 0; + + double tmp; + + + + if (value > 1e6) { + while (value > 10.0) { + value *= 0.1; + e++; + } + + } else if (value < 1e-4) { + while (value < 1.0) { + value *= 10.0; + e--; + } + } else if (value > 1.0) { + if (!(spec->flags & VSN_PRECISION)) + spec->prec = 0; + } + + + (*exp) = e; + + /* adjust precision to be at most prec_max (stops on first zero) */ + if (!(spec->flags & VSN_PRECISION)) { + + tmp = value - (double) ((int) value); + + spec->prec = 0; + + while (tmp > (1.0 / pow_max) ) { + + if (spec->prec >= prec_max) + break; + + spec->prec++; + + if (e < 0) + tmp *= 10.0; + else + tmp *= 0.1; + + tmp = tmp - (double) ((int) tmp); + } + } + + + return value; +} + + +/** + * @brief separate and round a float into characteristic and mantissa + * + * @param value the value of the float + * @param[out] characteristic the value of the characteristic + * @param[out] mantissa the value of the mantissa + * @param precision the selected precision + * @param pow10 the power-of-10 of the selected precision + */ + +static void separate_and_round(double value, unsigned long *characteristic, + unsigned long *mantissa, unsigned int precision, + double pw10) +{ + + unsigned long ch; + unsigned long m; + double tmp; + + + /* left of comma */ + ch = (unsigned long) value; + tmp = (value - (double) ch) * pw10; + + /* right of comma */ + m = (unsigned long) tmp; + + + /* check rounding of digits beyond precision */ + tmp = tmp - (double) m; + + if (tmp > 0.5) { + m++; + if (m >= pw10) { + m = 0; + ch++; + } + } else if (tmp == 0.5) { + if (!m || (m & 0x1)) + m++; + } + + /* special rounding case: precision == 0 */ + if (!precision) { + tmp = value - (double) ch; + + if (tmp > 0.5) + ch++; + else if ((tmp == 0.5) && (ch & 0x1)) + ch++; + } + + + (*characteristic) = ch; + (*mantissa) = m; +} + + +/** + * @brief render floating point types + * + * @param str the destination buffer, NULL to print to stdout + * @param end the end of the destination buffer + * @param pr_exp whether to render the float in exponential format + * @param spec a struct fmt_spec + * @param args the arguments to the format string + * + * @return the number of bytes written + */ + +static size_t render_float(char *str, const char *end, bool pr_exp, + struct fmt_spec *spec, va_list *args) +{ + size_t n = 0; + + unsigned long characteristic; + unsigned long mantissa; + + int exp = 0; + + double val; + + bool sign = false; + + char buf[STACK_BUF_SIZE]; + + /* we use unsigned long for the mantissa, so we'll have overflows for + * any power-of-10 > 1e9, as 2^32-1 gives us at most 10 digits + */ + static const double pow10[] = {1e0, 1e1, 1e2, 1e3, 1e4, + 1e5, 1e6, 1e7, 1e8, 1e9}; + + + + val = (double) va_arg((*args), double); + + if (val < 0.0) { + sign = true; + val = -val; + } + + /* set default precision, see ISO C99 specification, 7.19.6.1/7 */ + if (!(spec->flags & VSN_PRECISION)) + spec->prec = 6; + + /* clamp precision */ + if (spec->prec >= ARRAY_SIZE(pow10)) + spec->prec = ARRAY_SIZE(pow10) - 1; + + /* force %g if value would overflow */ + if (val > (double) 0xFFFFFFF) + pr_exp = true; + + if (pr_exp) + val = get_exp_float_val_param(val, &exp, + pow10[spec->prec], + spec->prec, spec); + + separate_and_round(val, &characteristic, &mantissa, spec->prec, + pow10[spec->prec]); + + if (exp) + n = render_exponent(buf, n, STACK_BUF_SIZE, exp); + + if (spec->prec) + n = render_mantissa(buf, n, STACK_BUF_SIZE, mantissa, spec); + + n = render_characteristic(buf, n, STACK_BUF_SIZE, characteristic); + + + return render_final(str, end, sign, buf, n, spec); +} + + +/** + * @brief render a single character + * + * @param str the destination buffer, NULL to print to stdout + * @param end the end of the destination buffer + * @param spec a struct fmt_spec + * @param args the arguments to the format string + * + * @return the number of bytes written + */ + +static size_t render_char(char *str, const char *end, struct fmt_spec *spec, + va_list *args) +{ + size_t n = 1; + + char c = va_arg((*args), int); + + if (!(spec->flags & VSN_LEFT)) { + for (; n < spec->width; n++) + _sprintc(' ', (char **) str, end); + } + + _sprintc(c, (char **) str, end); + + if (spec->flags & VSN_LEFT) { + for (; n < spec->width; n++) + _sprintc(' ', (char **) str, end); + } + + return n; +} + + +/** + * @brief render a string + * + * @param str the destination buffer, NULL to print to stdout + * @param end the end of the destination buffer + * @param spec a struct fmt_spec + * @param args the arguments to the format string + * + * @return the number of bytes written + */ + +static size_t render_string(char *str, const char *end, struct fmt_spec *spec, + va_list *args) +{ + size_t len; + size_t n = 0; + + const char *buf = va_arg((*args), const char *); + + + if (!buf) + return 0; + + + len = strlen(buf); + + if (spec->flags & VSN_PRECISION) { + if (len > spec->prec) + len = spec->prec; + } + + if (!(spec->flags & VSN_LEFT)) { + for (; n < spec->width; n++) + _sprintc(' ', (char **) str, end); + } + + n = _printn(str, buf, len); + + if (spec->flags & VSN_LEFT) { + for (; n < spec->width; n++) + _sprintc(' ', (char **) str, end); + } + + return n; +} + + +/** + * @brief render the format specifier + * + * @param str the destination buffer, NULL to print to stdout + * @param fmt a format string buffer + * @param spec a struct fmt_spec + * @param args the arguments to the format string + * + * @return the number of bytes rendered + */ + +static size_t render_specifier(char *str, const char *end, const char *fmt, + struct fmt_spec *spec, va_list *args) +{ + config_specifier(fmt, spec); + + if (spec->base) + return render_integer(str, end, fmt, spec, args); + + + /* the other stuff */ + switch((*fmt)) { + case 'f' : + case 'F' : + return render_float(str, end, false, spec, args); + case 'g' : + return render_float(str, end, true, spec, args); + case 'c' : + return render_char(str, end, spec, args); + case 's' : + return render_string(str, end, spec, args); + + default: + /* just print whatever the type specifier is */ + _sprintc((*fmt), (char **) str, end); + return 1; + } + return 0; +} + + +static size_t decode_format(const char *fmt, struct fmt_spec *spec, va_list *ap) +{ + const char *begin = fmt; + + fmt += eval_flags(fmt, spec); + fmt += eval_width(fmt, spec, ap); + fmt += eval_prec(fmt, spec, ap); + fmt += eval_length(fmt, spec); + + return (size_t) (fmt - begin); +} + + +/** + * @brief format a string and print it into a buffer or stdout + * + * @param str the destination buffer, NULL to print to stdout + * @param format the format string buffer + * @param args the arguments to the format string + * + * @return the number of characters written to buf + * + * @note this is a slightly improved version on xen_printf + * + * @note from the C99 Standard Document, Section 7.15, Footnote 221: + * "It is permitted to create a pointer to a va_list and pass that + * pointer to another function, in which case the original function + * may make further use of the original list after the other function + * returns." + */ + +int vsnprintf(char *str, size_t size, const char *format, va_list ap) +{ + struct fmt_spec spec = {0}; + + char *buf, *end; + const char *tmp; + + + + /* clamp */ + if (size > INT_MAX) + size = INT_MAX; + + + buf = str; + end = buf + size; + + /* make sure end > buf */ + if (end < buf) { + end = ((void *) -1); + size = end - buf; + } + + + while ((*format)) { + + /* + * if we encounter format specifier, we will evaluate + * it, otherwise they are normal characters and we + * will copy them + */ + + tmp = format; + for (; (*format); format++) { + if ((*format) == '%') + break; + } + + if (!str) { /* stdout is infinte... */ + buf += _printn(NULL, tmp, format - tmp); + } else { + if ((format - tmp) < (end - buf)) + buf += _printn(buf, tmp, format - tmp); + else + buf += _printn(buf, tmp, end - buf); + + if (buf >= end) + break; /* out of buffer to write */ + } + + /* have we arrived at the end? */ + if (!(*format)) + break; + + /* we have encountered a format specifier, try to evaluate it */ + format++; + format += decode_format(format, &spec, &ap); + + /* and render */ + if (!str) /* to stdout? */ + buf += render_specifier(NULL, NULL, format, &spec, &ap); + else + buf += render_specifier(buf, end, format, &spec, &ap); + + format++; /* the type is always a single char */ + } + + + /* place termination char if we rendered to a buffer */ + if (size) { + if (str) { + if (buf < end) + buf[0] = '\0'; + else + end[-1] = '\0'; + } + } + + /* return written chars without terminating '\0' */ + return buf - str; +} +