1 /* expand-common - common functionality for expand/unexpand
2    Copyright (C) 1989-2023 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 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 #include <config.h>
18 
19 #include <stdio.h>
20 #include <sys/types.h>
21 #include "system.h"
22 #include "fadvise.h"
23 #include "quote.h"
24 
25 #include "expand-common.h"
26 
27 /* If true, convert blanks even after nonblank characters have been
28    read on the line.  */
29 bool convert_entire_line = false;
30 
31 /* If nonzero, the size of all tab stops.  If zero, use 'tab_list' instead.  */
32 static uintmax_t tab_size = 0;
33 
34 /* If nonzero, the size of all tab stops after the last specified.  */
35 static uintmax_t extend_size = 0;
36 
37 /* If nonzero, an increment for additional tab stops after the last specified.*/
38 static uintmax_t increment_size = 0;
39 
40 /* The maximum distance between tab stops.  */
41 size_t max_column_width;
42 
43 /* Array of the explicit column numbers of the tab stops;
44    after 'tab_list' is exhausted, each additional tab is replaced
45    by a space.  The first column is column 0.  */
46 static uintmax_t *tab_list = nullptr;
47 
48 /* The number of allocated entries in 'tab_list'.  */
49 static size_t n_tabs_allocated = 0;
50 
51 /* The index of the first invalid element of 'tab_list',
52    where the next element can be added.  */
53 static size_t first_free_tab = 0;
54 
55 /* Null-terminated array of input filenames.  */
56 static char **file_list = nullptr;
57 
58 /* Default for 'file_list' if no files are given on the command line.  */
59 static char *stdin_argv[] =
60 {
61   (char *) "-", nullptr
62 };
63 
64 /* True if we have ever read standard input.  */
65 static bool have_read_stdin = false;
66 
67 /* The desired exit status.  */
68 int exit_status = EXIT_SUCCESS;
69 
70 
71 
72 /* Add tab stop TABVAL to the end of 'tab_list'.  */
73 extern void
add_tab_stop(uintmax_t tabval)74 add_tab_stop (uintmax_t tabval)
75 {
76   uintmax_t prev_column = first_free_tab ? tab_list[first_free_tab - 1] : 0;
77   uintmax_t column_width = prev_column <= tabval ? tabval - prev_column : 0;
78 
79   if (first_free_tab == n_tabs_allocated)
80     tab_list = X2NREALLOC (tab_list, &n_tabs_allocated);
81   tab_list[first_free_tab++] = tabval;
82 
83   if (max_column_width < column_width)
84     {
85       if (SIZE_MAX < column_width)
86         error (EXIT_FAILURE, 0, _("tabs are too far apart"));
87       max_column_width = column_width;
88     }
89 }
90 
91 static bool
set_extend_size(uintmax_t tabval)92 set_extend_size (uintmax_t tabval)
93 {
94   bool ok = true;
95 
96   if (extend_size)
97     {
98       error (0, 0,
99              _("'/' specifier only allowed"
100                " with the last value"));
101       ok = false;
102     }
103   extend_size = tabval;
104 
105   return ok;
106 }
107 
108 static bool
set_increment_size(uintmax_t tabval)109 set_increment_size (uintmax_t tabval)
110 {
111   bool ok = true;
112 
113   if (increment_size)
114     {
115       error (0,0,
116              _("'+' specifier only allowed"
117                " with the last value"));
118       ok = false;
119     }
120   increment_size = tabval;
121 
122   return ok;
123 }
124 
125 /* Add the comma or blank separated list of tab stops STOPS
126    to the list of tab stops.  */
127 extern void
parse_tab_stops(char const * stops)128 parse_tab_stops (char const *stops)
129 {
130   bool have_tabval = false;
131   uintmax_t tabval = 0;
132   bool extend_tabval = false;
133   bool increment_tabval = false;
134   char const *num_start = nullptr;
135   bool ok = true;
136 
137   for (; *stops; stops++)
138     {
139       if (*stops == ',' || isblank (to_uchar (*stops)))
140         {
141           if (have_tabval)
142             {
143               if (extend_tabval)
144                 {
145                   if (! set_extend_size (tabval))
146                     {
147                       ok = false;
148                       break;
149                     }
150                 }
151               else if (increment_tabval)
152                 {
153                   if (! set_increment_size (tabval))
154                     {
155                       ok = false;
156                       break;
157                     }
158                 }
159               else
160                 add_tab_stop (tabval);
161             }
162           have_tabval = false;
163         }
164       else if (*stops == '/')
165         {
166           if (have_tabval)
167             {
168               error (0, 0, _("'/' specifier not at start of number: %s"),
169                      quote (stops));
170               ok = false;
171             }
172           extend_tabval = true;
173           increment_tabval = false;
174         }
175       else if (*stops == '+')
176         {
177           if (have_tabval)
178             {
179               error (0, 0, _("'+' specifier not at start of number: %s"),
180                      quote (stops));
181               ok = false;
182             }
183           increment_tabval = true;
184           extend_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           if (!DECIMAL_DIGIT_ACCUMULATE (tabval, *stops - '0', uintmax_t))
197             {
198               size_t len = strspn (num_start, "0123456789");
199               char *bad_num = ximemdup0 (num_start, len);
200               error (0, 0, _("tab stop is too large %s"), quote (bad_num));
201               free (bad_num);
202               ok = false;
203               stops = num_start + len - 1;
204             }
205         }
206       else
207         {
208           error (0, 0, _("tab size contains invalid character(s): %s"),
209                  quote (stops));
210           ok = false;
211           break;
212         }
213     }
214 
215   if (ok && have_tabval)
216     {
217       if (extend_tabval)
218         ok &= set_extend_size (tabval);
219       else if (increment_tabval)
220         ok &= set_increment_size (tabval);
221       else
222         add_tab_stop (tabval);
223     }
224 
225   if (! ok)
226     exit (EXIT_FAILURE);
227 }
228 
229 /* Check that the list of tab stops TABS, with ENTRIES entries,
230    contains only nonzero, ascending values.  */
231 
232 static void
validate_tab_stops(uintmax_t const * tabs,size_t entries)233 validate_tab_stops (uintmax_t const *tabs, size_t entries)
234 {
235   uintmax_t prev_tab = 0;
236 
237   for (size_t i = 0; i < entries; i++)
238     {
239       if (tabs[i] == 0)
240         error (EXIT_FAILURE, 0, _("tab size cannot be 0"));
241       if (tabs[i] <= prev_tab)
242         error (EXIT_FAILURE, 0, _("tab sizes must be ascending"));
243       prev_tab = tabs[i];
244     }
245 
246   if (increment_size && extend_size)
247     error (EXIT_FAILURE, 0, _("'/' specifier is mutually exclusive with '+'"));
248 }
249 
250 /* Called after all command-line options have been parsed,
251    and add_tab_stop/parse_tab_stops have been called.
252    Will validate the tab-stop values,
253    and set the final values to:
254    tab-stops = 8 (if no tab-stops given on command line)
255    tab-stops = N (if value N specified as the only value).
256    tab-stops = distinct values given on command line (if multiple values given).
257 */
258 extern void
finalize_tab_stops(void)259 finalize_tab_stops (void)
260 {
261   validate_tab_stops (tab_list, first_free_tab);
262 
263   if (first_free_tab == 0)
264     tab_size = max_column_width = extend_size
265                                   ? extend_size : increment_size
266                                                   ? increment_size : 8;
267   else if (first_free_tab == 1 && ! extend_size && ! increment_size)
268     tab_size = tab_list[0];
269   else
270     tab_size = 0;
271 }
272 
273 
274 extern uintmax_t
get_next_tab_column(const uintmax_t column,size_t * tab_index,bool * last_tab)275 get_next_tab_column (const uintmax_t column, size_t *tab_index,
276                      bool *last_tab)
277 {
278   *last_tab = false;
279 
280   /* single tab-size - return multiples of it */
281   if (tab_size)
282     return column + (tab_size - column % tab_size);
283 
284   /* multiple tab-sizes - iterate them until the tab position is beyond
285      the current input column. */
286   for ( ; *tab_index < first_free_tab ; (*tab_index)++ )
287     {
288         uintmax_t tab = tab_list[*tab_index];
289         if (column < tab)
290             return tab;
291     }
292 
293   /* relative last tab - return multiples of it */
294   if (extend_size)
295     return column + (extend_size - column % extend_size);
296 
297   /* incremental last tab - add increment_size to the previous tab stop */
298   if (increment_size)
299     {
300       uintmax_t end_tab = tab_list[first_free_tab - 1];
301 
302       return column + (increment_size - ((column - end_tab) % increment_size));
303     }
304 
305   *last_tab = true;
306   return 0;
307 }
308 
309 
310 
311 
312 /* Sets new file-list */
313 extern void
set_file_list(char ** list)314 set_file_list (char **list)
315 {
316   have_read_stdin = false;
317 
318   if (!list)
319     file_list = stdin_argv;
320   else
321     file_list = list;
322 }
323 
324 /* Close the old stream pointer FP if it is non-null,
325    and return a new one opened to read the next input file.
326    Open a filename of '-' as the standard input.
327    Return nullptr if there are no more input files.  */
328 
329 extern FILE *
next_file(FILE * fp)330 next_file (FILE *fp)
331 {
332   static char *prev_file;
333   char *file;
334 
335   if (fp)
336     {
337       int err = errno;
338       if (!ferror (fp))
339         err = 0;
340       if (STREQ (prev_file, "-"))
341         clearerr (fp);		/* Also clear EOF.  */
342       else if (fclose (fp) != 0)
343         err = errno;
344       if (err)
345         {
346           error (0, err, "%s", quotef (prev_file));
347           exit_status = EXIT_FAILURE;
348         }
349     }
350 
351   while ((file = *file_list++) != nullptr)
352     {
353       if (STREQ (file, "-"))
354         {
355           have_read_stdin = true;
356           fp = stdin;
357         }
358       else
359         fp = fopen (file, "r");
360       if (fp)
361         {
362           prev_file = file;
363           fadvise (fp, FADVISE_SEQUENTIAL);
364           return fp;
365         }
366       error (0, errno, "%s", quotef (file));
367       exit_status = EXIT_FAILURE;
368     }
369   return nullptr;
370 }
371 
372 /* */
373 extern void
cleanup_file_list_stdin(void)374 cleanup_file_list_stdin (void)
375 {
376     if (have_read_stdin && fclose (stdin) != 0)
377       error (EXIT_FAILURE, errno, "-");
378 }
379 
380 
381 extern void
emit_tab_list_info(void)382 emit_tab_list_info (void)
383 {
384   /* suppress syntax check for emit_mandatory_arg_note() */
385   fputs (_("\
386   -t, --tabs=LIST  use comma separated list of tab positions.\n\
387 "), stdout);
388   fputs (_("\
389                      The last specified position can be prefixed with '/'\n\
390                      to specify a tab size to use after the last\n\
391                      explicitly specified tab stop.  Also a prefix of '+'\n\
392                      can be used to align remaining tab stops relative to\n\
393                      the last specified tab stop instead of the first column\n\
394 "), stdout);
395 }
396