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