summaryrefslogtreecommitdiffstats
path: root/lib/vsprintf.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vsprintf.c')
-rw-r--r--lib/vsprintf.c246
1 files changed, 225 insertions, 21 deletions
diff --git a/lib/vsprintf.c b/lib/vsprintf.c
index 237aab0c02..1b7d568e8f 100644
--- a/lib/vsprintf.c
+++ b/lib/vsprintf.c
@@ -1,3 +1,5 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
/*
* linux/lib/vsprintf.c
*
@@ -16,8 +18,12 @@
#include <linux/math64.h>
#include <malloc.h>
#include <kallsyms.h>
+#include <wchar.h>
+#include <of.h>
+#include <efi.h>
#include <common.h>
+#include <pbl.h>
/* we use this so that we can do without the ctype library */
#define is_digit(c) ((c) >= '0' && (c) <= '9')
@@ -147,6 +153,32 @@ static char *number(char *buf, const char *end, unsigned long long num, int base
#define PAGE_SIZE 4096
#endif
+static char *leading_spaces(char *buf, const char *end,
+ int len, int *field_width, int flags)
+{
+ if (!(flags & LEFT)) {
+ while (len < (*field_width)--) {
+ if (buf < end)
+ *buf = ' ';
+ ++buf;
+ }
+ }
+
+ return buf;
+}
+
+static char *trailing_spaces(char *buf, const char *end,
+ int len, int *field_width, int flags)
+{
+ while (len < (*field_width)--) {
+ if (buf < end)
+ *buf = ' ';
+ ++buf;
+ }
+
+ return buf;
+}
+
static char *string(char *buf, const char *end, const char *s, int field_width,
int precision, int flags)
{
@@ -156,25 +188,56 @@ static char *string(char *buf, const char *end, const char *s, int field_width,
s = "<NULL>";
len = strnlen(s, precision);
+ buf = leading_spaces(buf, end, len, &field_width, flags);
- if (!(flags & LEFT)) {
- while (len < field_width--) {
+ for (i = 0; i < len; ++i) {
+ if (buf < end)
+ *buf = *s;
+ ++buf; ++s;
+ }
+
+ return trailing_spaces(buf, end, len, &field_width, flags);
+}
+
+static __maybe_unused char *string_array(char *buf, const char *end, char *const *s,
+ int field_width, int precision, int flags,
+ const char *separator)
+{
+ size_t i, len = strlen(separator);
+
+ while (*s) {
+ buf = string(buf, end, *s, field_width, precision, flags);
+ if (!*++s)
+ break;
+
+ for (i = 0; i < len; ++i) {
if (buf < end)
- *buf = ' ';
+ *buf = separator[i];
++buf;
}
}
+
+ return buf;
+}
+
+static char *wstring(char *buf, const char *end, const wchar_t *s, int field_width,
+ int precision, int flags)
+{
+ int len, i;
+
+ if ((unsigned long)s < PAGE_SIZE)
+ s = L"<NULL>";
+
+ len = wcsnlen(s, precision);
+ leading_spaces(buf, end, len, &field_width, flags);
+
for (i = 0; i < len; ++i) {
if (buf < end)
- *buf = *s;
+ wctomb(buf, *s);
++buf; ++s;
}
- while (len < field_width--) {
- if (buf < end)
- *buf = ' ';
- ++buf;
- }
- return buf;
+
+ return trailing_spaces(buf, end, len, &field_width, flags);
}
static char *raw_pointer(char *buf, const char *end, const void *ptr, int field_width,
@@ -205,6 +268,26 @@ static char *symbol_string(char *buf, const char *end, const void *ptr, int fiel
}
static noinline_for_stack
+char *mac_address_string(char *buf, const char *end, const u8 *addr, int field_width,
+ int precision, int flags, const char *fmt)
+{
+ char mac_addr[sizeof("xx:xx:xx:xx:xx:xx")];
+ char *p = mac_addr;
+ int i;
+ char separator = ':';
+
+ for (i = 0; i < 6; i++) {
+ p = hex_byte_pack(p, addr[i]);
+
+ if (i != 5)
+ *p++ = separator;
+ }
+ *p = '\0';
+
+ return string(buf, end, mac_addr, field_width, precision, flags);
+}
+
+static noinline_for_stack
char *ip4_addr_string(char *buf, const char *end, const u8 *addr, int field_width,
int precision, int flags, const char *fmt)
{
@@ -247,6 +330,10 @@ char *uuid_string(char *buf, const char *end, const u8 *addr, int field_width,
const u8 *index = be;
bool uc = false;
+ /* If addr == NULL output the string '<NULL>' */
+ if (!addr)
+ return string(buf, end, NULL, field_width, precision, flags);
+
switch (*(++fmt)) {
case 'L':
uc = true; /* fall-through */
@@ -282,6 +369,81 @@ char *uuid_string(char *buf, const char *end, const u8 *addr, int field_width,
return string(buf, end, uuid, field_width, precision, flags);
}
+static char *device_path_string(char *buf, const char *end, const struct efi_device_path *dp,
+ int field_width, int precision, int flags)
+{
+ if (!dp)
+ return string(buf, end, NULL, field_width, precision, flags);
+
+ return buf + device_path_to_str_buf(dp, buf, end - buf);
+}
+
+static noinline_for_stack
+char *hex_string(char *buf, const char *end, const u8 *addr, int field_width,
+ int precision, int flags, const char *fmt)
+{
+ char separator;
+ int i, len;
+
+ if (field_width == 0)
+ /* nothing to print */
+ return buf;
+
+ switch (fmt[1]) {
+ case 'C':
+ separator = ':';
+ break;
+ case 'D':
+ separator = '-';
+ break;
+ case 'N':
+ separator = 0;
+ break;
+ default:
+ separator = ' ';
+ break;
+ }
+
+ len = field_width > 0 ? field_width : 1;
+
+ for (i = 0; i < len; ++i) {
+ if (buf < end)
+ *buf = hex_asc_hi(addr[i]);
+ ++buf;
+ if (buf < end)
+ *buf = hex_asc_lo(addr[i]);
+ ++buf;
+
+ if (separator && i != len - 1) {
+ if (buf < end)
+ *buf = separator;
+ ++buf;
+ }
+ }
+
+ return buf;
+}
+
+static noinline_for_stack
+char *jsonpath_string(char *buf, const char *end, char *const *path, int field_width,
+ int precision, int flags, const char *fmt)
+{
+ if ((unsigned long)path < PAGE_SIZE)
+ return string(buf, end, "<NULL>", field_width, precision, flags);
+
+ if (buf < end)
+ *buf = '$';
+ ++buf;
+
+ if (*path) {
+ if (buf < end)
+ *buf = '.';
+ ++buf;
+ }
+
+ return string_array(buf, end, path, field_width, precision, flags, ".");
+}
+
static noinline_for_stack
char *address_val(char *buf, const char *end, const void *addr,
int field_width, int precision, int flags, const char *fmt)
@@ -305,15 +467,21 @@ char *address_val(char *buf, const char *end, const void *addr,
return number(buf, end, num, 16, field_width, precision, flags);
}
+static noinline_for_stack
+char *device_node_string(char *buf, const char *end, const struct device_node *np,
+ int field_width, int precision, int flags, const char *fmt)
+{
+ return string(buf, end, of_node_full_name(np), field_width,
+ precision, flags);
+}
+
/*
* Show a '%p' thing. A kernel extension is that the '%p' is followed
* by an extra set of alphanumeric characters that are extended format
* specifiers.
*
- * Right now we handle:
+ * Right now we handle following Linux-compatible format specifiers:
*
- * - 'I' [4] for IPv4 addresses printed in the usual way
- * IPv4 uses dot-separated decimal without leading 0's (1.2.3.4)
* - 'S' For symbolic direct pointers
* - 'U' For a 16 byte UUID/GUID, it prints the UUID/GUID in the form
* "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
@@ -333,10 +501,22 @@ char *address_val(char *buf, const char *end, const void *addr,
* correctness of the format string and va_list arguments.
* - 'a[pd]' For address types [p] phys_addr_t, [d] dma_addr_t and derivatives
* (default assumed to be phys_addr_t, passed by reference)
+ * - 'I' [4] for IPv4 addresses printed in the usual way
+ * IPv4 uses dot-separated decimal without leading 0's (1.2.3.4)
+ * - 'e' For formatting error pointers as string descriptions
+ * - 'OF' For a device tree node
+ * - 'h[CDN]' For a variable-length buffer, it prints it as a hex string with
+ * a certain separator (' ' by default):
+ * C colon
+ * D dash
+ * N no separator
+ * - 'JP' For a JSON path
+ * - 'M' For a 6-byte MAC address, it prints the address in the
+ * usual colon-separated hex notation
*
- * Note: The difference between 'S' and 'F' is that on ia64 and ppc64
- * function pointers are really function descriptors, which contain a
- * pointer to the real address.
+ * Additionally, we support following barebox-specific format specifiers:
+ *
+ * - 'D' For EFI device paths
*/
static char *pointer(const char *fmt, char *buf, const char *end, const void *ptr,
int field_width, int precision, int flags)
@@ -368,6 +548,24 @@ static char *pointer(const char *fmt, char *buf, const char *end, const void *pt
break;
case 'e':
return error_string(buf, end, ptr, field_width, precision, flags, fmt);
+ case 'O':
+ if (IS_ENABLED(CONFIG_OFTREE))
+ return device_node_string(buf, end, ptr, field_width, precision, flags, fmt + 1);
+ break;
+ case 'h':
+ if (IS_ENABLED(CONFIG_PRINTF_HEXSTR))
+ return hex_string(buf, end, ptr, field_width, precision, flags, fmt);
+ break;
+ case 'J':
+ if (fmt[1] == 'P' && IS_ENABLED(CONFIG_JSMN))
+ return jsonpath_string(buf, end, ptr, field_width, precision, flags, fmt);
+ case 'M':
+ /* Colon separated: 00:01:02:03:04:05 */
+ return mac_address_string(buf, end, ptr, field_width, precision, flags, fmt);
+ case 'D':
+ if (IS_ENABLED(CONFIG_EFI_DEVICEPATH))
+ return device_path_string(buf, end, ptr, field_width, precision, flags);
+ break;
}
return raw_pointer(buf, end, ptr, field_width, precision, flags);
@@ -397,10 +595,11 @@ static char *errno_string(char *buf, const char *end, int field_width, int preci
* @fmt: The format string to use
* @args: Arguments for the format string
*
- * This function follows C99 vsnprintf, but has some extensions:
- * %pS output the name of a text symbol
- * %pF output the name of a function pointer
- * %pR output the address range in a struct resource
+ * This function generally follows C99 vsnprintf, but has some
+ * extensions and a few limitations:
+ *
+ * - ``%n`` is unsupported
+ * - ``%p*`` is handled by pointer()
*
* The return value is the number of characters which would
* be generated for the given input, excluding the trailing
@@ -528,7 +727,12 @@ int vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
continue;
case 's':
- str = string(str, end, va_arg(args, char *), field_width, precision, flags);
+ if (IS_ENABLED(CONFIG_PRINTF_WCHAR) && !IN_PBL && qualifier == 'l')
+ str = wstring(str, end, va_arg(args, wchar_t *),
+ field_width, precision, flags);
+ else
+ str = string(str, end, va_arg(args, char *),
+ field_width, precision, flags);
continue;
case 'p':