(parse_tabstops): Detect overflow in tabstop sizes.
[platform/upstream/coreutils.git] / src / expand.c
1 /* expand - convert tabs to spaces
2    Copyright (C) 89, 91, 1995-2003 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 all tabs to spaces.
19    Preserves backspace characters in the output; they decrement the
20    column count for tab calculations.
21    The default action is equivalent to -8.
22
23    Options:
24    --tabs=tab1[,tab2[,...]]
25    -t tab1[,tab2[,...]]
26    -tab1[,tab2[,...]]   If only one tab stop is given, set the tabs tab1
27                         spaces apart instead of the default 8.  Otherwise,
28                         set the tabs at columns tab1, tab2, etc. (numbered from
29                         0); replace any tabs beyond the tabstops given with
30                         single spaces.
31    --initial
32    -i                   Only convert initial tabs on each line to spaces.
33
34    David MacKenzie <djm@gnu.ai.mit.edu> */
35
36 #include <config.h>
37
38 #include <stdio.h>
39 #include <getopt.h>
40 #include <sys/types.h>
41 #include "system.h"
42 #include "closeout.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 "expand"
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 number of bytes added at a time to the amount of memory
58    allocated for the list of tabstops. */
59 #define TABLIST_BLOCK 256
60
61 /* The name this program was run with. */
62 char *program_name;
63
64 /* If nonzero, convert blanks even after nonblank characters have been
65    read on the line. */
66 static int convert_entire_line;
67
68 /* If nonzero, the size of all tab stops.  If zero, use `tab_list' instead. */
69 static int tab_size;
70
71 /* Array of the explicit column numbers of the tab stops;
72    after `tab_list' is exhausted, each additional tab is replaced
73    by a space.  The first column is column 0. */
74 static int *tab_list;
75
76 /* The index of the first invalid element of `tab_list',
77    where the next element can be added. */
78 static int first_free_tab;
79
80 /* Null-terminated array of input filenames. */
81 static char **file_list;
82
83 /* Default for `file_list' if no files are given on the command line. */
84 static char *stdin_argv[] =
85 {
86   "-", NULL
87 };
88
89 /* Nonzero if we have ever read standard input. */
90 static int have_read_stdin;
91
92 /* Status to return to the system. */
93 static int exit_status;
94
95 static struct option const longopts[] =
96 {
97   {"tabs", required_argument, NULL, 't'},
98   {"initial", no_argument, NULL, 'i'},
99   {GETOPT_HELP_OPTION_DECL},
100   {GETOPT_VERSION_OPTION_DECL},
101   {NULL, 0, NULL, 0}
102 };
103
104 void
105 usage (int status)
106 {
107   if (status != 0)
108     fprintf (stderr, _("Try `%s --help' for more information.\n"),
109              program_name);
110   else
111     {
112       printf (_("\
113 Usage: %s [OPTION]... [FILE]...\n\
114 "),
115               program_name);
116       fputs (_("\
117 Convert tabs in each FILE to spaces, writing to standard output.\n\
118 With no FILE, or when FILE is -, read standard input.\n\
119 \n\
120 "), stdout);
121       fputs (_("\
122 Mandatory arguments to long options are mandatory for short options too.\n\
123 "), stdout);
124       fputs (_("\
125   -i, --initial       do not convert TABs after non whitespace\n\
126   -t, --tabs=NUMBER   have tabs NUMBER characters apart, not 8\n\
127 "), stdout);
128       fputs (_("\
129   -t, --tabs=LIST     use comma separated list of explicit tab positions\n\
130 "), stdout);
131       fputs (HELP_OPTION_DESCRIPTION, stdout);
132       fputs (VERSION_OPTION_DESCRIPTION, stdout);
133       printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
134     }
135   exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
136 }
137
138 /* Add tab stop TABVAL to the end of `tab_list', except
139    if TABVAL is -1, do nothing. */
140
141 static void
142 add_tabstop (int tabval)
143 {
144   if (tabval == -1)
145     return;
146   if (first_free_tab % TABLIST_BLOCK == 0)
147     tab_list = xrealloc (tab_list, (first_free_tab
148                                     + TABLIST_BLOCK * sizeof (tab_list[0])));
149   tab_list[first_free_tab++] = tabval;
150 }
151
152 /* Add the comma or blank separated list of tabstops STOPS
153    to the list of tabstops. */
154
155 static void
156 parse_tabstops (char const *stops)
157 {
158   int tabval = -1;
159   char const *num_start IF_LINT (= NULL);
160   int fail = 0;
161
162   for (; *stops; stops++)
163     {
164       if (*stops == ',' || ISBLANK (*stops))
165         {
166           add_tabstop (tabval);
167           tabval = -1;
168         }
169       else if (ISDIGIT (*stops))
170         {
171           if (tabval == -1)
172             {
173               tabval = 0;
174               num_start = stops;
175             }
176           {
177             /* Detect overflow.  */
178             int prev = tabval;
179             tabval = tabval * 10 + *stops - '0';
180             if (tabval < prev)
181               {
182                 size_t len = strspn (num_start, "0123456789");
183                 char *bad_num = xstrndup (num_start, len);
184                 error (0, 0, _("tab stop is too large %s"), quote (bad_num));
185                 fail = 1;
186                 stops = num_start + len - 1;
187               }
188           }
189         }
190       else
191         {
192           error (0, 0, _("tab size contains invalid character(s): %s"),
193                  quote (stops));
194           fail = 1;
195           break;
196         }
197     }
198
199   if (fail)
200     exit (EXIT_FAILURE);
201
202   add_tabstop (tabval);
203 }
204
205 /* Check that the list of tabstops TABS, with ENTRIES entries,
206    contains only nonzero, ascending values. */
207
208 static void
209 validate_tabstops (int *tabs, int entries)
210 {
211   int prev_tab = 0;
212   int i;
213
214   for (i = 0; i < entries; i++)
215     {
216       if (tabs[i] == 0)
217         error (EXIT_FAILURE, 0, _("tab size cannot be 0"));
218       if (tabs[i] <= prev_tab)
219         error (EXIT_FAILURE, 0, _("tab sizes must be ascending"));
220       prev_tab = tabs[i];
221     }
222 }
223
224 /* Close the old stream pointer FP if it is non-NULL,
225    and return a new one opened to read the next input file.
226    Open a filename of `-' as the standard input.
227    Return NULL if there are no more input files.  */
228
229 static FILE *
230 next_file (FILE *fp)
231 {
232   static char *prev_file;
233   char *file;
234
235   if (fp)
236     {
237       if (ferror (fp))
238         {
239           error (0, errno, "%s", prev_file);
240           exit_status = 1;
241         }
242       if (fp == stdin)
243         clearerr (fp);          /* Also clear EOF. */
244       else if (fclose (fp) == EOF)
245         {
246           error (0, errno, "%s", prev_file);
247           exit_status = 1;
248         }
249     }
250
251   while ((file = *file_list++) != NULL)
252     {
253       if (file[0] == '-' && file[1] == '\0')
254         {
255           have_read_stdin = 1;
256           prev_file = file;
257           return stdin;
258         }
259       fp = fopen (file, "r");
260       if (fp)
261         {
262           prev_file = file;
263           return fp;
264         }
265       error (0, errno, "%s", file);
266       exit_status = 1;
267     }
268   return NULL;
269 }
270
271 /* Change tabs to spaces, writing to stdout.
272    Read each file in `file_list', in order. */
273
274 static void
275 expand (void)
276 {
277   FILE *fp;                     /* Input stream. */
278   int c;                        /* Each input character. */
279   int tab_index = 0;            /* Index in `tab_list' of next tabstop. */
280   int column = 0;               /* Column on screen of the next char. */
281   int next_tab_column;          /* Column the next tab stop is on. */
282   int convert = 1;              /* If nonzero, perform translations. */
283
284   fp = next_file ((FILE *) NULL);
285   if (fp == NULL)
286     return;
287
288   /* Binary I/O will preserve the original EOL style (DOS/Unix) of files.  */
289   SET_BINARY2 (fileno (fp), STDOUT_FILENO);
290
291   for (;;)
292     {
293       c = getc (fp);
294       if (c == EOF)
295         {
296           fp = next_file (fp);
297           if (fp == NULL)
298             break;              /* No more files. */
299           else
300             {
301               SET_BINARY2 (fileno (fp), STDOUT_FILENO);
302               continue;
303             }
304         }
305
306       if (c == '\n')
307         {
308           putchar (c);
309           tab_index = 0;
310           column = 0;
311           convert = 1;
312         }
313       else if (c == '\t' && convert)
314         {
315           if (tab_size == 0)
316             {
317               /* Do not let tab_index == first_free_tab;
318                  stop when it is 1 less. */
319               while (tab_index < first_free_tab - 1
320                      && column >= tab_list[tab_index])
321                 tab_index++;
322               next_tab_column = tab_list[tab_index];
323               if (tab_index < first_free_tab - 1)
324                 tab_index++;
325               if (column >= next_tab_column)
326                 next_tab_column = column + 1; /* Ran out of tab stops. */
327             }
328           else
329             {
330               next_tab_column = column + tab_size - column % tab_size;
331             }
332           while (column < next_tab_column)
333             {
334               putchar (' ');
335               ++column;
336             }
337         }
338       else
339         {
340           if (convert)
341             {
342               if (c == '\b')
343                 {
344                   if (column > 0)
345                     --column;
346                 }
347               else
348                 {
349                   ++column;
350                   if (convert_entire_line == 0)
351                     convert = 0;
352                 }
353             }
354           putchar (c);
355         }
356     }
357 }
358
359 int
360 main (int argc, char **argv)
361 {
362   int tabval = -1;              /* Value of tabstop being read, or -1. */
363   int c;                        /* Option character. */
364
365   bool obsolete_tablist = false;
366
367   have_read_stdin = 0;
368   exit_status = 0;
369   convert_entire_line = 1;
370   tab_list = NULL;
371   first_free_tab = 0;
372   initialize_main (&argc, &argv);
373   program_name = argv[0];
374   setlocale (LC_ALL, "");
375   bindtextdomain (PACKAGE, LOCALEDIR);
376   textdomain (PACKAGE);
377
378   atexit (close_stdout);
379
380   while ((c = getopt_long (argc, argv, "it:,0123456789", longopts, NULL)) != -1)
381     {
382       switch (c)
383         {
384         case 0:
385           break;
386
387         case '?':
388           usage (EXIT_FAILURE);
389         case 'i':
390           convert_entire_line = 0;
391           break;
392         case 't':
393           parse_tabstops (optarg);
394           break;
395         case ',':
396           add_tabstop (tabval);
397           tabval = -1;
398           obsolete_tablist = true;
399           break;
400         case_GETOPT_HELP_CHAR;
401         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
402         default:
403           if (tabval == -1)
404             tabval = 0;
405           tabval = tabval * 10 + c - '0';
406           obsolete_tablist = true;
407           break;
408         }
409     }
410
411   if (obsolete_tablist && 200112 <= posix2_version ())
412     {
413       error (0, 0, _("`-LIST' option is obsolete; use `-t LIST'"));
414       usage (EXIT_FAILURE);
415     }
416
417   add_tabstop (tabval);
418
419   validate_tabstops (tab_list, first_free_tab);
420
421   if (first_free_tab == 0)
422     tab_size = 8;
423   else if (first_free_tab == 1)
424     tab_size = tab_list[0];
425   else
426     tab_size = 0;
427
428   file_list = (optind < argc ? &argv[optind] : stdin_argv);
429
430   expand ();
431
432   if (have_read_stdin && fclose (stdin) == EOF)
433     error (EXIT_FAILURE, errno, "-");
434
435   exit (exit_status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
436 }