doc: correct a diagnostic in man page to match actual
[debian/gzip] / gzip.c
diff --git a/gzip.c b/gzip.c
index 4762e88a17f9660ea76a7d4ed85015d806f98344..a013540c4960609e3dae62f9741afaba4ac00da3 100644 (file)
--- a/gzip.c
+++ b/gzip.c
@@ -1,6 +1,6 @@
 /* gzip (GNU zip) -- compress files with zip algorithm and 'compress' interface
 
-   Copyright (C) 1999, 2001-2002, 2006-2007, 2009-2010 Free Software
+   Copyright (C) 1999, 2001-2002, 2006-2007, 2009-2016 Free Software
    Foundation, Inc.
    Copyright (C) 1992-1993 Jean-loup Gailly
 
@@ -29,7 +29,7 @@
  */
 
 static char const *const license_msg[] = {
-"Copyright (C) 2007, 2010 Free Software Foundation, Inc.",
+"Copyright (C) 2016 Free Software Foundation, Inc.",
 "Copyright (C) 1993 Jean-loup Gailly.",
 "This is free software.  You may redistribute copies of it under the terms of",
 "the GNU General Public License <http://www.gnu.org/licenses/gpl.html>.",
@@ -65,6 +65,7 @@ static char const *const license_msg[] = {
 #include "closein.h"
 #include "tailor.h"
 #include "gzip.h"
+#include "intprops.h"
 #include "lzw.h"
 #include "revision.h"
 #include "timespec.h"
@@ -74,6 +75,7 @@ static char const *const license_msg[] = {
 #include "ignore-value.h"
 #include "stat-time.h"
 #include "version.h"
+#include "yesno.h"
 
                 /* configuration */
 
@@ -88,15 +90,7 @@ static char const *const license_msg[] = {
 #endif
 #if !NO_DIR
 # include <dirent.h>
-# ifndef _D_EXACT_NAMLEN
-#  define _D_EXACT_NAMLEN(dp) strlen ((dp)->d_name)
-# endif
-#endif
-
-#ifdef CLOSEDIR_VOID
-# define CLOSEDIR(d) (closedir(d), 0)
-#else
-# define CLOSEDIR(d) closedir(d)
+# include <savedir.h>
 #endif
 
 #ifndef NO_UTIME
@@ -118,15 +112,11 @@ static char const *const license_msg[] = {
 #endif
 
 #ifdef off_t
-  off_t lseek OF((int fd, off_t offset, int whence));
-#endif
-
-#ifndef OFF_T_MIN
-#define OFF_T_MIN (~ (off_t) 0 << (sizeof (off_t) * CHAR_BIT - 1))
+  off_t lseek (int fd, off_t offset, int whence);
 #endif
 
 #ifndef OFF_T_MAX
-#define OFF_T_MAX (~ (off_t) 0 - OFF_T_MIN)
+# define OFF_T_MAX TYPE_MAXIMUM (off_t)
 #endif
 
 /* Use SA_NOCLDSTOP as a proxy for whether the sigaction machinery is
@@ -144,10 +134,6 @@ static char const *const license_msg[] = {
 # define HAVE_WORKING_O_NOFOLLOW 0
 #endif
 
-#ifndef ELOOP
-# define ELOOP EINVAL
-#endif
-
 /* Separator for file name parts (see shorten_name()) */
 #ifdef NO_MULTIPLE_DOTS
 #  define PART_SEP "-"
@@ -174,33 +160,33 @@ DECLARE(uch, window, 2L*WSIZE);
    is deliberately not documented, and only for testing.  */
 static bool presume_input_tty;
 
-int ascii = 0;        /* convert end-of-lines to local OS conventions */
-int to_stdout = 0;    /* output to stdout (-c) */
-int decompress = 0;   /* decompress (-d) */
-int force = 0;        /* don't ask questions, compress links (-f) */
-int no_name = -1;     /* don't save or restore the original file name */
-int no_time = -1;     /* don't save or restore the original file time */
-int recursive = 0;    /* recurse through directories (-r) */
-int list = 0;         /* list the file contents (-l) */
-int verbose = 0;      /* be verbose (-v) */
-int quiet = 0;        /* be very quiet (-q) */
-int do_lzw = 0;       /* generate output compatible with old compress (-Z) */
-int test = 0;         /* test .gz file integrity */
-int foreground = 0;   /* set if program run in foreground */
-char *program_name;   /* program name */
-int maxbits = BITS;   /* max bits per code for LZW */
-int method = DEFLATED;/* compression method */
-int level = 6;        /* compression level */
-int exit_code = OK;   /* program exit code */
-int save_orig_name;   /* set if original name must be saved */
-int last_member;      /* set for .zip and .Z files */
-int part_nb;          /* number of parts in .gz file */
-struct timespec time_stamp; /* original time stamp (modification time) */
-off_t ifile_size;      /* input file size, -1 for devices (debug only) */
-char *env;            /* contents of GZIP env variable */
-char **args = NULL;   /* argv pointer if GZIP env variable defined */
-char const *z_suffix; /* default suffix (can be set with --suffix) */
-size_t z_len;         /* strlen(z_suffix) */
+static int ascii = 0;        /* convert end-of-lines to local OS conventions */
+       int to_stdout = 0;    /* output to stdout (-c) */
+static int decompress = 0;   /* decompress (-d) */
+static int force = 0;        /* don't ask questions, compress links (-f) */
+static int keep = 0;         /* keep (don't delete) input files */
+static int no_name = -1;     /* don't save or restore the original file name */
+static int no_time = -1;     /* don't save or restore the original file time */
+static int recursive = 0;    /* recurse through directories (-r) */
+static int list = 0;         /* list the file contents (-l) */
+       int verbose = 0;      /* be verbose (-v) */
+       int quiet = 0;        /* be very quiet (-q) */
+static int do_lzw = 0;       /* generate output compatible with old compress (-Z) */
+       int test = 0;         /* test .gz file integrity */
+static int foreground = 0;   /* set if program run in foreground */
+       char *program_name;   /* program name */
+       int maxbits = BITS;   /* max bits per code for LZW */
+       int method = DEFLATED;/* compression method */
+       int level = 6;        /* compression level */
+       int exit_code = OK;   /* program exit code */
+       int save_orig_name;   /* set if original name must be saved */
+static int last_member;      /* set for .zip and .Z files */
+static int part_nb;          /* number of parts in .gz file */
+       struct timespec time_stamp; /* original time stamp (modification time) */
+       off_t ifile_size;      /* input file size, -1 for devices (debug only) */
+static char *env;            /* contents of GZIP env variable */
+static char const *z_suffix; /* default suffix (can be set with --suffix) */
+static size_t z_len;         /* strlen(z_suffix) */
 
 /* The set of signals that are caught.  */
 static sigset_t caught_signals;
@@ -215,11 +201,11 @@ static int volatile remove_ofname_fd = -1;
 
 off_t bytes_in;             /* number of input bytes */
 off_t bytes_out;            /* number of output bytes */
-off_t total_in;                    /* input bytes for all files */
-off_t total_out;           /* output bytes for all files */
+static off_t total_in;      /* input bytes for all files */
+static off_t total_out;            /* output bytes for all files */
 char ifname[MAX_PATH_LEN]; /* input file name */
 char ofname[MAX_PATH_LEN]; /* output file name */
-struct stat istat;         /* status for input file */
+static struct stat istat;         /* status for input file */
 int  ifd;                  /* input file descriptor */
 int  ofd;                  /* output file descriptor */
 unsigned insize;           /* valid bytes in inbuf */
@@ -234,10 +220,8 @@ static int handled_sig[] =
 #ifdef SIGHUP
     , SIGHUP
 #endif
-#ifdef SIGPIPE
+#if SIGPIPE
     , SIGPIPE
-#else
-# define SIGPIPE 0
 #endif
 #ifdef SIGTERM
     , SIGTERM
@@ -254,10 +238,16 @@ static int handled_sig[] =
    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
 enum
 {
-  PRESUME_INPUT_TTY_OPTION = CHAR_MAX + 1
+  PRESUME_INPUT_TTY_OPTION = CHAR_MAX + 1,
+
+  /* A value greater than all valid long options, used as a flag to
+     distinguish options derived from the GZIP environment variable.  */
+  ENV_OPTION
 };
 
-struct option longopts[] =
+static char const shortopts[] = "ab:cdfhH?klLmMnNqrS:tvVZ123456789";
+
+static const struct option longopts[] =
 {
  /* { name  has_arg  *flag  val } */
     {"ascii",      0, 0, 'a'}, /* ascii text mode */
@@ -269,6 +259,7 @@ struct option longopts[] =
     {"force",      0, 0, 'f'}, /* force overwrite of output file */
     {"help",       0, 0, 'h'}, /* give help */
  /* {"pkzip",      0, 0, 'k'},    force output in pkzip format */
+    {"keep",       0, 0, 'k'}, /* keep (don't delete) input files */
     {"list",       0, 0, 'l'}, /* list .gz file contents */
     {"license",    0, 0, 'L'}, /* display software license */
     {"no-name",    0, 0, 'n'}, /* don't save or restore original name & time */
@@ -292,31 +283,32 @@ struct option longopts[] =
 
 /* local functions */
 
-local void try_help     OF((void)) ATTRIBUTE_NORETURN;
-local void help         OF((void));
-local void license      OF((void));
-local void version      OF((void));
-local int input_eof    OF((void));
-local void treat_stdin  OF((void));
-local void treat_file   OF((char *iname));
-local int create_outfile OF((void));
-local char *get_suffix  OF((char *name));
-local int  open_input_file OF((char *iname, struct stat *sbuf));
-local int  make_ofname  OF((void));
-local void shorten_name  OF((char *name));
-local int  get_method   OF((int in));
-local void do_list      OF((int ifd, int method));
-local int  check_ofname OF((void));
-local void copy_stat    OF((struct stat *ifstat));
-local void install_signal_handlers OF((void));
-local void remove_output_file OF((void));
-local RETSIGTYPE abort_gzip_signal OF((int));
-local void do_exit      OF((int exitcode)) ATTRIBUTE_NORETURN;
-      int main          OF((int argc, char **argv));
-int (*work) OF((int infile, int outfile)) = zip; /* function to call */
+local void try_help     (void) ATTRIBUTE_NORETURN;
+local void help         (void);
+local void license      (void);
+local void version      (void);
+local int input_eof    (void);
+local void treat_stdin  (void);
+local void treat_file   (char *iname);
+local int create_outfile (void);
+local char *get_suffix  (char *name);
+local int  open_input_file (char *iname, struct stat *sbuf);
+local void discard_input_bytes (size_t nbytes, unsigned int flags);
+local int  make_ofname  (void);
+local void shorten_name  (char *name);
+local int  get_method   (int in);
+local void do_list      (int ifd, int method);
+local int  check_ofname (void);
+local void copy_stat    (struct stat *ifstat);
+local void install_signal_handlers (void);
+local void remove_output_file (void);
+local RETSIGTYPE abort_gzip_signal (int);
+local void do_exit      (int exitcode) ATTRIBUTE_NORETURN;
+      int main          (int argc, char **argv);
+static int (*work) (int infile, int outfile) = zip; /* function to call */
 
 #if ! NO_DIR
-local void treat_dir    OF((int fd, char *dir));
+local void treat_dir    (int fd, char *dir);
 #endif
 
 #define strequ(s1, s2) (strcmp((s1),(s2)) == 0)
@@ -346,6 +338,7 @@ local void help()
  "  -f, --force       force overwrite of output file and compress links",
  "  -h, --help        give this help",
 /*  -k, --pkzip       force output in pkzip format */
+ "  -k, --keep        keep (don't delete) input files",
  "  -l, --list        list compressed file contents",
  "  -L, --license     display software license",
 #ifdef UNDOCUMENTED
@@ -410,7 +403,9 @@ int main (int argc, char **argv)
 {
     int file_count;     /* number of files to process */
     size_t proglen;     /* length of program_name */
-    int optc;           /* current option */
+    char **argv_copy;
+    int env_argc;
+    char **env_argv;
 
     EXPAND(argc, argv); /* wild card expansion if necessary */
 
@@ -424,8 +419,9 @@ int main (int argc, char **argv)
       program_name[proglen - 4] = '\0';
 
     /* Add options in GZIP environment variable if there is one */
-    env = add_envopt(&argc, &argv, OPTIONS_VAR);
-    if (env != NULL) args = argv;
+    argv_copy = argv;
+    env = add_envopt (&env_argc, &argv_copy, OPTIONS_VAR);
+    env_argv = env ? argv_copy : NULL;
 
 #ifndef GNU_STANDARD
 # define GNU_STANDARD 1
@@ -449,8 +445,53 @@ int main (int argc, char **argv)
     z_suffix = Z_SUFFIX;
     z_len = strlen(z_suffix);
 
-    while ((optc = getopt_long (argc, argv, "ab:cdfhH?lLmMnNqrS:tvVZ123456789",
-                                longopts, (int *)0)) != -1) {
+    while (true) {
+        int optc;
+        int longind = -1;
+
+        if (env_argv)
+          {
+            if (env_argv[optind] && strequ (env_argv[optind], "--"))
+              optc = ENV_OPTION + '-';
+            else
+              {
+                optc = getopt_long (env_argc, env_argv, shortopts, longopts,
+                                    &longind);
+                if (0 <= optc)
+                  optc += ENV_OPTION;
+                else
+                  {
+                    if (optind != env_argc)
+                      {
+                        fprintf (stderr,
+                                 ("%s: %s: non-option in "OPTIONS_VAR
+                                  " environment variable\n"),
+                                 program_name, env_argv[optind]);
+                        try_help ();
+                      }
+
+                    /* Wait until here before warning, so that GZIP='-q'
+                       doesn't warn.  */
+                    if (env_argc != 1 && !quiet)
+                      fprintf (stderr,
+                               ("%s: warning: "OPTIONS_VAR" environment variable"
+                                " is deprecated; use an alias or script\n"),
+                               program_name);
+
+                    /* Start processing ARGC and ARGV instead.  */
+                    free (env_argv);
+                    env_argv = NULL;
+                    optind = 1;
+                    longind = -1;
+                  }
+              }
+          }
+
+        if (!env_argv)
+          optc = getopt_long (argc, argv, shortopts, longopts, &longind);
+        if (optc < 0)
+          break;
+
         switch (optc) {
         case 'a':
             ascii = 1; break;
@@ -472,6 +513,8 @@ int main (int argc, char **argv)
             force++; break;
         case 'h': case 'H':
             help(); do_exit(OK); break;
+        case 'k':
+            keep = 1; break;
         case 'l':
             list = decompress = to_stdout = 1; break;
         case 'L':
@@ -481,12 +524,15 @@ int main (int argc, char **argv)
         case 'M': /* undocumented, may change later */
             no_time = 0; break;
         case 'n':
+        case 'n' + ENV_OPTION:
             no_name = no_time = 1; break;
         case 'N':
+        case 'N' + ENV_OPTION:
             no_name = no_time = 0; break;
         case PRESUME_INPUT_TTY_OPTION:
             presume_input_tty = true; break;
         case 'q':
+        case 'q' + ENV_OPTION:
             quiet = 1; verbose = 0; break;
         case 'r':
 #if NO_DIR
@@ -508,6 +554,7 @@ int main (int argc, char **argv)
             test = decompress = to_stdout = 1;
             break;
         case 'v':
+        case 'v' + ENV_OPTION:
             verbose++; quiet = 0; break;
         case 'V':
             version(); do_exit(OK); break;
@@ -520,12 +567,28 @@ int main (int argc, char **argv)
             try_help ();
             break;
 #endif
+        case '1' + ENV_OPTION:  case '2' + ENV_OPTION:  case '3' + ENV_OPTION:
+        case '4' + ENV_OPTION:  case '5' + ENV_OPTION:  case '6' + ENV_OPTION:
+        case '7' + ENV_OPTION:  case '8' + ENV_OPTION:  case '9' + ENV_OPTION:
+            optc -= ENV_OPTION;
+            /* Fall through.  */
         case '1':  case '2':  case '3':  case '4':
         case '5':  case '6':  case '7':  case '8':  case '9':
             level = optc - '0';
             break;
+
         default:
-            /* Error message already emitted by getopt_long. */
+            if (ENV_OPTION <= optc && optc != ENV_OPTION + '?')
+              {
+                /* Output a diagnostic, since getopt_long didn't.  */
+                fprintf (stderr, "%s: ", program_name);
+                if (longind < 0)
+                  fprintf (stderr, "-%c: ", optc - ENV_OPTION);
+                else
+                  fprintf (stderr, "--%s: ", longopts[longind].name);
+                fprintf (stderr, ("option not valid in "OPTIONS_VAR
+                                  " environment variable\n"));
+              }
             try_help ();
         }
     } /* loop on all arguments */
@@ -624,11 +687,15 @@ local void treat_stdin()
          *
          * Here we use the --force option to get the other behavior.
          */
-        fprintf(stderr,
-    "%s: compressed data not %s a terminal. Use -f to force %scompression.\n",
-                program_name, decompress ? "read from" : "written to",
-                decompress ? "de" : "");
-        fprintf (stderr, "For help, type: %s -h\n", program_name);
+        if (! quiet)
+          fprintf (stderr,
+                   ("%s: compressed data not %s a terminal."
+                    " Use -f to force %scompression.\n"
+                    "For help, type: %s -h\n"),
+                   program_name,
+                   decompress ? "read from" : "written to",
+                   decompress ? "de" : "",
+                   program_name);
         do_exit(ERROR);
     }
 
@@ -859,25 +926,29 @@ local void treat_file(iname)
 
     if (!to_stdout)
       {
-        sigset_t oldset;
-        int unlink_errno;
 
         copy_stat (&istat);
         if (close (ofd) != 0)
           write_error ();
 
-        sigprocmask (SIG_BLOCK, &caught_signals, &oldset);
-        remove_ofname_fd = -1;
-        unlink_errno = xunlink (ifname) == 0 ? 0 : errno;
-        sigprocmask (SIG_SETMASK, &oldset, NULL);
-
-        if (unlink_errno)
+        if (!keep)
           {
-            WARN ((stderr, "%s: ", program_name));
-            if (!quiet)
+            sigset_t oldset;
+            int unlink_errno;
+
+            sigprocmask (SIG_BLOCK, &caught_signals, &oldset);
+            remove_ofname_fd = -1;
+            unlink_errno = xunlink (ifname) == 0 ? 0 : errno;
+            sigprocmask (SIG_SETMASK, &oldset, NULL);
+
+            if (unlink_errno)
               {
-                errno = unlink_errno;
-                perror (ifname);
+                WARN ((stderr, "%s: ", program_name));
+                if (!quiet)
+                  {
+                    errno = unlink_errno;
+                    perror (ifname);
+                  }
               }
           }
       }
@@ -897,9 +968,9 @@ local void treat_file(iname)
         } else {
             display_ratio(bytes_in-(bytes_out-header_bytes), bytes_in, stderr);
         }
-        if (!test && !to_stdout) {
-            fprintf(stderr, " -- replaced with %s", ofname);
-        }
+        if (!test && !to_stdout)
+          fprintf(stderr, " -- %s %s", keep ? "created" : "replaced with",
+                  ofname);
         fprintf(stderr, "\n");
     }
 }
@@ -988,11 +1059,25 @@ local char *get_suffix(name)
 #ifdef MAX_EXT_CHARS
           "z",
 #endif
-          NULL};
-    char const **suf = known_suffixes;
+        NULL, NULL};
+    char const **suf;
+    bool suffix_of_builtin = false;
 
-    *suf = z_suffix;
-    if (strequ(z_suffix, "z")) suf++; /* check long suffixes first */
+    /* Normally put Z_SUFFIX at the start of KNOWN_SUFFIXES, but if it
+       is a suffix of one of them, put it at the end.  */
+    for (suf = known_suffixes + 1; *suf; suf++)
+      {
+        size_t suflen = strlen (*suf);
+        if (z_len < suflen && strequ (z_suffix, *suf + suflen - z_len))
+          {
+            suffix_of_builtin = true;
+            break;
+          }
+      }
+    known_suffixes[suffix_of_builtin
+                   ? sizeof known_suffixes / sizeof *known_suffixes - 2
+                   : 0] = z_suffix;
+    suf = known_suffixes + suffix_of_builtin;
 
 #ifdef SUFFIX_SEP
     /* strip a version number from the file name */
@@ -1246,6 +1331,25 @@ local int make_ofname()
     return WARNING;
 }
 
+/* Discard NBYTES input bytes from the input, or up through the next
+   zero byte if NBYTES == (size_t) -1.  If FLAGS say that the header
+   CRC should be computed, update the CRC accordingly.  */
+static void
+discard_input_bytes (nbytes, flags)
+    size_t nbytes;
+    unsigned int flags;
+{
+  while (nbytes != 0)
+    {
+      uch c = get_byte ();
+      if (flags & HEADER_CRC)
+        updcrc (&c, 1);
+      if (nbytes != (size_t) -1)
+        nbytes--;
+      else if (! c)
+        break;
+    }
+}
 
 /* ========================================================================
  * Check the magic number of the input file and update ofname if an
@@ -1262,7 +1366,7 @@ local int get_method(in)
     int in;        /* input file descriptor */
 {
     uch flags;     /* compression flags */
-    char magic[2]; /* magic header */
+    uch magic[10]; /* magic header */
     int imagic0;   /* first magic byte or EOF */
     int imagic1;   /* like magic[1], but can represent EOF */
     ulg stamp;     /* time stamp */
@@ -1272,19 +1376,19 @@ local int get_method(in)
      */
     if (force && to_stdout) {
         imagic0 = try_byte();
-        magic[0] = (char) imagic0;
+        magic[0] = imagic0;
         imagic1 = try_byte ();
-        magic[1] = (char) imagic1;
+        magic[1] = imagic1;
         /* If try_byte returned EOF, magic[1] == (char) EOF.  */
     } else {
-        magic[0] = (char)get_byte();
+        magic[0] = get_byte ();
         imagic0 = 0;
         if (magic[0]) {
-            magic[1] = (char)get_byte();
+            magic[1] = get_byte ();
             imagic1 = 0; /* avoid lint warning */
         } else {
             imagic1 = try_byte ();
-            magic[1] = (char) imagic1;
+            magic[1] = imagic1;
         }
     }
     method = -1;                 /* unknown yet */
@@ -1314,13 +1418,6 @@ local int get_method(in)
             exit_code = ERROR;
             return -1;
         }
-        if ((flags & CONTINUATION) != 0) {
-            fprintf(stderr,
-                    "%s: %s is a multi-part gzip file -- not supported\n",
-                    program_name, ifname);
-            exit_code = ERROR;
-            if (force <= 1) return -1;
-        }
         if ((flags & RESERVED) != 0) {
             fprintf(stderr,
                     "%s: %s has flags 0x%x -- not supported\n",
@@ -1338,44 +1435,52 @@ local int get_method(in)
             time_stamp.tv_nsec = 0;
           }
 
-        (void)get_byte();  /* Ignore extra flags for the moment */
-        (void)get_byte();  /* Ignore OS type for the moment */
+        magic[8] = get_byte ();  /* Ignore extra flags.  */
+        magic[9] = get_byte ();  /* Ignore OS type.  */
+
+        if (flags & HEADER_CRC)
+          {
+            magic[2] = DEFLATED;
+            magic[3] = flags;
+            magic[4] = stamp & 0xff;
+            magic[5] = (stamp >> 8) & 0xff;
+            magic[6] = (stamp >> 16) & 0xff;
+            magic[7] = stamp >> 24;
+            updcrc (NULL, 0);
+            updcrc (magic, 10);
+          }
 
-        if ((flags & CONTINUATION) != 0) {
-            unsigned part = (unsigned)get_byte();
-            part |= ((unsigned)get_byte())<<8;
-            if (verbose) {
-                fprintf(stderr,"%s: %s: part number %u\n",
-                        program_name, ifname, part);
-            }
-        }
         if ((flags & EXTRA_FIELD) != 0) {
-            unsigned len = (unsigned)get_byte();
-            len |= ((unsigned)get_byte())<<8;
+            uch lenbuf[2];
+            unsigned int len = lenbuf[0] = get_byte ();
+            len |= (lenbuf[1] = get_byte ()) << 8;
             if (verbose) {
                 fprintf(stderr,"%s: %s: extra field of %u bytes ignored\n",
                         program_name, ifname, len);
             }
-            while (len--) (void)get_byte();
+            if (flags & HEADER_CRC)
+              updcrc (lenbuf, 2);
+            discard_input_bytes (len, flags);
         }
 
         /* Get original file name if it was truncated */
         if ((flags & ORIG_NAME) != 0) {
             if (no_name || (to_stdout && !list) || part_nb > 1) {
                 /* Discard the old name */
-                char c; /* dummy used for NeXTstep 3.0 cc optimizer bug */
-                do {c=get_byte();} while (c != 0);
+                discard_input_bytes (-1, flags);
             } else {
                 /* Copy the base name. Keep a directory prefix intact. */
                 char *p = gzip_base_name (ofname);
                 char *base = p;
                 for (;;) {
-                    *p = (char)get_char();
+                    *p = (char) get_byte ();
                     if (*p++ == '\0') break;
                     if (p >= ofname+sizeof(ofname)) {
                         gzip_error ("corrupted input -- file name too large");
                     }
                 }
+                if (flags & HEADER_CRC)
+                  updcrc ((uch *) base, p - base);
                 p = gzip_base_name (base);
                 memmove (base, p, strlen (p) + 1);
                 /* If necessary, adapt the name to local OS conventions: */
@@ -1388,10 +1493,27 @@ local int get_method(in)
 
         /* Discard file comment if any */
         if ((flags & COMMENT) != 0) {
-            while (get_char() != 0) /* null */ ;
+            discard_input_bytes (-1, flags);
         }
+
+        if (flags & HEADER_CRC)
+          {
+            unsigned int crc16 = updcrc (magic, 0) & 0xffff;
+            unsigned int header16 = get_byte ();
+            header16 |= ((unsigned int) get_byte ()) << 8;
+            if (header16 != crc16)
+              {
+                fprintf (stderr,
+                         "%s: %s: header checksum 0x%04x != computed checksum 0x%04x\n",
+                         program_name, ifname, header16, crc16);
+                exit_code = ERROR;
+                if (force <= 1)
+                  return -1;
+              }
+          }
+
         if (part_nb == 1) {
-            header_bytes = inptr + 2*sizeof(long); /* include crc and size */
+            header_bytes = inptr + 2*4; /* include crc and size */
         }
 
     } else if (memcmp(magic, PKZIP_MAGIC, 2) == 0 && inptr == 2
@@ -1515,8 +1637,7 @@ local void do_list(ifd, method)
     bytes_out = -1L;
     bytes_in = ifile_size;
 
-#if RECORD_IO == 0
-    if (method == DEFLATED && !last_member) {
+    if (!RECORD_IO && method == DEFLATED && !last_member) {
         /* Get the crc and uncompressed size for gzip'ed (not zip'ed) files.
          * If the lseek fails, we could use read() to get to the end, but
          * --list is used to get quick results.
@@ -1534,7 +1655,7 @@ local void do_list(ifd, method)
             bytes_out = LG(buf+4);
         }
     }
-#endif /* RECORD_IO */
+
     if (verbose)
       {
         struct tm *tm = localtime (&time_stamp.tv_sec);
@@ -1659,6 +1780,21 @@ local int check_ofname()
     return OK;
 }
 
+/* Change the owner and group of a file.  FD is a file descriptor for
+   the file and NAME its name.  Change it to user UID and to group GID.
+   If UID or GID is -1, though, do not change the corresponding user
+   or group.  */
+static void
+do_chown (int fd, char const *name, uid_t uid, gid_t gid)
+{
+#ifndef NO_CHOWN
+# if HAVE_FCHOWN
+  ignore_value (fchown (fd, uid, gid));
+# else
+  ignore_value (chown (name, uid, gid));
+# endif
+#endif
+}
 
 /* ========================================================================
  * Copy modes, times, ownership from input file to output file.
@@ -1685,7 +1821,7 @@ local void copy_stat(ifstat)
         }
       }
 
-    if (gl_futimens (ofd, ofname, timespec) != 0)
+    if (fdutimens (ofd, ofname, timespec) != 0)
       {
         int e = errno;
         WARN ((stderr, "%s: ", program_name));
@@ -1697,16 +1833,14 @@ local void copy_stat(ifstat)
       }
 #endif
 
-#ifndef NO_CHOWN
-    /* Copy ownership */
-# if HAVE_FCHOWN
-    ignore_value (fchown (ofd, ifstat->st_uid, ifstat->st_gid));
-# elif HAVE_CHOWN
-    ignore_value (chown (ofname, ifstat->st_uid, ifstat->st_gid));
-# endif
-#endif
+    /* Change the group first, then the permissions, then the owner.
+       That way, the permissions will be correct on systems that allow
+       users to give away files, without introducing a security hole.
+       Security depends on permissions not containing the setuid or
+       setgid bits.  */
+
+    do_chown (ofd, ofname, -1, ifstat->st_gid);
 
-    /* Copy the protection modes */
 #if HAVE_FCHMOD
     r = fchmod (ofd, mode);
 #else
@@ -1720,21 +1854,24 @@ local void copy_stat(ifstat)
             perror(ofname);
         }
     }
+
+    do_chown (ofd, ofname, ifstat->st_uid, -1);
 }
 
 #if ! NO_DIR
 
 /* ========================================================================
- * Recurse through the given directory. This code is taken from ncompress.
+ * Recurse through the given directory.
  */
 local void treat_dir (fd, dir)
     int fd;
     char *dir;
 {
-    struct dirent *dp;
     DIR      *dirp;
     char     nbuf[MAX_PATH_LEN];
-    int      len;
+    char *entries;
+    char const *entry;
+    size_t entrylen;
 
     dirp = fdopendir (fd);
 
@@ -1743,29 +1880,21 @@ local void treat_dir (fd, dir)
         close (fd);
         return ;
     }
-    /*
-     ** WARNING: the following algorithm could occasionally cause
-     ** compress to produce error warnings of the form "<filename>.gz
-     ** already has .gz suffix - ignored". This occurs when the
-     ** .gz output file is inserted into the directory below
-     ** readdir's current pointer.
-     ** These warnings are harmless but annoying, so they are suppressed
-     ** with option -r (except when -v is on). An alternative
-     ** to allowing this would be to store the entire directory
-     ** list in memory, then compress the entries in the stored
-     ** list. Given the depth-first recursive algorithm used here,
-     ** this could use up a tremendous amount of memory. I don't
-     ** think it's worth it. -- Dave Mack
-     ** (An other alternative might be two passes to avoid depth-first.)
-     */
 
-    while ((errno = 0, dp = readdir(dirp)) != NULL) {
+    entries = streamsavedir (dirp, SAVEDIR_SORT_NONE);
+    if (! entries)
+      progerror (dir);
+    if (closedir (dirp) != 0)
+      progerror (dir);
+    if (! entries)
+      return;
 
-        if (strequ(dp->d_name,".") || strequ(dp->d_name,"..")) {
-            continue;
-        }
-        len = strlen(dir);
-        if (len + _D_EXACT_NAMLEN (dp) + 1 < MAX_PATH_LEN - 1) {
+    for (entry = entries; *entry; entry += entrylen + 1) {
+        size_t len = strlen (dir);
+        entrylen = strlen (entry);
+        if (strequ (entry, ".") || strequ (entry, ".."))
+          continue;
+        if (len + entrylen < MAX_PATH_LEN - 2) {
             strcpy(nbuf,dir);
             if (len != 0 /* dir = "" means current dir on Amiga */
 #ifdef PATH_SEP2
@@ -1777,18 +1906,15 @@ local void treat_dir (fd, dir)
             ) {
                 nbuf[len++] = PATH_SEP;
             }
-            strcpy(nbuf+len, dp->d_name);
+            strcpy (nbuf + len, entry);
             treat_file(nbuf);
         } else {
             fprintf(stderr,"%s: %s/%s: pathname too long\n",
-                    program_name, dir, dp->d_name);
+                    program_name, dir, entry);
             exit_code = ERROR;
         }
     }
-    if (errno != 0)
-        progerror(dir);
-    if (CLOSEDIR(dirp) != 0)
-        progerror(dir);
+    free (entries);
 }
 #endif /* ! NO_DIR */
 
@@ -1846,8 +1972,6 @@ local void do_exit(exitcode)
     in_exit = 1;
     free(env);
     env  = NULL;
-    free(args);
-    args = NULL;
     FREE(inbuf);
     FREE(outbuf);
     FREE(d_buf);