]> git.ipfire.org Git - thirdparty/bash.git/blame - examples/loadables/seq.c
add more overflow handling for printf builtin; start incorporating C23 stdckdint...
[thirdparty/bash.git] / examples / loadables / seq.c
CommitLineData
93e3d9f6 1/* seq - print sequence of numbers to standard output.
81e3a4fb 2 Copyright (C) 2018-2022 Free Software Foundation, Inc.
93e3d9f6
CR
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 3 of the License, or
7 (at your option) 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, see <https://www.gnu.org/licenses/>. */
16
17/* Written as bash builtin by Chet Ramey. Portions from seq.c by Ulrich Drepper. */
18
19#include <config.h>
20
21#include <sys/types.h>
22
23#ifdef HAVE_UNISTD_H
24# include <unistd.h>
25#endif
26
27#include <stdio.h>
28#include <errno.h>
29
30#include "bashansi.h"
31#include "loadables.h"
32#include "bashintl.h"
33
34#ifndef errno
35extern int errno;
36#endif
37
38#if defined (HAVE_LONG_DOUBLE) && HAVE_DECL_STRTOLD && !defined(STRTOLD_BROKEN)
39typedef long double floatmax_t;
40# define FLOATMAX_CONV "L"
41# define strtofltmax strtold
42# define FLOATMAX_FMT "%Lg"
43# define FLOATMAX_WFMT "%0.Lf"
44# define USE_LONG_DOUBLE
45#else
46typedef double floatmax_t;
47# define FLOATMAX_CONV ""
48# define strtofltmax strtod
49# define FLOATMAX_FMT "%g"
50# define FLOATMAX_WFMT "%0.f"
51#endif
81e3a4fb
CR
52static floatmax_t getfloatmax (const char *);
53static char *genformat (floatmax_t, floatmax_t, floatmax_t);
93e3d9f6
CR
54
55#define MAX(a, b) (((a) < (b))? (b) : (a))
56
57static int conversion_error = 0;
58
59/* If true print all number with equal width. */
60static int equal_width;
61
62/* The string used to separate two numbers. */
63static char const *separator;
64
65/* The string output after all numbers have been output. */
66static char const terminator[] = "\n";
67
68static char decimal_point;
69
70/* Pretty much the same as the version in builtins/printf.def */
71static floatmax_t
81e3a4fb 72getfloatmax (const char *arg)
93e3d9f6
CR
73{
74 floatmax_t ret;
75 char *ep;
76
77 errno = 0;
78 ret = strtofltmax (arg, &ep);
79
80 if (*ep)
81 {
82 sh_invalidnum ((char *)arg);
83 conversion_error = 1;
84 }
85 else if (errno == ERANGE)
86 {
87 builtin_error ("warning: %s: %s", arg, strerror(ERANGE));
88 conversion_error = 1;
89 }
90
91 if (ret == -0.0)
92 ret = 0.0;
93
94 return (ret);
95}
96
97/* If FORMAT is a valid printf format for a double argument, return
98 its long double equivalent, allocated from dynamic storage. This
99 was written by Ulrich Drepper, taken from coreutils:seq.c */
100static char *
101long_double_format (char const *fmt)
102{
103 size_t i;
104 size_t length_modifier_offset;
105 int has_L;
106
107 for (i = 0; ! (fmt[i] == '%' && fmt[i + 1] != '%'); i += (fmt[i] == '%') + 1)
108 {
109 if (!fmt[i])
110 {
111 builtin_error ("format %s has no %% directive", fmt);
112 return 0;
113 }
114 }
115
116 i++;
117 i += strspn (fmt + i, "-+#0 '"); /* zero or more flags */
118 i += strspn (fmt + i, "0123456789"); /* optional minimum field width */
119 if (fmt[i] == '.') /* optional precision */
120 {
121 i++;
122 i += strspn (fmt + i, "0123456789");
123 }
124
125 length_modifier_offset = i; /* optional length modifier */
126 /* we could ignore an 'l' length modifier here */
127 has_L = (fmt[i] == 'L');
128 i += has_L;
129 switch (fmt[i])
130 {
131 case '\0':
132 builtin_error ("format %s ends in %%", fmt);
133 return 0;
134 case 'A':
135 case 'a':
136 case 'e':
137 case 'E':
138 case 'f':
139 case 'F':
140 case 'g':
141 case 'G':
142 break;
143 default:
144 builtin_error ("format %s has unknown `%%%c' directive", fmt, fmt[i]);
145 return 0;
146 }
147 for (i++; ; i += (fmt[i] == '%') + 1)
148 if (fmt[i] == '%' && fmt[i + 1] != '%')
149 {
150 builtin_error ("format %s has too many %% directives", fmt);
151 return 0;
152 }
153 else if (fmt[i] == 0)
154 {
155 size_t format_size = i + 1;
156 char *ldfmt = xmalloc (format_size + 1);
157 memcpy (ldfmt, fmt, length_modifier_offset);
158#ifdef USE_LONG_DOUBLE
159 ldfmt[length_modifier_offset] = 'L';
160 strcpy (ldfmt + length_modifier_offset + 1,
161 fmt + length_modifier_offset + has_L);
162#else
0712a90c 163 strcpy (ldfmt + length_modifier_offset, fmt + length_modifier_offset);
93e3d9f6
CR
164#endif
165 return ldfmt;
166 }
167}
168
169/* Return the number of digits following the decimal point in NUMBUF */
170static int
81e3a4fb 171getprec (const char *numbuf)
93e3d9f6
CR
172{
173 int p;
174 char *dp;
175
176 if (dp = strchr (numbuf, decimal_point))
177 dp++; /* skip over decimal point */
178 for (p = 0; dp && *dp && ISDIGIT (*dp); dp++)
179 p++;
180 return p;
181}
182
183/* Return the default format given FIRST, INCR, and LAST. */
184static char *
81e3a4fb 185genformat (floatmax_t first, floatmax_t incr, floatmax_t last)
93e3d9f6
CR
186{
187 static char buf[6 + 2 * INT_STRLEN_BOUND (int)];
188 int wfirst, wlast, width;
189 int iprec, fprec, lprec, prec;
190
191 if (equal_width == 0)
192 return (FLOATMAX_FMT);
193
194 /* OK, we have to figure out the largest number of decimal places. This is
195 a little more expensive than using the original strings. */
196 snprintf (buf, sizeof (buf), FLOATMAX_FMT, incr);
197 iprec = getprec (buf);
198
199 wfirst = snprintf (buf, sizeof (buf), FLOATMAX_FMT, first);
200 fprec = getprec (buf);
201
202 prec = MAX (fprec, iprec);
203
204 wlast = snprintf (buf, sizeof (buf), FLOATMAX_FMT, last);
205 lprec = getprec (buf);
206
207 /* increase first width by any increased precision in increment */
208 wfirst += (prec - fprec);
209
210 /* adjust last width to use precision from first/incr */
211 wlast += (prec - lprec);
212
213 if (lprec && prec == 0)
214 wlast--; /* no decimal point */
215 if (lprec == 0 && prec)
216 wlast++; /* include decimal point */
217 if (fprec == 0 && prec)
218 wfirst++; /* include decimal point */
219
220 width = MAX (wfirst, wlast);
221 if (width)
222 sprintf (buf, "%%0%d.%d%sf", width, prec, FLOATMAX_CONV);
223 else
224 sprintf (buf, "%%.%d%sf", prec, FLOATMAX_CONV);
225
226 return buf;
227}
228
229int
81e3a4fb 230print_fltseq (const char *fmt, floatmax_t first, floatmax_t last, floatmax_t incr)
93e3d9f6
CR
231{
232 int n;
233 floatmax_t next;
234 const char *s;
235
be4078d2 236 n = 0; /* iteration counter */
93e3d9f6
CR
237 s = "";
238 for (next = first; incr >= 0 ? (next <= last) : (next >= last); next = first + n * incr)
239 {
240 QUIT;
241 if (*s && fputs (s, stdout) == EOF)
242 return (sh_chkwrite (EXECUTION_FAILURE));
243 if (printf (fmt, next) < 0)
244 return (sh_chkwrite (EXECUTION_FAILURE));
245 s = separator;
246 n++;
247 }
248
249 if (n > 0 && fputs (terminator, stdout) == EOF)
250 return (sh_chkwrite (EXECUTION_FAILURE));
251 return (sh_chkwrite (EXECUTION_SUCCESS));
252}
253
254/* must be <= INT_STRLEN_BOUND(intmax_t) */
255int
81e3a4fb 256width_needed (intmax_t num)
93e3d9f6
CR
257{
258 int ret;
259
260 ret = num < 0; /* sign */
261 if (ret)
262 num = -num;
263 do
264 ret++;
265 while (num /= 10);
266 return ret;
267}
268
269int
81e3a4fb 270print_intseq (intmax_t ifirst, intmax_t ilast, intmax_t iincr)
93e3d9f6
CR
271{
272 char intwfmt[6 + INT_STRLEN_BOUND(int) + sizeof (PRIdMAX)];
273 const char *s;
274 intmax_t i, next;
275
276 /* compute integer format string */
277 if (equal_width) /* -w supplied */
278 {
279 int wfirst, wlast, width;
280
281 wfirst = width_needed (ifirst);
282 wlast = width_needed (ilast);
283 width = MAX(wfirst, wlast);
284
285 /* The leading %s is for the separator */
286 snprintf (intwfmt, sizeof (intwfmt), "%%s%%0%u" PRIdMAX, width);
287 }
288
289 /* We could use braces.c:mkseq here but that allocates lots of memory */
290 s = "";
291 for (i = ifirst; (ifirst <= ilast) ? (i <= ilast) : (i >= ilast); i = next)
292 {
293 QUIT;
294 /* The leading %s is for the separator */
295 if (printf (equal_width ? intwfmt : "%s%" PRIdMAX, s, i) < 0)
296 return (sh_chkwrite (EXECUTION_FAILURE));
297 s = separator;
298 next = i + iincr;
299 }
300
301 if (fputs (terminator, stdout) == EOF)
302 return (sh_chkwrite (EXECUTION_FAILURE));
303 return (sh_chkwrite (EXECUTION_SUCCESS));
304}
305
306int
81e3a4fb 307seq_builtin (WORD_LIST *list)
93e3d9f6
CR
308{
309 floatmax_t first, last, incr;
310 intmax_t ifirst, ilast, iincr;
311 WORD_LIST *l;
312 int opt, nargs, intseq, freefmt;
313 char *first_str, *incr_str, *last_str;
314 char const *fmtstr; /* The printf(3) format used for output. */
315
316 equal_width = 0;
317 separator = "\n";
318 fmtstr = NULL;
319
320 first = 1.0;
321 last = 0.0;
322 incr = 0.0; /* set later */
323 ifirst = ilast = iincr = 0;
324 first_str = incr_str = last_str = 0;
325
326 intseq = freefmt = 0;
327 opt = 0;
328
329 reset_internal_getopt ();
330 while (opt != -1)
331 {
332 l = lcurrent ? lcurrent : list;
333 if (l && l->word && l->word->word && l->word->word[0] == '-' &&
334 (l->word->word[1] == '.' || DIGIT (l->word->word[1])))
335 {
336 loptend = l;
337 break; /* negative number */
338 }
339 if ((opt = internal_getopt (list, "f:s:w")) == -1)
340 break;
341
342 switch (opt)
343 {
344 case 'f':
345 fmtstr = list_optarg;
346 break;
347 case 's':
348 separator = list_optarg;
349 break;
350 case 'w':
351 equal_width = 1;
352 break;
353 CASE_HELPOPT;
354 default:
355 builtin_usage ();
356 return (EX_USAGE);
357 }
358 }
359 list = loptend;
360
361 if (list == 0)
362 {
363 builtin_usage ();
364 return (EXECUTION_FAILURE);
365 }
366
367 for (nargs = 1, l = list; l->next; l = l->next)
368 nargs++;
369 if (nargs > 3)
370 {
371 builtin_usage ();
372 return (EXECUTION_FAILURE);
373 }
374
375 /* LAST */
376 conversion_error = 0;
377 last = getfloatmax (last_str = l->word->word);
378 if (conversion_error)
379 return (EXECUTION_FAILURE);
380
381 /* FIRST LAST */
382 if (nargs > 1)
383 {
384 conversion_error = 0;
385 first = getfloatmax (first_str = list->word->word);
386 if (conversion_error)
387 return (EXECUTION_FAILURE);
388 }
389
390 /* FIRST INCR LAST */
391 if (nargs > 2)
392 {
393 conversion_error = 0;
394 incr = getfloatmax (incr_str = list->next->word->word);
395 if (conversion_error)
396 return (EXECUTION_FAILURE);
397 if (incr == 0.0)
398 {
399 builtin_error ("zero %screment", (first < last) ? "in" : "de");
400 return (EXECUTION_FAILURE);
401 }
402 }
403
404 /* Sanitize arguments */
405 if (incr == 0.0)
406 incr = (first <= last) ? 1.0 : -1.0;
407 if ((incr < 0.0 && first < last) || (incr > 0 && first > last))
408 {
409 builtin_error ("incorrect %screment", (first < last) ? "in" : "de");
410 return (EXECUTION_FAILURE);
411 }
412
413 /* validate format here */
414 if (fmtstr)
415 {
416 fmtstr = long_double_format (fmtstr);
417 freefmt = 1;
418 if (fmtstr == 0)
419 return (EXECUTION_FAILURE);
420 }
421
422 if (fmtstr != NULL && equal_width)
423 {
424 builtin_warning ("-w ignored when the format string is specified");
425 equal_width = 0;
426 }
427
428 /* Placeholder for later additional conditions */
429 if (last_str && all_digits (last_str) &&
430 (first_str == 0 || all_digits (first_str)) &&
431 (incr_str == 0 || all_digits (incr_str)) &&
432 fmtstr == NULL)
433 intseq = 1;
434
435 if (intseq)
436 {
437 ifirst = (intmax_t)first; /* truncation */
438 ilast = (intmax_t)last;
439 iincr = (intmax_t)incr;
440
441 return (print_intseq (ifirst, ilast, iincr));
442 }
443
444 decimal_point = locale_decpoint ();
445 if (fmtstr == NULL)
446 fmtstr = genformat (first, incr, last);
447
448 print_fltseq (fmtstr, first, last, incr);
449
450 if (freefmt)
451 free ((void *)fmtstr);
452 return sh_chkwrite (EXECUTION_SUCCESS);
453}
454
455/* Taken largely from GNU seq. */
456char *seq_doc[] = {
457 "Print numbers from FIRST to LAST, in steps of INCREMENT.",
458 "",
459 "-f FORMAT use printf style floating-point FORMAT",
460 "-s STRING use STRING to separate numbers (default: \\n)",
461 "-w equalize width by padding with leading zeroes",
462 "",
463 "If FIRST or INCREMENT is omitted, it defaults to 1. However, an",
464 "omitted INCREMENT defaults to -1 when LAST is smaller than FIRST.",
465 "The sequence of numbers ends when the sum of the current number and",
466 "INCREMENT would become greater than LAST.",
467 "FIRST, INCREMENT, and LAST are interpreted as floating point values.",
468 "",
469 "FORMAT must be suitable for printing one argument of type 'double';",
470 "it defaults to %.PRECf if FIRST, INCREMENT, and LAST are all fixed point",
471 "decimal numbers with maximum precision PREC, and to %g otherwise.",
472 (char *)NULL
473};
474
475struct builtin seq_struct = {
476 "seq",
477 seq_builtin,
478 BUILTIN_ENABLED,
479 seq_doc,
480 "seq [-f format] [-s separator] [-w] [FIRST [INCR]] LAST",
481 0
482};