paste -d\\: avoid heap overrun for backslash at end of delim list
[platform/upstream/coreutils.git] / src / paste.c
1 /* paste - merge lines of files
2    Copyright (C) 1997-2005, 2008 Free Software Foundation, Inc.
3    Copyright (C) 1984 David M. Ihnat
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
17
18 /* Written by David Ihnat.  */
19
20 /* The list of valid escape sequences has been expanded over the Unix
21    version, to include \b, \f, \r, and \v.
22
23    POSIX changes, bug fixes, long-named options, and cleanup
24    by David MacKenzie <djm@gnu.ai.mit.edu>.
25
26    Options:
27    --serial
28    -s                           Paste one file at a time rather than
29                                 one line from each file.
30    --delimiters=delim-list
31    -d delim-list                Consecutively use the characters in
32                                 DELIM-LIST instead of tab to separate
33                                 merged lines.  When DELIM-LIST is exhausted,
34                                 start again at its beginning.
35    A FILE of `-' means standard input.
36    If no FILEs are given, standard input is used. */
37
38 #include <config.h>
39
40 #include <stdio.h>
41 #include <getopt.h>
42 #include <sys/types.h>
43 #include "system.h"
44 #include "error.h"
45 #include "quotearg.h"
46
47 /* The official name of this program (e.g., no `g' prefix).  */
48 #define PROGRAM_NAME "paste"
49
50 #define AUTHORS "David M. Ihnat", "David MacKenzie"
51
52 /* Indicates that no delimiter should be added in the current position. */
53 #define EMPTY_DELIM '\0'
54
55 /* Name this program was run with. */
56 char *program_name;
57
58 /* If nonzero, we have read standard input at some point. */
59 static bool have_read_stdin;
60
61 /* If nonzero, merge subsequent lines of each file rather than
62    corresponding lines from each file in parallel. */
63 static bool serial_merge;
64
65 /* The delimeters between lines of input files (used cyclically). */
66 static char *delims;
67
68 /* A pointer to the character after the end of `delims'. */
69 static char const *delim_end;
70
71 static struct option const longopts[] =
72 {
73   {"serial", no_argument, NULL, 's'},
74   {"delimiters", required_argument, NULL, 'd'},
75   {GETOPT_HELP_OPTION_DECL},
76   {GETOPT_VERSION_OPTION_DECL},
77   {NULL, 0, NULL, 0}
78 };
79
80 /* Set globals delims and delim_end.  Copy STRPTR to DELIMS, converting
81    backslash representations of special characters in STRPTR to their actual
82    values. The set of possible backslash characters has been expanded beyond
83    that recognized by the Unix version.
84    Return 0 upon success.
85    If the string ends in an odd number of backslashes, ignore the
86    final backslash and return nonzero.  */
87
88 static int
89 collapse_escapes (char const *strptr)
90 {
91   char *strout = xstrdup (strptr);
92   bool backslash_at_end = false;
93
94   delims = strout;
95
96   while (*strptr)
97     {
98       if (*strptr != '\\')      /* Is it an escape character? */
99         *strout++ = *strptr++;  /* No, just transfer it. */
100       else
101         {
102           switch (*++strptr)
103             {
104             case '0':
105               *strout++ = EMPTY_DELIM;
106               break;
107
108             case 'b':
109               *strout++ = '\b';
110               break;
111
112             case 'f':
113               *strout++ = '\f';
114               break;
115
116             case 'n':
117               *strout++ = '\n';
118               break;
119
120             case 'r':
121               *strout++ = '\r';
122               break;
123
124             case 't':
125               *strout++ = '\t';
126               break;
127
128             case 'v':
129               *strout++ = '\v';
130               break;
131
132             case '\\':
133               *strout++ = '\\';
134               break;
135
136             case '\0':
137               backslash_at_end = true;
138               goto done;
139
140             default:
141               *strout++ = *strptr;
142               break;
143             }
144           strptr++;
145         }
146     }
147
148  done:;
149
150   delim_end = strout;
151   return backslash_at_end ? 1 : 0;
152 }
153
154 /* Report a write error and exit.  */
155
156 static void write_error (void) ATTRIBUTE_NORETURN;
157 static void
158 write_error (void)
159 {
160   error (EXIT_FAILURE, errno, _("write error"));
161   abort ();
162 }
163
164 /* Output a single byte, reporting any write errors.  */
165
166 static inline void
167 xputchar (char c)
168 {
169   if (putchar (c) < 0)
170     write_error ();
171 }
172
173 /* Perform column paste on the NFILES files named in FNAMPTR.
174    Return true if successful, false if one or more files could not be
175    opened or read. */
176
177 static bool
178 paste_parallel (size_t nfiles, char **fnamptr)
179 {
180   bool ok = true;
181   /* If all files are just ready to be closed, or will be on this
182      round, the string of delimiters must be preserved.
183      delbuf[0] through delbuf[nfiles]
184      store the delimiters for closed files. */
185   char *delbuf = xmalloc (nfiles + 2);
186
187   /* Streams open to the files to process; NULL if the corresponding
188      stream is closed.  */
189   FILE **fileptr = xnmalloc (nfiles + 1, sizeof *fileptr);
190
191   /* Number of files still open to process.  */
192   size_t files_open;
193
194   /* True if any fopen got fd == STDIN_FILENO.  */
195   bool opened_stdin = false;
196
197   /* Attempt to open all files.  This could be expanded to an infinite
198      number of files, but at the (considerable) expense of remembering
199      each file and its current offset, then opening/reading/closing.  */
200
201   for (files_open = 0; files_open < nfiles; ++files_open)
202     {
203       if (STREQ (fnamptr[files_open], "-"))
204         {
205           have_read_stdin = true;
206           fileptr[files_open] = stdin;
207         }
208       else
209         {
210           fileptr[files_open] = fopen (fnamptr[files_open], "r");
211           if (fileptr[files_open] == NULL)
212             error (EXIT_FAILURE, errno, "%s", fnamptr[files_open]);
213           else if (fileno (fileptr[files_open]) == STDIN_FILENO)
214             opened_stdin = true;
215         }
216     }
217
218   if (opened_stdin && have_read_stdin)
219     error (EXIT_FAILURE, 0, _("standard input is closed"));
220
221   /* Read a line from each file and output it to stdout separated by a
222      delimiter, until we go through the loop without successfully
223      reading from any of the files. */
224
225   while (files_open)
226     {
227       /* Set up for the next line. */
228       bool somedone = false;
229       char const *delimptr = delims;
230       size_t delims_saved = 0;  /* Number of delims saved in `delbuf'. */
231       size_t i;
232
233       for (i = 0; i < nfiles && files_open; i++)
234         {
235           int chr IF_LINT (= 0);        /* Input character. */
236           int err IF_LINT (= 0);        /* Input errno value.  */
237           size_t line_length = 0;       /* Number of chars in line. */
238
239           if (fileptr[i])
240             {
241               chr = getc (fileptr[i]);
242               err = errno;
243               if (chr != EOF && delims_saved)
244                 {
245                   if (fwrite (delbuf, 1, delims_saved, stdout) != delims_saved)
246                     write_error ();
247                   delims_saved = 0;
248                 }
249
250               while (chr != EOF)
251                 {
252                   line_length++;
253                   if (chr == '\n')
254                     break;
255                   xputchar (chr);
256                   chr = getc (fileptr[i]);
257                   err = errno;
258                 }
259             }
260
261           if (line_length == 0)
262             {
263               /* EOF, read error, or closed file.
264                  If an EOF or error, close the file.  */
265               if (fileptr[i])
266                 {
267                   if (ferror (fileptr[i]))
268                     {
269                       error (0, err, "%s", fnamptr[i]);
270                       ok = false;
271                     }
272                   if (fileptr[i] == stdin)
273                     clearerr (fileptr[i]); /* Also clear EOF. */
274                   else if (fclose (fileptr[i]) == EOF)
275                     {
276                       error (0, errno, "%s", fnamptr[i]);
277                       ok = false;
278                     }
279
280                   fileptr[i] = NULL;
281                   files_open--;
282                 }
283
284               if (i + 1 == nfiles)
285                 {
286                   /* End of this output line.
287                      Is this the end of the whole thing? */
288                   if (somedone)
289                     {
290                       /* No.  Some files were not closed for this line. */
291                       if (delims_saved)
292                         {
293                           if (fwrite (delbuf, 1, delims_saved, stdout)
294                               != delims_saved)
295                             write_error ();
296                           delims_saved = 0;
297                         }
298                       xputchar ('\n');
299                     }
300                   continue;     /* Next read of files, or exit. */
301                 }
302               else
303                 {
304                   /* Closed file; add delimiter to `delbuf'. */
305                   if (*delimptr != EMPTY_DELIM)
306                     delbuf[delims_saved++] = *delimptr;
307                   if (++delimptr == delim_end)
308                     delimptr = delims;
309                 }
310             }
311           else
312             {
313               /* Some data read. */
314               somedone = true;
315
316               /* Except for last file, replace last newline with delim. */
317               if (i + 1 != nfiles)
318                 {
319                   if (chr != '\n' && chr != EOF)
320                     xputchar (chr);
321                   if (*delimptr != EMPTY_DELIM)
322                     xputchar (*delimptr);
323                   if (++delimptr == delim_end)
324                     delimptr = delims;
325                 }
326               else
327                 {
328                   /* If the last line of the last file lacks a newline,
329                      print one anyhow.  POSIX requires this.  */
330                   char c = (chr == EOF ? '\n' : chr);
331                   xputchar (c);
332                 }
333             }
334         }
335     }
336   free (fileptr);
337   free (delbuf);
338   return ok;
339 }
340
341 /* Perform serial paste on the NFILES files named in FNAMPTR.
342    Return true if no errors, false if one or more files could not be
343    opened or read. */
344
345 static bool
346 paste_serial (size_t nfiles, char **fnamptr)
347 {
348   bool ok = true;       /* false if open or read errors occur. */
349   int charnew, charold; /* Current and previous char read. */
350   char const *delimptr; /* Current delimiter char. */
351   FILE *fileptr;        /* Open for reading current file. */
352
353   for (; nfiles; nfiles--, fnamptr++)
354     {
355       int saved_errno;
356       bool is_stdin = STREQ (*fnamptr, "-");
357       if (is_stdin)
358         {
359           have_read_stdin = true;
360           fileptr = stdin;
361         }
362       else
363         {
364           fileptr = fopen (*fnamptr, "r");
365           if (fileptr == NULL)
366             {
367               error (0, errno, "%s", *fnamptr);
368               ok = false;
369               continue;
370             }
371         }
372
373       delimptr = delims;        /* Set up for delimiter string. */
374
375       charold = getc (fileptr);
376       saved_errno = errno;
377       if (charold != EOF)
378         {
379           /* `charold' is set up.  Hit it!
380              Keep reading characters, stashing them in `charnew';
381              output `charold', converting to the appropriate delimiter
382              character if needed.  After the EOF, output `charold'
383              if it's a newline; otherwise, output it and then a newline. */
384
385           while ((charnew = getc (fileptr)) != EOF)
386             {
387               /* Process the old character. */
388               if (charold == '\n')
389                 {
390                   if (*delimptr != EMPTY_DELIM)
391                     xputchar (*delimptr);
392
393                   if (++delimptr == delim_end)
394                     delimptr = delims;
395                 }
396               else
397                 xputchar (charold);
398
399               charold = charnew;
400             }
401           saved_errno = errno;
402
403           /* Hit EOF.  Process that last character. */
404           xputchar (charold);
405         }
406
407       if (charold != '\n')
408         xputchar ('\n');
409
410       if (ferror (fileptr))
411         {
412           error (0, saved_errno, "%s", *fnamptr);
413           ok = false;
414         }
415       if (is_stdin)
416         clearerr (fileptr);     /* Also clear EOF. */
417       else if (fclose (fileptr) == EOF)
418         {
419           error (0, errno, "%s", *fnamptr);
420           ok = false;
421         }
422     }
423   return ok;
424 }
425
426 void
427 usage (int status)
428 {
429   if (status != EXIT_SUCCESS)
430     fprintf (stderr, _("Try `%s --help' for more information.\n"),
431              program_name);
432   else
433     {
434       printf (_("\
435 Usage: %s [OPTION]... [FILE]...\n\
436 "),
437               program_name);
438       fputs (_("\
439 Write lines consisting of the sequentially corresponding lines from\n\
440 each FILE, separated by TABs, to standard output.\n\
441 With no FILE, or when FILE is -, read standard input.\n\
442 \n\
443 "), stdout);
444       fputs (_("\
445 Mandatory arguments to long options are mandatory for short options too.\n\
446 "), stdout);
447       fputs (_("\
448   -d, --delimiters=LIST   reuse characters from LIST instead of TABs\n\
449   -s, --serial            paste one file at a time instead of in parallel\n\
450 "), stdout);
451       fputs (HELP_OPTION_DESCRIPTION, stdout);
452       fputs (VERSION_OPTION_DESCRIPTION, stdout);
453       /* FIXME: add a couple of examples.  */
454       emit_bug_reporting_address ();
455     }
456   exit (status);
457 }
458
459 int
460 main (int argc, char **argv)
461 {
462   int optc;
463   bool ok;
464   char const *delim_arg = "\t";
465
466   initialize_main (&argc, &argv);
467   program_name = argv[0];
468   setlocale (LC_ALL, "");
469   bindtextdomain (PACKAGE, LOCALEDIR);
470   textdomain (PACKAGE);
471
472   atexit (close_stdout);
473
474   have_read_stdin = false;
475   serial_merge = false;
476
477   while ((optc = getopt_long (argc, argv, "d:s", longopts, NULL)) != -1)
478     {
479       switch (optc)
480         {
481         case 'd':
482           /* Delimiter character(s). */
483           delim_arg = (optarg[0] == '\0' ? "\\0" : optarg);
484           break;
485
486         case 's':
487           serial_merge = true;
488           break;
489
490         case_GETOPT_HELP_CHAR;
491
492         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
493
494         default:
495           usage (EXIT_FAILURE);
496         }
497     }
498
499   if (optind == argc)
500     argv[argc++] = "-";
501
502   if (collapse_escapes (delim_arg))
503     {
504       /* Don't use the default quoting style, because that would double the
505          number of displayed backslashes, making the diagnostic look bogus.  */
506       set_quoting_style (NULL, escape_quoting_style);
507       error (EXIT_FAILURE, 0,
508              _("delimiter list ends with an unescaped backslash: %s"),
509              quotearg_colon (delim_arg));
510     }
511
512   if (!serial_merge)
513     ok = paste_parallel (argc - optind, &argv[optind]);
514   else
515     ok = paste_serial (argc - optind, &argv[optind]);
516
517   free (delims);
518
519   if (have_read_stdin && fclose (stdin) == EOF)
520     error (EXIT_FAILURE, errno, "-");
521   exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
522 }