656 lines
18 KiB
C
656 lines
18 KiB
C
|
#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);
|
|||
|
}
|
|||
|
}
|