655 lines
18 KiB
C
Executable file
655 lines
18 KiB
C
Executable file
#include <stdio.h>
|
||
#include <ctype.h>
|
||
#include <inttypes.h>
|
||
#include <round.h>
|
||
#include <stdint.h>
|
||
#include <string.h>
|
||
|
||
/* Auxiliary data for vsnprintf_helper(). */
|
||
struct vsnprintf_aux
|
||
{
|
||
char *p; /* Current output position. */
|
||
int length; /* Length of output string. */
|
||
int max_length; /* Max length of output string. */
|
||
};
|
||
|
||
static void vsnprintf_helper (char, void *);
|
||
|
||
/* Like vprintf(), except that output is stored into BUFFER,
|
||
which must have space for BUF_SIZE characters. Writes at most
|
||
BUF_SIZE - 1 characters to BUFFER, followed by a null
|
||
terminator. BUFFER will always be null-terminated unless
|
||
BUF_SIZE is zero. Returns the number of characters that would
|
||
have been written to BUFFER, not including a null terminator,
|
||
had there been enough room. */
|
||
int
|
||
vsnprintf (char *buffer, size_t buf_size, const char *format, va_list args)
|
||
{
|
||
/* Set up aux data for vsnprintf_helper(). */
|
||
struct vsnprintf_aux aux;
|
||
aux.p = buffer;
|
||
aux.length = 0;
|
||
aux.max_length = buf_size > 0 ? buf_size - 1 : 0;
|
||
|
||
/* Do most of the work. */
|
||
__vprintf (format, args, vsnprintf_helper, &aux);
|
||
|
||
/* Add null terminator. */
|
||
if (buf_size > 0)
|
||
*aux.p = '\0';
|
||
|
||
return aux.length;
|
||
}
|
||
|
||
/* Helper function for vsnprintf(). */
|
||
static void
|
||
vsnprintf_helper (char ch, void *aux_)
|
||
{
|
||
struct vsnprintf_aux *aux = aux_;
|
||
|
||
if (aux->length++ < aux->max_length)
|
||
*aux->p++ = ch;
|
||
}
|
||
|
||
/* Like printf(), except that output is stored into BUFFER,
|
||
which must have space for BUF_SIZE characters. Writes at most
|
||
BUF_SIZE - 1 characters to BUFFER, followed by a null
|
||
terminator. BUFFER will always be null-terminated unless
|
||
BUF_SIZE is zero. Returns the number of characters that would
|
||
have been written to BUFFER, not including a null terminator,
|
||
had there been enough room. */
|
||
int
|
||
snprintf (char *buffer, size_t buf_size, const char *format, ...)
|
||
{
|
||
va_list args;
|
||
int retval;
|
||
|
||
va_start (args, format);
|
||
retval = vsnprintf (buffer, buf_size, format, args);
|
||
va_end (args);
|
||
|
||
return retval;
|
||
}
|
||
|
||
/* Writes formatted output to the console.
|
||
In the kernel, the console is both the video display and first
|
||
serial port.
|
||
In userspace, the console is file descriptor 1. */
|
||
int
|
||
printf (const char *format, ...)
|
||
{
|
||
va_list args;
|
||
int retval;
|
||
|
||
va_start (args, format);
|
||
retval = vprintf (format, args);
|
||
va_end (args);
|
||
|
||
return retval;
|
||
}
|
||
|
||
/* printf() formatting internals. */
|
||
|
||
/* A printf() conversion. */
|
||
struct printf_conversion
|
||
{
|
||
/* Flags. */
|
||
enum
|
||
{
|
||
MINUS = 1 << 0, /* '-' */
|
||
PLUS = 1 << 1, /* '+' */
|
||
SPACE = 1 << 2, /* ' ' */
|
||
POUND = 1 << 3, /* '#' */
|
||
ZERO = 1 << 4, /* '0' */
|
||
GROUP = 1 << 5 /* '\'' */
|
||
}
|
||
flags;
|
||
|
||
/* Minimum field width. */
|
||
int width;
|
||
|
||
/* Numeric precision.
|
||
-1 indicates no precision was specified. */
|
||
int precision;
|
||
|
||
/* Type of argument to format. */
|
||
enum
|
||
{
|
||
CHAR = 1, /* hh */
|
||
SHORT = 2, /* h */
|
||
INT = 3, /* (none) */
|
||
INTMAX = 4, /* j */
|
||
LONG = 5, /* l */
|
||
LONGLONG = 6, /* ll */
|
||
PTRDIFFT = 7, /* t */
|
||
SIZET = 8 /* z */
|
||
}
|
||
type;
|
||
};
|
||
|
||
struct integer_base
|
||
{
|
||
int base; /* Base. */
|
||
const char *digits; /* Collection of digits. */
|
||
int x; /* `x' character to use, for base 16 only. */
|
||
int group; /* Number of digits to group with ' flag. */
|
||
};
|
||
|
||
static const struct integer_base base_d = {10, "0123456789", 0, 3};
|
||
static const struct integer_base base_o = {8, "01234567", 0, 3};
|
||
static const struct integer_base base_x = {16, "0123456789abcdef", 'x', 4};
|
||
static const struct integer_base base_X = {16, "0123456789ABCDEF", 'X', 4};
|
||
|
||
static const char *parse_conversion (const char *format,
|
||
struct printf_conversion *,
|
||
va_list *);
|
||
static void format_integer (uintmax_t value, bool is_signed, bool negative,
|
||
const struct integer_base *,
|
||
const struct printf_conversion *,
|
||
void (*output) (char, void *), void *aux);
|
||
static void output_dup (char ch, size_t cnt,
|
||
void (*output) (char, void *), void *aux);
|
||
static void format_string (const char *string, int length,
|
||
struct printf_conversion *,
|
||
void (*output) (char, void *), void *aux);
|
||
|
||
void
|
||
__vprintf (const char *format, va_list args,
|
||
void (*output) (char, void *), void *aux)
|
||
{
|
||
for (; *format != '\0'; format++)
|
||
{
|
||
struct printf_conversion c;
|
||
|
||
/* Literally copy non-conversions to output. */
|
||
if (*format != '%')
|
||
{
|
||
output (*format, aux);
|
||
continue;
|
||
}
|
||
format++;
|
||
|
||
/* %% => %. */
|
||
if (*format == '%')
|
||
{
|
||
output ('%', aux);
|
||
continue;
|
||
}
|
||
|
||
/* Parse conversion specifiers. */
|
||
format = parse_conversion (format, &c, &args);
|
||
|
||
/* Do conversion. */
|
||
switch (*format)
|
||
{
|
||
case 'd':
|
||
case 'i':
|
||
{
|
||
/* Signed integer conversions. */
|
||
intmax_t value;
|
||
|
||
switch (c.type)
|
||
{
|
||
case CHAR:
|
||
value = (signed char) va_arg (args, int);
|
||
break;
|
||
case SHORT:
|
||
value = (short) va_arg (args, int);
|
||
break;
|
||
case INT:
|
||
value = va_arg (args, int);
|
||
break;
|
||
case INTMAX:
|
||
value = va_arg (args, intmax_t);
|
||
break;
|
||
case LONG:
|
||
value = va_arg (args, long);
|
||
break;
|
||
case LONGLONG:
|
||
value = va_arg (args, long long);
|
||
break;
|
||
case PTRDIFFT:
|
||
value = va_arg (args, ptrdiff_t);
|
||
break;
|
||
case SIZET:
|
||
value = va_arg (args, size_t);
|
||
if (value > SIZE_MAX / 2)
|
||
value = value - SIZE_MAX - 1;
|
||
break;
|
||
default:
|
||
NOT_REACHED ();
|
||
}
|
||
|
||
format_integer (value < 0 ? -value : value,
|
||
true, value < 0, &base_d, &c, output, aux);
|
||
}
|
||
break;
|
||
|
||
case 'o':
|
||
case 'u':
|
||
case 'x':
|
||
case 'X':
|
||
{
|
||
/* Unsigned integer conversions. */
|
||
uintmax_t value;
|
||
const struct integer_base *b;
|
||
|
||
switch (c.type)
|
||
{
|
||
case CHAR:
|
||
value = (unsigned char) va_arg (args, unsigned);
|
||
break;
|
||
case SHORT:
|
||
value = (unsigned short) va_arg (args, unsigned);
|
||
break;
|
||
case INT:
|
||
value = va_arg (args, unsigned);
|
||
break;
|
||
case INTMAX:
|
||
value = va_arg (args, uintmax_t);
|
||
break;
|
||
case LONG:
|
||
value = va_arg (args, unsigned long);
|
||
break;
|
||
case LONGLONG:
|
||
value = va_arg (args, unsigned long long);
|
||
break;
|
||
case PTRDIFFT:
|
||
value = va_arg (args, ptrdiff_t);
|
||
#if UINTMAX_MAX != PTRDIFF_MAX
|
||
value &= ((uintmax_t) PTRDIFF_MAX << 1) | 1;
|
||
#endif
|
||
break;
|
||
case SIZET:
|
||
value = va_arg (args, size_t);
|
||
break;
|
||
default:
|
||
NOT_REACHED ();
|
||
}
|
||
|
||
switch (*format)
|
||
{
|
||
case 'o': b = &base_o; break;
|
||
case 'u': b = &base_d; break;
|
||
case 'x': b = &base_x; break;
|
||
case 'X': b = &base_X; break;
|
||
default: NOT_REACHED ();
|
||
}
|
||
|
||
format_integer (value, false, false, b, &c, output, aux);
|
||
}
|
||
break;
|
||
|
||
case 'c':
|
||
{
|
||
/* Treat character as single-character string. */
|
||
char ch = va_arg (args, int);
|
||
format_string (&ch, 1, &c, output, aux);
|
||
}
|
||
break;
|
||
|
||
case 's':
|
||
{
|
||
/* String conversion. */
|
||
const char *s = va_arg (args, char *);
|
||
if (s == NULL)
|
||
s = "(null)";
|
||
|
||
/* Limit string length according to precision.
|
||
Note: if c.precision == -1 then strnlen() will get
|
||
SIZE_MAX for MAXLEN, which is just what we want. */
|
||
format_string (s, strnlen (s, c.precision), &c, output, aux);
|
||
}
|
||
break;
|
||
|
||
case 'p':
|
||
{
|
||
/* Pointer conversion.
|
||
Format pointers as %#x. */
|
||
void *p = va_arg (args, void *);
|
||
|
||
c.flags = POUND;
|
||
format_integer ((uintptr_t) p, false, false,
|
||
&base_x, &c, output, aux);
|
||
}
|
||
break;
|
||
|
||
case 'f':
|
||
case 'e':
|
||
case 'E':
|
||
case 'g':
|
||
case 'G':
|
||
case 'n':
|
||
/* We don't support floating-point arithmetic,
|
||
and %n can be part of a security hole. */
|
||
__printf ("<<no %%%c in kernel>>", output, aux, *format);
|
||
break;
|
||
|
||
default:
|
||
__printf ("<<no %%%c conversion>>", output, aux, *format);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Parses conversion option characters starting at FORMAT and
|
||
initializes C appropriately. Returns the character in FORMAT
|
||
that indicates the conversion (e.g. the `d' in `%d'). Uses
|
||
*ARGS for `*' field widths and precisions. */
|
||
static const char *
|
||
parse_conversion (const char *format, struct printf_conversion *c,
|
||
va_list *args)
|
||
{
|
||
/* Parse flag characters. */
|
||
c->flags = 0;
|
||
for (;;)
|
||
{
|
||
switch (*format++)
|
||
{
|
||
case '-':
|
||
c->flags |= MINUS;
|
||
break;
|
||
case '+':
|
||
c->flags |= PLUS;
|
||
break;
|
||
case ' ':
|
||
c->flags |= SPACE;
|
||
break;
|
||
case '#':
|
||
c->flags |= POUND;
|
||
break;
|
||
case '0':
|
||
c->flags |= ZERO;
|
||
break;
|
||
case '\'':
|
||
c->flags |= GROUP;
|
||
break;
|
||
default:
|
||
format--;
|
||
goto not_a_flag;
|
||
}
|
||
}
|
||
not_a_flag:
|
||
if (c->flags & MINUS)
|
||
c->flags &= ~ZERO;
|
||
if (c->flags & PLUS)
|
||
c->flags &= ~SPACE;
|
||
|
||
/* Parse field width. */
|
||
c->width = 0;
|
||
if (*format == '*')
|
||
{
|
||
format++;
|
||
c->width = va_arg (*args, int);
|
||
}
|
||
else
|
||
{
|
||
for (; isdigit (*format); format++)
|
||
c->width = c->width * 10 + *format - '0';
|
||
}
|
||
if (c->width < 0)
|
||
{
|
||
c->width = -c->width;
|
||
c->flags |= MINUS;
|
||
}
|
||
|
||
/* Parse precision. */
|
||
c->precision = -1;
|
||
if (*format == '.')
|
||
{
|
||
format++;
|
||
if (*format == '*')
|
||
{
|
||
format++;
|
||
c->precision = va_arg (*args, int);
|
||
}
|
||
else
|
||
{
|
||
c->precision = 0;
|
||
for (; isdigit (*format); format++)
|
||
c->precision = c->precision * 10 + *format - '0';
|
||
}
|
||
if (c->precision < 0)
|
||
c->precision = -1;
|
||
}
|
||
if (c->precision >= 0)
|
||
c->flags &= ~ZERO;
|
||
|
||
/* Parse type. */
|
||
c->type = INT;
|
||
switch (*format++)
|
||
{
|
||
case 'h':
|
||
if (*format == 'h')
|
||
{
|
||
format++;
|
||
c->type = CHAR;
|
||
}
|
||
else
|
||
c->type = SHORT;
|
||
break;
|
||
|
||
case 'j':
|
||
c->type = INTMAX;
|
||
break;
|
||
|
||
case 'l':
|
||
if (*format == 'l')
|
||
{
|
||
format++;
|
||
c->type = LONGLONG;
|
||
}
|
||
else
|
||
c->type = LONG;
|
||
break;
|
||
|
||
case 't':
|
||
c->type = PTRDIFFT;
|
||
break;
|
||
|
||
case 'z':
|
||
c->type = SIZET;
|
||
break;
|
||
|
||
default:
|
||
format--;
|
||
break;
|
||
}
|
||
|
||
return format;
|
||
}
|
||
|
||
/* Performs an integer conversion, writing output to OUTPUT with
|
||
auxiliary data AUX. The integer converted has absolute value
|
||
VALUE. If IS_SIGNED is true, does a signed conversion with
|
||
NEGATIVE indicating a negative value; otherwise does an
|
||
unsigned conversion and ignores NEGATIVE. The output is done
|
||
according to the provided base B. Details of the conversion
|
||
are in C. */
|
||
static void
|
||
format_integer (uintmax_t value, bool is_signed, bool negative,
|
||
const struct integer_base *b,
|
||
const struct printf_conversion *c,
|
||
void (*output) (char, void *), void *aux)
|
||
{
|
||
char buf[64], *cp; /* Buffer and current position. */
|
||
int x; /* `x' character to use or 0 if none. */
|
||
int sign; /* Sign character or 0 if none. */
|
||
int precision; /* Rendered precision. */
|
||
int pad_cnt; /* # of pad characters to fill field width. */
|
||
int digit_cnt; /* # of digits output so far. */
|
||
|
||
/* Determine sign character, if any.
|
||
An unsigned conversion will never have a sign character,
|
||
even if one of the flags requests one. */
|
||
sign = 0;
|
||
if (is_signed)
|
||
{
|
||
if (c->flags & PLUS)
|
||
sign = negative ? '-' : '+';
|
||
else if (c->flags & SPACE)
|
||
sign = negative ? '-' : ' ';
|
||
else if (negative)
|
||
sign = '-';
|
||
}
|
||
|
||
/* Determine whether to include `0x' or `0X'.
|
||
It will only be included with a hexadecimal conversion of a
|
||
nonzero value with the # flag. */
|
||
x = (c->flags & POUND) && value ? b->x : 0;
|
||
|
||
/* Accumulate digits into buffer.
|
||
This algorithm produces digits in reverse order, so later we
|
||
will output the buffer's content in reverse. */
|
||
cp = buf;
|
||
digit_cnt = 0;
|
||
while (value > 0)
|
||
{
|
||
if ((c->flags & GROUP) && digit_cnt > 0 && digit_cnt % b->group == 0)
|
||
*cp++ = ',';
|
||
*cp++ = b->digits[value % b->base];
|
||
value /= b->base;
|
||
digit_cnt++;
|
||
}
|
||
|
||
/* Append enough zeros to match precision.
|
||
If requested precision is 0, then a value of zero is
|
||
rendered as a null string, otherwise as "0".
|
||
If the # flag is used with base 8, the result must always
|
||
begin with a zero. */
|
||
precision = c->precision < 0 ? 1 : c->precision;
|
||
while (cp - buf < precision && cp < buf + sizeof buf - 1)
|
||
*cp++ = '0';
|
||
if ((c->flags & POUND) && b->base == 8 && (cp == buf || cp[-1] != '0'))
|
||
*cp++ = '0';
|
||
|
||
/* Calculate number of pad characters to fill field width. */
|
||
pad_cnt = c->width - (cp - buf) - (x ? 2 : 0) - (sign != 0);
|
||
if (pad_cnt < 0)
|
||
pad_cnt = 0;
|
||
|
||
/* Do output. */
|
||
if ((c->flags & (MINUS | ZERO)) == 0)
|
||
output_dup (' ', pad_cnt, output, aux);
|
||
if (sign)
|
||
output (sign, aux);
|
||
if (x)
|
||
{
|
||
output ('0', aux);
|
||
output (x, aux);
|
||
}
|
||
if (c->flags & ZERO)
|
||
output_dup ('0', pad_cnt, output, aux);
|
||
while (cp > buf)
|
||
output (*--cp, aux);
|
||
if (c->flags & MINUS)
|
||
output_dup (' ', pad_cnt, output, aux);
|
||
}
|
||
|
||
/* Writes CH to OUTPUT with auxiliary data AUX, CNT times. */
|
||
static void
|
||
output_dup (char ch, size_t cnt, void (*output) (char, void *), void *aux)
|
||
{
|
||
while (cnt-- > 0)
|
||
output (ch, aux);
|
||
}
|
||
|
||
/* Formats the LENGTH characters starting at STRING according to
|
||
the conversion specified in C. Writes output to OUTPUT with
|
||
auxiliary data AUX. */
|
||
static void
|
||
format_string (const char *string, int length,
|
||
struct printf_conversion *c,
|
||
void (*output) (char, void *), void *aux)
|
||
{
|
||
int i;
|
||
if (c->width > length && (c->flags & MINUS) == 0)
|
||
output_dup (' ', c->width - length, output, aux);
|
||
for (i = 0; i < length; i++)
|
||
output (string[i], aux);
|
||
if (c->width > length && (c->flags & MINUS) != 0)
|
||
output_dup (' ', c->width - length, output, aux);
|
||
}
|
||
|
||
/* Wrapper for __vprintf() that converts varargs into a
|
||
va_list. */
|
||
void
|
||
__printf (const char *format,
|
||
void (*output) (char, void *), void *aux, ...)
|
||
{
|
||
va_list args;
|
||
|
||
va_start (args, aux);
|
||
__vprintf (format, args, output, aux);
|
||
va_end (args);
|
||
}
|
||
|
||
/* Dumps the SIZE bytes in BUF to the console as hex bytes
|
||
arranged 16 per line. Numeric offsets are also included,
|
||
starting at OFS for the first byte in BUF. If ASCII is true
|
||
then the corresponding ASCII characters are also rendered
|
||
alongside. */
|
||
void
|
||
hex_dump (uintptr_t ofs, const void *buf_, size_t size, bool ascii)
|
||
{
|
||
const uint8_t *buf = buf_;
|
||
const size_t per_line = 16; /* Maximum bytes per line. */
|
||
|
||
while (size > 0)
|
||
{
|
||
size_t start, end, n;
|
||
size_t i;
|
||
|
||
/* Number of bytes on this line. */
|
||
start = ofs % per_line;
|
||
end = per_line;
|
||
if (end - start > size)
|
||
end = start + size;
|
||
n = end - start;
|
||
|
||
/* Print line. */
|
||
printf ("%08jx ", (uintmax_t) ROUND_DOWN (ofs, per_line));
|
||
for (i = 0; i < start; i++)
|
||
printf (" ");
|
||
for (; i < end; i++)
|
||
printf ("%02hhx%c",
|
||
buf[i - start], i == per_line / 2 - 1? '-' : ' ');
|
||
if (ascii)
|
||
{
|
||
for (; i < per_line; i++)
|
||
printf (" ");
|
||
printf ("|");
|
||
for (i = 0; i < start; i++)
|
||
printf (" ");
|
||
for (; i < end; i++)
|
||
printf ("%c",
|
||
isprint (buf[i - start]) ? buf[i - start] : '.');
|
||
for (; i < per_line; i++)
|
||
printf (" ");
|
||
printf ("|");
|
||
}
|
||
printf ("\n");
|
||
|
||
ofs += n;
|
||
buf += n;
|
||
size -= n;
|
||
}
|
||
}
|
||
|
||
/* Prints SIZE, which represents a number of bytes, in a
|
||
human-readable format, e.g. "256 kB". */
|
||
void
|
||
print_human_readable_size (uint64_t size)
|
||
{
|
||
if (size == 1)
|
||
printf ("1 byte");
|
||
else
|
||
{
|
||
static const char *factors[] = {"bytes", "kB", "MB", "GB", "TB", NULL};
|
||
const char **fp;
|
||
|
||
for (fp = factors; size >= 1024 && fp[1] != NULL; fp++)
|
||
size /= 1024;
|
||
printf ("%"PRIu64" %s", size, *fp);
|
||
}
|
||
}
|