/* 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 # 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, 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; }