diff options
Diffstat (limited to 'src/boot/efi/efi-string.c')
-rw-r--r-- | src/boot/efi/efi-string.c | 1084 |
1 files changed, 1084 insertions, 0 deletions
diff --git a/src/boot/efi/efi-string.c b/src/boot/efi/efi-string.c new file mode 100644 index 0000000..4144c0d --- /dev/null +++ b/src/boot/efi/efi-string.c @@ -0,0 +1,1084 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "efi-string.h" + +#if SD_BOOT +# include "proto/simple-text-io.h" +# include "util.h" +#else +# include <stdlib.h> +# include "alloc-util.h" +# define xnew(t, n) ASSERT_SE_PTR(new(t, n)) +# define xmalloc(n) ASSERT_SE_PTR(malloc(n)) +#endif + +/* String functions for both char and char16_t that should behave the same way as their respective + * counterpart in userspace. Where it makes sense, these accept NULL and do something sensible whereas + * userspace does not allow for this (strlen8(NULL) returns 0 like strlen_ptr(NULL) for example). To make it + * easier to tell in code which kind of string they work on, we use 8/16 suffixes. This also makes is easier + * to unit test them. */ + +#define DEFINE_STRNLEN(type, name) \ + size_t name(const type *s, size_t n) { \ + if (!s) \ + return 0; \ + \ + size_t len = 0; \ + while (len < n && *s) { \ + s++; \ + len++; \ + } \ + \ + return len; \ + } + +DEFINE_STRNLEN(char, strnlen8); +DEFINE_STRNLEN(char16_t, strnlen16); + +#define TOLOWER(c) \ + ({ \ + typeof(c) _c = (c); \ + (_c >= 'A' && _c <= 'Z') ? _c + ('a' - 'A') : _c; \ + }) + +#define DEFINE_STRTOLOWER(type, name) \ + void name(type *s) { \ + if (!s) \ + return; \ + for (; *s; s++) \ + *s = TOLOWER(*s); \ + } + +DEFINE_STRTOLOWER(char, strtolower8); +DEFINE_STRTOLOWER(char16_t, strtolower16); + +#define DEFINE_STRNCASECMP(type, name, tolower) \ + int name(const type *s1, const type *s2, size_t n) { \ + if (!s1 || !s2) \ + return CMP(s1, s2); \ + \ + while (n > 0) { \ + type c1 = *s1, c2 = *s2; \ + if (tolower) { \ + c1 = TOLOWER(c1); \ + c2 = TOLOWER(c2); \ + } \ + if (!c1 || c1 != c2) \ + return CMP(c1, c2); \ + \ + s1++; \ + s2++; \ + n--; \ + } \ + \ + return 0; \ + } + +DEFINE_STRNCASECMP(char, strncmp8, false); +DEFINE_STRNCASECMP(char16_t, strncmp16, false); +DEFINE_STRNCASECMP(char, strncasecmp8, true); +DEFINE_STRNCASECMP(char16_t, strncasecmp16, true); + +#define DEFINE_STRCPY(type, name) \ + type *name(type * restrict dest, const type * restrict src) { \ + type *ret = ASSERT_PTR(dest); \ + \ + if (!src) { \ + *dest = '\0'; \ + return ret; \ + } \ + \ + while (*src) { \ + *dest = *src; \ + dest++; \ + src++; \ + } \ + \ + *dest = '\0'; \ + return ret; \ + } + +DEFINE_STRCPY(char, strcpy8); +DEFINE_STRCPY(char16_t, strcpy16); + +#define DEFINE_STRCHR(type, name) \ + type *name(const type *s, type c) { \ + if (!s) \ + return NULL; \ + \ + while (*s) { \ + if (*s == c) \ + return (type *) s; \ + s++; \ + } \ + \ + return c ? NULL : (type *) s; \ + } + +DEFINE_STRCHR(char, strchr8); +DEFINE_STRCHR(char16_t, strchr16); + +#define DEFINE_STRNDUP(type, name, len_func) \ + type *name(const type *s, size_t n) { \ + if (!s) \ + return NULL; \ + \ + size_t len = len_func(s, n); \ + size_t size = len * sizeof(type); \ + \ + type *dup = xmalloc(size + sizeof(type)); \ + if (size > 0) \ + memcpy(dup, s, size); \ + dup[len] = '\0'; \ + \ + return dup; \ + } + +DEFINE_STRNDUP(char, xstrndup8, strnlen8); +DEFINE_STRNDUP(char16_t, xstrndup16, strnlen16); + +static unsigned utf8_to_unichar(const char *utf8, size_t n, char32_t *c) { + char32_t unichar; + unsigned len; + + assert(utf8); + assert(c); + + if (!(utf8[0] & 0x80)) { + *c = utf8[0]; + return 1; + } else if ((utf8[0] & 0xe0) == 0xc0) { + len = 2; + unichar = utf8[0] & 0x1f; + } else if ((utf8[0] & 0xf0) == 0xe0) { + len = 3; + unichar = utf8[0] & 0x0f; + } else if ((utf8[0] & 0xf8) == 0xf0) { + len = 4; + unichar = utf8[0] & 0x07; + } else if ((utf8[0] & 0xfc) == 0xf8) { + len = 5; + unichar = utf8[0] & 0x03; + } else if ((utf8[0] & 0xfe) == 0xfc) { + len = 6; + unichar = utf8[0] & 0x01; + } else { + *c = UINT32_MAX; + return 1; + } + + if (len > n) { + *c = UINT32_MAX; + return len; + } + + for (unsigned i = 1; i < len; i++) { + if ((utf8[i] & 0xc0) != 0x80) { + *c = UINT32_MAX; + return len; + } + unichar <<= 6; + unichar |= utf8[i] & 0x3f; + } + + *c = unichar; + return len; +} + +/* Convert UTF-8 to UCS-2, skipping any invalid or short byte sequences. */ +char16_t *xstrn8_to_16(const char *str8, size_t n) { + if (!str8 || n == 0) + return NULL; + + size_t i = 0; + char16_t *str16 = xnew(char16_t, n + 1); + + while (n > 0 && *str8 != '\0') { + char32_t unichar; + + size_t utf8len = utf8_to_unichar(str8, n, &unichar); + str8 += utf8len; + n = LESS_BY(n, utf8len); + + switch (unichar) { + case 0 ... 0xd7ffU: + case 0xe000U ... 0xffffU: + str16[i++] = unichar; + break; + } + } + + str16[i] = '\0'; + return str16; +} + +char *startswith8(const char *s, const char *prefix) { + size_t l; + + assert(prefix); + + if (!s) + return NULL; + + l = strlen8(prefix); + if (!strneq8(s, prefix, l)) + return NULL; + + return (char*) s + l; +} + +static bool efi_fnmatch_prefix(const char16_t *p, const char16_t *h, const char16_t **ret_p, const char16_t **ret_h) { + assert(p); + assert(h); + assert(ret_p); + assert(ret_h); + + for (;; p++, h++) + switch (*p) { + case '\0': + /* End of pattern. Check that haystack is now empty. */ + return *h == '\0'; + + case '\\': + p++; + if (*p == '\0' || *p != *h) + /* Trailing escape or no match. */ + return false; + break; + + case '?': + if (*h == '\0') + /* Early end of haystack. */ + return false; + break; + + case '*': + /* Point ret_p at the remainder of the pattern. */ + while (*p == '*') + p++; + *ret_p = p; + *ret_h = h; + return true; + + case '[': + if (*h == '\0') + /* Early end of haystack. */ + return false; + + bool first = true, can_range = true, match = false; + for (;; first = false) { + p++; + if (*p == '\0') + return false; + + if (*p == '\\') { + p++; + if (*p == '\0') + return false; + if (*p == *h) + match = true; + can_range = true; + continue; + } + + /* End of set unless it's the first char. */ + if (*p == ']' && !first) + break; + + /* Range pattern if '-' is not first or last in set. */ + if (*p == '-' && can_range && !first && *(p + 1) != ']') { + char16_t low = *(p - 1); + p++; + if (*p == '\\') + p++; + if (*p == '\0') + return false; + + if (low <= *h && *h <= *p) + match = true; + + /* Ranges cannot be chained: [a-c-f] == [-abcf] */ + can_range = false; + continue; + } + + if (*p == *h) + match = true; + can_range = true; + } + + if (!match) + return false; + break; + + default: + if (*p != *h) + /* Single char mismatch. */ + return false; + } +} + +/* Patterns are fnmatch-compatible (with reduced feature support). */ +bool efi_fnmatch(const char16_t *pattern, const char16_t *haystack) { + /* Patterns can be considered as simple patterns (without '*') concatenated by '*'. By doing so we + * simply have to make sure the very first simple pattern matches the start of haystack. Then we just + * look for the remaining simple patterns *somewhere* within the haystack (in order) as any extra + * characters in between would be matches by the '*'. We then only have to ensure that the very last + * simple pattern matches at the actual end of the haystack. + * + * This means we do not need to use backtracking which could have catastrophic runtimes with the + * right input data. */ + + for (bool first = true;;) { + const char16_t *pattern_tail = NULL, *haystack_tail = NULL; + bool match = efi_fnmatch_prefix(pattern, haystack, &pattern_tail, &haystack_tail); + if (first) { + if (!match) + /* Initial simple pattern must match. */ + return false; + if (!pattern_tail) + /* No '*' was in pattern, we can return early. */ + return true; + first = false; + } + + if (pattern_tail) { + assert(match); + pattern = pattern_tail; + haystack = haystack_tail; + } else { + /* If we have a match this must be at the end of the haystack. Note that + * efi_fnmatch_prefix compares the NUL-bytes at the end, so we cannot match the end + * of pattern in the middle of haystack). */ + if (match || *haystack == '\0') + return match; + + /* Match one character using '*'. */ + haystack++; + } + } +} + +#define DEFINE_PARSE_NUMBER(type, name) \ + bool name(const type *s, uint64_t *ret_u, const type **ret_tail) { \ + assert(ret_u); \ + \ + if (!s) \ + return false; \ + \ + /* Need at least one digit. */ \ + if (*s < '0' || *s > '9') \ + return false; \ + \ + uint64_t u = 0; \ + while (*s >= '0' && *s <= '9') { \ + if (__builtin_mul_overflow(u, 10, &u)) \ + return false; \ + if (__builtin_add_overflow(u, *s - '0', &u)) \ + return false; \ + s++; \ + } \ + \ + if (!ret_tail && *s != '\0') \ + return false; \ + \ + *ret_u = u; \ + if (ret_tail) \ + *ret_tail = s; \ + return true; \ + } + +DEFINE_PARSE_NUMBER(char, parse_number8); +DEFINE_PARSE_NUMBER(char16_t, parse_number16); + +bool parse_boolean(const char *v, bool *ret) { + assert(ret); + + if (!v) + return false; + + if (streq8(v, "1") || streq8(v, "yes") || streq8(v, "y") || streq8(v, "true") || streq8(v, "t") || + streq8(v, "on")) { + *ret = true; + return true; + } + + if (streq8(v, "0") || streq8(v, "no") || streq8(v, "n") || streq8(v, "false") || streq8(v, "f") || + streq8(v, "off")) { + *ret = false; + return true; + } + + return false; +} + +char *line_get_key_value(char *s, const char *sep, size_t *pos, char **ret_key, char **ret_value) { + char *line, *value; + size_t linelen; + + assert(s); + assert(sep); + assert(pos); + assert(ret_key); + assert(ret_value); + + for (;;) { + line = s + *pos; + if (*line == '\0') + return NULL; + + linelen = 0; + while (line[linelen] && !strchr8("\n\r", line[linelen])) + linelen++; + + /* move pos to next line */ + *pos += linelen; + if (s[*pos]) + (*pos)++; + + /* empty line */ + if (linelen == 0) + continue; + + /* terminate line */ + line[linelen] = '\0'; + + /* remove leading whitespace */ + while (linelen > 0 && strchr8(" \t", *line)) { + line++; + linelen--; + } + + /* remove trailing whitespace */ + while (linelen > 0 && strchr8(" \t", line[linelen - 1])) + linelen--; + line[linelen] = '\0'; + + if (*line == '#') + continue; + + /* split key/value */ + value = line; + while (*value && !strchr8(sep, *value)) + value++; + if (*value == '\0') + continue; + *value = '\0'; + value++; + while (*value && strchr8(sep, *value)) + value++; + + /* unquote */ + if (value[0] == '"' && line[linelen - 1] == '"') { + value++; + line[linelen - 1] = '\0'; + } + + *ret_key = line; + *ret_value = value; + return line; + } +} + +char16_t *hexdump(const void *data, size_t size) { + static const char hex[16] = "0123456789abcdef"; + const uint8_t *d = data; + + assert(data || size == 0); + + char16_t *buf = xnew(char16_t, size * 2 + 1); + + for (size_t i = 0; i < size; i++) { + buf[i * 2] = hex[d[i] >> 4]; + buf[i * 2 + 1] = hex[d[i] & 0x0F]; + } + + buf[size * 2] = 0; + return buf; +} + +static const char * const warn_table[] = { + [EFI_SUCCESS] = "Success", + [EFI_WARN_UNKNOWN_GLYPH] = "Unknown glyph", + [EFI_WARN_DELETE_FAILURE] = "Delete failure", + [EFI_WARN_WRITE_FAILURE] = "Write failure", + [EFI_WARN_BUFFER_TOO_SMALL] = "Buffer too small", + [EFI_WARN_STALE_DATA] = "Stale data", + [EFI_WARN_FILE_SYSTEM] = "File system", + [EFI_WARN_RESET_REQUIRED] = "Reset required", +}; + +/* Errors have MSB set, remove it to keep the table compact. */ +#define NOERR(err) ((err) & ~EFI_ERROR_MASK) + +static const char * const err_table[] = { + [NOERR(EFI_ERROR_MASK)] = "Error", + [NOERR(EFI_LOAD_ERROR)] = "Load error", + [NOERR(EFI_INVALID_PARAMETER)] = "Invalid parameter", + [NOERR(EFI_UNSUPPORTED)] = "Unsupported", + [NOERR(EFI_BAD_BUFFER_SIZE)] = "Bad buffer size", + [NOERR(EFI_BUFFER_TOO_SMALL)] = "Buffer too small", + [NOERR(EFI_NOT_READY)] = "Not ready", + [NOERR(EFI_DEVICE_ERROR)] = "Device error", + [NOERR(EFI_WRITE_PROTECTED)] = "Write protected", + [NOERR(EFI_OUT_OF_RESOURCES)] = "Out of resources", + [NOERR(EFI_VOLUME_CORRUPTED)] = "Volume corrupt", + [NOERR(EFI_VOLUME_FULL)] = "Volume full", + [NOERR(EFI_NO_MEDIA)] = "No media", + [NOERR(EFI_MEDIA_CHANGED)] = "Media changed", + [NOERR(EFI_NOT_FOUND)] = "Not found", + [NOERR(EFI_ACCESS_DENIED)] = "Access denied", + [NOERR(EFI_NO_RESPONSE)] = "No response", + [NOERR(EFI_NO_MAPPING)] = "No mapping", + [NOERR(EFI_TIMEOUT)] = "Time out", + [NOERR(EFI_NOT_STARTED)] = "Not started", + [NOERR(EFI_ALREADY_STARTED)] = "Already started", + [NOERR(EFI_ABORTED)] = "Aborted", + [NOERR(EFI_ICMP_ERROR)] = "ICMP error", + [NOERR(EFI_TFTP_ERROR)] = "TFTP error", + [NOERR(EFI_PROTOCOL_ERROR)] = "Protocol error", + [NOERR(EFI_INCOMPATIBLE_VERSION)] = "Incompatible version", + [NOERR(EFI_SECURITY_VIOLATION)] = "Security violation", + [NOERR(EFI_CRC_ERROR)] = "CRC error", + [NOERR(EFI_END_OF_MEDIA)] = "End of media", + [NOERR(EFI_ERROR_RESERVED_29)] = "Reserved (29)", + [NOERR(EFI_ERROR_RESERVED_30)] = "Reserved (30)", + [NOERR(EFI_END_OF_FILE)] = "End of file", + [NOERR(EFI_INVALID_LANGUAGE)] = "Invalid language", + [NOERR(EFI_COMPROMISED_DATA)] = "Compromised data", + [NOERR(EFI_IP_ADDRESS_CONFLICT)] = "IP address conflict", + [NOERR(EFI_HTTP_ERROR)] = "HTTP error", +}; + +static const char *status_to_string(EFI_STATUS status) { + if (status <= ELEMENTSOF(warn_table) - 1) + return warn_table[status]; + if (status >= EFI_ERROR_MASK && status <= ((ELEMENTSOF(err_table) - 1) | EFI_ERROR_MASK)) + return err_table[NOERR(status)]; + return NULL; +} + +typedef struct { + size_t padded_len; /* Field width in printf. */ + size_t len; /* Precision in printf. */ + bool pad_zero; + bool align_left; + bool alternative_form; + bool long_arg; + bool longlong_arg; + bool have_field_width; + + const char *str; + const wchar_t *wstr; + + /* For numbers. */ + bool is_signed; + bool lowercase; + int8_t base; + char sign_pad; /* For + and (space) flags. */ +} SpecifierContext; + +typedef struct { + char16_t stack_buf[128]; /* We use stack_buf first to avoid allocations in most cases. */ + char16_t *dyn_buf; /* Allocated buf or NULL if stack_buf is used. */ + char16_t *buf; /* Points to the current active buf. */ + size_t n_buf; /* Len of buf (in char16_t's, not bytes!). */ + size_t n; /* Used len of buf (in char16_t's). This is always <n_buf. */ + + EFI_STATUS status; + const char *format; + va_list ap; +} FormatContext; + +static void grow_buf(FormatContext *ctx, size_t need) { + assert(ctx); + + assert_se(!__builtin_add_overflow(ctx->n, need, &need)); + + if (need < ctx->n_buf) + return; + + /* Greedily allocate if we can. */ + if (__builtin_mul_overflow(need, 2, &ctx->n_buf)) + ctx->n_buf = need; + + /* We cannot use realloc here as ctx->buf may be ctx->stack_buf, which we cannot free. */ + char16_t *new_buf = xnew(char16_t, ctx->n_buf); + memcpy(new_buf, ctx->buf, ctx->n * sizeof(*ctx->buf)); + + free(ctx->dyn_buf); + ctx->buf = ctx->dyn_buf = new_buf; +} + +static void push_padding(FormatContext *ctx, char pad, size_t len) { + assert(ctx); + while (len > 0) { + len--; + ctx->buf[ctx->n++] = pad; + } +} + +static bool push_str(FormatContext *ctx, SpecifierContext *sp) { + assert(ctx); + assert(sp); + + sp->padded_len = LESS_BY(sp->padded_len, sp->len); + + grow_buf(ctx, sp->padded_len + sp->len); + + if (!sp->align_left) + push_padding(ctx, ' ', sp->padded_len); + + /* In userspace unit tests we cannot just memcpy() the wide string. */ + if (sp->wstr && sizeof(wchar_t) == sizeof(char16_t)) { + memcpy(ctx->buf + ctx->n, sp->wstr, sp->len * sizeof(*sp->wstr)); + ctx->n += sp->len; + } else { + assert(sp->str || sp->wstr); + for (size_t i = 0; i < sp->len; i++) + ctx->buf[ctx->n++] = sp->str ? sp->str[i] : sp->wstr[i]; + } + + if (sp->align_left) + push_padding(ctx, ' ', sp->padded_len); + + assert(ctx->n < ctx->n_buf); + return true; +} + +static bool push_num(FormatContext *ctx, SpecifierContext *sp, uint64_t u) { + const char *digits = sp->lowercase ? "0123456789abcdef" : "0123456789ABCDEF"; + char16_t tmp[32]; + size_t n = 0; + + assert(ctx); + assert(sp); + assert(IN_SET(sp->base, 10, 16)); + + /* "%.0u" prints nothing if value is 0. */ + if (u == 0 && sp->len == 0) + return true; + + if (sp->is_signed && (int64_t) u < 0) { + /* We cannot just do "u = -(int64_t)u" here because -INT64_MIN overflows. */ + + uint64_t rem = -((int64_t) u % sp->base); + u = (int64_t) u / -sp->base; + tmp[n++] = digits[rem]; + sp->sign_pad = '-'; + } + + while (u > 0 || n == 0) { + uint64_t rem = u % sp->base; + u /= sp->base; + tmp[n++] = digits[rem]; + } + + /* Note that numbers never get truncated! */ + size_t prefix = (sp->sign_pad != 0 ? 1 : 0) + (sp->alternative_form ? 2 : 0); + size_t number_len = prefix + MAX(n, sp->len); + grow_buf(ctx, MAX(sp->padded_len, number_len)); + + size_t padding = 0; + if (sp->pad_zero) + /* Leading zeroes go after the sign or 0x prefix. */ + number_len = MAX(number_len, sp->padded_len); + else + padding = LESS_BY(sp->padded_len, number_len); + + if (!sp->align_left) + push_padding(ctx, ' ', padding); + + if (sp->sign_pad != 0) + ctx->buf[ctx->n++] = sp->sign_pad; + if (sp->alternative_form) { + ctx->buf[ctx->n++] = '0'; + ctx->buf[ctx->n++] = sp->lowercase ? 'x' : 'X'; + } + + push_padding(ctx, '0', LESS_BY(number_len, n + prefix)); + + while (n > 0) + ctx->buf[ctx->n++] = tmp[--n]; + + if (sp->align_left) + push_padding(ctx, ' ', padding); + + assert(ctx->n < ctx->n_buf); + return true; +} + +/* This helps unit testing. */ +#if SD_BOOT +# define NULLSTR "(null)" +# define wcsnlen strnlen16 +#else +# define NULLSTR "(nil)" +#endif + +static bool handle_format_specifier(FormatContext *ctx, SpecifierContext *sp) { + /* Parses one item from the format specifier in ctx and put the info into sp. If we are done with + * this specifier returns true, otherwise this function should be called again. */ + + /* This implementation assumes 32-bit ints. Also note that all types smaller than int are promoted to + * int in vararg functions, which is why we fetch only ints for any such types. The compiler would + * otherwise warn about fetching smaller types. */ + assert_cc(sizeof(int) == 4); + assert_cc(sizeof(wchar_t) <= sizeof(int)); + assert_cc(sizeof(intmax_t) <= sizeof(long long)); + + assert(ctx); + assert(sp); + + switch (*ctx->format) { + case '#': + sp->alternative_form = true; + return false; + case '.': + sp->have_field_width = true; + return false; + case '-': + sp->align_left = true; + return false; + case '+': + case ' ': + sp->sign_pad = *ctx->format; + return false; + + case '0': + if (!sp->have_field_width) { + sp->pad_zero = true; + return false; + } + + /* If field width has already been provided then 0 is part of precision (%.0s). */ + _fallthrough_; + + case '*': + case '1' ... '9': { + int64_t i; + + if (*ctx->format == '*') + i = va_arg(ctx->ap, int); + else { + uint64_t u; + if (!parse_number8(ctx->format, &u, &ctx->format) || u > INT_MAX) + assert_not_reached(); + ctx->format--; /* Point it back to the last digit. */ + i = u; + } + + if (sp->have_field_width) { + /* Negative precision is ignored. */ + if (i >= 0) + sp->len = (size_t) i; + } else { + /* Negative field width is treated as positive field width with '-' flag. */ + if (i < 0) { + i *= -1; + sp->align_left = true; + } + sp->padded_len = i; + } + + return false; + } + + case 'h': + if (*(ctx->format + 1) == 'h') + ctx->format++; + /* char/short gets promoted to int, nothing to do here. */ + return false; + + case 'l': + if (*(ctx->format + 1) == 'l') { + ctx->format++; + sp->longlong_arg = true; + } else + sp->long_arg = true; + return false; + + case 'z': + sp->long_arg = sizeof(size_t) == sizeof(long); + sp->longlong_arg = !sp->long_arg && sizeof(size_t) == sizeof(long long); + return false; + + case 'j': + sp->long_arg = sizeof(intmax_t) == sizeof(long); + sp->longlong_arg = !sp->long_arg && sizeof(intmax_t) == sizeof(long long); + return false; + + case 't': + sp->long_arg = sizeof(ptrdiff_t) == sizeof(long); + sp->longlong_arg = !sp->long_arg && sizeof(ptrdiff_t) == sizeof(long long); + return false; + + case '%': + sp->str = "%"; + sp->len = 1; + return push_str(ctx, sp); + + case 'c': + sp->wstr = &(wchar_t){ va_arg(ctx->ap, int) }; + sp->len = 1; + return push_str(ctx, sp); + + case 's': + if (sp->long_arg) { + sp->wstr = va_arg(ctx->ap, const wchar_t *) ?: L"(null)"; + sp->len = wcsnlen(sp->wstr, sp->len); + } else { + sp->str = va_arg(ctx->ap, const char *) ?: "(null)"; + sp->len = strnlen8(sp->str, sp->len); + } + return push_str(ctx, sp); + + case 'd': + case 'i': + case 'u': + case 'x': + case 'X': + sp->lowercase = *ctx->format == 'x'; + sp->is_signed = IN_SET(*ctx->format, 'd', 'i'); + sp->base = IN_SET(*ctx->format, 'x', 'X') ? 16 : 10; + if (sp->len == SIZE_MAX) + sp->len = 1; + + uint64_t v; + if (sp->longlong_arg) + v = sp->is_signed ? (uint64_t) va_arg(ctx->ap, long long) : + va_arg(ctx->ap, unsigned long long); + else if (sp->long_arg) + v = sp->is_signed ? (uint64_t) va_arg(ctx->ap, long) : va_arg(ctx->ap, unsigned long); + else + v = sp->is_signed ? (uint64_t) va_arg(ctx->ap, int) : va_arg(ctx->ap, unsigned); + + return push_num(ctx, sp, v); + + case 'p': { + const void *ptr = va_arg(ctx->ap, const void *); + if (!ptr) { + sp->str = NULLSTR; + sp->len = STRLEN(NULLSTR); + return push_str(ctx, sp); + } + + sp->base = 16; + sp->lowercase = true; + sp->alternative_form = true; + sp->len = 0; /* Precision is ignored for %p. */ + return push_num(ctx, sp, (uintptr_t) ptr); + } + + case 'm': { + sp->str = status_to_string(ctx->status); + if (sp->str) { + sp->len = strlen8(sp->str); + return push_str(ctx, sp); + } + + sp->base = 16; + sp->lowercase = true; + sp->alternative_form = true; + sp->len = 0; + return push_num(ctx, sp, ctx->status); + } + + default: + assert_not_reached(); + } +} + +/* printf_internal is largely compatible to userspace vasprintf. Any features omitted should trigger asserts. + * + * Supported: + * - Flags: #, 0, +, -, space + * - Lengths: h, hh, l, ll, z, j, t + * - Specifiers: %, c, s, u, i, d, x, X, p, m + * - Precision and width (inline or as int arg using *) + * + * Notable differences: + * - Passing NULL to %s is permitted and will print "(null)" + * - %p will also use "(null)" + * - The provided EFI_STATUS is used for %m instead of errno + * - "\n" is translated to "\r\n" */ +_printf_(2, 0) static char16_t *printf_internal(EFI_STATUS status, const char *format, va_list ap, bool ret) { + assert(format); + + FormatContext ctx = { + .buf = ctx.stack_buf, + .n_buf = ELEMENTSOF(ctx.stack_buf), + .format = format, + .status = status, + }; + + /* We cannot put this into the struct without making a copy. */ + va_copy(ctx.ap, ap); + + while (*ctx.format != '\0') { + SpecifierContext sp = { .len = SIZE_MAX }; + + switch (*ctx.format) { + case '%': + ctx.format++; + while (!handle_format_specifier(&ctx, &sp)) + ctx.format++; + ctx.format++; + break; + case '\n': + ctx.format++; + sp.str = "\r\n"; + sp.len = 2; + push_str(&ctx, &sp); + break; + default: + sp.str = ctx.format++; + while (!IN_SET(*ctx.format, '%', '\n', '\0')) + ctx.format++; + sp.len = ctx.format - sp.str; + push_str(&ctx, &sp); + } + } + + va_end(ctx.ap); + + assert(ctx.n < ctx.n_buf); + ctx.buf[ctx.n++] = '\0'; + + if (ret) { + if (ctx.dyn_buf) + return TAKE_PTR(ctx.dyn_buf); + + char16_t *ret_buf = xnew(char16_t, ctx.n); + memcpy(ret_buf, ctx.buf, ctx.n * sizeof(*ctx.buf)); + return ret_buf; + } + +#if SD_BOOT + ST->ConOut->OutputString(ST->ConOut, ctx.buf); +#endif + + return mfree(ctx.dyn_buf); +} + +void printf_status(EFI_STATUS status, const char *format, ...) { + va_list ap; + va_start(ap, format); + printf_internal(status, format, ap, false); + va_end(ap); +} + +void vprintf_status(EFI_STATUS status, const char *format, va_list ap) { + printf_internal(status, format, ap, false); +} + +char16_t *xasprintf_status(EFI_STATUS status, const char *format, ...) { + va_list ap; + va_start(ap, format); + char16_t *ret = printf_internal(status, format, ap, true); + va_end(ap); + return ret; +} + +char16_t *xvasprintf_status(EFI_STATUS status, const char *format, va_list ap) { + return printf_internal(status, format, ap, true); +} + +#if SD_BOOT +/* To provide the actual implementation for these we need to remove the redirection to the builtins. */ +# undef memchr +# undef memcmp +# undef memcpy +# undef memset +_used_ void *memchr(const void *p, int c, size_t n); +_used_ int memcmp(const void *p1, const void *p2, size_t n); +_used_ void *memcpy(void * restrict dest, const void * restrict src, size_t n); +_used_ void *memset(void *p, int c, size_t n); +#else +/* And for userspace unit testing we need to give them an efi_ prefix. */ +# define memchr efi_memchr +# define memcmp efi_memcmp +# define memcpy efi_memcpy +# define memset efi_memset +#endif + +void *memchr(const void *p, int c, size_t n) { + if (!p || n == 0) + return NULL; + + const uint8_t *q = p; + for (size_t i = 0; i < n; i++) + if (q[i] == (unsigned char) c) + return (void *) (q + i); + + return NULL; +} + +int memcmp(const void *p1, const void *p2, size_t n) { + const uint8_t *up1 = p1, *up2 = p2; + int r; + + if (!p1 || !p2) + return CMP(p1, p2); + + while (n > 0) { + r = CMP(*up1, *up2); + if (r != 0) + return r; + + up1++; + up2++; + n--; + } + + return 0; +} + +void *memcpy(void * restrict dest, const void * restrict src, size_t n) { + if (!dest || !src || n == 0) + return dest; + +#if SD_BOOT + /* The firmware-provided memcpy is likely optimized, so use that. The function is guaranteed to be + * available by the UEFI spec. We still make it depend on the boot services pointer being set just in + * case the compiler emits a call before it is available. */ + if (_likely_(BS)) { + BS->CopyMem(dest, (void *) src, n); + return dest; + } +#endif + + uint8_t *d = dest; + const uint8_t *s = src; + + while (n > 0) { + *d = *s; + d++; + s++; + n--; + } + + return dest; +} + +void *memset(void *p, int c, size_t n) { + if (!p || n == 0) + return p; + +#if SD_BOOT + /* See comment in efi_memcpy. Note that the signature has c and n swapped! */ + if (_likely_(BS)) { + BS->SetMem(p, n, c); + return p; + } +#endif + + uint8_t *q = p; + while (n > 0) { + *q = c; + q++; + n--; + } + + return p; +} |