1 /*
2 * Copyright (C) 2025 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 // Minimal format string implementation used by GBL to provide printing
18 // functionality to third-party C code. Currently used by:
19 //
20 // 1. `external/libufdt` via `dto_print()`
21 // 2. `external/avb/libavb` via `avb_printf()`
22 //
23 // Because this implementation is used by a limited set of consumers, it
24 // provides a simplified format parser that meets the current requirements. The
25 // integrator must ensure it remains sufficient for any new use cases.
26 //
27 // Current functionality is based on the format string specification:
28 // (https://cplusplus.com/reference/cstdio/printf/)
29 //
30 // %[flags][width][precision][length modifier][conversion specifier]
31 //
32 // - flags: not supported (skipped by `skip_flags`)
33 // - width: not supported (skipped by `skip_width`)
34 // - precision: not supported (skipped by `skip_precision`)
35 // - length modifier: all are supported (l, ll, h, hh, z, etc.)
36 // - conversion specifier:
37 // * signed numeric values (i, d)
38 // * unsigned numeric values (u, o, x)
39 // * characters (c)
40 // * nul-terminated strings (s)
41 // Others are not supported (undefined behaviour).
42 //
43 // TODO(b/394149272): Support floating pointers formatting.
44 //
45 // The maximum supported output length is 2048 bytes (see `PRINT_BUFFER_SIZE`).
46 // Any additional content is silently truncated.
47
48 #include <limits.h>
49 #include <stddef.h>
50 #include <stdint.h>
51 #include <stdlib.h>
52 #include <string.h>
53
54 #include "gbl/print.h"
55
56 // Maximum amount of characters can be printed at once. The rest of symbols
57 // are getting silently cut.
58 #define PRINT_BUFFER_SIZE 2048
59
60 #define NUMBER_ALPHABET "0123456789abcdef"
61
62 #define BASE_DEC 10U
63 #define BASE_OCT 8U
64 #define BASE_HEX 16U
65
66 #define FLAGS_LONG (1U << 0U)
67 #define FLAGS_LONG_LONG (1U << 1U)
68 #define FLAGS_CHAR (1U << 2U)
69 #define FLAGS_SHORT (1U << 3U)
70
71 #define ULL_MAX_DIGITS 20
72
73 // Formats unsigned `value` in base `base` into `buffer`.
74 //
75 // Returns number of characters written to the result buffer.
format_number_unsigned(unsigned long long value,char * buffer,size_t buffer_len,unsigned int base)76 static size_t format_number_unsigned(unsigned long long value, char *buffer,
77 size_t buffer_len, unsigned int base) {
78 if (buffer_len == 0) return 0;
79
80 if (value == 0) {
81 buffer[0] = '0';
82 return 1;
83 }
84
85 char tmp[ULL_MAX_DIGITS];
86 int tmp_pos = 0;
87
88 // Convert number to reversed string
89 while (value > 0) {
90 tmp[tmp_pos++] = NUMBER_ALPHABET[value % base];
91 value /= base;
92 }
93
94 // Copy reversed number to buffer
95 size_t used = 0;
96 for (int i = tmp_pos - 1; i >= 0 && used < buffer_len; i--) {
97 buffer[used++] = tmp[i];
98 }
99
100 return used;
101 }
102
103 // Formats signed `value` in base `base` into `buffer`.
104 //
105 // Returns number of characters written to the result buffer.
format_number_signed(long long value,char * buffer,size_t buffer_len,unsigned int base)106 static size_t format_number_signed(long long value, char *buffer,
107 size_t buffer_len, unsigned int base) {
108 size_t used = 0;
109 unsigned long long abs = 0;
110
111 if (value < 0) {
112 if (used < buffer_len) {
113 buffer[used++] = '-';
114 }
115 abs = value == LLONG_MIN ? (unsigned long long)(-(value + 1)) + 1 : -value;
116 } else {
117 abs = value;
118 }
119
120 return used +
121 format_number_unsigned(abs, buffer + used, buffer_len - used, base);
122 }
123
124 // Formats nul-terminated string `s` into `buffer`.
125 //
126 // Returns number of characters written to the result buffer.
format_string(const char * s,char * buffer,size_t buffer_len)127 static size_t format_string(const char *s, char *buffer, size_t buffer_len) {
128 size_t used = 0;
129 while (*s && used < buffer_len) {
130 buffer[used++] = *s++;
131 }
132 return used;
133 }
134
135 // Formats a single character `c` into `buffer`.
136 //
137 // Returns number of characters written to the result buffer.
format_character(char c,char * buffer,size_t buffer_len)138 static size_t format_character(char c, char *buffer, size_t buffer_len) {
139 size_t used = 0;
140 if (buffer_len) {
141 buffer[used++] = c;
142 }
143 return used;
144 }
145
146 // Noop implementation of the number format used in both width and precision
147 // segments to represent the number. Can be asterisk symbol or dec number.
148 //
149 // Returns number of processed symbols in the format string.
skip_format_number(const char * fmt)150 static size_t skip_format_number(const char *fmt) {
151 if (*fmt == '*') return 1;
152
153 size_t used = 0;
154 while (*fmt >= '0' && *fmt <= '9') {
155 fmt++;
156 used++;
157 }
158 return used;
159 }
160
161 // Width segment isn't supported by this implementation. It's getting parsed,
162 // but ignored.
163 //
164 // Returns number of processed symbols in the format string.
skip_width(const char * fmt)165 static size_t skip_width(const char *fmt) { return skip_format_number(fmt); }
166
167 // Precision segment isn't supported by this implementation. It's getting
168 // parsed, but ignored.
169 //
170 // Returns number of processed symbols in the format string.
skip_precision(const char * fmt)171 static size_t skip_precision(const char *fmt) {
172 if (*fmt == '.') {
173 return 1 + skip_format_number(fmt + 1);
174 }
175 return 0;
176 }
177
178 // Format flags aren't supported by this implementation. They are getting
179 // parsed, but ignored. Skipped symbols: '-', '+', ' ', '#', '0'.
180 //
181 // Returns number of processed symbols in the format string.
skip_flags(const char * fmt)182 static size_t skip_flags(const char *fmt) {
183 size_t used = 0;
184 while (strchr("-+ #0", *fmt) != NULL) {
185 fmt++;
186 used++;
187 }
188 return used;
189 }
190
191 // Parse length modifiers flags.
192 //
193 // Returns number of processed symbols in the format string.
parse_length_modifiers(const char * fmt,unsigned int * out_flags)194 static size_t parse_length_modifiers(const char *fmt, unsigned int *out_flags) {
195 size_t used = 0;
196 switch (*fmt) {
197 case 'l':
198 *out_flags = FLAGS_LONG;
199 used++;
200 fmt++;
201 if (*fmt == 'l') {
202 *out_flags = FLAGS_LONG_LONG;
203 used++;
204 }
205 break;
206 case 'h':
207 *out_flags = FLAGS_SHORT;
208 used++;
209 fmt++;
210 if (*fmt == 'h') {
211 *out_flags = FLAGS_CHAR;
212 used++;
213 }
214 break;
215 case 'z':
216 *out_flags =
217 sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG;
218 used++;
219 break;
220 case 'j':
221 *out_flags |=
222 sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG;
223 used++;
224 break;
225 case 't':
226 *out_flags |=
227 sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG;
228 used++;
229 break;
230 }
231 return used;
232 }
233
234 // Appends an error message into `buffer` to handle unsupported format string
235 // symbol error.
236 //
237 // Returns number of processed symbols in the error message.
append_cannot_handle_error(char * buffer,size_t buffer_len,char current)238 static size_t append_cannot_handle_error(char *buffer, size_t buffer_len,
239 char current) {
240 size_t used = 0;
241 used += format_string(
242 "GBL print implementation cannot handle format string at symbol: ",
243 buffer, buffer_len);
244 used += format_character(current, buffer + used, buffer_len - used);
245 return used;
246 }
247
248 // Format `fmt` into `buffer` using provided `args`.
249 //
250 // Only `buffer_len` bytes will be formatted. The rest of `fmt` string and
251 // provided `args` will be ignored.
gbl_printf_buffer(const char * fmt,va_list args,char * buffer,size_t buffer_len)252 static void gbl_printf_buffer(const char *fmt, va_list args, char *buffer,
253 size_t buffer_len) {
254 // Ensure can nul terminate.
255 const size_t buffer_available = buffer_len - 1;
256
257 size_t i = 0;
258 while (*fmt && i < buffer_available) {
259 if (*fmt == '%') {
260 // %% case
261 if (*(fmt + 1) == '%') {
262 // Skip one % to print another.
263 fmt++;
264 } else {
265 unsigned int base = BASE_DEC;
266 unsigned int flags = 0;
267 fmt++;
268
269 fmt += skip_flags(fmt);
270 fmt += skip_width(fmt);
271 fmt += skip_precision(fmt);
272 fmt += parse_length_modifiers(fmt, &flags);
273
274 switch (*fmt) {
275 case 's':
276 i += format_string(va_arg(args, char *), buffer + i,
277 buffer_available - i);
278 fmt++;
279 continue;
280 case 'o':
281 case 'x':
282 case 'u':
283 switch (*fmt) {
284 case 'o':
285 base = BASE_OCT;
286 break;
287 case 'x':
288 base = BASE_HEX;
289 break;
290 }
291 if (flags & FLAGS_LONG_LONG) {
292 i += format_number_unsigned(va_arg(args, unsigned long long),
293 buffer + i, buffer_available - i,
294 base);
295 } else if (flags & FLAGS_LONG) {
296 i += format_number_unsigned(va_arg(args, unsigned long),
297 buffer + i, buffer_available - i,
298 base);
299 } else {
300 i +=
301 format_number_unsigned(va_arg(args, unsigned int), buffer + i,
302 buffer_available - i, base);
303 }
304 fmt++;
305 continue;
306 case 'd':
307 case 'i':
308 if (flags & FLAGS_LONG_LONG) {
309 i += format_number_signed(va_arg(args, long long), buffer + i,
310 buffer_available - i, base);
311 } else if (flags & FLAGS_LONG) {
312 i += format_number_signed(va_arg(args, long), buffer + i,
313 buffer_available - i, base);
314 } else {
315 i += format_number_signed(va_arg(args, int), buffer + i,
316 buffer_available - i, base);
317 }
318 fmt++;
319 continue;
320 case 'c':
321 i += format_character(va_arg(args, int), buffer + i,
322 buffer_available - i);
323 fmt++;
324 break;
325 default:
326 i += append_cannot_handle_error(buffer + i, buffer_available - i,
327 *fmt);
328 goto out;
329 }
330 }
331 }
332 buffer[i++] = *fmt++;
333 }
334
335 out:
336 buffer[i] = 0;
337 }
338
339 // Generic output format implementation to be exposed to 3d party C code.
gbl_printf(const char * fmt,va_list args)340 void gbl_printf(const char *fmt, va_list args) {
341 char output_buffer[PRINT_BUFFER_SIZE];
342 gbl_printf_buffer(fmt, args, output_buffer, sizeof(output_buffer));
343 gbl_print_string(output_buffer);
344 }
345