diff mbox series

[11/24] efi/printf: Factor out width/precision parsing

Message ID 20200518190716.751506-12-nivedita@alum.mit.edu
State New
Headers show
Series [01/24] efi/libstub: Include dependencies of efistub.h | expand

Commit Message

Arvind Sankar May 18, 2020, 7:07 p.m. UTC
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(-)
diff mbox series

Patch

diff --git a/drivers/firmware/efi/libstub/vsprintf.c b/drivers/firmware/efi/libstub/vsprintf.c
index 88c503077b92..1b71651fe6bc 100644
--- a/drivers/firmware/efi/libstub/vsprintf.c
+++ b/drivers/firmware/efi/libstub/vsprintf.c
@@ -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;
 }