/* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Minimal format string implementation used by GBL to provide printing // functionality to third-party C code. Currently used by: // // 1. `external/libufdt` via `dto_print()` // 2. `external/avb/libavb` via `avb_printf()` // // Because this implementation is used by a limited set of consumers, it // provides a simplified format parser that meets the current requirements. The // integrator must ensure it remains sufficient for any new use cases. // // Current functionality is based on the format string specification: // (https://cplusplus.com/reference/cstdio/printf/) // // %[flags][width][precision][length modifier][conversion specifier] // // - flags: not supported (skipped by `skip_flags`) // - width: not supported (skipped by `skip_width`) // - precision: not supported (skipped by `skip_precision`) // - length modifier: all are supported (l, ll, h, hh, z, etc.) // - conversion specifier: // * signed numeric values (i, d) // * unsigned numeric values (u, o, x) // * characters (c) // * nul-terminated strings (s) // Others are not supported (undefined behaviour). // // TODO(b/394149272): Support floating pointers formatting. // // The maximum supported output length is 2048 bytes (see `PRINT_BUFFER_SIZE`). // Any additional content is silently truncated. #include #include #include #include #include #include "gbl/print.h" // Maximum amount of characters can be printed at once. The rest of symbols // are getting silently cut. #define PRINT_BUFFER_SIZE 2048 #define NUMBER_ALPHABET "0123456789abcdef" #define BASE_DEC 10U #define BASE_OCT 8U #define BASE_HEX 16U #define FLAGS_LONG (1U << 0U) #define FLAGS_LONG_LONG (1U << 1U) #define FLAGS_CHAR (1U << 2U) #define FLAGS_SHORT (1U << 3U) #define ULL_MAX_DIGITS 20 // Formats unsigned `value` in base `base` into `buffer`. // // Returns number of characters written to the result buffer. static size_t format_number_unsigned(unsigned long long value, char *buffer, size_t buffer_len, unsigned int base) { if (buffer_len == 0) return 0; if (value == 0) { buffer[0] = '0'; return 1; } char tmp[ULL_MAX_DIGITS]; int tmp_pos = 0; // Convert number to reversed string while (value > 0) { tmp[tmp_pos++] = NUMBER_ALPHABET[value % base]; value /= base; } // Copy reversed number to buffer size_t used = 0; for (int i = tmp_pos - 1; i >= 0 && used < buffer_len; i--) { buffer[used++] = tmp[i]; } return used; } // Formats signed `value` in base `base` into `buffer`. // // Returns number of characters written to the result buffer. static size_t format_number_signed(long long value, char *buffer, size_t buffer_len, unsigned int base) { size_t used = 0; unsigned long long abs = 0; if (value < 0) { if (used < buffer_len) { buffer[used++] = '-'; } abs = value == LLONG_MIN ? (unsigned long long)(-(value + 1)) + 1 : -value; } else { abs = value; } return used + format_number_unsigned(abs, buffer + used, buffer_len - used, base); } // Formats nul-terminated string `s` into `buffer`. // // Returns number of characters written to the result buffer. static size_t format_string(const char *s, char *buffer, size_t buffer_len) { size_t used = 0; while (*s && used < buffer_len) { buffer[used++] = *s++; } return used; } // Formats a single character `c` into `buffer`. // // Returns number of characters written to the result buffer. static size_t format_character(char c, char *buffer, size_t buffer_len) { size_t used = 0; if (buffer_len) { buffer[used++] = c; } return used; } // Noop implementation of the number format used in both width and precision // segments to represent the number. Can be asterisk symbol or dec number. // // Returns number of processed symbols in the format string. static size_t skip_format_number(const char *fmt) { if (*fmt == '*') return 1; size_t used = 0; while (*fmt >= '0' && *fmt <= '9') { fmt++; used++; } return used; } // Width segment isn't supported by this implementation. It's getting parsed, // but ignored. // // Returns number of processed symbols in the format string. static size_t skip_width(const char *fmt) { return skip_format_number(fmt); } // Precision segment isn't supported by this implementation. It's getting // parsed, but ignored. // // Returns number of processed symbols in the format string. static size_t skip_precision(const char *fmt) { if (*fmt == '.') { return 1 + skip_format_number(fmt + 1); } return 0; } // Format flags aren't supported by this implementation. They are getting // parsed, but ignored. Skipped symbols: '-', '+', ' ', '#', '0'. // // Returns number of processed symbols in the format string. static size_t skip_flags(const char *fmt) { size_t used = 0; while (strchr("-+ #0", *fmt) != NULL) { fmt++; used++; } return used; } // Parse length modifiers flags. // // Returns number of processed symbols in the format string. static size_t parse_length_modifiers(const char *fmt, unsigned int *out_flags) { size_t used = 0; switch (*fmt) { case 'l': *out_flags = FLAGS_LONG; used++; fmt++; if (*fmt == 'l') { *out_flags = FLAGS_LONG_LONG; used++; } break; case 'h': *out_flags = FLAGS_SHORT; used++; fmt++; if (*fmt == 'h') { *out_flags = FLAGS_CHAR; used++; } break; case 'z': *out_flags = sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG; used++; break; case 'j': *out_flags |= sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG; used++; break; case 't': *out_flags |= sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG; used++; break; } return used; } // Appends an error message into `buffer` to handle unsupported format string // symbol error. // // Returns number of processed symbols in the error message. static size_t append_cannot_handle_error(char *buffer, size_t buffer_len, char current) { size_t used = 0; used += format_string( "GBL print implementation cannot handle format string at symbol: ", buffer, buffer_len); used += format_character(current, buffer + used, buffer_len - used); return used; } // Format `fmt` into `buffer` using provided `args`. // // Only `buffer_len` bytes will be formatted. The rest of `fmt` string and // provided `args` will be ignored. static void gbl_printf_buffer(const char *fmt, va_list args, char *buffer, size_t buffer_len) { // Ensure can nul terminate. const size_t buffer_available = buffer_len - 1; size_t i = 0; while (*fmt && i < buffer_available) { if (*fmt == '%') { // %% case if (*(fmt + 1) == '%') { // Skip one % to print another. fmt++; } else { unsigned int base = BASE_DEC; unsigned int flags = 0; fmt++; fmt += skip_flags(fmt); fmt += skip_width(fmt); fmt += skip_precision(fmt); fmt += parse_length_modifiers(fmt, &flags); switch (*fmt) { case 's': i += format_string(va_arg(args, char *), buffer + i, buffer_available - i); fmt++; continue; case 'o': case 'x': case 'u': switch (*fmt) { case 'o': base = BASE_OCT; break; case 'x': base = BASE_HEX; break; } if (flags & FLAGS_LONG_LONG) { i += format_number_unsigned(va_arg(args, unsigned long long), buffer + i, buffer_available - i, base); } else if (flags & FLAGS_LONG) { i += format_number_unsigned(va_arg(args, unsigned long), buffer + i, buffer_available - i, base); } else { i += format_number_unsigned(va_arg(args, unsigned int), buffer + i, buffer_available - i, base); } fmt++; continue; case 'd': case 'i': if (flags & FLAGS_LONG_LONG) { i += format_number_signed(va_arg(args, long long), buffer + i, buffer_available - i, base); } else if (flags & FLAGS_LONG) { i += format_number_signed(va_arg(args, long), buffer + i, buffer_available - i, base); } else { i += format_number_signed(va_arg(args, int), buffer + i, buffer_available - i, base); } fmt++; continue; case 'c': i += format_character(va_arg(args, int), buffer + i, buffer_available - i); fmt++; break; default: i += append_cannot_handle_error(buffer + i, buffer_available - i, *fmt); goto out; } } } buffer[i++] = *fmt++; } out: buffer[i] = 0; } // Generic output format implementation to be exposed to 3d party C code. void gbl_printf(const char *fmt, va_list args) { char output_buffer[PRINT_BUFFER_SIZE]; gbl_printf_buffer(fmt, args, output_buffer, sizeof(output_buffer)); gbl_print_string(output_buffer); }