• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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