Improve the check for departures from C89, and fix the departures
[platform/upstream/coreutils.git] / src / shuf.c
1 /* Shuffle lines of text.
2
3    Copyright (C) 2006 Free Software Foundation, Inc.
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 2, or (at your option)
8    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, write to the Free Software
17    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19    Written by Paul Eggert.  */
20
21 #include <config.h>
22
23 #include <stdio.h>
24 #include <sys/types.h>
25 #include "system.h"
26
27 #include "error.h"
28 #include "getopt.h"
29 #include "quote.h"
30 #include "quotearg.h"
31 #include "randint.h"
32 #include "randperm.h"
33 #include "xstrtol.h"
34
35 /* The official name of this program (e.g., no `g' prefix).  */
36 #define PROGRAM_NAME "shuf"
37
38 #define AUTHORS "Paul Eggert"
39
40 /* The name this program was run with. */
41 char *program_name;
42
43 void
44 usage (int status)
45 {
46   if (status != EXIT_SUCCESS)
47     fprintf (stderr, _("Try `%s --help' for more information.\n"),
48              program_name);
49   else
50     {
51       printf (_("\
52 Usage: %s [OPTION]... [FILE]\n\
53   or:  %s -e [OPTION]... [ARG]...\n\
54   or:  %s -i LO-HI [OPTION]...\n\
55 "),
56               program_name, program_name, program_name);
57       fputs (_("\
58 Write a random permutation of the input lines to standard output.\n\
59 \n\
60 "), stdout);
61       fputs (_("\
62 Mandatory arguments to long options are mandatory for short options too.\n\
63 "), stdout);
64       fputs (_("\
65   -e, --echo                treat each ARG as an input line\n\
66   -i, --input-range=LO-HI   treat each number LO through HI as an input line\n\
67   -n, --head-lines=LINES    output at most LINES lines\n\
68   -o, --output=FILE         write result to FILE instead of standard output\n\
69       --random-source=FILE  get random bytes from FILE (default /dev/urandom)\n\
70   -z, --zero-terminated     end lines with 0 byte, not newline\n\
71 "), stdout);
72       fputs (HELP_OPTION_DESCRIPTION, stdout);
73       fputs (VERSION_OPTION_DESCRIPTION, stdout);
74       fputs (_("\
75 \n\
76 With no FILE, or when FILE is -, read standard input.\n\
77 "), stdout);
78       printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
79     }
80
81   exit (status);
82 }
83
84 /* For long options that have no equivalent short option, use a
85    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
86 enum
87 {
88   RANDOM_SOURCE_OPTION = CHAR_MAX + 1
89 };
90
91 static struct option const long_opts[] =
92 {
93   {"echo", no_argument, NULL, 'e'},
94   {"input-range", required_argument, NULL, 'i'},
95   {"head-count", required_argument, NULL, 'n'},
96   {"output", required_argument, NULL, 'o'},
97   {"random-source", required_argument, NULL, RANDOM_SOURCE_OPTION},
98   {"zero-terminated", no_argument, NULL, 'z'},
99   {GETOPT_HELP_OPTION_DECL},
100   {GETOPT_VERSION_OPTION_DECL},
101   {0, 0, 0, 0},
102 };
103
104 static bool
105 input_numbers_option_used (size_t lo_input, size_t hi_input)
106 {
107   return ! (lo_input == SIZE_MAX && hi_input == 0);
108 }
109
110 static void
111 input_from_argv (char **operand, int n_operands, char eolbyte)
112 {
113   char *p;
114   size_t size = n_operands;
115   int i;
116
117   for (i = 0; i < n_operands; i++)
118     size += strlen (operand[i]);
119   p = xmalloc (size);
120
121   for (i = 0; i < n_operands; i++)
122     {
123       char *p1 = stpcpy (p, operand[i]);
124       operand[i] = p;
125       p = p1;
126       *p++ = eolbyte;
127     }
128
129   operand[n_operands] = p;
130 }
131
132 /* Return the start of the next line after LINE.  The current line
133    ends in EOLBYTE, and is guaranteed to end before LINE + N.  */
134
135 static char *
136 next_line (char *line, char eolbyte, size_t n)
137 {
138   char *p = memchr (line, eolbyte, n);
139   return p + 1;
140 }
141
142 /* Read data from file IN.  Input lines are delimited by EOLBYTE;
143    silently append a trailing EOLBYTE if the file ends in some other
144    byte.  Store a pointer to the resulting array of lines into *PLINE.
145    Return the number of lines read.  Report an error and exit on
146    failure.  */
147
148 static size_t
149 read_input (FILE *in, char eolbyte, char ***pline)
150 {
151   char *p;
152   char *buf = NULL;
153   char *lim;
154   size_t alloc = 0;
155   size_t used = 0;
156   size_t next_alloc = (1 << 13) + 1;
157   size_t bytes_to_read;
158   size_t nread;
159   char **line;
160   size_t i;
161   size_t n_lines;
162   int fread_errno;
163   struct stat instat;
164
165   if (fstat (fileno (in), &instat) == 0 && S_ISREG (instat.st_mode))
166     {
167       off_t file_size = instat.st_size;
168       off_t current_offset = ftello (in);
169       if (0 <= current_offset)
170         {
171           off_t remaining_size =
172             (current_offset < file_size ? file_size - current_offset : 0);
173           if (SIZE_MAX - 2 < remaining_size)
174             xalloc_die ();
175           next_alloc = remaining_size + 2;
176         }
177     }
178
179   do
180     {
181       if (alloc <= used + 1)
182         {
183           if (alloc == SIZE_MAX)
184             xalloc_die ();
185           alloc = next_alloc;
186           next_alloc = alloc * 2;
187           if (next_alloc < alloc)
188             next_alloc = SIZE_MAX;
189           buf = xrealloc (buf, alloc);
190         }
191
192       bytes_to_read = alloc - used - 1;
193       nread = fread (buf + used, sizeof (char), bytes_to_read, in);
194       used += nread;
195     }
196   while (nread == bytes_to_read);
197
198   fread_errno = errno;
199
200   if (used && buf[used - 1] != eolbyte)
201     buf[used++] = eolbyte;
202
203   lim = buf + used;
204
205   n_lines = 0;
206   for (p = buf; p < lim; p = next_line (p, eolbyte, lim - p))
207     n_lines++;
208
209   *pline = line = xnmalloc (n_lines + 1, sizeof *line);
210
211   line[0] = p = buf;
212   for (i = 1; i <= n_lines; i++)
213     line[i] = p = next_line (p, eolbyte, lim - p);
214
215   errno = fread_errno;
216   return n_lines;
217 }
218
219 static int
220 write_permuted_output (size_t n_lines, char * const *line, size_t lo_input,
221                        size_t const *permutation)
222 {
223   size_t i;
224
225   if (line)
226     for (i = 0; i < n_lines; i++)
227       {
228         char * const *p = line + permutation[i];
229         size_t len = p[1] - p[0];
230         if (fwrite (p[0], sizeof *p[0], len, stdout) != len)
231           return -1;
232       }
233   else
234     for (i = 0; i < n_lines; i++)
235       {
236         unsigned long int n = lo_input + permutation[i];
237         if (printf ("%lu\n", n) < 0)
238           return -1;
239       }
240
241   return 0;
242 }
243
244 int
245 main (int argc, char **argv)
246 {
247   bool echo = false;
248   size_t lo_input = SIZE_MAX;
249   size_t hi_input = 0;
250   size_t head_lines = SIZE_MAX;
251   char const *outfile = NULL;
252   char *random_source = NULL;
253   char eolbyte = '\n';
254
255   int optc;
256   int n_operands;
257   char **operand;
258   size_t n_lines;
259   char **line;
260   struct randint_source *randint_source;
261   size_t const *permutation;
262
263   initialize_main (&argc, &argv);
264   program_name = argv[0];
265   setlocale (LC_ALL, "");
266   bindtextdomain (PACKAGE, LOCALEDIR);
267   textdomain (PACKAGE);
268
269   atexit (close_stdout);
270
271   while ((optc = getopt_long (argc, argv, "ei:n:o:z", long_opts, NULL)) != -1)
272     switch (optc)
273       {
274       case 'e':
275         echo = true;
276         break;
277
278       case 'i':
279         {
280           unsigned long int argval = 0;
281           char *p = strchr (optarg, '-');
282           char const *hi_optarg = optarg;
283           bool invalid = !p;
284
285           if (input_numbers_option_used (lo_input, hi_input))
286             error (EXIT_FAILURE, 0, _("multiple -i options specified"));
287
288           if (p)
289             {
290               *p = '\0';
291               invalid = ((xstrtoul (optarg, NULL, 10, &argval, NULL)
292                           != LONGINT_OK)
293                          || SIZE_MAX < argval);
294               *p = '-';
295               lo_input = argval;
296               hi_optarg = p + 1;
297             }
298
299           invalid |= ((xstrtoul (hi_optarg, NULL, 10, &argval, NULL)
300                        != LONGINT_OK)
301                       || SIZE_MAX < argval);
302           hi_input = argval;
303           n_lines = hi_input - lo_input + 1;
304           invalid |= ((lo_input <= hi_input) == (n_lines == 0));
305           if (invalid)
306             error (EXIT_FAILURE, 0, _("invalid input range %s"),
307                    quote (optarg));
308         }
309         break;
310
311       case 'n':
312         {
313           unsigned long int argval;
314           strtol_error e = xstrtoul (optarg, NULL, 10, &argval, NULL);
315
316           if (e == LONGINT_OK)
317             head_lines = MIN (head_lines, argval);
318           else if (e != LONGINT_OVERFLOW)
319             error (EXIT_FAILURE, 0, _("invalid line count %s"),
320                    quote (optarg));
321         }
322         break;
323
324       case 'o':
325         if (outfile && !STREQ (outfile, optarg))
326           error (EXIT_FAILURE, 0, _("multiple output files specified"));
327         outfile = optarg;
328         break;
329
330       case RANDOM_SOURCE_OPTION:
331         if (random_source && !STREQ (random_source, optarg))
332           error (EXIT_FAILURE, 0, _("multiple random sources specified"));
333         random_source = optarg;
334         break;
335
336       case 'z':
337         eolbyte = '\0';
338         break;
339
340       case_GETOPT_HELP_CHAR;
341       case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
342       default:
343         usage (EXIT_FAILURE);
344       }
345
346   n_operands = argc - optind;
347   operand = argv + optind;
348
349   if (echo)
350     {
351       if (input_numbers_option_used (lo_input, hi_input))
352         error (EXIT_FAILURE, 0, _("cannot combine -e and -i options"));
353       input_from_argv (operand, n_operands, eolbyte);
354       n_lines = n_operands;
355       line = operand;
356     }
357   else if (input_numbers_option_used (lo_input, hi_input))
358     {
359       if (n_operands)
360         {
361           error (0, 0, _("extra operand %s\n"), quote (operand[0]));
362           usage (EXIT_FAILURE);
363         }
364       n_lines = hi_input - lo_input + 1;
365       line = NULL;
366     }
367   else
368     {
369       char **input_lines;
370
371       switch (n_operands)
372         {
373         case 0:
374           break;
375
376         case 1:
377           if (! (STREQ (operand[0], "-") || freopen (operand[0], "r", stdin)))
378             error (EXIT_FAILURE, errno, "%s", operand[0]);
379           break;
380
381         default:
382           error (0, 0, _("extra operand %s"), quote (operand[1]));
383           usage (EXIT_FAILURE);
384         }
385
386       n_lines = read_input (stdin, eolbyte, &input_lines);
387       line = input_lines;
388     }
389
390   head_lines = MIN (head_lines, n_lines);
391
392   randint_source = randint_all_new (random_source,
393                                     randperm_bound (head_lines, n_lines));
394   if (! randint_source)
395     error (EXIT_FAILURE, errno, "%s", quotearg_colon (random_source));
396
397   /* Close stdin now, rather than earlier, so that randint_all_new
398      doesn't have to worry about opening something other than
399      stdin.  */
400   if (! (echo || input_numbers_option_used (lo_input, hi_input))
401       && (ferror (stdin) || fclose (stdin) != 0))
402     error (EXIT_FAILURE, errno, _("read error"));
403
404   permutation = randperm_new (randint_source, head_lines, n_lines);
405
406   if (outfile && ! freopen (outfile, "w", stdout))
407     error (EXIT_FAILURE, errno, "%s", quotearg_colon (outfile));
408   if (write_permuted_output (head_lines, line, lo_input, permutation) != 0)
409     error (EXIT_FAILURE, errno, _("write error"));
410
411   return EXIT_SUCCESS;
412 }