Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0+ |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 2 | /* |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 3 | * Copyright (C) 2021 Weidmüller Interface GmbH & Co. KG |
| 4 | * Roland Gaudig <roland.gaudig@weidmueller.com> |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 5 | * |
| 6 | * Copyright 1999 Dave Cinege |
| 7 | * Portions copyright (C) 1990-1996 Free Software Foundation, Inc. |
| 8 | * |
| 9 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. |
| 10 | */ |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 11 | /* |
| 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() |
| 17 | * function. |
| 18 | */ |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 19 | /* Usage: printf format [argument...] |
| 20 | * |
| 21 | * A front end to the printf function that lets it be used from the shell. |
| 22 | * |
| 23 | * Backslash escapes: |
| 24 | * |
| 25 | * \" = double quote |
| 26 | * \\ = backslash |
| 27 | * \a = alert (bell) |
| 28 | * \b = backspace |
| 29 | * \c = produce no further output |
| 30 | * \f = form feed |
| 31 | * \n = new line |
| 32 | * \r = carriage return |
| 33 | * \t = horizontal tab |
| 34 | * \v = vertical tab |
| 35 | * \0ooo = octal number (ooo is 0 to 3 digits) |
| 36 | * \xhhh = hexadecimal number (hhh is 1 to 3 digits) |
| 37 | * |
| 38 | * Additional directive: |
| 39 | * |
| 40 | * %b = print an argument string, interpreting backslash escapes |
| 41 | * |
| 42 | * The 'format' argument is re-used as many times as necessary |
| 43 | * to convert all of the given arguments. |
| 44 | * |
| 45 | * David MacKenzie <djm@gnu.ai.mit.edu> |
| 46 | */ |
| 47 | /* 19990508 Busy Boxed! Dave Cinege */ |
| 48 | |
| 49 | //config:config PRINTF |
| 50 | //config: bool "printf (3.8 kb)" |
| 51 | //config: default y |
| 52 | //config: help |
| 53 | //config: printf is used to format and print specified strings. |
| 54 | //config: It's similar to 'echo' except it has more options. |
| 55 | |
| 56 | //applet:IF_PRINTF(APPLET_NOFORK(printf, printf, BB_DIR_USR_BIN, BB_SUID_DROP, printf)) |
| 57 | |
| 58 | //kbuild:lib-$(CONFIG_PRINTF) += printf.o |
| 59 | //kbuild:lib-$(CONFIG_ASH_PRINTF) += printf.o |
| 60 | //kbuild:lib-$(CONFIG_HUSH_PRINTF) += printf.o |
| 61 | |
| 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)" |
| 66 | //usage: |
| 67 | //usage:#define printf_example_usage |
| 68 | //usage: "$ printf \"Val=%d\\n\" 5\n" |
| 69 | //usage: "Val=5\n" |
| 70 | |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 71 | /* A note on bad input: neither bash 3.2 nor coreutils 6.10 stop on it. |
| 72 | * They report 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. |
| 83 | * |
| 84 | * We try to be compatible. |
| 85 | */ |
| 86 | |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 87 | #include <common.h> |
| 88 | #include <ctype.h> |
| 89 | #include <errno.h> |
| 90 | #include <stddef.h> |
| 91 | #include <stdio.h> |
| 92 | #include <stdlib.h> |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 93 | |
Roland Gaudig | 6244cda | 2021-07-23 12:29:20 +0000 | [diff] [blame] | 94 | #define WANT_HEX_ESCAPES 0 |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 95 | #define PRINT_CONVERSION_ERROR 1 |
| 96 | #define PRINT_TRUNCATED_ERROR 2 |
| 97 | #define PRINT_SIZE_ERROR 4 |
Roland Gaudig | 6244cda | 2021-07-23 12:29:20 +0000 | [diff] [blame] | 98 | |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 99 | struct print_inf { |
| 100 | char *str; |
| 101 | size_t size; |
| 102 | size_t offset; |
| 103 | unsigned int error; |
| 104 | }; |
Roland Gaudig | 6244cda | 2021-07-23 12:29:20 +0000 | [diff] [blame] | 105 | |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 106 | typedef void (*converter)(const char *arg, void *result); |
| 107 | |
| 108 | /** |
| 109 | * printf_str() - print formatted into char array with length checks |
| 110 | * |
| 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. |
| 119 | * |
| 120 | * @inf: Info structure for print operation |
| 121 | * @char: format string with optional arguments |
| 122 | */ |
| 123 | static void printf_str(struct print_inf *inf, char *format, ...) |
| 124 | { |
| 125 | va_list args; |
| 126 | int i; |
| 127 | |
| 128 | if (!inf) |
| 129 | return; |
| 130 | |
| 131 | /* Do not write anything if previous error is pending */ |
| 132 | if (inf->error) |
| 133 | return; |
| 134 | |
| 135 | /* Check if end of receiving buffer is already reached */ |
| 136 | if (inf->offset >= inf->size) { |
| 137 | inf->error |= PRINT_SIZE_ERROR; |
| 138 | return; |
| 139 | } |
| 140 | |
| 141 | size_t remaining = inf->size - inf->offset; |
| 142 | |
| 143 | va_start(args, format); |
| 144 | i = vsnprintf(inf->str + inf->offset, remaining, format, args); |
| 145 | va_end(args); |
| 146 | |
| 147 | if (i >= remaining) |
| 148 | inf->error |= PRINT_TRUNCATED_ERROR; |
| 149 | else if (i < 0) |
| 150 | inf->error |= PRINT_CONVERSION_ERROR; |
| 151 | else |
| 152 | inf->offset += i; |
| 153 | } |
| 154 | |
| 155 | /** |
| 156 | * putchar_str() - Print single character into char array with length checks |
| 157 | * |
| 158 | * This function provices a putchar like function, which stores the output |
| 159 | * into a char array with checking boundaries. |
| 160 | * |
| 161 | * @inf: Info structure for print operation |
| 162 | * @char: Single character to be printed |
| 163 | */ |
| 164 | static void putchar_str(struct print_inf *inf, char c) |
| 165 | { |
| 166 | printf_str(inf, "%c", c); |
| 167 | } |
| 168 | |
| 169 | static char process_escape_sequence(const char **ptr) |
Roland Gaudig | 6244cda | 2021-07-23 12:29:20 +0000 | [diff] [blame] | 170 | { |
| 171 | const char *q; |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 172 | unsigned int num_digits; |
| 173 | unsigned int n; |
| 174 | unsigned int base; |
Roland Gaudig | 6244cda | 2021-07-23 12:29:20 +0000 | [diff] [blame] | 175 | |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 176 | num_digits = 0; |
| 177 | n = 0; |
Roland Gaudig | 6244cda | 2021-07-23 12:29:20 +0000 | [diff] [blame] | 178 | base = 8; |
| 179 | q = *ptr; |
| 180 | |
| 181 | if (WANT_HEX_ESCAPES && *q == 'x') { |
| 182 | ++q; |
| 183 | base = 16; |
| 184 | ++num_digits; |
| 185 | } |
| 186 | |
| 187 | /* bash requires leading 0 in octal escapes: |
| 188 | * \02 works, \2 does not (prints \ and 2). |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 189 | * We treat \2 as a valid octal escape sequence. |
| 190 | */ |
Roland Gaudig | 6244cda | 2021-07-23 12:29:20 +0000 | [diff] [blame] | 191 | do { |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 192 | unsigned int r; |
| 193 | unsigned int d = (unsigned char)(*q) - '0'; |
Roland Gaudig | 6244cda | 2021-07-23 12:29:20 +0000 | [diff] [blame] | 194 | #if WANT_HEX_ESCAPES |
| 195 | if (d >= 10) { |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 196 | d = (unsigned char)tolower(*q) - 'a'; |
Roland Gaudig | 6244cda | 2021-07-23 12:29:20 +0000 | [diff] [blame] | 197 | //d += 10; |
| 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: |
| 201 | */ |
| 202 | if ((int)d >= 0) |
| 203 | d += 10; |
| 204 | } |
| 205 | #endif |
| 206 | if (d >= base) { |
| 207 | if (WANT_HEX_ESCAPES && base == 16) { |
| 208 | --num_digits; |
| 209 | if (num_digits == 0) { |
| 210 | /* \x<bad_char>: return '\', |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 211 | * leave ptr pointing to x |
| 212 | */ |
Roland Gaudig | 6244cda | 2021-07-23 12:29:20 +0000 | [diff] [blame] | 213 | return '\\'; |
| 214 | } |
| 215 | } |
| 216 | break; |
| 217 | } |
| 218 | |
| 219 | r = n * base + d; |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 220 | if (r > 255) |
Roland Gaudig | 6244cda | 2021-07-23 12:29:20 +0000 | [diff] [blame] | 221 | break; |
Roland Gaudig | 6244cda | 2021-07-23 12:29:20 +0000 | [diff] [blame] | 222 | |
| 223 | n = r; |
| 224 | ++q; |
| 225 | } while (++num_digits < 3); |
| 226 | |
| 227 | if (num_digits == 0) { |
| 228 | /* Not octal or hex escape sequence. |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 229 | * Is it one-letter one? |
| 230 | */ |
Roland Gaudig | 6244cda | 2021-07-23 12:29:20 +0000 | [diff] [blame] | 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. |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 234 | * Update: coreutils added support for \e on 28 Oct 2009. |
| 235 | */ |
| 236 | static const char charmap[] = { |
Roland Gaudig | 6244cda | 2021-07-23 12:29:20 +0000 | [diff] [blame] | 237 | 'a', 'b', 'e', 'f', 'n', 'r', 't', 'v', '\\', '\0', |
| 238 | '\a', '\b', 27, '\f', '\n', '\r', '\t', '\v', '\\', '\\', |
| 239 | }; |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 240 | |
Roland Gaudig | 6244cda | 2021-07-23 12:29:20 +0000 | [diff] [blame] | 241 | const char *p = charmap; |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 242 | |
Roland Gaudig | 6244cda | 2021-07-23 12:29:20 +0000 | [diff] [blame] | 243 | do { |
| 244 | if (*p == *q) { |
| 245 | q++; |
| 246 | break; |
| 247 | } |
| 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 '\' |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 252 | * and leave ptr pointing to NUL or z. |
| 253 | */ |
Roland Gaudig | 6244cda | 2021-07-23 12:29:20 +0000 | [diff] [blame] | 254 | n = p[sizeof(charmap) / 2]; |
| 255 | } |
| 256 | |
| 257 | *ptr = q; |
| 258 | |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 259 | return (char)n; |
Roland Gaudig | 6244cda | 2021-07-23 12:29:20 +0000 | [diff] [blame] | 260 | } |
| 261 | |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 262 | static char *skip_whitespace(const char *s) |
Roland Gaudig | 6244cda | 2021-07-23 12:29:20 +0000 | [diff] [blame] | 263 | { |
| 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. |
| 268 | * Use that. |
| 269 | */ |
| 270 | while (*s == ' ' || (unsigned char)(*s - 9) <= (13 - 9)) |
| 271 | s++; |
| 272 | |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 273 | return (char *)s; |
Roland Gaudig | 6244cda | 2021-07-23 12:29:20 +0000 | [diff] [blame] | 274 | } |
| 275 | |
| 276 | /* Like strcpy but can copy overlapping strings. */ |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 277 | static void overlapping_strcpy(char *dst, const char *src) |
Roland Gaudig | 6244cda | 2021-07-23 12:29:20 +0000 | [diff] [blame] | 278 | { |
| 279 | /* Cheap optimization for dst == src case - |
| 280 | * better to have it here than in many callers. |
| 281 | */ |
| 282 | if (dst != src) { |
| 283 | while ((*dst = *src) != '\0') { |
| 284 | dst++; |
| 285 | src++; |
| 286 | } |
| 287 | } |
| 288 | } |
| 289 | |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 290 | static int multiconvert(const char *arg, void *result, converter convert) |
| 291 | { |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 292 | if (*arg == '"' || *arg == '\'') |
| 293 | sprintf((char *)arg + strlen(arg), "%u", (unsigned char)arg[1]); |
| 294 | //errno = 0; |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 295 | convert(arg, result); |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 296 | /* Unlike their Posix counterparts, simple_strtoll and |
| 297 | * simple_strtoull do not set errno |
| 298 | * |
| 299 | * if (errno) { |
| 300 | * printf("error invalid number '%s'", arg); |
| 301 | * return 1; |
| 302 | * } |
| 303 | */ |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 304 | return 0; |
| 305 | } |
| 306 | |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 307 | static void conv_strtoull(const char *arg, void *result) |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 308 | { |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 309 | /* both coreutils 6.10 and bash 3.2: |
| 310 | * $ printf '%x\n' -2 |
| 311 | * fffffffffffffffe |
| 312 | * Mimic that: |
| 313 | */ |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 314 | if (arg[0] == '-') { |
| 315 | *(unsigned long long *)result = simple_strtoll(arg, NULL, 16); |
| 316 | return; |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 317 | } |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 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. |
| 321 | */ |
| 322 | if (arg[0] == '+') |
| 323 | arg++; |
| 324 | *(unsigned long long *)result = simple_strtoull(arg, NULL, 16); |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 325 | } |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 326 | |
| 327 | static void conv_strtoll(const char *arg, void *result) |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 328 | { |
| 329 | if (arg[0] == '+') |
| 330 | arg++; |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 331 | *(long long *)result = simple_strtoll(arg, NULL, 16); |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 332 | } |
| 333 | |
| 334 | /* Callers should check errno to detect errors */ |
| 335 | static unsigned long long my_xstrtoull(const char *arg) |
| 336 | { |
| 337 | unsigned long long result; |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 338 | |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 339 | if (multiconvert(arg, &result, conv_strtoull)) |
| 340 | result = 0; |
| 341 | return result; |
| 342 | } |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 343 | |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 344 | static long long my_xstrtoll(const char *arg) |
| 345 | { |
| 346 | long long result; |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 347 | |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 348 | if (multiconvert(arg, &result, conv_strtoll)) |
| 349 | result = 0; |
| 350 | return result; |
| 351 | } |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 352 | |
| 353 | /* Handles %b; return 1 if output is to be short-circuited by \c */ |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 354 | static int print_esc_string(struct print_inf *inf, const char *str) |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 355 | { |
| 356 | char c; |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 357 | |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 358 | while ((c = *str) != '\0') { |
| 359 | str++; |
| 360 | if (c == '\\') { |
| 361 | /* %b also accepts 4-digit octals of the form \0### */ |
| 362 | if (*str == '0') { |
| 363 | if ((unsigned char)(str[1] - '0') < 8) { |
| 364 | /* 2nd char is 0..7: skip leading '0' */ |
| 365 | str++; |
| 366 | } |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 367 | } else if (*str == 'c') { |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 368 | return 1; |
| 369 | } |
| 370 | { |
| 371 | /* optimization: don't force arg to be on-stack, |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 372 | * use another variable for that. |
| 373 | */ |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 374 | const char *z = str; |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 375 | |
| 376 | c = process_escape_sequence(&z); |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 377 | str = z; |
| 378 | } |
| 379 | } |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 380 | putchar_str(inf, c); |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 381 | } |
| 382 | |
| 383 | return 0; |
| 384 | } |
| 385 | |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 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) |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 389 | { |
| 390 | long long llv; |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 391 | char saved; |
| 392 | char *have_prec, *have_width; |
| 393 | |
| 394 | saved = format[fmt_length]; |
| 395 | format[fmt_length] = '\0'; |
| 396 | |
| 397 | have_prec = strstr(format, ".*"); |
| 398 | have_width = strchr(format, '*'); |
| 399 | if (have_width - 1 == have_prec) |
| 400 | have_width = NULL; |
| 401 | |
| 402 | /* multiconvert sets errno = 0, but %s needs it cleared */ |
| 403 | errno = 0; |
| 404 | |
| 405 | switch (format[fmt_length - 1]) { |
| 406 | case 'c': |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 407 | printf_str(inf, format, *argument); |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 408 | break; |
| 409 | case 'd': |
| 410 | case 'i': |
| 411 | llv = my_xstrtoll(skip_whitespace(argument)); |
| 412 | print_long: |
| 413 | if (!have_width) { |
| 414 | if (!have_prec) |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 415 | printf_str(inf, format, llv); |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 416 | else |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 417 | printf_str(inf, format, precision, llv); |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 418 | } else { |
| 419 | if (!have_prec) |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 420 | printf_str(inf, format, field_width, llv); |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 421 | else |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 422 | printf_str(inf, format, field_width, precision, llv); |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 423 | } |
| 424 | break; |
| 425 | case 'o': |
| 426 | case 'u': |
| 427 | case 'x': |
| 428 | case 'X': |
| 429 | llv = my_xstrtoull(skip_whitespace(argument)); |
| 430 | /* cheat: unsigned long and long have same width, so... */ |
| 431 | goto print_long; |
| 432 | case 's': |
| 433 | /* Are char* and long long the same? */ |
| 434 | if (sizeof(argument) == sizeof(llv)) { |
| 435 | llv = (long long)(ptrdiff_t)argument; |
| 436 | goto print_long; |
| 437 | } else { |
| 438 | /* Hope compiler will optimize it out by moving call |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 439 | * instruction after the ifs... |
| 440 | */ |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 441 | if (!have_width) { |
| 442 | if (!have_prec) |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 443 | printf_str(inf, format, argument, |
| 444 | /*unused:*/ argument, argument); |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 445 | else |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 446 | printf_str(inf, format, precision, |
| 447 | argument, /*unused:*/ argument); |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 448 | } else { |
| 449 | if (!have_prec) |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 450 | printf_str(inf, format, field_width, |
| 451 | argument, /*unused:*/ argument); |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 452 | else |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 453 | printf_str(inf, format, field_width, |
| 454 | precision, argument); |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 455 | } |
| 456 | break; |
| 457 | } |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 458 | break; |
| 459 | } /* switch */ |
| 460 | |
| 461 | format[fmt_length] = saved; |
| 462 | } |
| 463 | |
| 464 | /* Handle params for "%*.*f". Negative numbers are ok (compat). */ |
| 465 | static int get_width_prec(const char *str) |
| 466 | { |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 467 | long v = simple_strtol(str, NULL, 10); |
| 468 | |
| 469 | /* Unlike its Posix counterpart, simple_strtol does not set errno |
| 470 | * |
| 471 | * if (errno) { |
| 472 | * printf("error invalid number '%s'", str); |
| 473 | * v = 0; |
| 474 | * } |
| 475 | */ |
| 476 | return (int)v; |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 477 | } |
| 478 | |
| 479 | /* Print the text in FORMAT, using ARGV for arguments to any '%' directives. |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 480 | * Return advanced ARGV. |
| 481 | */ |
| 482 | static char **print_formatted(struct print_inf *inf, char *f, char **argv, int *conv_err) |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 483 | { |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 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 '*' */ |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 488 | char **saved_argv = argv; |
| 489 | |
| 490 | for (; *f; ++f) { |
| 491 | switch (*f) { |
| 492 | case '%': |
| 493 | direc_start = f++; |
| 494 | direc_length = 1; |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 495 | field_width = 0; |
| 496 | precision = 0; |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 497 | if (*f == '%') { |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 498 | putchar_str(inf, '%'); |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 499 | break; |
| 500 | } |
| 501 | if (*f == 'b') { |
| 502 | if (*argv) { |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 503 | if (print_esc_string(inf, *argv)) |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 504 | return saved_argv; /* causes main() to exit */ |
| 505 | ++argv; |
| 506 | } |
| 507 | break; |
| 508 | } |
| 509 | if (*f && strchr("-+ #", *f)) { |
| 510 | ++f; |
| 511 | ++direc_length; |
| 512 | } |
| 513 | if (*f == '*') { |
| 514 | ++f; |
| 515 | ++direc_length; |
| 516 | if (*argv) |
| 517 | field_width = get_width_prec(*argv++); |
| 518 | } else { |
| 519 | while (isdigit(*f)) { |
| 520 | ++f; |
| 521 | ++direc_length; |
| 522 | } |
| 523 | } |
| 524 | if (*f == '.') { |
| 525 | ++f; |
| 526 | ++direc_length; |
| 527 | if (*f == '*') { |
| 528 | ++f; |
| 529 | ++direc_length; |
| 530 | if (*argv) |
| 531 | precision = get_width_prec(*argv++); |
| 532 | } else { |
| 533 | while (isdigit(*f)) { |
| 534 | ++f; |
| 535 | ++direc_length; |
| 536 | } |
| 537 | } |
| 538 | } |
| 539 | |
| 540 | /* Remove "lLhz" size modifiers, repeatedly. |
| 541 | * bash does not like "%lld", but coreutils |
| 542 | * happily takes even "%Llllhhzhhzd"! |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 543 | * We are permissive like coreutils |
| 544 | */ |
| 545 | while ((*f | 0x20) == 'l' || *f == 'h' || *f == 'z') |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 546 | overlapping_strcpy(f, f + 1); |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 547 | /* Add "ll" if integer modifier, then print */ |
| 548 | { |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 549 | static const char format_chars[] = "diouxXcs"; |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 550 | char *p = strchr(format_chars, *f); |
| 551 | /* needed - try "printf %" without it */ |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 552 | if (!p || *f == '\0') { |
| 553 | printf("`%s': invalid format\n", direc_start); |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 554 | /* causes main() to exit with error */ |
| 555 | return saved_argv - 1; |
| 556 | } |
| 557 | ++direc_length; |
| 558 | if (p - format_chars <= 5) { |
| 559 | /* it is one of "diouxX" */ |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 560 | p = malloc(direc_length + 3); |
| 561 | if (!p) { |
| 562 | /* exit with error */ |
| 563 | return saved_argv - 1; |
| 564 | } |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 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); |
| 570 | direc_length += 2; |
| 571 | direc_start = p; |
| 572 | } else { |
| 573 | p = NULL; |
| 574 | } |
| 575 | if (*argv) { |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 576 | print_direc(inf, direc_start, direc_length, |
| 577 | field_width, precision, *argv++); |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 578 | } else { |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 579 | print_direc(inf, direc_start, direc_length, |
| 580 | field_width, precision, ""); |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 581 | } |
| 582 | *conv_err |= errno; |
| 583 | free(p); |
| 584 | } |
| 585 | break; |
| 586 | case '\\': |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 587 | if (*++f == 'c') |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 588 | return saved_argv; /* causes main() to exit */ |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 589 | putchar_str(inf, process_escape_sequence((const char **)&f)); |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 590 | f--; |
| 591 | break; |
| 592 | default: |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 593 | putchar_str(inf, *f); |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 594 | } |
| 595 | } |
| 596 | |
| 597 | return argv; |
| 598 | } |
| 599 | |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 600 | /** |
| 601 | * printf_setexpr() - Implements the setexpr <name> fmt <format> command |
| 602 | * |
| 603 | * This function implements the format string evaluation for the |
| 604 | * setexpr <name> fmt <format> <value> command. |
| 605 | * |
| 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 |
| 611 | */ |
| 612 | int printf_setexpr(char *str, size_t size, int argc, char *const *argv) |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 613 | { |
| 614 | int conv_err; |
| 615 | char *format; |
| 616 | char **argv2; |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 617 | struct print_inf inf = { |
| 618 | .str = str, |
| 619 | .size = size, |
| 620 | .offset = 0, |
| 621 | .error = 0, |
| 622 | }; |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 623 | |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 624 | if (!str || !size) |
| 625 | return 1; |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 626 | |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 627 | inf.str[0] = '\0'; |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 628 | |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 629 | format = argv[0]; |
| 630 | argv2 = (char **)argv + 1; |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 631 | |
| 632 | conv_err = 0; |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 633 | argv = argv2; |
| 634 | /* In case any print_str call raises an error inf.error will be |
| 635 | * set after print_formatted returns. |
| 636 | */ |
| 637 | argv2 = print_formatted(&inf, format, (char **)argv, &conv_err); |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 638 | |
| 639 | /* coreutils compat (bash doesn't do this): |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 640 | *if (*argv) |
| 641 | * fprintf(stderr, "excess args ignored"); |
| 642 | */ |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 643 | |
Roland Gaudig | f4f8d8b | 2021-07-23 12:29:21 +0000 | [diff] [blame] | 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 */ |
Roland Gaudig | 9571f1a | 2021-07-23 12:29:19 +0000 | [diff] [blame] | 647 | } |