Merge branch 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git

This commit is contained in:
Stephen Rothwell 2025-01-14 09:59:42 +11:00
commit d6f0234b76

View File

@ -160,8 +160,7 @@ long long simple_strtoll(const char *cp, char **endp, unsigned int base)
}
EXPORT_SYMBOL(simple_strtoll);
static noinline_for_stack
int skip_atoi(const char **s)
static inline int skip_atoi(const char **s)
{
int i = 0;
@ -407,7 +406,7 @@ int num_to_str(char *buf, int size, unsigned long long num, unsigned int width)
return len + width;
}
#define SIGN 1 /* unsigned/signed, must be 1 */
#define SIGN 1 /* unsigned/signed */
#define LEFT 2 /* left justified */
#define PLUS 4 /* show plus */
#define SPACE 8 /* space if plus */
@ -415,38 +414,26 @@ int num_to_str(char *buf, int size, unsigned long long num, unsigned int width)
#define SMALL 32 /* use lowercase in hex (must be 32 == 0x20) */
#define SPECIAL 64 /* prefix hex with "0x", octal with "0" */
static_assert(SIGN == 1);
static_assert(ZEROPAD == ('0' - ' '));
static_assert(SMALL == ('a' ^ 'A'));
enum format_type {
FORMAT_TYPE_NONE, /* Just a string part */
FORMAT_TYPE_WIDTH,
FORMAT_TYPE_PRECISION,
FORMAT_TYPE_CHAR,
FORMAT_TYPE_STR,
FORMAT_TYPE_PTR,
FORMAT_TYPE_PERCENT_CHAR,
FORMAT_TYPE_INVALID,
FORMAT_TYPE_LONG_LONG,
FORMAT_TYPE_ULONG,
FORMAT_TYPE_LONG,
FORMAT_TYPE_UBYTE,
FORMAT_TYPE_BYTE,
FORMAT_TYPE_USHORT,
FORMAT_TYPE_SHORT,
FORMAT_TYPE_UINT,
FORMAT_TYPE_INT,
FORMAT_TYPE_SIZE_T,
FORMAT_TYPE_PTRDIFF
enum format_state {
FORMAT_STATE_NONE, /* Just a string part */
FORMAT_STATE_NUM,
FORMAT_STATE_WIDTH,
FORMAT_STATE_PRECISION,
FORMAT_STATE_CHAR,
FORMAT_STATE_STR,
FORMAT_STATE_PTR,
FORMAT_STATE_PERCENT_CHAR,
FORMAT_STATE_INVALID,
};
struct printf_spec {
unsigned int type:8; /* format_type enum */
signed int field_width:24; /* width of output field */
unsigned int flags:8; /* flags to number() */
unsigned int base:8; /* number base, 8, 10 or 16 only */
signed int precision:16; /* # of digits/chars */
unsigned char flags; /* flags to number() */
unsigned char base; /* number base, 8, 10 or 16 only */
short precision; /* # of digits/chars */
int field_width; /* width of output field */
} __packed;
static_assert(sizeof(struct printf_spec) == 8);
@ -579,7 +566,6 @@ char *special_hex_number(char *buf, char *end, unsigned long long num, int size)
{
struct printf_spec spec;
spec.type = FORMAT_TYPE_PTR;
spec.field_width = 2 + 2 * size; /* 0x + hex */
spec.flags = SPECIAL | SMALL | ZEROPAD;
spec.base = 16;
@ -2530,6 +2516,26 @@ char *pointer(const char *fmt, char *buf, char *end, void *ptr,
}
}
struct fmt {
const char *str;
unsigned char state; // enum format_state
unsigned char size; // size of numbers
};
#define SPEC_CHAR(x, flag) [(x)-32] = flag
static unsigned char spec_flag(unsigned char c)
{
static const unsigned char spec_flag_array[] = {
SPEC_CHAR(' ', SPACE),
SPEC_CHAR('#', SPECIAL),
SPEC_CHAR('+', PLUS),
SPEC_CHAR('-', LEFT),
SPEC_CHAR('0', ZEROPAD),
};
c -= 32;
return (c < sizeof(spec_flag_array)) ? spec_flag_array[c] : 0;
}
/*
* Helper function to decode printf style format.
* Each call decode a token from the format and return the
@ -2552,181 +2558,141 @@ char *pointer(const char *fmt, char *buf, char *end, void *ptr,
* @qualifier: qualifier of a number (long, size_t, ...)
*/
static noinline_for_stack
int format_decode(const char *fmt, struct printf_spec *spec)
struct fmt format_decode(struct fmt fmt, struct printf_spec *spec)
{
const char *start = fmt;
char qualifier;
const char *start = fmt.str;
char flag;
/* we finished early by reading the field width */
if (spec->type == FORMAT_TYPE_WIDTH) {
if (unlikely(fmt.state == FORMAT_STATE_WIDTH)) {
if (spec->field_width < 0) {
spec->field_width = -spec->field_width;
spec->flags |= LEFT;
}
spec->type = FORMAT_TYPE_NONE;
fmt.state = FORMAT_STATE_NONE;
goto precision;
}
/* we finished early by reading the precision */
if (spec->type == FORMAT_TYPE_PRECISION) {
if (unlikely(fmt.state == FORMAT_STATE_PRECISION)) {
if (spec->precision < 0)
spec->precision = 0;
spec->type = FORMAT_TYPE_NONE;
fmt.state = FORMAT_STATE_NONE;
goto qualifier;
}
/* By default */
spec->type = FORMAT_TYPE_NONE;
fmt.state = FORMAT_STATE_NONE;
for (; *fmt ; ++fmt) {
if (*fmt == '%')
for (; *fmt.str ; fmt.str++) {
if (*fmt.str == '%')
break;
}
/* Return the current non-format string */
if (fmt != start || !*fmt)
return fmt - start;
if (fmt.str != start || !*fmt.str)
return fmt;
/* Process flags */
/* Process flags. This also skips the first '%' */
spec->flags = 0;
while (1) { /* this also skips first '%' */
bool found = true;
++fmt;
switch (*fmt) {
case '-': spec->flags |= LEFT; break;
case '+': spec->flags |= PLUS; break;
case ' ': spec->flags |= SPACE; break;
case '#': spec->flags |= SPECIAL; break;
case '0': spec->flags |= ZEROPAD; break;
default: found = false;
}
if (!found)
break;
}
do {
/* this also skips first '%' */
flag = spec_flag(*++fmt.str);
spec->flags |= flag;
} while (flag);
/* get field width */
spec->field_width = -1;
if (isdigit(*fmt))
spec->field_width = skip_atoi(&fmt);
else if (*fmt == '*') {
if (isdigit(*fmt.str))
spec->field_width = skip_atoi(&fmt.str);
else if (unlikely(*fmt.str == '*')) {
/* it's the next argument */
spec->type = FORMAT_TYPE_WIDTH;
return ++fmt - start;
fmt.state = FORMAT_STATE_WIDTH;
fmt.str++;
return fmt;
}
precision:
/* get the precision */
spec->precision = -1;
if (*fmt == '.') {
++fmt;
if (isdigit(*fmt)) {
spec->precision = skip_atoi(&fmt);
if (unlikely(*fmt.str == '.')) {
fmt.str++;
if (isdigit(*fmt.str)) {
spec->precision = skip_atoi(&fmt.str);
if (spec->precision < 0)
spec->precision = 0;
} else if (*fmt == '*') {
} else if (*fmt.str == '*') {
/* it's the next argument */
spec->type = FORMAT_TYPE_PRECISION;
return ++fmt - start;
fmt.state = FORMAT_STATE_PRECISION;
fmt.str++;
return fmt;
}
}
qualifier:
/* get the conversion qualifier */
qualifier = 0;
if (*fmt == 'h' || _tolower(*fmt) == 'l' ||
*fmt == 'z' || *fmt == 't') {
qualifier = *fmt++;
if (unlikely(qualifier == *fmt)) {
if (qualifier == 'l') {
qualifier = 'L';
++fmt;
} else if (qualifier == 'h') {
qualifier = 'H';
++fmt;
}
}
}
/* default base */
/* Set up default numeric format */
spec->base = 10;
switch (*fmt) {
case 'c':
spec->type = FORMAT_TYPE_CHAR;
return ++fmt - start;
fmt.state = FORMAT_STATE_NUM;
fmt.size = sizeof(int);
static const struct format_state {
unsigned char state;
unsigned char size;
unsigned char flags_or_double_size;
unsigned char base;
} lookup_state[256] = {
// Length
['l'] = { 0, sizeof(long), sizeof(long long) },
['L'] = { 0, sizeof(long long) },
['h'] = { 0, sizeof(short), sizeof(char) },
['H'] = { 0, sizeof(char) }, // Questionable historical
['z'] = { 0, sizeof(size_t) },
['t'] = { 0, sizeof(ptrdiff_t) },
case 's':
spec->type = FORMAT_TYPE_STR;
return ++fmt - start;
// Non-numeric formats
['c'] = { FORMAT_STATE_CHAR },
['s'] = { FORMAT_STATE_STR },
['p'] = { FORMAT_STATE_PTR },
['%'] = { FORMAT_STATE_PERCENT_CHAR },
case 'p':
spec->type = FORMAT_TYPE_PTR;
return ++fmt - start;
// Numerics
['o'] = { FORMAT_STATE_NUM, 0, 0, 8 },
['x'] = { FORMAT_STATE_NUM, 0, SMALL, 16 },
['X'] = { FORMAT_STATE_NUM, 0, 0, 16 },
['d'] = { FORMAT_STATE_NUM, 0, SIGN, 10 },
['i'] = { FORMAT_STATE_NUM, 0, SIGN, 10 },
['u'] = { FORMAT_STATE_NUM, 0, 0, 10, },
case '%':
spec->type = FORMAT_TYPE_PERCENT_CHAR;
return ++fmt - start;
/* integer number formats - set up the flags and "break" */
case 'o':
spec->base = 8;
break;
case 'x':
spec->flags |= SMALL;
fallthrough;
case 'X':
spec->base = 16;
break;
case 'd':
case 'i':
spec->flags |= SIGN;
break;
case 'u':
break;
case 'n':
/*
* Since %n poses a greater security risk than
* utility, treat it as any other invalid or
* unsupported format specifier.
*/
fallthrough;
};
default:
WARN_ONCE(1, "Please remove unsupported %%%c in format string\n", *fmt);
spec->type = FORMAT_TYPE_INVALID;
return fmt - start;
const struct format_state *p = lookup_state + (u8)*fmt.str;
if (p->size) {
fmt.size = p->size;
if (p->flags_or_double_size && fmt.str[0] == fmt.str[1]) {
fmt.size = p->flags_or_double_size;
fmt.str++;
}
fmt.str++;
p = lookup_state + *fmt.str;
}
if (p->state) {
if (p->base)
spec->base = p->base;
spec->flags |= p->flags_or_double_size;
fmt.state = p->state;
fmt.str++;
return fmt;
}
if (qualifier == 'L')
spec->type = FORMAT_TYPE_LONG_LONG;
else if (qualifier == 'l') {
BUILD_BUG_ON(FORMAT_TYPE_ULONG + SIGN != FORMAT_TYPE_LONG);
spec->type = FORMAT_TYPE_ULONG + (spec->flags & SIGN);
} else if (qualifier == 'z') {
spec->type = FORMAT_TYPE_SIZE_T;
} else if (qualifier == 't') {
spec->type = FORMAT_TYPE_PTRDIFF;
} else if (qualifier == 'H') {
BUILD_BUG_ON(FORMAT_TYPE_UBYTE + SIGN != FORMAT_TYPE_BYTE);
spec->type = FORMAT_TYPE_UBYTE + (spec->flags & SIGN);
} else if (qualifier == 'h') {
BUILD_BUG_ON(FORMAT_TYPE_USHORT + SIGN != FORMAT_TYPE_SHORT);
spec->type = FORMAT_TYPE_USHORT + (spec->flags & SIGN);
} else {
BUILD_BUG_ON(FORMAT_TYPE_UINT + SIGN != FORMAT_TYPE_INT);
spec->type = FORMAT_TYPE_UINT + (spec->flags & SIGN);
}
return ++fmt - start;
WARN_ONCE(1, "Please remove unsupported %%%c in format string\n", *fmt.str);
fmt.state = FORMAT_STATE_INVALID;
return fmt;
}
static void
@ -2747,11 +2713,27 @@ set_precision(struct printf_spec *spec, int prec)
}
}
/*
* Turn a 1/2/4-byte value into a 64-bit one for printing: truncate
* as necessary and deal with signedness.
*
* 'size' is the size of the value in bytes.
*/
static unsigned long long convert_num_spec(unsigned int val, int size, struct printf_spec spec)
{
unsigned int shift = 32 - size*8;
val <<= shift;
if (!(spec.flags & SIGN))
return val >> shift;
return (int)val >> shift;
}
/**
* vsnprintf - Format a string and place it in a buffer
* @buf: The buffer to place the result into
* @size: The size of the buffer, including the trailing null space
* @fmt: The format string to use
* @fmt_str: The format string to use
* @args: Arguments for the format string
*
* This function generally follows C99 vsnprintf, but has some
@ -2775,11 +2757,14 @@ set_precision(struct printf_spec *spec, int prec)
*
* If you're not already dealing with a va_list consider using snprintf().
*/
int vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
int vsnprintf(char *buf, size_t size, const char *fmt_str, va_list args)
{
unsigned long long num;
char *str, *end;
struct printf_spec spec = {0};
struct fmt fmt = {
.str = fmt_str,
.state = FORMAT_STATE_NONE,
};
/* Reject out-of-range values early. Large positive sizes are
used for unknown buffer sizes. */
@ -2795,33 +2780,43 @@ int vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
size = end - buf;
}
while (*fmt) {
const char *old_fmt = fmt;
int read = format_decode(fmt, &spec);
while (*fmt.str) {
const char *old_fmt = fmt.str;
fmt += read;
fmt = format_decode(fmt, &spec);
switch (spec.type) {
case FORMAT_TYPE_NONE: {
int copy = read;
switch (fmt.state) {
case FORMAT_STATE_NONE: {
int read = fmt.str - old_fmt;
if (str < end) {
int copy = read;
if (copy > end - str)
copy = end - str;
memcpy(str, old_fmt, copy);
}
str += read;
break;
continue;
}
case FORMAT_TYPE_WIDTH:
case FORMAT_STATE_NUM: {
unsigned long long num;
if (fmt.size <= sizeof(int))
num = convert_num_spec(va_arg(args, int), fmt.size, spec);
else
num = va_arg(args, long long);
str = number(str, end, num, spec);
continue;
}
case FORMAT_STATE_WIDTH:
set_field_width(&spec, va_arg(args, int));
break;
continue;
case FORMAT_TYPE_PRECISION:
case FORMAT_STATE_PRECISION:
set_precision(&spec, va_arg(args, int));
break;
continue;
case FORMAT_TYPE_CHAR: {
case FORMAT_STATE_CHAR: {
char c;
if (!(spec.flags & LEFT)) {
@ -2841,27 +2836,27 @@ int vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
*str = ' ';
++str;
}
break;
continue;
}
case FORMAT_TYPE_STR:
case FORMAT_STATE_STR:
str = string(str, end, va_arg(args, char *), spec);
break;
continue;
case FORMAT_TYPE_PTR:
str = pointer(fmt, str, end, va_arg(args, void *),
case FORMAT_STATE_PTR:
str = pointer(fmt.str, str, end, va_arg(args, void *),
spec);
while (isalnum(*fmt))
fmt++;
break;
while (isalnum(*fmt.str))
fmt.str++;
continue;
case FORMAT_TYPE_PERCENT_CHAR:
case FORMAT_STATE_PERCENT_CHAR:
if (str < end)
*str = '%';
++str;
break;
continue;
case FORMAT_TYPE_INVALID:
default:
/*
* Presumably the arguments passed gcc's type
* checking, but there is no safe or sane way
@ -2871,47 +2866,6 @@ int vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
* sync.
*/
goto out;
default:
switch (spec.type) {
case FORMAT_TYPE_LONG_LONG:
num = va_arg(args, long long);
break;
case FORMAT_TYPE_ULONG:
num = va_arg(args, unsigned long);
break;
case FORMAT_TYPE_LONG:
num = va_arg(args, long);
break;
case FORMAT_TYPE_SIZE_T:
if (spec.flags & SIGN)
num = va_arg(args, ssize_t);
else
num = va_arg(args, size_t);
break;
case FORMAT_TYPE_PTRDIFF:
num = va_arg(args, ptrdiff_t);
break;
case FORMAT_TYPE_UBYTE:
num = (unsigned char) va_arg(args, int);
break;
case FORMAT_TYPE_BYTE:
num = (signed char) va_arg(args, int);
break;
case FORMAT_TYPE_USHORT:
num = (unsigned short) va_arg(args, int);
break;
case FORMAT_TYPE_SHORT:
num = (short) va_arg(args, int);
break;
case FORMAT_TYPE_INT:
num = (int) va_arg(args, int);
break;
default:
num = va_arg(args, unsigned int);
}
str = number(str, end, num, spec);
}
}
@ -3067,7 +3021,7 @@ EXPORT_SYMBOL(sprintf);
* vbin_printf - Parse a format string and place args' binary value in a buffer
* @bin_buf: The buffer to place args' binary value
* @size: The size of the buffer(by words(32bits), not characters)
* @fmt: The format string to use
* @fmt_str: The format string to use
* @args: Arguments for the format string
*
* The format follows C99 vsnprintf, except %n is ignored, and its argument
@ -3080,8 +3034,12 @@ EXPORT_SYMBOL(sprintf);
* If the return value is greater than @size, the resulting bin_buf is NOT
* valid for bstr_printf().
*/
int vbin_printf(u32 *bin_buf, size_t size, const char *fmt, va_list args)
int vbin_printf(u32 *bin_buf, size_t size, const char *fmt_str, va_list args)
{
struct fmt fmt = {
.str = fmt_str,
.state = FORMAT_STATE_NONE,
};
struct printf_spec spec = {0};
char *str, *end;
int width;
@ -3113,31 +3071,29 @@ int vbin_printf(u32 *bin_buf, size_t size, const char *fmt, va_list args)
value; \
})
while (*fmt) {
int read = format_decode(fmt, &spec);
while (*fmt.str) {
fmt = format_decode(fmt, &spec);
fmt += read;
switch (spec.type) {
case FORMAT_TYPE_NONE:
case FORMAT_TYPE_PERCENT_CHAR:
switch (fmt.state) {
case FORMAT_STATE_NONE:
case FORMAT_STATE_PERCENT_CHAR:
break;
case FORMAT_TYPE_INVALID:
case FORMAT_STATE_INVALID:
goto out;
case FORMAT_TYPE_WIDTH:
case FORMAT_TYPE_PRECISION:
case FORMAT_STATE_WIDTH:
case FORMAT_STATE_PRECISION:
width = (int)save_arg(int);
/* Pointers may require the width */
if (*fmt == 'p')
if (*fmt.str == 'p')
set_field_width(&spec, width);
break;
case FORMAT_TYPE_CHAR:
case FORMAT_STATE_CHAR:
save_arg(char);
break;
case FORMAT_TYPE_STR: {
case FORMAT_STATE_STR: {
const char *save_str = va_arg(args, char *);
const char *err_msg;
size_t len;
@ -3153,9 +3109,9 @@ int vbin_printf(u32 *bin_buf, size_t size, const char *fmt, va_list args)
break;
}
case FORMAT_TYPE_PTR:
case FORMAT_STATE_PTR:
/* Dereferenced pointers must be done now */
switch (*fmt) {
switch (*fmt.str) {
/* Dereference of functions is still OK */
case 'S':
case 's':
@ -3165,11 +3121,11 @@ int vbin_printf(u32 *bin_buf, size_t size, const char *fmt, va_list args)
save_arg(void *);
break;
default:
if (!isalnum(*fmt)) {
if (!isalnum(*fmt.str)) {
save_arg(void *);
break;
}
str = pointer(fmt, str, end, va_arg(args, void *),
str = pointer(fmt.str, str, end, va_arg(args, void *),
spec);
if (str + 1 < end)
*str++ = '\0';
@ -3177,35 +3133,14 @@ int vbin_printf(u32 *bin_buf, size_t size, const char *fmt, va_list args)
end[-1] = '\0'; /* Must be nul terminated */
}
/* skip all alphanumeric pointer suffixes */
while (isalnum(*fmt))
fmt++;
while (isalnum(*fmt.str))
fmt.str++;
break;
default:
switch (spec.type) {
case FORMAT_TYPE_LONG_LONG:
case FORMAT_STATE_NUM:
if (fmt.size > sizeof(int)) {
save_arg(long long);
break;
case FORMAT_TYPE_ULONG:
case FORMAT_TYPE_LONG:
save_arg(unsigned long);
break;
case FORMAT_TYPE_SIZE_T:
save_arg(size_t);
break;
case FORMAT_TYPE_PTRDIFF:
save_arg(ptrdiff_t);
break;
case FORMAT_TYPE_UBYTE:
case FORMAT_TYPE_BYTE:
save_arg(char);
break;
case FORMAT_TYPE_USHORT:
case FORMAT_TYPE_SHORT:
save_arg(short);
break;
default:
} else {
save_arg(int);
}
}
@ -3221,7 +3156,7 @@ EXPORT_SYMBOL_GPL(vbin_printf);
* bstr_printf - Format a string from binary arguments and place it in a buffer
* @buf: The buffer to place the result into
* @size: The size of the buffer, including the trailing null space
* @fmt: The format string to use
* @fmt_str: The format string to use
* @bin_buf: Binary arguments for the format string
*
* This function like C99 vsnprintf, but the difference is that vsnprintf gets
@ -3239,8 +3174,12 @@ EXPORT_SYMBOL_GPL(vbin_printf);
* return is greater than or equal to @size, the resulting
* string is truncated.
*/
int bstr_printf(char *buf, size_t size, const char *fmt, const u32 *bin_buf)
int bstr_printf(char *buf, size_t size, const char *fmt_str, const u32 *bin_buf)
{
struct fmt fmt = {
.str = fmt_str,
.state = FORMAT_STATE_NONE,
};
struct printf_spec spec = {0};
char *str, *end;
const char *args = (const char *)bin_buf;
@ -3272,33 +3211,33 @@ int bstr_printf(char *buf, size_t size, const char *fmt, const u32 *bin_buf)
size = end - buf;
}
while (*fmt) {
const char *old_fmt = fmt;
int read = format_decode(fmt, &spec);
while (*fmt.str) {
const char *old_fmt = fmt.str;
unsigned long long num;
fmt += read;
switch (spec.type) {
case FORMAT_TYPE_NONE: {
int copy = read;
fmt = format_decode(fmt, &spec);
switch (fmt.state) {
case FORMAT_STATE_NONE: {
int read = fmt.str - old_fmt;
if (str < end) {
int copy = read;
if (copy > end - str)
copy = end - str;
memcpy(str, old_fmt, copy);
}
str += read;
break;
continue;
}
case FORMAT_TYPE_WIDTH:
case FORMAT_STATE_WIDTH:
set_field_width(&spec, get_arg(int));
break;
continue;
case FORMAT_TYPE_PRECISION:
case FORMAT_STATE_PRECISION:
set_precision(&spec, get_arg(int));
break;
continue;
case FORMAT_TYPE_CHAR: {
case FORMAT_STATE_CHAR: {
char c;
if (!(spec.flags & LEFT)) {
@ -3317,21 +3256,21 @@ int bstr_printf(char *buf, size_t size, const char *fmt, const u32 *bin_buf)
*str = ' ';
++str;
}
break;
continue;
}
case FORMAT_TYPE_STR: {
case FORMAT_STATE_STR: {
const char *str_arg = args;
args += strlen(str_arg) + 1;
str = string(str, end, (char *)str_arg, spec);
break;
continue;
}
case FORMAT_TYPE_PTR: {
case FORMAT_STATE_PTR: {
bool process = false;
int copy, len;
/* Non function dereferences were already done */
switch (*fmt) {
switch (*fmt.str) {
case 'S':
case 's':
case 'x':
@ -3340,7 +3279,7 @@ int bstr_printf(char *buf, size_t size, const char *fmt, const u32 *bin_buf)
process = true;
break;
default:
if (!isalnum(*fmt)) {
if (!isalnum(*fmt.str)) {
process = true;
break;
}
@ -3355,63 +3294,32 @@ int bstr_printf(char *buf, size_t size, const char *fmt, const u32 *bin_buf)
}
}
if (process)
str = pointer(fmt, str, end, get_arg(void *), spec);
str = pointer(fmt.str, str, end, get_arg(void *), spec);
while (isalnum(*fmt))
fmt++;
break;
while (isalnum(*fmt.str))
fmt.str++;
continue;
}
case FORMAT_TYPE_PERCENT_CHAR:
case FORMAT_STATE_PERCENT_CHAR:
if (str < end)
*str = '%';
++str;
break;
continue;
case FORMAT_TYPE_INVALID:
case FORMAT_STATE_INVALID:
goto out;
default: {
unsigned long long num;
switch (spec.type) {
case FORMAT_TYPE_LONG_LONG:
case FORMAT_STATE_NUM:
if (fmt.size > sizeof(int)) {
num = get_arg(long long);
break;
case FORMAT_TYPE_ULONG:
case FORMAT_TYPE_LONG:
num = get_arg(unsigned long);
break;
case FORMAT_TYPE_SIZE_T:
num = get_arg(size_t);
break;
case FORMAT_TYPE_PTRDIFF:
num = get_arg(ptrdiff_t);
break;
case FORMAT_TYPE_UBYTE:
num = get_arg(unsigned char);
break;
case FORMAT_TYPE_BYTE:
num = get_arg(signed char);
break;
case FORMAT_TYPE_USHORT:
num = get_arg(unsigned short);
break;
case FORMAT_TYPE_SHORT:
num = get_arg(short);
break;
case FORMAT_TYPE_UINT:
num = get_arg(unsigned int);
break;
default:
num = get_arg(int);
} else {
num = convert_num_spec(get_arg(int), fmt.size, spec);
}
str = number(str, end, num, spec);
} /* default: */
} /* switch(spec.type) */
} /* while(*fmt) */
continue;
}
} /* while(*fmt.str) */
out:
if (size > 0) {