1 // printf - format and print data
2 // Copyright (C) 1990-2007 Free Software Foundation, Inc.
3 //
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation; either version 2, or (at your option)
7 // any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software Foundation,
16 // Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
17
18 // Usage: printf format [argument...]
19 //
20 // A front end to the printf function that lets it be used from the shell.
21 //
22 // Backslash escapes:
23 //
24 // \" = double quote
25 // \\ = backslash
26 // \a = alert (bell)
27 // \b = backspace
28 // \c = produce no further output
29 // \e = escape
30 // \f = form feed
31 // \n = new line
32 // \r = carriage return
33 // \t = horizontal tab
34 // \v = vertical tab
35 // \ooo = octal number (ooo is 1 to 3 digits)
36 // \xhh = hexadecimal number (hhh is 1 to 2 digits)
37 // \uhhhh = 16-bit Unicode character (hhhh is 4 digits)
38 // \Uhhhhhhhh = 32-bit Unicode character (hhhhhhhh is 8 digits)
39 //
40 // Additional directive:
41 //
42 // %b = print an argument string, interpreting backslash escapes,
43 // except that octal escapes are of the form \0 or \0ooo.
44 //
45 // The `format' argument is re-used as many times as necessary
46 // to convert all of the given arguments.
47 //
48 // David MacKenzie <djm@gnu.ai.mit.edu>
49
50 // This file has been imported from source code of printf command in GNU Coreutils version 6.9.
51 #include "config.h" // IWYU pragma: keep
52
53 #include <sys/types.h>
54
55 #include <cerrno>
56 #include <climits>
57 #include <clocale>
58 #include <cstdarg>
59 #include <cstddef>
60 #include <cstdint>
61 #include <cstdlib>
62 #include <cstring>
63 #include <cwchar>
64 #include <cwctype>
65
66 #include "builtin.h"
67 #include "common.h"
68 #include "io.h"
69 #include "wcstringutil.h"
70 #include "wutil.h" // IWYU pragma: keep
71
72 class parser_t;
73
74 struct builtin_printf_state_t {
75 // Out and err streams. Note this is a captured reference!
76 io_streams_t &streams;
77
78 // The status of the operation.
79 int exit_code;
80
81 // Whether we should stop outputting. This gets set in the case of an error, and also with the
82 // \c escape.
83 bool early_exit;
84
builtin_printf_state_tbuiltin_printf_state_t85 explicit builtin_printf_state_t(io_streams_t &s)
86 : streams(s), exit_code(0), early_exit(false) {}
87
88 void verify_numeric(const wchar_t *s, const wchar_t *end, int errcode);
89
90 void print_direc(const wchar_t *start, size_t length, wchar_t conversion, bool have_field_width,
91 int field_width, bool have_precision, int precision, wchar_t const *argument);
92
93 int print_formatted(const wchar_t *format, int argc, const wchar_t **argv);
94
95 void nonfatal_error(const wchar_t *fmt, ...);
96 void fatal_error(const wchar_t *fmt, ...);
97
98 long print_esc(const wchar_t *escstart, bool octal_0);
99 void print_esc_string(const wchar_t *str);
100 void print_esc_char(wchar_t c);
101
102 void append_output(wchar_t c);
103 void append_output(const wchar_t *c);
104 void append_format_output(const wchar_t *fmt, ...);
105 };
106
is_octal_digit(wchar_t c)107 static bool is_octal_digit(wchar_t c) { return iswdigit(c) && c < L'8'; }
108
hex_to_bin(const wchar_t & c)109 static int hex_to_bin(const wchar_t &c) {
110 switch (c) {
111 case L'0': {
112 return 0;
113 }
114 case L'1': {
115 return 1;
116 }
117 case L'2': {
118 return 2;
119 }
120 case L'3': {
121 return 3;
122 }
123 case L'4': {
124 return 4;
125 }
126 case L'5': {
127 return 5;
128 }
129 case L'6': {
130 return 6;
131 }
132 case L'7': {
133 return 7;
134 }
135 case L'8': {
136 return 8;
137 }
138 case L'9': {
139 return 9;
140 }
141 case L'a':
142 case L'A': {
143 return 10;
144 }
145 case L'b':
146 case L'B': {
147 return 11;
148 }
149 case L'c':
150 case L'C': {
151 return 12;
152 }
153 case L'd':
154 case L'D': {
155 return 13;
156 }
157 case L'e':
158 case L'E': {
159 return 14;
160 }
161 case L'f':
162 case L'F': {
163 return 15;
164 }
165 default: {
166 return -1;
167 }
168 }
169 }
170
octal_to_bin(wchar_t c)171 static int octal_to_bin(wchar_t c) {
172 switch (c) {
173 case L'0': {
174 return 0;
175 }
176 case L'1': {
177 return 1;
178 }
179 case L'2': {
180 return 2;
181 }
182 case L'3': {
183 return 3;
184 }
185 case L'4': {
186 return 4;
187 }
188 case L'5': {
189 return 5;
190 }
191 case L'6': {
192 return 6;
193 }
194 case L'7': {
195 return 7;
196 }
197 default: {
198 return -1;
199 }
200 }
201 }
202
nonfatal_error(const wchar_t * fmt,...)203 void builtin_printf_state_t::nonfatal_error(const wchar_t *fmt, ...) {
204 // Don't error twice.
205 if (early_exit) return;
206
207 va_list va;
208 va_start(va, fmt);
209 wcstring errstr = vformat_string(fmt, va);
210 va_end(va);
211 streams.err.append(errstr);
212 if (!string_suffixes_string(L"\n", errstr)) streams.err.push_back(L'\n');
213
214 // We set the exit code to error, because one occurred,
215 // but we don't do an early exit so we still print what we can.
216 this->exit_code = STATUS_CMD_ERROR;
217 }
218
fatal_error(const wchar_t * fmt,...)219 void builtin_printf_state_t::fatal_error(const wchar_t *fmt, ...) {
220 // Don't error twice.
221 if (early_exit) return;
222
223 va_list va;
224 va_start(va, fmt);
225 wcstring errstr = vformat_string(fmt, va);
226 va_end(va);
227 streams.err.append(errstr);
228 if (!string_suffixes_string(L"\n", errstr)) streams.err.push_back(L'\n');
229
230 this->exit_code = STATUS_CMD_ERROR;
231 this->early_exit = true;
232 }
append_output(wchar_t c)233 void builtin_printf_state_t::append_output(wchar_t c) {
234 // Don't output if we're done.
235 if (early_exit) return;
236
237 streams.out.push_back(c);
238 }
239
append_output(const wchar_t * c)240 void builtin_printf_state_t::append_output(const wchar_t *c) {
241 // Don't output if we're done.
242 if (early_exit) return;
243
244 streams.out.append(c);
245 }
246
append_format_output(const wchar_t * fmt,...)247 void builtin_printf_state_t::append_format_output(const wchar_t *fmt, ...) {
248 // Don't output if we're done.
249 if (early_exit) return;
250
251 va_list va;
252 va_start(va, fmt);
253 wcstring tmp = vformat_string(fmt, va);
254 va_end(va);
255 streams.out.append(tmp);
256 }
257
verify_numeric(const wchar_t * s,const wchar_t * end,int errcode)258 void builtin_printf_state_t::verify_numeric(const wchar_t *s, const wchar_t *end, int errcode) {
259 if (errcode != 0) {
260 if (errcode == ERANGE) {
261 this->fatal_error(L"%ls: %ls", s, _(L"Number out of range"));
262 } else {
263 this->fatal_error(L"%ls: %s", s, std::strerror(errcode));
264 }
265 } else if (*end) {
266 if (s == end) {
267 this->fatal_error(_(L"%ls: expected a numeric value"), s);
268 } else {
269 // This isn't entirely fatal - the value should still be printed.
270 this->nonfatal_error(_(L"%ls: value not completely converted"), s);
271 }
272 }
273 }
274
275 template <typename T>
276 static T raw_string_to_scalar_type(const wchar_t *s, wchar_t **end);
277
278 // we use wcstoll instead of wcstoimax because FreeBSD 8 has busted wcstoumax and wcstoimax - see
279 // #626
280 template <>
raw_string_to_scalar_type(const wchar_t * s,wchar_t ** end)281 intmax_t raw_string_to_scalar_type(const wchar_t *s, wchar_t **end) {
282 return std::wcstoll(s, end, 0);
283 }
284
285 template <>
raw_string_to_scalar_type(const wchar_t * s,wchar_t ** end)286 uintmax_t raw_string_to_scalar_type(const wchar_t *s, wchar_t **end) {
287 return std::wcstoull(s, end, 0);
288 }
289
290 template <>
raw_string_to_scalar_type(const wchar_t * s,wchar_t ** end)291 long double raw_string_to_scalar_type(const wchar_t *s, wchar_t **end) {
292 double val = std::wcstod(s, end);
293 if (**end == L'\0') return val;
294 // The conversion using the user's locale failed. That may be due to the string not being a
295 // valid floating point value. It could also be due to the locale using different separator
296 // characters than the normal english convention. So try again by forcing the use of a locale
297 // that employs the english convention for writing floating point numbers.
298 return wcstod_l(s, end, fish_c_locale());
299 }
300
301 template <typename T>
string_to_scalar_type(const wchar_t * s,builtin_printf_state_t * state)302 static T string_to_scalar_type(const wchar_t *s, builtin_printf_state_t *state) {
303 T val;
304 if (*s == L'\"' || *s == L'\'') {
305 wchar_t ch = *++s;
306 val = ch;
307 } else {
308 wchar_t *end = nullptr;
309 errno = 0;
310 val = raw_string_to_scalar_type<T>(s, &end);
311 state->verify_numeric(s, end, errno);
312 }
313 return val;
314 }
315
316 /// Output a single-character \ escape.
print_esc_char(wchar_t c)317 void builtin_printf_state_t::print_esc_char(wchar_t c) {
318 switch (c) {
319 case L'a': { // alert
320 this->append_output(L'\a');
321 break;
322 }
323 case L'b': { // backspace
324 this->append_output(L'\b');
325 break;
326 }
327 case L'c': { // cancel the rest of the output
328 this->early_exit = true;
329 break;
330 }
331 case L'e': { // escape
332 this->append_output(L'\x1B');
333 break;
334 }
335 case L'f': { // form feed
336 this->append_output(L'\f');
337 break;
338 }
339 case L'n': { // new line
340 this->append_output(L'\n');
341 break;
342 }
343 case L'r': { // carriage return
344 this->append_output(L'\r');
345 break;
346 }
347 case L't': { // horizontal tab
348 this->append_output(L'\t');
349 break;
350 }
351 case L'v': { // vertical tab
352 this->append_output(L'\v');
353 break;
354 }
355 default: {
356 this->append_output(c);
357 break;
358 }
359 }
360 }
361
362 /// Print a \ escape sequence starting at ESCSTART.
363 /// Return the number of characters in the escape sequence besides the backslash..
364 /// If OCTAL_0 is nonzero, octal escapes are of the form \0ooo, where o
365 /// is an octal digit; otherwise they are of the form \ooo.
print_esc(const wchar_t * escstart,bool octal_0)366 long builtin_printf_state_t::print_esc(const wchar_t *escstart, bool octal_0) {
367 const wchar_t *p = escstart + 1;
368 int esc_value = 0; /* Value of \nnn escape. */
369 int esc_length; /* Length of \nnn escape. */
370
371 if (*p == L'x') {
372 // A hexadecimal \xhh escape sequence must have 1 or 2 hex. digits.
373 for (esc_length = 0, ++p; esc_length < 2 && iswxdigit(*p); ++esc_length, ++p)
374 esc_value = esc_value * 16 + hex_to_bin(*p);
375 if (esc_length == 0) this->fatal_error(_(L"missing hexadecimal number in escape"));
376 this->append_output(ENCODE_DIRECT_BASE + esc_value % 256);
377 } else if (is_octal_digit(*p)) {
378 // Parse \0ooo (if octal_0 && *p == L'0') or \ooo (otherwise). Allow \ooo if octal_0 && *p
379 // != L'0'; this is an undocumented extension to POSIX that is compatible with Bash 2.05b.
380 // Wrap mod 256, which matches historic behavior.
381 for (esc_length = 0, p += octal_0 && *p == L'0'; esc_length < 3 && is_octal_digit(*p);
382 ++esc_length, ++p)
383 esc_value = esc_value * 8 + octal_to_bin(*p);
384 this->append_output(ENCODE_DIRECT_BASE + esc_value % 256);
385 } else if (*p && std::wcschr(L"\"\\abcefnrtv", *p)) {
386 print_esc_char(*p++);
387 } else if (*p == L'u' || *p == L'U') {
388 wchar_t esc_char = *p;
389 p++;
390 uint32_t uni_value = 0;
391 for (size_t esc_length = 0; esc_length < (esc_char == L'u' ? 4 : 8); esc_length++) {
392 if (!iswxdigit(*p)) {
393 // Escape sequence must be done. Complain if we didn't get anything.
394 if (esc_length == 0) {
395 this->fatal_error(_(L"Missing hexadecimal number in Unicode escape"));
396 }
397 break;
398 }
399 uni_value = uni_value * 16 + hex_to_bin(*p);
400 p++;
401 }
402
403 // PCA GNU printf respects the limitations described in ISO N717, about which universal
404 // characters "shall not" be specified. I believe this limitation is for the benefit of
405 // compilers; I see no reason to impose it in builtin_printf.
406 //
407 // If __STDC_ISO_10646__ is defined, then it means wchar_t can and does hold Unicode code
408 // points, so just use that. If not defined, use the %lc printf conversion; this probably
409 // won't do anything good if your wide character set is not Unicode, but such platforms are
410 // exceedingly rare.
411 if (uni_value > 0x10FFFF) {
412 this->fatal_error(_(L"Unicode character out of range: \\%c%0*x"), esc_char,
413 (esc_char == L'u' ? 4 : 8), uni_value);
414 } else {
415 #if defined(__STDC_ISO_10646__)
416 this->append_output(uni_value);
417 #else
418 this->append_format_output(L"%lc", uni_value);
419 #endif
420 }
421 } else {
422 this->append_output(L'\\');
423 if (*p) {
424 this->append_output(*p);
425 p++;
426 }
427 }
428 return p - escstart - 1;
429 }
430
431 /// Print string STR, evaluating \ escapes.
print_esc_string(const wchar_t * str)432 void builtin_printf_state_t::print_esc_string(const wchar_t *str) {
433 for (; *str; str++)
434 if (*str == L'\\')
435 str += print_esc(str, true);
436 else
437 this->append_output(*str);
438 }
439
440 /// Evaluate a printf conversion specification. START is the start of the directive, LENGTH is its
441 /// length, and CONVERSION specifies the type of conversion. LENGTH does not include any length
442 /// modifier or the conversion specifier itself. FIELD_WIDTH and PRECISION are the field width and
443 /// precision for '*' values, if HAVE_FIELD_WIDTH and HAVE_PRECISION are true, respectively.
444 /// ARGUMENT is the argument to be formatted.
print_direc(const wchar_t * start,size_t length,wchar_t conversion,bool have_field_width,int field_width,bool have_precision,int precision,wchar_t const * argument)445 void builtin_printf_state_t::print_direc(const wchar_t *start, size_t length, wchar_t conversion,
446 bool have_field_width, int field_width,
447 bool have_precision, int precision,
448 wchar_t const *argument) {
449 // Start with everything except the conversion specifier.
450 wcstring fmt(start, length);
451
452 // Create a copy of the % directive, with an intmax_t-wide width modifier substituted for any
453 // existing integer length modifier.
454 switch (conversion) {
455 case L'x':
456 case L'X':
457 case L'd':
458 case L'i':
459 case L'o':
460 case L'u': {
461 fmt.append(L"ll");
462 break;
463 }
464 case L'a':
465 case L'e':
466 case L'f':
467 case L'g':
468 case L'A':
469 case L'E':
470 case L'F':
471 case L'G': {
472 fmt.append(L"L");
473 break;
474 }
475 case L's':
476 case L'c': {
477 fmt.append(L"l");
478 break;
479 }
480 default: {
481 break;
482 }
483 }
484
485 // Append the conversion itself.
486 fmt.push_back(conversion);
487
488 switch (conversion) {
489 case L'd':
490 case L'i': {
491 auto arg = string_to_scalar_type<intmax_t>(argument, this);
492 if (!have_field_width) {
493 if (!have_precision)
494 this->append_format_output(fmt.c_str(), arg);
495 else
496 this->append_format_output(fmt.c_str(), precision, arg);
497 } else {
498 if (!have_precision)
499 this->append_format_output(fmt.c_str(), field_width, arg);
500 else
501 this->append_format_output(fmt.c_str(), field_width, precision, arg);
502 }
503 break;
504 }
505 case L'o':
506 case L'u':
507 case L'x':
508 case L'X': {
509 auto arg = string_to_scalar_type<uintmax_t>(argument, this);
510 if (!have_field_width) {
511 if (!have_precision)
512 this->append_format_output(fmt.c_str(), arg);
513 else
514 this->append_format_output(fmt.c_str(), precision, arg);
515 } else {
516 if (!have_precision)
517 this->append_format_output(fmt.c_str(), field_width, arg);
518 else
519 this->append_format_output(fmt.c_str(), field_width, precision, arg);
520 }
521 break;
522 }
523 case L'a':
524 case L'A':
525 case L'e':
526 case L'E':
527 case L'f':
528 case L'F':
529 case L'g':
530 case L'G': {
531 auto arg = string_to_scalar_type<long double>(argument, this);
532 if (!have_field_width) {
533 if (!have_precision) {
534 this->append_format_output(fmt.c_str(), arg);
535 } else {
536 this->append_format_output(fmt.c_str(), precision, arg);
537 }
538 } else {
539 if (!have_precision) {
540 this->append_format_output(fmt.c_str(), field_width, arg);
541 } else {
542 this->append_format_output(fmt.c_str(), field_width, precision, arg);
543 }
544 }
545 break;
546 }
547 case L'c': {
548 if (!have_field_width) {
549 this->append_format_output(fmt.c_str(), *argument);
550 } else {
551 this->append_format_output(fmt.c_str(), field_width, *argument);
552 }
553 break;
554 }
555 case L's': {
556 if (!have_field_width) {
557 if (!have_precision) {
558 this->append_format_output(fmt.c_str(), argument);
559 } else {
560 this->append_format_output(fmt.c_str(), precision, argument);
561 }
562 } else {
563 if (!have_precision) {
564 this->append_format_output(fmt.c_str(), field_width, argument);
565 } else {
566 this->append_format_output(fmt.c_str(), field_width, precision, argument);
567 }
568 }
569 break;
570 }
571 default: {
572 DIE("unexpected opt");
573 }
574 }
575 }
576
577 /// For each character in str, set the corresponding boolean in the array to the given flag.
modify_allowed_format_specifiers(bool ok[UCHAR_MAX+1],const char * str,bool flag)578 static inline void modify_allowed_format_specifiers(bool ok[UCHAR_MAX + 1], const char *str,
579 bool flag) {
580 for (const char *c = str; *c != '\0'; c++) {
581 auto idx = static_cast<unsigned char>(*c);
582 ok[idx] = flag;
583 }
584 }
585
586 /// Print the text in FORMAT, using ARGV (with ARGC elements) for arguments to any `%' directives.
587 /// Return the number of elements of ARGV used.
print_formatted(const wchar_t * format,int argc,const wchar_t ** argv)588 int builtin_printf_state_t::print_formatted(const wchar_t *format, int argc, const wchar_t **argv) {
589 int save_argc = argc; /* Preserve original value. */
590 const wchar_t *f; /* Pointer into `format'. */
591 const wchar_t *direc_start; /* Start of % directive. */
592 size_t direc_length; /* Length of % directive. */
593 bool have_field_width; /* True if FIELD_WIDTH is valid. */
594 int field_width = 0; /* Arg to first '*'. */
595 bool have_precision; /* True if PRECISION is valid. */
596 int precision = 0; /* Arg to second '*'. */
597 bool ok[UCHAR_MAX + 1] = {}; /* ok['x'] is true if %x is allowed. */
598
599 for (f = format; *f != L'\0'; ++f) {
600 switch (*f) {
601 case L'%': {
602 direc_start = f++;
603 direc_length = 1;
604 have_field_width = have_precision = false;
605 if (*f == L'%') {
606 this->append_output(L'%');
607 break;
608 }
609 if (*f == L'b') {
610 // FIXME: Field width and precision are not supported for %b, even though POSIX
611 // requires it.
612 if (argc > 0) {
613 print_esc_string(*argv);
614 ++argv;
615 --argc;
616 }
617 break;
618 }
619
620 modify_allowed_format_specifiers(ok, "aAcdeEfFgGiosuxX", true);
621 for (bool continue_looking_for_flags = true; continue_looking_for_flags;) {
622 switch (*f) {
623 case L'I':
624 case L'\'': {
625 modify_allowed_format_specifiers(ok, "aAceEosxX", false);
626 break;
627 }
628 case '-':
629 case '+':
630 case ' ': {
631 break;
632 }
633 case L'#': {
634 modify_allowed_format_specifiers(ok, "cdisu", false);
635 break;
636 }
637 case '0': {
638 modify_allowed_format_specifiers(ok, "cs", false);
639 break;
640 }
641 default: {
642 continue_looking_for_flags = false;
643 break;
644 }
645 }
646 if (continue_looking_for_flags) {
647 f++;
648 direc_length++;
649 }
650 }
651
652 if (*f == L'*') {
653 ++f;
654 ++direc_length;
655 if (argc > 0) {
656 auto width = string_to_scalar_type<intmax_t>(*argv, this);
657 if (INT_MIN <= width && width <= INT_MAX)
658 field_width = static_cast<int>(width);
659 else
660 this->fatal_error(_(L"invalid field width: %ls"), *argv);
661 ++argv;
662 --argc;
663 } else {
664 field_width = 0;
665 }
666 have_field_width = true;
667 } else {
668 while (iswdigit(*f)) {
669 ++f;
670 ++direc_length;
671 }
672 }
673 if (*f == L'.') {
674 ++f;
675 ++direc_length;
676 modify_allowed_format_specifiers(ok, "c", false);
677 if (*f == L'*') {
678 ++f;
679 ++direc_length;
680 if (argc > 0) {
681 auto prec = string_to_scalar_type<intmax_t>(*argv, this);
682 if (prec < 0) {
683 // A negative precision is taken as if the precision were omitted,
684 // so -1 is safe here even if prec < INT_MIN.
685 precision = -1;
686 } else if (INT_MAX < prec)
687 this->fatal_error(_(L"invalid precision: %ls"), *argv);
688 else {
689 precision = static_cast<int>(prec);
690 }
691 ++argv;
692 --argc;
693 } else {
694 precision = 0;
695 }
696 have_precision = true;
697 } else {
698 while (iswdigit(*f)) {
699 ++f;
700 ++direc_length;
701 }
702 }
703 }
704
705 while (*f == L'l' || *f == L'L' || *f == L'h' || *f == L'j' || *f == L't' ||
706 *f == L'z') {
707 ++f;
708 }
709
710 wchar_t conversion = *f;
711 if (conversion > 0xFF || !ok[conversion]) {
712 this->fatal_error(_(L"%.*ls: invalid conversion specification"),
713 static_cast<int>(f + 1 - direc_start), direc_start);
714 return 0;
715 }
716
717 const wchar_t *argument = L"";
718 if (argc > 0) {
719 argument = *argv++;
720 argc--;
721 }
722 print_direc(direc_start, direc_length, *f, have_field_width, field_width,
723 have_precision, precision, argument);
724 break;
725 }
726 case L'\\': {
727 f += print_esc(f, false);
728 break;
729 }
730 default: {
731 this->append_output(*f);
732 break;
733 }
734 }
735 }
736 return save_argc - argc;
737 }
738
739 /// The printf builtin.
builtin_printf(parser_t & parser,io_streams_t & streams,const wchar_t ** argv)740 maybe_t<int> builtin_printf(parser_t &parser, io_streams_t &streams, const wchar_t **argv) {
741 const wchar_t *cmd = argv[0];
742 int argc = builtin_count_args(argv);
743 help_only_cmd_opts_t opts;
744
745 int optind;
746 int retval = parse_help_only_cmd_opts(opts, &optind, argc, argv, parser, streams);
747 if (retval != STATUS_CMD_OK) return retval;
748
749 if (opts.print_help) {
750 builtin_print_help(parser, streams, cmd);
751 return STATUS_CMD_OK;
752 }
753
754 argc -= optind;
755 argv += optind;
756 if (argc < 1) {
757 return STATUS_INVALID_ARGS;
758 }
759
760 builtin_printf_state_t state(streams);
761 int args_used;
762 const wchar_t *format = argv[0];
763 argc--;
764 argv++;
765
766 do {
767 args_used = state.print_formatted(format, argc, argv);
768 argc -= args_used;
769 argv += args_used;
770 } while (args_used > 0 && argc > 0 && !state.early_exit);
771 return state.exit_code;
772 }
773