1 // SPDX-License-Identifier: GPL-2.0+
3 * Copyright (C) 2021 Weidmüller Interface GmbH & Co. KG
4 * Roland Gaudig <roland.gaudig@weidmueller.com>
6 * Copyright 1999 Dave Cinege
7 * Portions copyright (C) 1990-1996 Free Software Foundation, Inc.
9 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
12 * This file provides a shell printf like format string expansion as required
13 * for the setexpr <name> fmt <format> <value> command.
14 * This source file was mostly taken from the BusyBox project (www.busybox.net)
15 * In contrast to the original sources the output is not written to stdout
16 * anymore but into a char array, which can be used as input for the env_set()
19 /* Usage: printf format [argument...]
21 * A front end to the printf function that lets it be used from the shell.
29 * \c = produce no further output
32 * \r = carriage return
35 * \0ooo = octal number (ooo is 0 to 3 digits)
36 * \xhhh = hexadecimal number (hhh is 1 to 3 digits)
38 * Additional directive:
40 * %b = print an argument string, interpreting backslash escapes
42 * The 'format' argument is re-used as many times as necessary
43 * to convert all of the given arguments.
45 * David MacKenzie <djm@gnu.ai.mit.edu>
47 /* 19990508 Busy Boxed! Dave Cinege */
49 //config:config PRINTF
50 //config: bool "printf (3.8 kb)"
53 //config: printf is used to format and print specified strings.
54 //config: It's similar to 'echo' except it has more options.
56 //applet:IF_PRINTF(APPLET_NOFORK(printf, printf, BB_DIR_USR_BIN, BB_SUID_DROP, printf))
58 //kbuild:lib-$(CONFIG_PRINTF) += printf.o
59 //kbuild:lib-$(CONFIG_ASH_PRINTF) += printf.o
60 //kbuild:lib-$(CONFIG_HUSH_PRINTF) += printf.o
62 //usage:#define printf_trivial_usage
63 //usage: "FORMAT [ARG]..."
64 //usage:#define printf_full_usage "\n\n"
65 //usage: "Format and print ARG(s) according to FORMAT (a-la C printf)"
67 //usage:#define printf_example_usage
68 //usage: "$ printf \"Val=%d\\n\" 5\n"
71 /* A note on bad input: neither bash 3.2 nor coreutils 6.10 stop on it.
73 * bash: printf: XXX: invalid number
74 * printf: XXX: expected a numeric value
75 * bash: printf: 123XXX: invalid number
76 * printf: 123XXX: value not completely converted
77 * but then they use 0 (or partially converted numeric prefix) as a value
78 * and continue. They exit with 1 in this case.
79 * Both accept insane field width/precision (e.g. %9999999999.9999999999d).
80 * Both print error message and assume 0 if %*.*f width/precision is "bad"
81 * (but negative numbers are not "bad").
82 * Both accept negative numbers for %u specifier.
84 * We try to be compatible.
94 #define WANT_HEX_ESCAPES 0
95 #define PRINT_CONVERSION_ERROR 1
96 #define PRINT_TRUNCATED_ERROR 2
97 #define PRINT_SIZE_ERROR 4
106 typedef void (*converter)(const char *arg, void *result);
109 * printf_str() - print formatted into char array with length checks
111 * This function povides a printf like function for printing into a char array
112 * with checking the boundaries.
113 * Unlike snprintf, all checks are performed inside this function and status
114 * reports are stored inside the print_inf struct. That way, this function can
115 * be used almost as drop-in replacement without needing much code changes.
116 * Unlike snprintf errors are not reported by return value, but inside the
117 * error member of struct print_inf. The output stored inside the struct
118 * print_inf str member shall only be used when the error member is 0.
120 * @inf: Info structure for print operation
121 * @char: format string with optional arguments
123 static void printf_str(struct print_inf *inf, char *format, ...)
131 /* Do not write anything if previous error is pending */
135 /* Check if end of receiving buffer is already reached */
136 if (inf->offset >= inf->size) {
137 inf->error |= PRINT_SIZE_ERROR;
141 size_t remaining = inf->size - inf->offset;
143 va_start(args, format);
144 i = vsnprintf(inf->str + inf->offset, remaining, format, args);
148 inf->error |= PRINT_TRUNCATED_ERROR;
150 inf->error |= PRINT_CONVERSION_ERROR;
156 * putchar_str() - Print single character into char array with length checks
158 * This function provices a putchar like function, which stores the output
159 * into a char array with checking boundaries.
161 * @inf: Info structure for print operation
162 * @char: Single character to be printed
164 static void putchar_str(struct print_inf *inf, char c)
166 printf_str(inf, "%c", c);
169 static char process_escape_sequence(const char **ptr)
172 unsigned int num_digits;
181 if (WANT_HEX_ESCAPES && *q == 'x') {
187 /* bash requires leading 0 in octal escapes:
188 * \02 works, \2 does not (prints \ and 2).
189 * We treat \2 as a valid octal escape sequence.
193 unsigned int d = (unsigned char)(*q) - '0';
196 d = (unsigned char)tolower(*q) - 'a';
198 /* The above would map 'A'-'F' and 'a'-'f' to 10-15,
199 * however, some chars like '@' would map to 9 < base.
200 * Do not allow that, map invalid chars to N > base:
207 if (WANT_HEX_ESCAPES && base == 16) {
209 if (num_digits == 0) {
210 /* \x<bad_char>: return '\',
211 * leave ptr pointing to x
225 } while (++num_digits < 3);
227 if (num_digits == 0) {
228 /* Not octal or hex escape sequence.
229 * Is it one-letter one?
231 /* bash builtin "echo -e '\ec'" interprets \e as ESC,
232 * but coreutils "/bin/echo -e '\ec'" does not.
233 * Manpages tend to support coreutils way.
234 * Update: coreutils added support for \e on 28 Oct 2009.
236 static const char charmap[] = {
237 'a', 'b', 'e', 'f', 'n', 'r', 't', 'v', '\\', '\0',
238 '\a', '\b', 27, '\f', '\n', '\r', '\t', '\v', '\\', '\\',
241 const char *p = charmap;
248 } while (*++p != '\0');
249 /* p points to found escape char or NUL,
250 * advance it and find what it translates to.
251 * Note that \NUL and unrecognized sequence \z return '\'
252 * and leave ptr pointing to NUL or z.
254 n = p[sizeof(charmap) / 2];
262 static char *skip_whitespace(const char *s)
264 /* In POSIX/C locale (the only locale we care about: do we REALLY want
265 * to allow Unicode whitespace in, say, .conf files? nuts!)
266 * isspace is only these chars: "\t\n\v\f\r" and space.
267 * "\t\n\v\f\r" happen to have ASCII codes 9,10,11,12,13.
270 while (*s == ' ' || (unsigned char)(*s - 9) <= (13 - 9))
276 /* Like strcpy but can copy overlapping strings. */
277 static void overlapping_strcpy(char *dst, const char *src)
279 /* Cheap optimization for dst == src case -
280 * better to have it here than in many callers.
283 while ((*dst = *src) != '\0') {
290 static int multiconvert(const char *arg, void *result, converter convert)
292 if (*arg == '"' || *arg == '\'')
293 sprintf((char *)arg + strlen(arg), "%u", (unsigned char)arg[1]);
295 convert(arg, result);
296 /* Unlike their Posix counterparts, simple_strtoll and
297 * simple_strtoull do not set errno
300 * printf("error invalid number '%s'", arg);
307 static void conv_strtoull(const char *arg, void *result)
309 /* both coreutils 6.10 and bash 3.2:
315 *(unsigned long long *)result = simple_strtoll(arg, NULL, 16);
318 /* Allow leading '+' - simple_strtoull() by itself does not allow it,
319 * and probably shouldn't (other callers might require purely numeric
320 * inputs to be allowed.
324 *(unsigned long long *)result = simple_strtoull(arg, NULL, 16);
327 static void conv_strtoll(const char *arg, void *result)
331 *(long long *)result = simple_strtoll(arg, NULL, 16);
334 /* Callers should check errno to detect errors */
335 static unsigned long long my_xstrtoull(const char *arg)
337 unsigned long long result;
339 if (multiconvert(arg, &result, conv_strtoull))
344 static long long my_xstrtoll(const char *arg)
348 if (multiconvert(arg, &result, conv_strtoll))
353 /* Handles %b; return 1 if output is to be short-circuited by \c */
354 static int print_esc_string(struct print_inf *inf, const char *str)
358 while ((c = *str) != '\0') {
361 /* %b also accepts 4-digit octals of the form \0### */
363 if ((unsigned char)(str[1] - '0') < 8) {
364 /* 2nd char is 0..7: skip leading '0' */
367 } else if (*str == 'c') {
371 /* optimization: don't force arg to be on-stack,
372 * use another variable for that.
376 c = process_escape_sequence(&z);
386 static void print_direc(struct print_inf *inf, char *format, unsigned int fmt_length,
387 int field_width, int precision,
388 const char *argument)
392 char *have_prec, *have_width;
394 saved = format[fmt_length];
395 format[fmt_length] = '\0';
397 have_prec = strstr(format, ".*");
398 have_width = strchr(format, '*');
399 if (have_width - 1 == have_prec)
402 /* multiconvert sets errno = 0, but %s needs it cleared */
405 switch (format[fmt_length - 1]) {
407 printf_str(inf, format, *argument);
411 llv = my_xstrtoll(skip_whitespace(argument));
415 printf_str(inf, format, llv);
417 printf_str(inf, format, precision, llv);
420 printf_str(inf, format, field_width, llv);
422 printf_str(inf, format, field_width, precision, llv);
429 llv = my_xstrtoull(skip_whitespace(argument));
430 /* cheat: unsigned long and long have same width, so... */
433 /* Are char* and long long the same? */
434 if (sizeof(argument) == sizeof(llv)) {
435 llv = (long long)(ptrdiff_t)argument;
438 /* Hope compiler will optimize it out by moving call
439 * instruction after the ifs...
443 printf_str(inf, format, argument,
444 /*unused:*/ argument, argument);
446 printf_str(inf, format, precision,
447 argument, /*unused:*/ argument);
450 printf_str(inf, format, field_width,
451 argument, /*unused:*/ argument);
453 printf_str(inf, format, field_width,
454 precision, argument);
461 format[fmt_length] = saved;
464 /* Handle params for "%*.*f". Negative numbers are ok (compat). */
465 static int get_width_prec(const char *str)
467 long v = simple_strtol(str, NULL, 10);
469 /* Unlike its Posix counterpart, simple_strtol does not set errno
472 * printf("error invalid number '%s'", str);
479 /* Print the text in FORMAT, using ARGV for arguments to any '%' directives.
480 * Return advanced ARGV.
482 static char **print_formatted(struct print_inf *inf, char *f, char **argv, int *conv_err)
484 char *direc_start; /* Start of % directive. */
485 unsigned int direc_length; /* Length of % directive. */
486 int field_width; /* Arg to first '*' */
487 int precision; /* Arg to second '*' */
488 char **saved_argv = argv;
498 putchar_str(inf, '%');
503 if (print_esc_string(inf, *argv))
504 return saved_argv; /* causes main() to exit */
509 if (*f && strchr("-+ #", *f)) {
517 field_width = get_width_prec(*argv++);
519 while (isdigit(*f)) {
531 precision = get_width_prec(*argv++);
533 while (isdigit(*f)) {
540 /* Remove "lLhz" size modifiers, repeatedly.
541 * bash does not like "%lld", but coreutils
542 * happily takes even "%Llllhhzhhzd"!
543 * We are permissive like coreutils
545 while ((*f | 0x20) == 'l' || *f == 'h' || *f == 'z')
546 overlapping_strcpy(f, f + 1);
547 /* Add "ll" if integer modifier, then print */
549 static const char format_chars[] = "diouxXcs";
550 char *p = strchr(format_chars, *f);
551 /* needed - try "printf %" without it */
552 if (!p || *f == '\0') {
553 printf("`%s': invalid format\n", direc_start);
554 /* causes main() to exit with error */
555 return saved_argv - 1;
558 if (p - format_chars <= 5) {
559 /* it is one of "diouxX" */
560 p = malloc(direc_length + 3);
562 /* exit with error */
563 return saved_argv - 1;
565 memcpy(p, direc_start, direc_length);
566 p[direc_length + 1] = p[direc_length - 1];
567 p[direc_length - 1] = 'l';
568 p[direc_length] = 'l';
569 //bb_error_msg("<%s>", p);
576 print_direc(inf, direc_start, direc_length,
577 field_width, precision, *argv++);
579 print_direc(inf, direc_start, direc_length,
580 field_width, precision, "");
588 return saved_argv; /* causes main() to exit */
589 putchar_str(inf, process_escape_sequence((const char **)&f));
593 putchar_str(inf, *f);
601 * printf_setexpr() - Implements the setexpr <name> fmt <format> command
603 * This function implements the format string evaluation for the
604 * setexpr <name> fmt <format> <value> command.
606 * @str: Output string of the evaluated expression
607 * @size: Length of @str buffer
608 * @argc: Number of arguments
609 * @argv: Argument list
610 * @return: 0 if OK, 1 on error
612 int printf_setexpr(char *str, size_t size, int argc, char *const *argv)
617 struct print_inf inf = {
630 argv2 = (char **)argv + 1;
634 /* In case any print_str call raises an error inf.error will be
635 * set after print_formatted returns.
637 argv2 = print_formatted(&inf, format, (char **)argv, &conv_err);
639 /* coreutils compat (bash doesn't do this):
641 * fprintf(stderr, "excess args ignored");
644 return (argv2 < argv) || /* if true, print_formatted errored out */
645 conv_err || /* print_formatted saw invalid number */
646 inf.error; /* print_str reported error */