(main): Check for overflow in tabstop values
[platform/upstream/coreutils.git] / src / unexpand.c
1 /* unexpand - convert blanks to tabs
2    Copyright (C) 89, 91, 1995-2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
17
18 /* By default, convert only maximal strings of initial blanks and tabs
19    into tabs.
20    Preserves backspace characters in the output; they decrement the
21    column count for tab calculations.
22    The default action is equivalent to -8.
23
24    Options:
25    --tabs=tab1[,tab2[,...]]
26    -t tab1[,tab2[,...]]
27    -tab1[,tab2[,...]]   If only one tab stop is given, set the tabs tab1
28                         columns apart instead of the default 8.  Otherwise,
29                         set the tabs at columns tab1, tab2, etc. (numbered from
30                         0); preserve any blanks beyond the tab stops given.
31    --all
32    -a                   Use tabs wherever they would replace 2 or more blanks,
33                         not just at the beginnings of lines.
34
35    David MacKenzie <djm@gnu.ai.mit.edu> */
36
37 #include <config.h>
38
39 #include <stdio.h>
40 #include <getopt.h>
41 #include <sys/types.h>
42 #include "system.h"
43 #include "error.h"
44 #include "posixver.h"
45 #include "quote.h"
46 #include "xstrndup.h"
47
48 /* The official name of this program (e.g., no `g' prefix).  */
49 #define PROGRAM_NAME "unexpand"
50
51 #define AUTHORS "David MacKenzie"
52
53 /* The number of bytes added at a time to the amount of memory
54    allocated for the output line.  */
55 #define OUTPUT_BLOCK 256
56
57 /* The name this program was run with.  */
58 char *program_name;
59
60 /* If true, convert blanks even after nonblank characters have been
61    read on the line.  */
62 static bool convert_entire_line;
63
64 /* If nonzero, the size of all tab stops.  If zero, use `tab_list' instead.  */
65 static size_t tab_size;
66
67 /* The maximum distance between tab stops.  */
68 static size_t max_column_width;
69
70 /* Array of the explicit column numbers of the tab stops;
71    after `tab_list' is exhausted, the rest of the line is printed
72    unchanged.  The first column is column 0.  */
73 static uintmax_t *tab_list;
74
75 /* The number of allocated entries in `tab_list'.  */
76 static size_t n_tabs_allocated;
77
78 /* The index of the first invalid element of `tab_list',
79    where the next element can be added.  */
80 static size_t first_free_tab;
81
82 /* Null-terminated array of input filenames.  */
83 static char **file_list;
84
85 /* Default for `file_list' if no files are given on the command line.  */
86 static char *stdin_argv[] =
87 {
88   "-", NULL
89 };
90
91 /* True if we have ever read standard input.  */
92 static bool have_read_stdin;
93
94 /* The desired exit status.  */
95 static int exit_status;
96
97 /* For long options that have no equivalent short option, use a
98    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
99 enum
100 {
101   CONVERT_FIRST_ONLY_OPTION = CHAR_MAX + 1
102 };
103
104 static struct option const longopts[] =
105 {
106   {"tabs", required_argument, NULL, 't'},
107   {"all", no_argument, NULL, 'a'},
108   {"first-only", no_argument, NULL, CONVERT_FIRST_ONLY_OPTION},
109   {GETOPT_HELP_OPTION_DECL},
110   {GETOPT_VERSION_OPTION_DECL},
111   {NULL, 0, NULL, 0}
112 };
113
114 void
115 usage (int status)
116 {
117   if (status != EXIT_SUCCESS)
118     fprintf (stderr, _("Try `%s --help' for more information.\n"),
119              program_name);
120   else
121     {
122       printf (_("\
123 Usage: %s [OPTION]... [FILE]...\n\
124 "),
125               program_name);
126       fputs (_("\
127 Convert blanks in each FILE to tabs, writing to standard output.\n\
128 With no FILE, or when FILE is -, read standard input.\n\
129 \n\
130 "), stdout);
131       fputs (_("\
132 Mandatory arguments to long options are mandatory for short options too.\n\
133 "), stdout);
134       fputs (_("\
135   -a, --all        convert all blanks, instead of just initial blanks\n\
136       --first-only convert only leading sequences of blanks (overrides -a)\n\
137   -t, --tabs=N     have tabs N characters apart instead of 8 (enables -a)\n\
138   -t, --tabs=LIST  use comma separated LIST of tab positions (enables -a)\n\
139 "), stdout);
140       fputs (HELP_OPTION_DESCRIPTION, stdout);
141       fputs (VERSION_OPTION_DESCRIPTION, stdout);
142       printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
143     }
144   exit (status);
145 }
146
147 /* Add tab stop TABVAL to the end of `tab_list'.  */
148
149 static void
150 add_tab_stop (uintmax_t tabval)
151 {
152   uintmax_t prev_column = first_free_tab ? tab_list[first_free_tab - 1] : 0;
153   uintmax_t column_width = prev_column <= tabval ? tabval - prev_column : 0;
154
155   if (first_free_tab == n_tabs_allocated)
156     tab_list = x2nrealloc (tab_list, &n_tabs_allocated, sizeof *tab_list);
157   tab_list[first_free_tab++] = tabval;
158
159   if (max_column_width < column_width)
160     {
161       if (SIZE_MAX < column_width)
162         error (EXIT_FAILURE, 0, _("tabs are too far apart"));
163       max_column_width = column_width;
164     }
165 }
166
167 /* Add the comma or blank separated list of tab stops STOPS
168    to the list of tab stops.  */
169
170 static void
171 parse_tab_stops (char const *stops)
172 {
173   bool have_tabval = false;
174   uintmax_t tabval IF_LINT (= 0);
175   char const *num_start IF_LINT (= NULL);
176   bool ok = true;
177
178   for (; *stops; stops++)
179     {
180       if (*stops == ',' || ISBLANK (to_uchar (*stops)))
181         {
182           if (have_tabval)
183             add_tab_stop (tabval);
184           have_tabval = false;
185         }
186       else if (ISDIGIT (*stops))
187         {
188           if (!have_tabval)
189             {
190               tabval = 0;
191               have_tabval = true;
192               num_start = stops;
193             }
194           {
195             /* Detect overflow.  */
196             uintmax_t new_t = 10 * tabval + *stops - '0';
197             if (UINTMAX_MAX / 10 < tabval || new_t < tabval * 10)
198               {
199                 size_t len = strspn (num_start, "0123456789");
200                 char *bad_num = xstrndup (num_start, len);
201                 error (0, 0, _("tab stop is too large %s"), quote (bad_num));
202                 free (bad_num);
203                 ok = false;
204                 stops = num_start + len - 1;
205               }
206             tabval = new_t;
207           }
208         }
209       else
210         {
211           error (0, 0, _("tab size contains invalid character(s): %s"),
212                  quote (stops));
213           ok = false;
214           break;
215         }
216     }
217
218   if (!ok)
219     exit (EXIT_FAILURE);
220
221   if (have_tabval)
222     add_tab_stop (tabval);
223 }
224
225 /* Check that the list of tab stops TABS, with ENTRIES entries,
226    contains only nonzero, ascending values.  */
227
228 static void
229 validate_tab_stops (uintmax_t const *tabs, size_t entries)
230 {
231   uintmax_t prev_tab = 0;
232   size_t i;
233
234   for (i = 0; i < entries; i++)
235     {
236       if (tabs[i] == 0)
237         error (EXIT_FAILURE, 0, _("tab size cannot be 0"));
238       if (tabs[i] <= prev_tab)
239         error (EXIT_FAILURE, 0, _("tab sizes must be ascending"));
240       prev_tab = tabs[i];
241     }
242 }
243
244 /* Close the old stream pointer FP if it is non-NULL,
245    and return a new one opened to read the next input file.
246    Open a filename of `-' as the standard input.
247    Return NULL if there are no more input files.  */
248
249 static FILE *
250 next_file (FILE *fp)
251 {
252   static char *prev_file;
253   char *file;
254
255   if (fp)
256     {
257       if (ferror (fp))
258         {
259           error (0, errno, "%s", prev_file);
260           exit_status = EXIT_FAILURE;
261         }
262       if (fp == stdin)
263         clearerr (fp);          /* Also clear EOF.  */
264       else if (fclose (fp) != 0)
265         {
266           error (0, errno, "%s", prev_file);
267           exit_status = EXIT_FAILURE;
268         }
269     }
270
271   while ((file = *file_list++) != NULL)
272     {
273       if (file[0] == '-' && file[1] == '\0')
274         {
275           have_read_stdin = true;
276           prev_file = file;
277           return stdin;
278         }
279       fp = fopen (file, "r");
280       if (fp)
281         {
282           prev_file = file;
283           return fp;
284         }
285       error (0, errno, "%s", file);
286       exit_status = EXIT_FAILURE;
287     }
288   return NULL;
289 }
290
291 /* Change blanks to tabs, writing to stdout.
292    Read each file in `file_list', in order.  */
293
294 static void
295 unexpand (void)
296 {
297   /* Input stream.  */
298   FILE *fp = next_file (NULL);
299
300   /* The array of pending blanks.  In non-POSIX locales, blanks can
301      include characters other than spaces, so the blanks must be
302      stored, not merely counted.  */
303   char *pending_blank;
304
305   if (!fp)
306     return;
307
308   /* Binary I/O will preserve the original EOL style (DOS/Unix) of files.  */
309   SET_BINARY2 (fileno (fp), STDOUT_FILENO);
310
311   /* The worst case is a non-blank character, then one blank, then a
312      tab stop, then MAX_COLUMN_WIDTH - 1 blanks, then a non-blank; so
313      allocate MAX_COLUMN_WIDTH bytes to store the blanks.  */
314   pending_blank = xmalloc (max_column_width);
315
316   for (;;)
317     {
318       /* Input character, or EOF.  */
319       int c;
320
321       /* If true, perform translations.  */
322       bool convert = true;
323
324
325       /* The following variables have valid values only when CONVERT
326          is true:  */
327
328       /* Column of next input character.  */
329       uintmax_t column = 0;
330
331       /* Column the next input tab stop is on.  */
332       uintmax_t next_tab_column = 0;
333
334       /* Index in TAB_LIST of next tab stop to examine.  */
335       size_t tab_index = 0;
336
337       /* If true, the first pending blank came just before a tab stop.  */
338       bool one_blank_before_tab_stop = false;
339
340       /* If true, the previous input character was a blank.  This is
341          initially true, since initial strings of blanks are treated
342          as if the line was preceded by a blank.  */
343       bool prev_blank = true;
344
345       /* Number of pending columns of blanks.  */
346       size_t pending = 0;
347
348
349       /* Convert a line of text.  */
350
351       do
352         {
353           while ((c = getc (fp)) < 0 && (fp = next_file (fp)))
354             SET_BINARY2 (fileno (fp), STDOUT_FILENO);
355
356           if (convert)
357             {
358               bool blank = ISBLANK (c);
359
360               if (blank)
361                 {
362                   if (next_tab_column <= column)
363                     {
364                       if (tab_size)
365                         next_tab_column =
366                           column + (tab_size - column % tab_size);
367                       else
368                         for (;;)
369                           if (tab_index == first_free_tab)
370                             {
371                               convert = false;
372                               break;
373                             }
374                           else
375                             {
376                               uintmax_t tab = tab_list[tab_index++];
377                               if (column < tab)
378                                 {
379                                   next_tab_column = tab;
380                                   break;
381                                 }
382                             }
383                     }
384
385                   if (convert)
386                     {
387                       if (next_tab_column < column)
388                         error (EXIT_FAILURE, 0, _("input line is too long"));
389
390                       if (c == '\t')
391                         {
392                           column = next_tab_column;
393
394                           /* Discard pending blanks, unless it was a single
395                              blank just before the previous tab stop.  */
396                           if (! (pending == 1 && one_blank_before_tab_stop))
397                             {
398                               pending = 0;
399                               one_blank_before_tab_stop = false;
400                             }
401                         }
402                       else
403                         {
404                           column++;
405
406                           if (! (prev_blank && column == next_tab_column))
407                             {
408                               /* It is not yet known whether the pending blanks
409                                  will be replaced by tabs.  */
410                               if (column == next_tab_column)
411                                 one_blank_before_tab_stop = true;
412                               pending_blank[pending++] = c;
413                               prev_blank = true;
414                               continue;
415                             }
416
417                           /* Replace the pending blanks by a tab or two.  */
418                           pending_blank[0] = c = '\t';
419                           pending = one_blank_before_tab_stop;
420                         }
421                     }
422                 }
423               else if (c == '\b')
424                 {
425                   /* Go back one column, and force recalculation of the
426                      next tab stop.  */
427                   column -= !!column;
428                   next_tab_column = column;
429                   tab_index -= !!tab_index;
430                 }
431               else
432                 {
433                   column++;
434                   if (!column)
435                     error (EXIT_FAILURE, 0, _("input line is too long"));
436                 }
437
438               if (pending)
439                 {
440                   if (fwrite (pending_blank, 1, pending, stdout) != pending)
441                     error (EXIT_FAILURE, errno, _("write error"));
442                   pending = 0;
443                   one_blank_before_tab_stop = false;
444                 }
445
446               prev_blank = blank;
447               convert &= convert_entire_line | blank;
448             }
449
450           if (c < 0)
451             {
452               free (pending_blank);
453               return;
454             }
455
456           if (putchar (c) < 0)
457             error (EXIT_FAILURE, errno, _("write error"));
458         }
459       while (c != '\n');
460     }
461 }
462
463 int
464 main (int argc, char **argv)
465 {
466   bool have_tabval = false;
467   uintmax_t tabval IF_LINT (= 0);
468   int c;
469
470   /* If true, cancel the effect of any -a (explicit or implicit in -t),
471      so that only leading blanks will be considered.  */
472   bool convert_first_only = false;
473
474   bool obsolete_tablist = false;
475
476   initialize_main (&argc, &argv);
477   program_name = argv[0];
478   setlocale (LC_ALL, "");
479   bindtextdomain (PACKAGE, LOCALEDIR);
480   textdomain (PACKAGE);
481
482   atexit (close_stdout);
483
484   have_read_stdin = false;
485   exit_status = EXIT_SUCCESS;
486   convert_entire_line = false;
487   tab_list = NULL;
488   first_free_tab = 0;
489
490   while ((c = getopt_long (argc, argv, ",0123456789at:", longopts, NULL))
491          != -1)
492     {
493       switch (c)
494         {
495         case '?':
496           usage (EXIT_FAILURE);
497         case 'a':
498           convert_entire_line = true;
499           break;
500         case 't':
501           convert_entire_line = true;
502           parse_tab_stops (optarg);
503           break;
504         case CONVERT_FIRST_ONLY_OPTION:
505           convert_first_only = true;
506           break;
507         case ',':
508           if (have_tabval)
509             add_tab_stop (tabval);
510           have_tabval = false;
511           obsolete_tablist = true;
512           break;
513         case_GETOPT_HELP_CHAR;
514         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
515         default:
516           if (!have_tabval)
517             {
518               tabval = 0;
519               have_tabval = true;
520             }
521           {
522             uintmax_t new_t = tabval * 10 + c - '0';
523             if (UINTMAX_MAX / 10 < tabval || new_t < tabval * 10)
524               error (EXIT_FAILURE, 0, _("tab stop value is too large"));
525             tabval = new_t;
526           }
527           obsolete_tablist = true;
528           break;
529         }
530     }
531
532   if (obsolete_tablist && 200112 <= posix2_version ())
533     {
534       error (0, 0,
535              _("`-LIST' option is obsolete; use `--first-only -t LIST'"));
536       usage (EXIT_FAILURE);
537     }
538
539   if (convert_first_only)
540     convert_entire_line = false;
541
542   if (have_tabval)
543     add_tab_stop (tabval);
544
545   validate_tab_stops (tab_list, first_free_tab);
546
547   if (first_free_tab == 0)
548     tab_size = max_column_width = 8;
549   else if (first_free_tab == 1)
550     tab_size = tab_list[0];
551   else
552     tab_size = 0;
553
554   file_list = (optind < argc ? &argv[optind] : stdin_argv);
555
556   unexpand ();
557
558   if (have_read_stdin && fclose (stdin) != 0)
559     error (EXIT_FAILURE, errno, "-");
560
561   exit (exit_status);
562 }