(usage): Say that
[platform/upstream/coreutils.git] / src / split.c
index a2aee89..a02e64a 100644 (file)
@@ -1,5 +1,5 @@
 /* split.c -- split a file into pieces.
-   Copyright (C) 1988, 1991 Free Software Foundation, Inc.
+   Copyright (C) 88, 91, 1995-2001 Free Software Foundation, Inc.
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -12,8 +12,8 @@
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
 \f
 /* By tege@sics.se, with rms.
 
    * Implement -t CHAR or -t REGEX to specify break characters other
      than newline. */
 
+#include <config.h>
+
 #include <stdio.h>
 #include <getopt.h>
-#include <ctype.h>
 #include <sys/types.h>
+
 #include "system.h"
+#include "closeout.h"
+#include "error.h"
+#include "full-write.h"
+#include "safe-read.h"
+#include "xstrtol.h"
 
-char *xmalloc ();
-void error ();
+/* The official name of this program (e.g., no `g' prefix).  */
+#define PROGRAM_NAME "split"
 
-static int convint ();
-static int isdigits ();
-static int stdread ();
-static void line_bytes_split ();
-static void bytes_split ();
-static void cwrite ();
-static void lines_split ();
-static void next_file_name ();
+#define AUTHORS N_ ("Torbjorn Granlund and Richard M. Stallman")
 
-/* Name under which this program was invoked.  */
+/* The name this program was run with. */
 char *program_name;
 
 /* Base name of output files.  */
@@ -52,11 +52,6 @@ static char *outfile_mid;
 /* Pointer to the end of OUTFILE. */
 static char *outfile_end;
 
-/* Status for outfile name generation.  */
-static unsigned outfile_count = -1;
-static unsigned outfile_name_limit = 25 * 26;
-static unsigned outfile_name_generation = 1;
-
 /* Name of input file.  May be "-".  */
 static char *infile;
 
@@ -65,256 +60,139 @@ static int input_desc;
 
 /* Descriptor on which output file is open.  */
 static int output_desc;
-\f
-static void
-usage (reason)
-    char *reason;
-{
-  if (reason != NULL)
-    fprintf (stderr, "%s: %s\n", program_name, reason);
-  fprintf (stderr, "\
-Usage: %s [-lines] [-l lines] [-b bytes[bkm]] [-C bytes[bkm]]\n\
-       [--lines=lines] [--bytes=bytes[bkm]] [--line-bytes=bytes[bkm]]\n\
-       [infile [outfile-prefix]]\n",
-          program_name);
-  exit (2);
-}
-\f
+
+/* If nonzero, print a diagnostic on standard error just before each
+   output file is opened. */
+static int verbose;
+
 static struct option const longopts[] =
 {
   {"bytes", required_argument, NULL, 'b'},
   {"lines", required_argument, NULL, 'l'},
   {"line-bytes", required_argument, NULL, 'C'},
+  {"verbose", no_argument, NULL, 2},
+  {GETOPT_HELP_OPTION_DECL},
+  {GETOPT_VERSION_OPTION_DECL},
   {NULL, 0, NULL, 0}
 };
 
 void
-main (argc, argv)
-    int argc;
-    char *argv[];
+usage (int status)
 {
-  struct stat stat_buf;
-  int num;                     /* numeric argument from command line */
-  enum
-    {
-      type_undef, type_bytes, type_byteslines, type_lines, type_digits
-    } split_type = type_undef;
-  int in_blk_size;             /* optimal block size of input file device */
-  char *buf;                   /* file i/o buffer */
-  int accum = 0;
-  char *outbase;
-  int c;
-  int digits_optind = 0;
-
-  program_name = argv[0];
-
-  /* Parse command line options.  */
-
-  infile = "-";
-  outbase = "x";
-
-  while (1)
-    {
-      /* This is the argv-index of the option we will read next.  */
-      int this_optind = optind ? optind : 1;
-
-      c = getopt_long (argc, argv, "0123456789b:l:C:", longopts, (int *) 0);
-      if (c == EOF)
-       break;
-
-      switch (c)
-       {
-       case 'b':
-         if (split_type != type_undef)
-           usage ("cannot split in more than one way");
-         split_type = type_bytes;
-         if (convint (optarg, &accum) == -1)
-           usage ("invalid number of bytes");
-         break;
-
-       case 'l':
-         if (split_type != type_undef)
-           usage ("cannot split in more than one way");
-         split_type = type_lines;
-         if (!isdigits (optarg))
-           usage ("invalid number of lines");
-         accum = atoi (optarg);
-         break;
-
-       case 'C':
-         if (split_type != type_undef)
-           usage ("cannot split in more than one way");
-         split_type = type_byteslines;
-         if (convint (optarg, &accum) == -1)
-           usage ("invalid number of bytes");
-         break;
-
-       case '0':
-       case '1':
-       case '2':
-       case '3':
-       case '4':
-       case '5':
-       case '6':
-       case '7':
-       case '8':
-       case '9':
-         if (split_type != type_undef && split_type != type_digits)
-           usage ("cannot split in more than one way");
-         if (digits_optind != 0 && digits_optind != this_optind)
-           accum = 0;          /* More than one number given; ignore other. */
-         digits_optind = this_optind;
-         split_type = type_digits;
-         accum = accum * 10 + c - '0';
-         break;
-
-       default:
-         usage ((char *)0);
-       }
-    }
-
-  /* Handle default case.  */
-  if (split_type == type_undef)
+  if (status != 0)
+    fprintf (stderr, _("Try `%s --help' for more information.\n"),
+            program_name);
+  else
     {
-      split_type = type_lines;
-      accum = 1000;
+      printf (_("\
+Usage: %s [OPTION] [INPUT [PREFIX]]\n\
+"),
+             program_name);
+    printf (_("\
+Output fixed-size pieces of INPUT to PREFIXaa, PREFIXab, ...; default\n\
+PREFIX is `x'.  With no INPUT, or when INPUT is -, read standard input.\n\
+\n\
+Mandatory arguments to long options are mandatory for short options too.\n\
+  -b, --bytes=SIZE        put SIZE bytes per output file\n\
+  -C, --line-bytes=SIZE   put at most SIZE bytes of lines per output file\n\
+  -l, --lines=NUMBER      put NUMBER lines per output file\n\
+  -NUMBER                 same as -l NUMBER\n\
+      --verbose           print a diagnostic to standard error just\n\
+                            before each output file is opened\n\
+      --help              display this help and exit\n\
+      --version           output version information and exit\n\
+\n\
+SIZE may have a multiplier suffix: b for 512, k for 1K, m for 1 Meg.\n\
+"));
+      puts (_("\nReport bugs to <bug-textutils@gnu.org>."));
     }
+  exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+}
 
-  if (accum < 1)
-    usage ("invalid number");
-  num = accum;
-
-  /* Get out the filename arguments.  */
-
-  if (optind < argc)
-    infile = argv[optind++];
-
-  if (optind < argc)
-    outbase = argv[optind++];
+/* Compute the next sequential output file name suffix and store it
+   into the string `outfile' at the position pointed to by `outfile_mid'.  */
 
-  if (optind < argc)
-    usage ("too many arguments");
+static void
+next_file_name (void)
+{
+  static unsigned n_digits = 2;
+  char *p;
 
-  /* Open the input file.  */
-  if (!strcmp (infile, "-"))
-    input_desc = 0;
-  else
+  /* Change any suffix of `z's to `a's.  */
+  for (p = outfile_end - 1; *p == 'z'; p--)
     {
-      input_desc = open (infile, O_RDONLY);
-      if (input_desc < 0)
-       error (1, errno, "%s", infile);
+      *p = 'a';
     }
 
-  /* No output file is open now.  */
-  output_desc = -1;
-
-  /* Copy the output file prefix so we can add suffixes to it.
-     26**29 is certainly enough output files!  */
+  /* Increment the rightmost non-`z' character that was present before the
+     above z/a substitutions.  There is guaranteed to be such a character.  */
+  ++(*p);
 
-  outfile = xmalloc (strlen (outbase) + 30);
-  strcpy (outfile, outbase);
-  outfile_mid = outfile + strlen (outfile);
-  outfile_end = outfile_mid + 2;
-  bzero (outfile_mid, 30);
-  outfile_mid[0] = 'a';
-  outfile_mid[1] = 'a' - 1;  /* first call to next_file_name makes it an 'a' */
-
-  /* Get the optimal block size of input device and make a buffer.  */
-
-  if (fstat (input_desc, &stat_buf) < 0)
-    error (1, errno, "%s", infile);
-  in_blk_size = ST_BLKSIZE (stat_buf);
-
-  buf = xmalloc (in_blk_size + 1);
-
-  switch (split_type)
+  /* If the result of that increment operation yielded a `z' and there
+     are only `z's to the left of it, then append two more `a' characters
+     to the end and add 1 (-1 + 2) to the number of digits (we're taking
+     out this `z' and adding two `a's).  */
+  if (*p == 'z' && p == outfile_mid)
     {
-    case type_digits:
-    case type_lines:
-      lines_split (num, buf, in_blk_size);
-      break;
-
-    case type_bytes:
-      bytes_split (num, buf, in_blk_size);
-      break;
-
-    case type_byteslines:
-      line_bytes_split (num);
-      break;
-
-    default:
-      abort ();
+      ++n_digits;
+      ++outfile_mid;
+      *outfile_end++ = 'a';
+      *outfile_end++ = 'a';
     }
-
-  if (close (input_desc) < 0)
-    error (1, errno, "%s", infile);
-  if (output_desc >= 0 && close (output_desc) < 0)
-    error (1, errno, "%s", outfile);
-
-  exit (0);
 }
 
-/* Return nonzero if the string STR is composed entirely of decimal digits.  */
+/* Write BYTES bytes at BP to an output file.
+   If NEW_FILE_FLAG is nonzero, open the next output file.
+   Otherwise add to the same output file already in use.  */
 
-static int
-isdigits (str)
-    char *str;
+static void
+cwrite (int new_file_flag, const char *bp, int bytes)
 {
-  do
+  if (new_file_flag)
     {
-      if (!isdigit (*str))
-       return 0;
-      str++;
+      if (output_desc >= 0 && close (output_desc) < 0)
+       error (EXIT_FAILURE, errno, "%s", outfile);
+
+      next_file_name ();
+      if (verbose)
+       fprintf (stderr, _("creating file `%s'\n"), outfile);
+      output_desc = open (outfile,
+                         O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666);
+      if (output_desc < 0)
+       error (EXIT_FAILURE, errno, "%s", outfile);
     }
-  while (*str);
-  return 1;
+  if (full_write (output_desc, bp, bytes) != bytes)
+    error (EXIT_FAILURE, errno, "%s", outfile);
 }
 
-/* Put the value of the number in STR into *VAL.
-   STR can specify a positive integer, optionally ending in `k'
-   to mean kilo or `m' to mean mega.
-   Return 0 if STR is valid, -1 if not. */
+/* Read NCHARS bytes from the input file into BUF.
+   Return the number of bytes successfully read.
+   If this is less than NCHARS, do not call `stdread' again.  */
 
 static int
-convint (str, val)
-     char *str;
-     int *val;
+stdread (char *buf, int nchars)
 {
-  int multiplier = 1;
-  int arglen = strlen (str);
+  int n_read;
+  int to_be_read = nchars;
 
-  if (arglen > 1)
+  while (to_be_read)
     {
-      switch (str[arglen - 1])
-       {
-       case 'b':
-         multiplier = 512;
-         str[arglen - 1] = '\0';
-         break;
-       case 'k':
-         multiplier = 1024;
-         str[arglen - 1] = '\0';
-         break;
-       case 'm':
-         multiplier = 1048576;
-         str[arglen - 1] = '\0';
-         break;
-       }
+      n_read = safe_read (input_desc, buf, to_be_read);
+      if (n_read < 0)
+       return -1;
+      if (n_read == 0)
+       break;
+      to_be_read -= n_read;
+      buf += n_read;
     }
-  if (!isdigits (str))
-    return -1;
-  *val = atoi (str) * multiplier;
-  return 0;
+  return nchars - to_be_read;
 }
-\f
+
 /* Split into pieces of exactly NCHARS bytes.
    Use buffer BUF, whose size is BUFSIZE.  */
 
 static void
-bytes_split (nchars, buf, bufsize)
-    int nchars;
-    char *buf;
-    int bufsize;
+bytes_split (int nchars, char *buf, int bufsize)
 {
   int n_read;
   int new_file_flag = 1;
@@ -326,7 +204,7 @@ bytes_split (nchars, buf, bufsize)
     {
       n_read = stdread (buf, bufsize);
       if (n_read < 0)
-        error (1, errno, "%s", infile);
+        error (EXIT_FAILURE, errno, "%s", infile);
       bp_out = buf;
       to_read = n_read;
       for (;;)
@@ -353,15 +231,12 @@ bytes_split (nchars, buf, bufsize)
     }
   while (n_read == bufsize);
 }
-\f
+
 /* Split into pieces of exactly NLINES lines.
    Use buffer BUF, whose size is BUFSIZE.  */
 
 static void
-lines_split (nlines, buf, bufsize)
-    int nlines;
-    char *buf;
-    int bufsize;
+lines_split (int nlines, char *buf, int bufsize)
 {
   int n_read;
   char *bp, *bp_out, *eob;
@@ -372,7 +247,7 @@ lines_split (nlines, buf, bufsize)
     {
       n_read = stdread (buf, bufsize);
       if (n_read < 0)
-       error (1, errno, "%s", infile);
+       error (EXIT_FAILURE, errno, "%s", infile);
       bp = bp_out = buf;
       eob = bp + n_read;
       *eob = '\n';
@@ -407,8 +282,7 @@ lines_split (nlines, buf, bufsize)
    where lines longer than NCHARS bytes occur. */
 
 static void
-line_bytes_split (nchars)
-    int nchars;
+line_bytes_split (int nchars)
 {
   int n_read;
   char *bp;
@@ -422,7 +296,7 @@ line_bytes_split (nchars)
 
       n_read = stdread (buf + n_buffered, nchars - n_buffered);
       if (n_read < 0)
-       error (1, errno, "%s", infile);
+       error (EXIT_FAILURE, errno, "%s", infile);
 
       n_buffered += n_read;
       if (n_buffered != nchars)
@@ -444,92 +318,228 @@ line_bytes_split (nchars)
       cwrite (1, buf, bp - buf);
 
       /* Discard the chars we just output; move rest of chunk
-        down to be the start of the next chunk.  */
+        down to be the start of the next chunk.  Source and
+        destination probably overlap.  */
       n_buffered -= bp - buf;
       if (n_buffered > 0)
-       bcopy (bp, buf, n_buffered);
+       memmove (buf, bp, n_buffered);
     }
   while (!eof);
   free (buf);
 }
-\f
-/* Write BYTES bytes at BP to an output file.
-   If NEW_FILE_FLAG is nonzero, open the next output file.
-   Otherwise add to the same output file already in use.  */
 
-static void
-cwrite (new_file_flag, bp, bytes)
-    int new_file_flag;
-    char *bp;
-    int bytes;
+int
+main (int argc, char **argv)
 {
-  if (new_file_flag)
+  struct stat stat_buf;
+  int num;                     /* numeric argument from command line */
+  enum
     {
-      if (output_desc >= 0 && close (output_desc) < 0)
-       error (1, errno, "%s", outfile);
+      type_undef, type_bytes, type_byteslines, type_lines, type_digits
+    } split_type = type_undef;
+  int in_blk_size;             /* optimal block size of input file device */
+  char *buf;                   /* file i/o buffer */
+  int accum = 0;
+  char *outbase;
+  int c;
+  int digits_optind = 0;
 
-      next_file_name ();
-      output_desc = open (outfile, O_WRONLY | O_CREAT | O_TRUNC, 0666);
-      if (output_desc < 0)
-       error (1, errno, "%s", outfile);
-    }
-  if (write (output_desc, bp, bytes) < 0)
-    error (1, errno, "%s", outfile);
-}
+  program_name = argv[0];
+  setlocale (LC_ALL, "");
+  bindtextdomain (PACKAGE, LOCALEDIR);
+  textdomain (PACKAGE);
 
-/* Read NCHARS bytes from the input file into BUF.
-   Return the number of bytes successfully read.
-   If this is less than NCHARS, do not call `stdread' again.  */
+  atexit (close_stdout);
 
-static int
-stdread (buf, nchars)
-    char *buf;
-    int nchars;
-{
-  int n_read;
-  int to_be_read = nchars;
+  /* Parse command line options.  */
 
-  while (to_be_read)
+  infile = "-";
+  outbase = "x";
+
+  while (1)
     {
-      n_read = read (input_desc, buf, to_be_read);
-      if (n_read < 0)
-       return -1;
-      if (n_read == 0)
+      /* This is the argv-index of the option we will read next.  */
+      int this_optind = optind ? optind : 1;
+      long int tmp_long;
+
+      c = getopt_long (argc, argv, "0123456789vb:l:C:", longopts, (int *) 0);
+      if (c == EOF)
        break;
-      to_be_read -= n_read;
-      buf += n_read;
+
+      switch (c)
+       {
+       case 0:
+         break;
+
+       case 'b':
+         if (split_type != type_undef)
+           {
+             error (0, 0, _("cannot split in more than one way"));
+             usage (EXIT_FAILURE);
+           }
+         split_type = type_bytes;
+         if (xstrtol (optarg, NULL, 10, &tmp_long, "bkm") != LONGINT_OK
+             || tmp_long < 0 || tmp_long > INT_MAX)
+           {
+             error (0, 0, _("%s: invalid number of bytes"), optarg);
+             usage (EXIT_FAILURE);
+           }
+         accum = (int) tmp_long;
+         break;
+
+       case 'l':
+         if (split_type != type_undef)
+           {
+             error (0, 0, _("cannot split in more than one way"));
+             usage (EXIT_FAILURE);
+           }
+         split_type = type_lines;
+         if (xstrtol (optarg, NULL, 10, &tmp_long, "") != LONGINT_OK
+             || tmp_long < 0 || tmp_long > INT_MAX)
+           {
+             error (0, 0, _("%s: invalid number of lines"), optarg);
+             usage (EXIT_FAILURE);
+           }
+         accum = (int) tmp_long;
+         break;
+
+       case 'C':
+         if (split_type != type_undef)
+           {
+             error (0, 0, _("cannot split in more than one way"));
+             usage (EXIT_FAILURE);
+           }
+
+         split_type = type_byteslines;
+         if (xstrtol (optarg, NULL, 10, &tmp_long, "bkm") != LONGINT_OK
+             || tmp_long < 0 ||  tmp_long > INT_MAX)
+           {
+             error (0, 0, _("%s: invalid number of bytes"), optarg);
+             usage (EXIT_FAILURE);
+           }
+         accum = (int) tmp_long;
+         break;
+
+       case '0':
+       case '1':
+       case '2':
+       case '3':
+       case '4':
+       case '5':
+       case '6':
+       case '7':
+       case '8':
+       case '9':
+         if (split_type != type_undef && split_type != type_digits)
+           {
+             error (0, 0, _("cannot split in more than one way"));
+             usage (EXIT_FAILURE);
+           }
+         if (digits_optind != 0 && digits_optind != this_optind)
+           accum = 0;          /* More than one number given; ignore other. */
+         digits_optind = this_optind;
+         split_type = type_digits;
+         accum = accum * 10 + c - '0';
+         break;
+
+       case 2:
+         verbose = 1;
+         break;
+
+       case_GETOPT_HELP_CHAR;
+
+       case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
+
+       default:
+         usage (EXIT_FAILURE);
+       }
     }
-  return nchars - to_be_read;
-}
 
-/* Compute the next sequential output file name suffix and store it
-   into the string `outfile' at the position pointed to by `outfile_mid'.  */
+  /* Handle default case.  */
+  if (split_type == type_undef)
+    {
+      split_type = type_lines;
+      accum = 1000;
+    }
 
-static void
-next_file_name ()
-{
-  int x;
-  char *ne;
+  if (accum < 1)
+    {
+      error (0, 0, _("invalid number"));
+      usage (EXIT_FAILURE);
+    }
+  num = accum;
+
+  /* Get out the filename arguments.  */
+
+  if (optind < argc)
+    infile = argv[optind++];
+
+  if (optind < argc)
+    outbase = argv[optind++];
 
-  outfile_count++;
-  if (outfile_count < outfile_name_limit)
+  if (optind < argc)
     {
-      for (ne = outfile_end - 1; ; ne--)
-       {
-         x = *ne;
-         if (x != 'z')
-           break;
-         *ne = 'a';
-       }
-      *ne = x + 1;
-      return;
+      error (0, 0, _("too many arguments"));
+      usage (EXIT_FAILURE);
     }
 
-  outfile_count = 0;
-  outfile_name_limit *= 26;
-  outfile_name_generation++;
-  *outfile_mid++ = 'z';
-  for (x = 0; x <= outfile_name_generation; x++)
-    outfile_mid[x] = 'a';
-  outfile_end += 2;
+  /* Open the input file.  */
+  if (STREQ (infile, "-"))
+    input_desc = 0;
+  else
+    {
+      input_desc = open (infile, O_RDONLY);
+      if (input_desc < 0)
+       error (EXIT_FAILURE, errno, "%s", infile);
+    }
+  /* Binary I/O is safer when bytecounts are used.  */
+  SET_BINARY (input_desc);
+
+  /* No output file is open now.  */
+  output_desc = -1;
+
+  /* Copy the output file prefix so we can add suffixes to it.
+     26**29 is certainly enough output files!  */
+
+  outfile = xmalloc (strlen (outbase) + 30);
+  strcpy (outfile, outbase);
+  outfile_mid = outfile + strlen (outfile);
+  outfile_end = outfile_mid + 2;
+  memset (outfile_mid, 0, 30);
+  outfile_mid[0] = 'a';
+  outfile_mid[1] = 'a' - 1;  /* first call to next_file_name makes it an 'a' */
+
+  /* Get the optimal block size of input device and make a buffer.  */
+
+  if (fstat (input_desc, &stat_buf) < 0)
+    error (EXIT_FAILURE, errno, "%s", infile);
+  in_blk_size = ST_BLKSIZE (stat_buf);
+
+  buf = xmalloc (in_blk_size + 1);
+
+  switch (split_type)
+    {
+    case type_digits:
+    case type_lines:
+      lines_split (num, buf, in_blk_size);
+      break;
+
+    case type_bytes:
+      bytes_split (num, buf, in_blk_size);
+      break;
+
+    case type_byteslines:
+      line_bytes_split (num);
+      break;
+
+    default:
+      abort ();
+    }
+
+  if (close (input_desc) < 0)
+    error (EXIT_FAILURE, errno, "%s", infile);
+  if (output_desc >= 0 && close (output_desc) < 0)
+    error (EXIT_FAILURE, errno, "%s", outfile);
+
+  exit (EXIT_SUCCESS);
 }