@@ -230,7 +230,20 @@ int get_flags(const char **fmt)
} while (1);
}
-int vsprintf(char *buf, const char *fmt, va_list args)
+static
+int get_int(const char **fmt, va_list *ap)
+{
+ if (isdigit(**fmt))
+ return skip_atoi(fmt);
+ if (**fmt == '*') {
+ ++(*fmt);
+ /* it's the next argument */
+ return va_arg(*ap, int);
+ }
+ return 0;
+}
+
+int vsprintf(char *buf, const char *fmt, va_list ap)
{
int len;
unsigned long long num;
@@ -245,6 +258,24 @@ int vsprintf(char *buf, const char *fmt, va_list args)
number of chars for from string */
int qualifier; /* 'h', 'hh', 'l' or 'll' for integer fields */
+ va_list args;
+
+ /*
+ * We want to pass our input va_list to helper functions by reference,
+ * but there's an annoying edge case. If va_list was originally passed
+ * to us by value, we could just pass &ap down to the helpers. This is
+ * the case on, for example, X86_32.
+ * However, on X86_64 (and possibly others), va_list is actually a
+ * size-1 array containing a structure. Our function parameter ap has
+ * decayed from T[1] to T*, and &ap has type T** rather than T(*)[1],
+ * which is what will be expected by a function taking a va_list *
+ * parameter.
+ * One standard way to solve this mess is by creating a copy in a local
+ * variable of type va_list and then passing a pointer to that local
+ * copy instead, which is what we do here.
+ */
+ va_copy(args, ap);
+
for (str = buf; *fmt; ++fmt) {
if (*fmt != '%' || *++fmt == '%') {
*str++ = *fmt;
@@ -255,31 +286,17 @@ int vsprintf(char *buf, const char *fmt, va_list args)
flags = get_flags(&fmt);
/* get field width */
- field_width = -1;
- if (isdigit(*fmt))
- field_width = skip_atoi(&fmt);
- else if (*fmt == '*') {
- ++fmt;
- /* it's the next argument */
- field_width = va_arg(args, int);
- if (field_width < 0) {
- field_width = -field_width;
- flags |= LEFT;
- }
+ field_width = get_int(&fmt, &args);
+ if (field_width < 0) {
+ field_width = -field_width;
+ flags |= LEFT;
}
/* get the precision */
precision = -1;
if (*fmt == '.') {
++fmt;
- if (isdigit(*fmt))
- precision = skip_atoi(&fmt);
- else if (*fmt == '*') {
- ++fmt;
- /* it's the next argument */
- precision = va_arg(args, int);
- } else
- precision = 0;
+ precision = get_int(&fmt, &args);
if (precision >= 0)
flags &= ~ZEROPAD;
}
@@ -390,6 +407,9 @@ int vsprintf(char *buf, const char *fmt, va_list args)
str = number(str, num, base, field_width, precision, flags);
}
*str = '\0';
+
+ va_end(args);
+
return str - buf;
}
Factor out the width/precision parsing into a helper function. Signed-off-by: Arvind Sankar <nivedita@alum.mit.edu> --- drivers/firmware/efi/libstub/vsprintf.c | 60 ++++++++++++++++--------- 1 file changed, 40 insertions(+), 20 deletions(-)