acrn-hypervisor/hypervisor/lib/sprintf.c

693 lines
17 KiB
C

/*
* Copyright (C) 2018 Intel Corporation. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <hv_lib.h>
#ifndef NULL
#define NULL ((void *) 0)
#endif
#define PRINT_STRING_MAX_LEN 4096
/** Use upper case letters for hexadecimal format. */
#define PRINT_FLAG_UPPER 0x00000001U
/** Use alternate form. */
#define PRINT_FLAG_ALTERNATE_FORM 0x00000002U
/** Use '0' instead of ' ' for padding. */
#define PRINT_FLAG_PAD_ZERO 0x00000004U
/** Use left instead of right justification. */
#define PRINT_FLAG_LEFT_JUSTIFY 0x00000008U
/** Always use the sign as prefix. */
#define PRINT_FLAG_SIGN 0x00000010U
/** Use ' ' as prefix if no sign is used. */
#define PRINT_FLAG_SPACE 0x00000020U
/** The original value was a (unsigned) char. */
#define PRINT_FLAG_CHAR 0x00000040U
/** The original value was a (unsigned) short. */
#define PRINT_FLAG_SHORT 0x00000080U
/** The original value was a (unsigned) long. */
#define PRINT_FLAG_LONG 0x00000100U
/** The original value was a (unsigned) long long. */
#define PRINT_FLAG_LONG_LONG 0x00000200U
/** The value is interpreted as unsigned. */
#define PRINT_FLAG_UINT32 0x00000400U
/** Structure used to save (v)snprintf() specific values */
struct snprint_param {
/** The destination buffer. */
char *dst;
/** The size of the destination buffer. */
int sz;
/** Counter for written chars. */
int wrtn;
};
/** The characters to use for upper case hexadecimal conversion.
*
* Note that this array is 17 bytes long. The first 16 characters
* are used to convert a 4 bit number to a printable character.
* The last character is used to determine the prefix for the
* alternate form.
*/
static const char upper_hex_digits[] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'X'
};
/** The characters to use for lower case hexadecimal conversion.
*
* Note that this array is 17 bytes long. The first 16 characters
* are used to convert a 4 bit number to a printable character.
* The last character is used to determine the prefix for the
* alternate form.
*/
static const char lower_hex_digits[] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f', 'x'
};
static const char *get_int(const char *s, int *x)
{
int negative = 0;
*x = 0;
/* evaluate leading '-' for negative numbers */
if (*s == '-') {
negative = 1;
++s;
}
/* parse uint32_teger */
while ((*s >= '0') && (*s <= '9'))
*x = *x * 10 + (*s++ - '0');
/* apply sign to result */
if (negative != 0)
*x = -*x;
return s;
}
static const char *get_flags(const char *s, int *flags)
{
/* contains the flag characters */
static const char flagchars[] = "#0- +";
/* contains the numeric flags for the characters above */
static const int fl[sizeof(flagchars)] = {
PRINT_FLAG_ALTERNATE_FORM, /* # */
PRINT_FLAG_PAD_ZERO, /* 0 */
PRINT_FLAG_LEFT_JUSTIFY, /* - */
PRINT_FLAG_SIGN, /* + */
PRINT_FLAG_SPACE /* ' ' */
};
const char *pos;
/* parse multiple flags */
while ((*s) != 0) {
/* get index of flag. Terminate loop if no flag character was
* found
*/
pos = strchr(flagchars, *s);
if (pos == NULL)
break;
/* apply matching flags and continue with the next character */
++s;
*flags |= fl[pos - flagchars];
}
/* Spec says that '-' has a higher priority than '0' */
if ((*flags & PRINT_FLAG_LEFT_JUSTIFY) != 0)
*flags &= ~PRINT_FLAG_PAD_ZERO;
/* Spec says that '+' has a higher priority than ' ' */
if ((*flags & PRINT_FLAG_SIGN) != 0)
*flags &= ~PRINT_FLAG_SPACE;
return s;
}
static const char *get_length_modifier(const char *s,
int *flags, uint64_t *mask)
{
/* check for h[h] (char/short) */
if (*s == 'h') {
if (*++s == 'h') {
*flags |= PRINT_FLAG_CHAR;
*mask = 0x000000FF;
++s;
} else {
*flags |= PRINT_FLAG_SHORT;
*mask = 0x0000FFFF;
}
}
/* check for l[l] (long/long long) */
else if (*s == 'l') {
if (*++s == 'l') {
*flags |= PRINT_FLAG_LONG_LONG;
++s;
} else
*flags |= PRINT_FLAG_LONG;
}
return s;
}
static int format_number(struct print_param *param)
{
/* contains the character used for padding */
char pad;
/* effective width of the result */
uint32_t width;
/* number of characters to insert for width (w) and precision (p) */
uint32_t p, w;
/* the result */
int res;
/* initialize variables */
p = w = 0;
res = 0;
width = param->vars.valuelen + param->vars.prefixlen;
/* calculate additional characters for precision */
if ((uint32_t)(param->vars.precision) > width)
p = param->vars.precision - width;
/* calculate additional characters for width */
if ((uint32_t)(param->vars.width) > (width + p))
w = param->vars.width - (width + p);
/* handle case of right justification */
if ((param->vars.flags & PRINT_FLAG_LEFT_JUSTIFY) == 0) {
/* assume ' ' as padding character */
pad = ' ';
/*
* if padding with 0 is used, we have to emit the prefix (if any
* ) first to achieve the expected result. However, if a blank is
* used for padding, the prefix is emitted after the padding.
*/
if ((param->vars.flags & PRINT_FLAG_PAD_ZERO) != 0) {
/* use '0' for padding */
pad = '0';
/* emit prefix, return early if an error occurred */
res = param->emit(PRINT_CMD_COPY, param->vars.prefix,
param->vars.prefixlen, param->data);
if ((param->vars.prefix != NULL) && (res < 0))
return res;
/* invalidate prefix */
param->vars.prefix = NULL;
param->vars.prefixlen = 0;
}
/* fill the width with the padding character, return early if
* an error occurred
*/
res = param->emit(PRINT_CMD_FILL, &pad, w, param->data);
if (res < 0)
return res;
}
/* emit prefix (if any), return early in case of an error */
res = param->emit(PRINT_CMD_COPY, param->vars.prefix,
param->vars.prefixlen, param->data);
if ((param->vars.prefix != NULL) && (res < 0))
return res;
/* insert additional 0's for precision, return early if an error
* occurred
*/
res = param->emit(PRINT_CMD_FILL, "0", p, param->data);
if (res < 0)
return res;
/* emit the pre-calculated result, return early in case of an error */
res = param->emit(PRINT_CMD_COPY, param->vars.value,
param->vars.valuelen, param->data);
if (res < 0)
return res;
/* handle left justification */
if ((param->vars.flags & PRINT_FLAG_LEFT_JUSTIFY) != 0) {
/* emit trailing blanks, return early in case of an error */
res = param->emit(PRINT_CMD_FILL, " ", w, param->data);
if (res < 0)
return res;
}
/* done, return the last result */
return res;
}
static int print_pow2(struct print_param *param,
uint64_t v, uint32_t shift)
{
/* max buffer required for octal representation of unsigned long long */
char digitbuff[22];
/* Insert position for the next character+1 */
char *pos = digitbuff + sizeof(digitbuff);
/* buffer for the 0/0x/0X prefix */
char prefix[2];
/* pointer to the digits translation table */
const char *digits;
/* mask to extract next character */
uint64_t mask;
int ret;
/* calculate mask */
mask = (1UL << shift) - 1;
/* determine digit translation table */
digits = ((param->vars.flags & PRINT_FLAG_UPPER) != 0) ?
upper_hex_digits : lower_hex_digits;
/* apply mask for short/char */
v &= param->vars.mask;
/* determine prefix for alternate form */
if ((v == 0) && ((param->vars.flags & PRINT_FLAG_ALTERNATE_FORM) != 0)) {
prefix[0] = '0';
param->vars.prefix = prefix;
param->vars.prefixlen = 1;
if (shift == 4) {
param->vars.prefixlen = 2;
prefix[1] = digits[16];
}
}
/* determine digits from right to left */
do {
*--pos = digits[(v & mask)];
} while ((v >>= shift) != 0);
/* assign parameter and apply width and precision */
param->vars.value = pos;
param->vars.valuelen = digitbuff + sizeof(digitbuff) - pos;
ret = format_number(param);
param->vars.value = NULL;
param->vars.valuelen = 0;
return ret;
}
static int print_decimal(struct print_param *param, int64_t value)
{
/* max. required buffer for unsigned long long in decimal format */
char digitbuff[20];
/* pointer to the next character position (+1) */
char *pos = digitbuff + sizeof(digitbuff);
/* current value in 32/64 bit */
union u_qword v;
/* next value in 32/64 bit */
union u_qword nv;
/* helper union for division result */
struct udiv_result d;
int ret;
/* assume an unsigned 64 bit value */
v.qword = ((uint64_t)value) & param->vars.mask;
/*
* assign sign and correct value if value is negative and
* value must be interpreted as signed
*/
if (((param->vars.flags & PRINT_FLAG_UINT32) == 0) && (value < 0)) {
v.qword = (uint64_t)-value;
param->vars.prefix = "-";
param->vars.prefixlen = 1;
}
/* determine sign if explicit requested in the format string */
if (param->vars.prefix == NULL) {
if ((param->vars.flags & PRINT_FLAG_SIGN) != 0) {
param->vars.prefix = "+";
param->vars.prefixlen = 1;
} else if ((param->vars.flags & PRINT_FLAG_SPACE) != 0) {
param->vars.prefix = " ";
param->vars.prefixlen = 1;
}
}
/* process 64 bit value as long as needed */
while (v.dwords.high != 0) {
/* determine digits from right to left */
udiv64(v.qword, 10, &d);
*--pos = d.r.dwords.low + '0';
v.qword = d.q.qword;
}
/* process 32 bit (or reduced 64 bit) value */
do {
/* determine digits from right to left. The compiler should be
* able to handle a division and multiplication by the constant
* 10.
*/
nv.dwords.low = v.dwords.low / 10;
*--pos = (v.dwords.low - (10 * nv.dwords.low)) + '0';
} while ((v.dwords.low = nv.dwords.low) != 0);
/* assign parameter and apply width and precision */
param->vars.value = pos;
param->vars.valuelen = digitbuff + sizeof(digitbuff) - pos;
ret = format_number(param);
param->vars.value = NULL;
param->vars.valuelen = 0;
return ret;
}
static int print_string(struct print_param *param, const char *s)
{
/* the length of the string (-1) if unknown */
int len;
/* the number of additional characters to insert to reach the required
* width
*/
uint32_t w;
/* the last result of the emit function */
int res;
w = 0;
len = -1;
/* we need the length of the string if either width or precision is
* given
*/
if ((param->vars.precision != 0)|| (param->vars.width != 0))
len = strnlen_s(s, PRINT_STRING_MAX_LEN);
/* precision gives the max. number of characters to emit. */
if ((param->vars.precision != 0) && (len > param->vars.precision))
len = param->vars.precision;
/* calculate the number of additional characters to get the required
* width
*/
if (param->vars.width > 0 && param->vars.width > len)
w = param->vars.width - len;
/* emit additional characters for width, return early if an error
* occurred
*/
if ((param->vars.flags & PRINT_FLAG_LEFT_JUSTIFY) == 0) {
res = param->emit(PRINT_CMD_FILL, " ", w, param->data);
if (res < 0)
return res;
}
/* emit the string, return early if an error occurred */
res = param->emit(PRINT_CMD_COPY, s, len, param->data);
if (res < 0)
return res;
/* emit additional characters on the right, return early if an error
* occurred
*/
if ((param->vars.flags & PRINT_FLAG_LEFT_JUSTIFY) != 0) {
res = param->emit(PRINT_CMD_FILL, " ", w, param->data);
if (res < 0)
return res;
}
return res;
}
int do_print(const char *fmt, struct print_param *param,
__builtin_va_list args)
{
/* the result of this function */
int res = 0;
/* temp. storage for the next character */
char ch;
/* temp. pointer to the start of an analysed character sequence */
const char *start;
/* main loop: analyse until there are no more characters */
while ((*fmt) != 0) {
/* mark the current position and search the next '%' */
start = fmt;
while (((*fmt) != 0) && (*fmt != '%'))
fmt++;
/*
* pass all characters until the next '%' to the emit function.
* Return early if the function fails
*/
res = param->emit(PRINT_CMD_COPY, start, fmt - start,
param->data);
if (res < 0)
return res;
/* continue only if the '%' character was found */
if (*fmt == '%') {
/* mark current position in the format string */
start = fmt++;
/* initialize the variables for the next argument */
memset(&(param->vars), 0, sizeof(param->vars));
param->vars.mask = 0xFFFFFFFFFFFFFFFFUL;
/*
* analyze the format specification:
* - get the flags
* - get the width
* - get the precision
* - get the length modifier
*/
fmt = get_flags(fmt, &(param->vars.flags));
fmt = get_int(fmt, &(param->vars.width));
if (*fmt == '.') {
fmt++;
fmt = get_int(fmt, &(param->vars.precision));
if (param->vars.precision < 0)
param->vars.precision = 0;
}
fmt = get_length_modifier(fmt, &(param->vars.flags),
&(param->vars.mask));
ch = *fmt++;
/* a single '%'? => print out a single '%' */
if (ch == '%') {
res = param->emit(PRINT_CMD_COPY, &ch, 1,
param->data);
}
/* decimal number */
else if ((ch == 'd') || (ch == 'i')) {
res = print_decimal(param,
((param->vars.flags &
PRINT_FLAG_LONG_LONG) != 0) ?
__builtin_va_arg(args,
long long)
: (long long)
__builtin_va_arg(args,
int));
}
/* unsigned decimal number */
else if (ch == 'u') {
param->vars.flags |= PRINT_FLAG_UINT32;
res = print_decimal(param,
((param->vars.flags &
PRINT_FLAG_LONG_LONG) != 0) ?
__builtin_va_arg(args,
unsigned long long)
: (unsigned long long)
__builtin_va_arg(args,
unsigned int));
}
/* octal number */
else if (ch == 'o') {
res = print_pow2(param,
((param->vars.flags &
PRINT_FLAG_LONG_LONG) != 0) ?
__builtin_va_arg(args,
unsigned long long)
: (unsigned long long)
__builtin_va_arg(args,
uint32_t),
3);
}
/* hexadecimal number */
else if ((ch == 'X') || (ch == 'x')) {
if (ch == 'X')
param->vars.flags |= PRINT_FLAG_UPPER;
res = print_pow2(param,
((param->vars.flags &
PRINT_FLAG_LONG_LONG) != 0) ?
__builtin_va_arg(args,
unsigned long long)
: (unsigned long long)
__builtin_va_arg(args,
uint32_t),
4);
}
/* string argument */
else if (ch == 's') {
const char *s = __builtin_va_arg(args, char *);
if (s == NULL)
s = "(null)";
res = print_string(param, s);
}
/* pointer argument */
else if (ch == 'p') {
param->vars.flags |= PRINT_FLAG_ALTERNATE_FORM;
/* XXXCRG res=print_pow2(param,
* (uint32_t) __builtin_va_arg(args,
* void *),4);
*/
res = print_pow2(param, (uint64_t)
__builtin_va_arg(args, void *), 4);
}
/* single character argument */
else if (ch == 'c') {
char c[2];
c[0] = __builtin_va_arg(args, int);
c[1] = 0;
res = print_string(param, c);
}
/* default: print the format specifier as it is */
else {
res = param->emit(PRINT_CMD_COPY, start,
fmt - start, param->data);
}
}
/* return if an error occurred */
if (res < 0)
return res;
}
/* done. Return the result of the last emit function call */
return res;
}
static int charmem(int cmd, const char *s, int sz, void *hnd)
{
/* pointer to the snprint parameter list */
struct snprint_param *param = (struct snprint_param *) hnd;
/* pointer to the destination */
char *p = param->dst + param->wrtn;
/* characters actually written */
int n = 0;
/* copy mode ? */
if (cmd == PRINT_CMD_COPY) {
if (sz < 0) {
while ((*s) != 0) {
if (n < param->sz - param->wrtn)
*p = *s;
p++;
s++;
n++;
}
} else if (sz > 0) {
while (((*s) != 0) && n < sz) {
if (n < param->sz - param->wrtn)
*p = *s;
p++;
s++;
n++;
}
}
param->wrtn += n;
return n;
}
/* fill mode */
else {
n = (sz < param->sz - param->wrtn) ? sz : 0;
param->wrtn += sz;
memset(p, *s, n);
}
return n;
}
int vsnprintf(char *dst, int sz, const char *fmt, va_list args)
{
char c[1];
/* the result of this function */
int res = 0;
if (sz <= 0 || (dst == NULL)) {
dst = c;
sz = 1;
}
/* struct to store all necessary parameters */
struct print_param param;
/* struct to store snprintf specific parameters */
struct snprint_param snparam;
/* initialize parameters */
memset(&snparam, 0, sizeof(snparam));
snparam.dst = dst;
snparam.sz = sz;
memset(&param, 0, sizeof(param));
param.emit = charmem;
param.data = &snparam;
/* execute the printf() */
if (do_print(fmt, &param, args) < 0)
return -1;
/* ensure the written string is NULL terminated */
if (snparam.wrtn < sz)
snparam.dst[snparam.wrtn] = '\0';
else
snparam.dst[sz - 1] = '\0';
/* return the number of chars which would be written */
res = snparam.wrtn;
/* done */
return res;
}
int snprintf(char *dest, int sz, const char *fmt, ...)
{
/* variable argument list needed for do_print() */
va_list args;
/* the result of this function */
int res;
va_start(args, fmt);
/* execute the printf() */
res = vsnprintf(dest, sz, fmt, args);
/* destroy parameter list */
va_end(args);
/* done */
return res;
}