diff options
Diffstat (limited to 'lib/vsprintf.c')
-rw-r--r-- | lib/vsprintf.c | 246 |
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': |