gzip --no-name: avoid spurious warning
[debian/gzip] / gzip.c
diff --git a/gzip.c b/gzip.c
index a013540c4960609e3dae62f9741afaba4ac00da3..0fca5a3b129851e7077ae05457e42e8ebc85fcc6 100644 (file)
--- a/gzip.c
+++ b/gzip.c
@@ -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
@@ -59,10 +59,10 @@ static char const *const license_msg[] = {
 #include <sys/types.h>
 #include <signal.h>
 #include <stdbool.h>
+#include <stddef.h>
 #include <sys/stat.h>
 #include <errno.h>
 
-#include "closein.h"
 #include "tailor.h"
 #include "gzip.h"
 #include "intprops.h"
@@ -70,7 +70,9 @@ static char const *const license_msg[] = {
 #include "revision.h"
 #include "timespec.h"
 
-#include "fcntl-safer.h"
+#include "dirname.h"
+#include "dosname.h"
+#include "fcntl--.h"
 #include "getopt.h"
 #include "ignore-value.h"
 #include "stat-time.h"
@@ -79,7 +81,6 @@ static char const *const license_msg[] = {
 
                 /* configuration */
 
-#include <fcntl.h>
 #include <limits.h>
 #include <unistd.h>
 #include <stdlib.h>
@@ -97,8 +98,6 @@ static char const *const license_msg[] = {
 #  include <utimens.h>
 #endif
 
-#define RW_USER (S_IRUSR | S_IWUSR)  /* creation mode for open() */
-
 #ifndef MAX_PATH_LEN
 #  define MAX_PATH_LEN   1024 /* max pathname length */
 #endif
@@ -160,6 +159,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) */
@@ -199,18 +206,23 @@ 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 */
 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 */
+static char dfname[MAX_PATH_LEN]; /* name of dir containing output file */
 static struct stat istat;         /* status for input file */
 int  ifd;                  /* input file descriptor */
 int  ofd;                  /* output file descriptor */
+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[] =
   {
@@ -239,6 +251,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.  */
@@ -267,6 +281,7 @@ 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 */
@@ -277,7 +292,7 @@ static const struct option longopts[] =
     {"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 }
 };
 
@@ -304,6 +319,7 @@ 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;
+static void finish_out (void);
       int main          (int argc, char **argv);
 static int (*work) (int infile, int outfile) = zip; /* function to call */
 
@@ -351,7 +367,9 @@ local void help()
 #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",
@@ -412,9 +430,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';
 
@@ -432,7 +448,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 */
@@ -512,13 +528,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 */
@@ -543,6 +559,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++;
@@ -550,6 +571,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;
@@ -557,7 +581,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;
@@ -633,7 +657,7 @@ int main (int argc, char **argv)
     /* And get to work */
     if (file_count != 0) {
         if (to_stdout && !test && !list && (!decompress || !ascii)) {
-            SET_BINARY_MODE(fileno(stdout));
+            SET_BINARY_MODE (STDOUT_FILENO);
         }
         while (optind < argc) {
             treat_file(argv[optind++]);
@@ -641,9 +665,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 */
 }
@@ -674,7 +714,7 @@ local void treat_stdin()
 {
     if (!force && !list
         && (presume_input_tty
-            || isatty(fileno((FILE *)(decompress ? stdin : stdout))))) {
+            || isatty (decompress ? STDIN_FILENO : STDOUT_FILENO))) {
         /* Do not send compressed data to the terminal or read it from
          * the terminal. We get here when user invoked the program
          * without parameters, so be helpful. According to the GNU standards:
@@ -700,16 +740,16 @@ local void treat_stdin()
     }
 
     if (decompress || !ascii) {
-        SET_BINARY_MODE(fileno(stdin));
+      SET_BINARY_MODE (STDIN_FILENO);
     }
     if (!test && !list && (!decompress || !ascii)) {
-        SET_BINARY_MODE(fileno(stdout));
+      SET_BINARY_MODE (STDOUT_FILENO);
     }
     strcpy(ifname, "stdin");
     strcpy(ofname, "stdout");
 
     /* Get the file's time stamp and size.  */
-    if (fstat (fileno (stdin), &istat) != 0)
+    if (fstat (STDIN_FILENO, &istat) != 0)
       {
         progerror ("standard input");
         do_exit (ERROR);
@@ -727,7 +767,8 @@ local void treat_stdin()
     clear_bufs(); /* clear input and output buffers */
     to_stdout = 1;
     part_nb = 0;
-    ifd = fileno(stdin);
+    ifd = STDIN_FILENO;
+    stdin_was_read = true;
 
     if (decompress) {
         method = get_method(ifd);
@@ -743,7 +784,8 @@ local void treat_stdin()
     /* Actually do the compression/decompression. Loop over zipped members.
      */
     for (;;) {
-        if ((*work)(fileno(stdin), fileno(stdout)) != OK) return;
+        if (work (STDIN_FILENO, STDOUT_FILENO) != OK)
+          return;
 
         if (input_eof ())
           break;
@@ -769,6 +811,48 @@ local void treat_stdin()
     }
 }
 
+static char const dot = '.';
+
+/* True if the cached directory for calls to openat etc. is DIR, with
+   length DIRLEN.  DIR need not be null-terminated.  DIRLEN must be
+   less than MAX_PATH_LEN.  */
+static bool
+atdir_eq (char const *dir, ptrdiff_t dirlen)
+{
+  if (dirlen == 0)
+    dir = &dot, dirlen = 1;
+  return memcmp (dfname, dir, dirlen) == 0 && !dfname[dirlen];
+}
+
+/* Set the directory used for calls to openat etc. to be the directory
+   DIR, with length DIRLEN.  DIR need not be null-terminated.
+   DIRLEN must be less than MAX_PATH_LEN.  Return a file descriptor for
+   the directory, or -1 if one could not be obtained.  */
+static int
+atdir_set (char const *dir, ptrdiff_t dirlen)
+{
+  /* Don't bother opening directories on older systems that
+     lack openat and unlinkat.  It's not worth the porting hassle.  */
+  #if HAVE_OPENAT && HAVE_UNLINKAT
+    enum { try_opening_directories = true };
+  #else
+    enum { try_opening_directories = false };
+  #endif
+
+  if (try_opening_directories && ! atdir_eq (dir, dirlen))
+    {
+      if (0 <= dfd)
+        close (dfd);
+      if (dirlen == 0)
+        dir = &dot, dirlen = 1;
+      memcpy (dfname, dir, dirlen);
+      dfname[dirlen] = '\0';
+      dfd = open (dfname, O_SEARCH | O_DIRECTORY);
+    }
+
+  return dfd;
+}
+
 /* ========================================================================
  * Compress or decompress the given file
  */
@@ -888,7 +972,7 @@ local void treat_file(iname)
      * a new ofname and save the original name in the compressed file.
      */
     if (to_stdout) {
-        ofd = fileno(stdout);
+        ofd = STDOUT_FILENO;
         /* Keep remove_ofname_fd negative.  */
     } else {
         if (create_outfile() != OK) return;
@@ -926,19 +1010,26 @@ local void treat_file(iname)
 
     if (!to_stdout)
       {
-
         copy_stat (&istat);
-        if (close (ofd) != 0)
+
+        if ((synchronous
+             && ((0 <= dfd && fdatasync (dfd) != 0 && errno != EINVAL)
+                 || (fsync (ofd) != 0 && errno != EINVAL)))
+            || close (ofd) != 0)
           write_error ();
 
         if (!keep)
           {
             sigset_t oldset;
             int unlink_errno;
+            char *ifbase = last_component (ifname);
+            int ufd = atdir_eq (ifname, ifbase - ifname) ? dfd : -1;
+            int res;
 
             sigprocmask (SIG_BLOCK, &caught_signals, &oldset);
             remove_ofname_fd = -1;
-            unlink_errno = xunlink (ifname) == 0 ? 0 : errno;
+            res = ufd < 0 ? xunlink (ifname) : unlinkat (ufd, ifbase, 0);
+            unlink_errno = res == 0 ? 0 : errno;
             sigprocmask (SIG_SETMASK, &oldset, NULL);
 
             if (unlink_errno)
@@ -989,6 +1080,19 @@ local int create_outfile()
   int name_shortened = 0;
   int flags = (O_WRONLY | O_CREAT | O_EXCL
                | (ascii && decompress ? 0 : O_BINARY));
+  char const *base = ofname;
+  int atfd = AT_FDCWD;
+
+  if (!keep)
+    {
+      char const *b = last_component (ofname);
+      int f = atdir_set (ofname, b - ofname);
+      if (0 <= f)
+        {
+          base = b;
+          atfd = f;
+        }
+    }
 
   for (;;)
     {
@@ -996,7 +1100,7 @@ local int create_outfile()
       sigset_t oldset;
 
       sigprocmask (SIG_BLOCK, &caught_signals, &oldset);
-      remove_ofname_fd = ofd = OPEN (ofname, flags, RW_USER);
+      remove_ofname_fd = ofd = openat (atfd, base, flags, S_IRUSR | S_IWUSR);
       open_errno = errno;
       sigprocmask (SIG_SETMASK, &oldset, NULL);
 
@@ -1046,8 +1150,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;
@@ -1079,13 +1181,6 @@ local char *get_suffix(name)
                    : 0] = z_suffix;
     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);
@@ -1096,7 +1191,7 @@ local char *get_suffix(name)
     slen = strlen(suffix);
     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;
        }
@@ -1106,13 +1201,15 @@ local char *get_suffix(name)
 }
 
 
-/* Open file NAME with the given flags and mode and store its status
+/* Open file NAME with the given flags and store its status
    into *ST.  Return a file descriptor to the newly opened file, or -1
    (setting errno) on failure.  */
 static int
-open_and_stat (char *name, int flags, mode_t mode, struct stat *st)
+open_and_stat (char *name, int flags, struct stat *st)
 {
   int fd;
+  int atfd = AT_FDCWD;
+  char const *base = name;
 
   /* Refuse to follow symbolic links unless -c or -f.  */
   if (!to_stdout && !force)
@@ -1121,7 +1218,7 @@ open_and_stat (char *name, int flags, mode_t mode, 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))
@@ -1133,7 +1230,18 @@ open_and_stat (char *name, int flags, mode_t mode, struct stat *st)
         }
     }
 
-  fd = OPEN (name, flags, mode);
+  if (!keep)
+    {
+      char const *b = last_component (name);
+      int f = atdir_set (name, b - name);
+      if (0 <= f)
+        {
+          base = b;
+          atfd = f;
+        }
+    }
+
+  fd = openat (atfd, base, flags);
   if (0 <= fd && fstat (fd, st) != 0)
     {
       int e = errno;
@@ -1177,7 +1285,7 @@ open_input_file (iname, sbuf)
     strcpy(ifname, iname);
 
     /* If input file exists, return OK. */
-    fd = open_and_stat (ifname, open_flags, RW_USER, sbuf);
+    fd = open_and_stat (ifname, open_flags, sbuf);
     if (0 <= fd)
       return fd;
 
@@ -1185,9 +1293,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 */
@@ -1218,7 +1324,7 @@ open_input_file (iname, sbuf)
         if (sizeof ifname <= ilen + strlen (s))
           goto name_too_long;
         strcat(ifname, s);
-        fd = open_and_stat (ifname, open_flags, RW_USER, sbuf);
+        fd = open_and_stat (ifname, open_flags, sbuf);
         if (0 <= fd)
           return fd;
         if (errno != ENOENT)
@@ -1394,7 +1500,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
@@ -1431,8 +1537,15 @@ 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));
           }
 
         magic[8] = get_byte ();  /* Ignore extra flags.  */
@@ -1548,7 +1661,7 @@ local int get_method(in)
             inptr--;
         last_member = 1;
         if (imagic0 != EOF) {
-            write_buf(fileno(stdout), magic, 1);
+            write_buf (STDOUT_FILENO, magic, 1);
             bytes_out++;
         }
     }
@@ -1637,7 +1750,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.
@@ -1658,12 +1771,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 ("??? ?? ??:?? ");
@@ -1728,8 +1842,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;
@@ -1762,7 +1875,7 @@ local int check_ofname()
     if (!force) {
         int ok = 0;
         fprintf (stderr, "%s: %s already exists;", program_name, ofname);
-        if (foreground && (presume_input_tty || isatty(fileno(stdin)))) {
+        if (foreground && (presume_input_tty || isatty (STDIN_FILENO))) {
             fprintf(stderr, " do you wish to overwrite (y or n)? ");
             fflush(stderr);
             ok = yesno();
@@ -1896,16 +2009,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 {
@@ -1985,6 +2090,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.
  */