tests: don’t be so strict about timestamps
[debian/gzip] / gzip.c
diff --git a/gzip.c b/gzip.c
index 6cfc561c3f1fb04562fb132867dc394d7579464b..59ab553e448a157a267dcf2cd9eaf84d9f2ac903 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-2016 Free Software
+   Copyright (C) 1999, 2001-2002, 2006-2007, 2009-2017 Free Software
    Foundation, Inc.
    Copyright (C) 1992-1993 Jean-loup Gailly
 
  */
 
 static char const *const license_msg[] = {
-"Copyright (C) 2016 Free Software Foundation, Inc.",
+"Copyright (C) 2017 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>.",
+"the GNU General Public License <https://www.gnu.org/licenses/gpl.html>.",
 "There is NO WARRANTY, to the extent permitted by law.",
 0};
 
@@ -43,7 +43,7 @@ static char const *const license_msg[] = {
  *     or stdout with -c option or if stdin used as input.
  * If the output file name had to be truncated, the original name is kept
  * in the compressed file.
- * On MSDOS, file.tmp -> file.tmz. On VMS, file.tmp -> file.tmp-gz.
+ * On MSDOS, file.tmp -> file.tmz.
  *
  * Using gz on MSDOS would create too many file name conflicts. For
  * example, foo.txt -> foo.tgz (.tgz must be reserved as shorthand for
@@ -63,7 +63,6 @@ static char const *const license_msg[] = {
 #include <sys/stat.h>
 #include <errno.h>
 
-#include "closein.h"
 #include "tailor.h"
 #include "gzip.h"
 #include "intprops.h"
@@ -72,11 +71,13 @@ static char const *const license_msg[] = {
 #include "timespec.h"
 
 #include "dirname.h"
+#include "dosname.h"
 #include "fcntl--.h"
 #include "getopt.h"
 #include "ignore-value.h"
 #include "stat-time.h"
 #include "version.h"
+#include "xalloc.h"
 #include "yesno.h"
 
                 /* configuration */
@@ -159,6 +160,14 @@ DECLARE(uch, window, 2L*WSIZE);
    is deliberately not documented, and only for testing.  */
 static bool presume_input_tty;
 
+/* If true, transfer output data to the output file's storage device
+   when supported.  Otherwise, if the system crashes around the time
+   gzip is run, the user might lose both input and output data.  See:
+   Pillai TS et al.  All file systems are not created equal: on the
+   complexity of crafting crash-consistent applications. OSDI'14. 2014:433-48.
+   https://www.usenix.org/conference/osdi14/technical-sessions/presentation/pillai */
+static bool synchronous;
+
 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) */
@@ -181,12 +190,19 @@ static int foreground = 0;   /* set if program run in foreground */
        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 original timestamp (modification time).  If the original is
+   unknown, TIME_STAMP.tv_nsec is negative.  If the original is
+   greater than struct timespec range, TIME_STAMP is the maximal
+   struct timespec value; this can happen on hosts with 32-bit signed
+   time_t because the gzip format's MTIME is 32-bit unsigned.
+   The original cannot be less than struct timespec range.  */
+struct timespec time_stamp;
+
 /* The set of signals that are caught.  */
 static sigset_t caught_signals;
 
@@ -198,6 +214,8 @@ static int volatile exiting_signal;
 /* If nonnegative, close this file descriptor and unlink ofname on error.  */
 static int volatile remove_ofname_fd = -1;
 
+static bool stdin_was_read;
+
 off_t bytes_in;             /* number of input bytes */
 off_t bytes_out;            /* number of output bytes */
 static off_t total_in;      /* input bytes for all files */
@@ -212,6 +230,7 @@ static int dfd = -1;       /* output directory file descriptor */
 unsigned insize;           /* valid bytes in inbuf */
 unsigned inptr;            /* index of next byte to be processed in inbuf */
 unsigned outcnt;           /* bytes in output buffer */
+int rsync = 0;             /* make ryncable chunks */
 
 static int handled_sig[] =
   {
@@ -240,6 +259,8 @@ static int handled_sig[] =
 enum
 {
   PRESUME_INPUT_TTY_OPTION = CHAR_MAX + 1,
+  RSYNCABLE_OPTION,
+  SYNCHRONOUS_OPTION,
 
   /* A value greater than all valid long options, used as a flag to
      distinguish options derived from the GZIP environment variable.  */
@@ -268,23 +289,23 @@ static const struct option longopts[] =
     {"-presume-input-tty", no_argument, NULL, PRESUME_INPUT_TTY_OPTION},
     {"quiet",      0, 0, 'q'}, /* quiet mode */
     {"silent",     0, 0, 'q'}, /* quiet mode */
+    {"synchronous",0, 0, SYNCHRONOUS_OPTION},
     {"recursive",  0, 0, 'r'}, /* recurse through directories */
     {"suffix",     1, 0, 'S'}, /* use given suffix instead of .gz */
     {"test",       0, 0, 't'}, /* test compressed file integrity */
-    {"no-time",    0, 0, 'T'}, /* don't save or restore the time stamp */
     {"verbose",    0, 0, 'v'}, /* verbose mode */
     {"version",    0, 0, 'V'}, /* display version number */
     {"fast",       0, 0, '1'}, /* compress faster */
     {"best",       0, 0, '9'}, /* compress better */
     {"lzw",        0, 0, 'Z'}, /* make output compatible with old compress */
     {"bits",       1, 0, 'b'}, /* max number of bits per code (implies -Z) */
-
+    {"rsyncable",  0, 0, RSYNCABLE_OPTION}, /* make rsync-friendly archive */
     { 0, 0, 0, 0 }
 };
 
 /* local functions */
 
-local void try_help     (void) ATTRIBUTE_NORETURN;
+local noreturn void try_help (void);
 local void help         (void);
 local void license      (void);
 local void version      (void);
@@ -304,7 +325,8 @@ 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;
+local noreturn void do_exit (int exitcode);
+static void finish_out (void);
       int main          (int argc, char **argv);
 static int (*work) (int infile, int outfile) = zip; /* function to call */
 
@@ -343,16 +365,18 @@ local void help()
  "  -l, --list        list compressed file contents",
  "  -L, --license     display software license",
 #ifdef UNDOCUMENTED
- "  -m, --no-time     do not save or restore the original modification time",
+ "  -m                do not save or restore the original modification time",
  "  -M, --time        save or restore the original modification time",
 #endif
- "  -n, --no-name     do not save or restore the original name and time stamp",
- "  -N, --name        save or restore the original name and time stamp",
+ "  -n, --no-name     do not save or restore the original name and timestamp",
+ "  -N, --name        save or restore the original name and timestamp",
  "  -q, --quiet       suppress all warnings",
 #if ! NO_DIR
  "  -r, --recursive   operate recursively on directories",
 #endif
+ "      --rsyncable   make rsync-friendly archive",
  "  -S, --suffix=SUF  use suffix SUF on compressed files",
+ "      --synchronous synchronous output (safer if system crashes, but slower)",
  "  -t, --test        test compressed file integrity",
  "  -v, --verbose     verbose mode",
  "  -V, --version     display version number",
@@ -413,9 +437,7 @@ int main (int argc, char **argv)
     program_name = gzip_base_name (argv[0]);
     proglen = strlen (program_name);
 
-    atexit (close_stdin);
-
-    /* Suppress .exe for MSDOS, OS/2 and VMS: */
+    /* Suppress .exe for MSDOS and OS/2: */
     if (4 < proglen && strequ (program_name + proglen - 4, ".exe"))
       program_name[proglen - 4] = '\0';
 
@@ -433,7 +455,7 @@ int main (int argc, char **argv)
      * gzip even if it is invoked under the name gunzip or zcat.
      *
      * Systems which do not support links can still use -d or -dc.
-     * Ignore an .exe extension for MSDOS, OS/2 and VMS.
+     * Ignore an .exe extension for MSDOS and OS/2.
      */
     if (strncmp (program_name, "un",  2) == 0     /* ungzip, uncompress */
         || strncmp (program_name, "gun", 3) == 0) /* gunzip */
@@ -513,13 +535,13 @@ int main (int argc, char **argv)
         case 'f':
             force++; break;
         case 'h': case 'H':
-            help(); do_exit(OK); break;
+            help (); finish_out (); break;
         case 'k':
             keep = 1; break;
         case 'l':
             list = decompress = to_stdout = 1; break;
         case 'L':
-            license(); do_exit(OK); break;
+            license (); finish_out (); break;
         case 'm': /* undocumented, may change later */
             no_time = 1; break;
         case 'M': /* undocumented, may change later */
@@ -544,6 +566,11 @@ int main (int argc, char **argv)
             recursive = 1;
 #endif
             break;
+
+        case RSYNCABLE_OPTION:
+        case RSYNCABLE_OPTION + ENV_OPTION:
+            rsync = 1;
+            break;
         case 'S':
 #ifdef NO_MULTIPLE_DOTS
             if (*optarg == '.') optarg++;
@@ -551,6 +578,9 @@ int main (int argc, char **argv)
             z_len = strlen(optarg);
             z_suffix = optarg;
             break;
+        case SYNCHRONOUS_OPTION:
+            synchronous = true;
+            break;
         case 't':
             test = decompress = to_stdout = 1;
             break;
@@ -558,7 +588,7 @@ int main (int argc, char **argv)
         case 'v' + ENV_OPTION:
             verbose++; quiet = 0; break;
         case 'V':
-            version(); do_exit(OK); break;
+            version (); finish_out (); break;
         case 'Z':
 #ifdef LZW
             do_lzw = 1; break;
@@ -572,7 +602,7 @@ int main (int argc, char **argv)
         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.  */
+            FALLTHROUGH;
         case '1':  case '2':  case '3':  case '4':
         case '5':  case '6':  case '7':  case '8':  case '9':
             level = optc - '0';
@@ -642,9 +672,25 @@ int main (int argc, char **argv)
     } else {  /* Standard input */
         treat_stdin();
     }
-    if (list && !quiet && file_count > 1) {
-        do_list(-1, -1); /* print totals */
-    }
+    if (stdin_was_read && close (STDIN_FILENO) != 0)
+      {
+        strcpy (ifname, "stdin");
+        read_error ();
+      }
+    if (list)
+      {
+        /* Output any totals, and check for output errors.  */
+        if (!quiet && 1 < file_count)
+          do_list (-1, -1);
+        if (fflush (stdout) != 0)
+          write_error ();
+      }
+    if (to_stdout
+        && ((synchronous
+             && fdatasync (STDOUT_FILENO) != 0 && errno != EINVAL)
+            || close (STDOUT_FILENO) != 0)
+        && errno != EBADF)
+      write_error ();
     do_exit(exit_code);
     return exit_code; /* just to avoid lint warning */
 }
@@ -709,7 +755,7 @@ local void treat_stdin()
     strcpy(ifname, "stdin");
     strcpy(ofname, "stdout");
 
-    /* Get the file's time stamp and size.  */
+    /* Get the file's timestamp and size.  */
     if (fstat (STDIN_FILENO, &istat) != 0)
       {
         progerror ("standard input");
@@ -729,6 +775,7 @@ local void treat_stdin()
     to_stdout = 1;
     part_nb = 0;
     ifd = STDIN_FILENO;
+    stdin_was_read = true;
 
     if (decompress) {
         method = get_method(ifd);
@@ -972,13 +1019,7 @@ local void treat_file(iname)
       {
         copy_stat (&istat);
 
-        /* If KEEP, transfer output data to the output file's storage device.
-           Otherwise, if the system crashed now the user might lose
-           both input and output data.  See: Pillai TS et al.  All
-           file systems are not created equal: on the complexity of
-           crafting crash-consistent applications. OSDI'14. 2014:433-48.
-           https://www.usenix.org/conference/osdi14/technical-sessions/presentation/pillai  */
-        if ((!keep
+        if ((synchronous
              && ((0 <= dfd && fdatasync (dfd) != 0 && errno != EINVAL)
                  || (fsync (ofd) != 0 && errno != EINVAL)))
             || close (ofd) != 0)
@@ -1116,8 +1157,6 @@ local int create_outfile()
  * also accepted suffixes. For Unix, we do not want to accept any
  * .??z suffix as indicating a compressed file; some people use .xyz
  * to denote volume data.
- *   On systems allowing multiple versions of the same file (such as VMS),
- * this function removes any version suffix in the given name.
  */
 local char *get_suffix(name)
     char *name;
@@ -1144,18 +1183,14 @@ local char *get_suffix(name)
             break;
           }
       }
+
+    char *z_lower = xstrdup(z_suffix);
+    strlwr(z_lower);
     known_suffixes[suffix_of_builtin
                    ? sizeof known_suffixes / sizeof *known_suffixes - 2
-                   : 0] = z_suffix;
+                   : 0] = z_lower;
     suf = known_suffixes + suffix_of_builtin;
 
-#ifdef SUFFIX_SEP
-    /* strip a version number from the file name */
-    {
-        char *v = strrchr(name, SUFFIX_SEP);
-        if (v != NULL) *v = '\0';
-    }
-#endif
     nlen = strlen(name);
     if (nlen <= MAX_SUFFIX+2) {
         strcpy(suffix, name);
@@ -1164,15 +1199,18 @@ local char *get_suffix(name)
     }
     strlwr(suffix);
     slen = strlen(suffix);
+    char *match = NULL;
     do {
        int s = strlen(*suf);
-       if (slen > s && suffix[slen-s-1] != PATH_SEP
+       if (slen > s && ! ISSLASH (suffix[slen - s - 1])
            && strequ(suffix + slen - s, *suf)) {
-           return name+nlen-s;
+           match = name+nlen-s;
+           break;
        }
     } while (*++suf != NULL);
+    free(z_lower);
 
-    return NULL;
+    return match;
 }
 
 
@@ -1193,7 +1231,7 @@ open_and_stat (char *name, int flags, struct stat *st)
         flags |= O_NOFOLLOW;
       else
         {
-#if HAVE_LSTAT || defined lstat
+#ifdef S_ISLNK
           if (lstat (name, st) != 0)
             return -1;
           else if (S_ISLNK (st->st_mode))
@@ -1268,9 +1306,7 @@ open_input_file (iname, sbuf)
         progerror(ifname);
         return -1;
     }
-    /* file.ext doesn't exist, try adding a suffix (after removing any
-     * version number for VMS).
-     */
+    /* File.ext doesn't exist.  Try adding a suffix.  */
     s = get_suffix(ifname);
     if (s != NULL) {
         progerror(ifname); /* ifname already has z suffix and does not exist */
@@ -1439,7 +1475,7 @@ discard_input_bytes (nbytes, flags)
  * original name was given and to_stdout is not set.
  * Return the compression method, -1 for error, -2 for warning.
  * Set inptr to the offset of the next byte to be processed.
- * Updates time_stamp if there is one and --no-time is not used.
+ * Updates time_stamp if there is one and neither -m nor -n is used.
  * This function may be called repeatedly for an input file consisting
  * of several contiguous gzip'ed members.
  * IN assertions: there is at least one remaining compressed member.
@@ -1452,7 +1488,7 @@ local int get_method(in)
     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 */
+    ulg stamp;     /* timestamp */
 
     /* If --force and --stdout, zcat == cat, so do not complain about
      * premature end of file: use try_byte instead of get_byte.
@@ -1477,7 +1513,7 @@ local int get_method(in)
     method = -1;                 /* unknown yet */
     part_nb++;                   /* number of parts in gzip file */
     header_bytes = 0;
-    last_member = RECORD_IO;
+    last_member = 0;
     /* assume multiple members in gzip file except for record oriented I/O */
 
     if (memcmp(magic, GZIP_MAGIC, 2) == 0
@@ -1514,8 +1550,19 @@ local int get_method(in)
         stamp |= ((ulg)get_byte()) << 24;
         if (stamp != 0 && !no_time)
           {
-            time_stamp.tv_sec = stamp;
-            time_stamp.tv_nsec = 0;
+            if (stamp <= TYPE_MAXIMUM (time_t))
+              {
+                time_stamp.tv_sec = stamp;
+                time_stamp.tv_nsec = 0;
+              }
+            else
+              {
+                WARN ((stderr,
+                       "%s: %s: MTIME %lu out of range for this platform\n",
+                       program_name, ifname, stamp));
+                time_stamp.tv_sec = TYPE_MAXIMUM (time_t);
+                time_stamp.tv_nsec = TIMESPEC_RESOLUTION - 1;
+              }
           }
 
         magic[8] = get_byte ();  /* Ignore extra flags.  */
@@ -1720,7 +1767,7 @@ local void do_list(ifd, method)
     bytes_out = -1L;
     bytes_in = ifile_size;
 
-    if (!RECORD_IO && method == DEFLATED && !last_member) {
+    if (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.
@@ -1741,12 +1788,13 @@ local void do_list(ifd, method)
 
     if (verbose)
       {
+        static char const month_abbr[][4]
+          = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+              "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
         struct tm *tm = localtime (&time_stamp.tv_sec);
         printf ("%5s %08lx ", methods[method], crc);
         if (tm)
-          printf ("%s%3d %02d:%02d ",
-                  ("Jan\0Feb\0Mar\0Apr\0May\0Jun\0Jul\0Aug\0Sep\0Oct\0Nov\0Dec"
-                   + 4 * tm->tm_mon),
+          printf ("%s%3d %02d:%02d ", month_abbr[tm->tm_mon],
                   tm->tm_mday, tm->tm_hour, tm->tm_min);
         else
           printf ("??? ?? ??:?? ");
@@ -1811,8 +1859,7 @@ local void shorten_name(name)
      * 1234.678.012.gz -> 123.678.012.gz
      */
     do {
-        p = strrchr(name, PATH_SEP);
-        p = p ? p+1 : name;
+        p = last_component (name);
         while (*p) {
             plen = strcspn(p, PART_SEP);
             p += plen;
@@ -1890,21 +1937,23 @@ local void copy_stat(ifstat)
     int r;
 
 #ifndef NO_UTIME
+    bool restoring;
     struct timespec timespec[2];
     timespec[0] = get_stat_atime (ifstat);
     timespec[1] = get_stat_mtime (ifstat);
+    restoring = (decompress && 0 <= time_stamp.tv_nsec
+                 && ! (timespec[1].tv_sec == time_stamp.tv_sec
+                       && timespec[1].tv_nsec == time_stamp.tv_nsec));
+    if (restoring)
+      timespec[1] = time_stamp;
 
-    if (decompress && 0 <= time_stamp.tv_nsec
-        && ! (timespec[1].tv_sec == time_stamp.tv_sec
-              && timespec[1].tv_nsec == time_stamp.tv_nsec))
+    if (fdutimens (ofd, ofname, timespec) == 0)
       {
-        timespec[1] = time_stamp;
-        if (verbose > 1) {
-            fprintf(stderr, "%s: time stamp restored\n", ofname);
+        if (restoring && 1 < verbose) {
+            fprintf(stderr, "%s: timestamp restored\n", ofname);
         }
       }
-
-    if (fdutimens (ofd, ofname, timespec) != 0)
+    else
       {
         int e = errno;
         WARN ((stderr, "%s: ", program_name));
@@ -1979,16 +2028,8 @@ local void treat_dir (fd, dir)
           continue;
         if (len + entrylen < MAX_PATH_LEN - 2) {
             strcpy(nbuf,dir);
-            if (len != 0 /* dir = "" means current dir on Amiga */
-#ifdef PATH_SEP2
-                && dir[len-1] != PATH_SEP2
-#endif
-#ifdef PATH_SEP3
-                && dir[len-1] != PATH_SEP3
-#endif
-            ) {
-                nbuf[len++] = PATH_SEP;
-            }
+            if (*last_component (nbuf) && !ISSLASH (nbuf[len - 1]))
+              nbuf[len++] = '/';
             strcpy (nbuf + len, entry);
             treat_file(nbuf);
         } else {
@@ -2068,6 +2109,14 @@ local void do_exit(exitcode)
     exit(exitcode);
 }
 
+static void
+finish_out (void)
+{
+  if (fclose (stdout) != 0)
+    write_error ();
+  do_exit (OK);
+}
+
 /* ========================================================================
  * Close and unlink the output file.
  */