Port to Oracle Solaris Studio 12.4
[debian/gzip] / gzip.c
diff --git a/gzip.c b/gzip.c
index a013540c4960609e3dae62f9741afaba4ac00da3..d66530a70bdce1d19a2687658e9ef0b9f19a1e18 100644 (file)
--- a/gzip.c
+++ b/gzip.c
@@ -59,6 +59,7 @@ 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>
 
@@ -70,7 +71,8 @@ static char const *const license_msg[] = {
 #include "revision.h"
 #include "timespec.h"
 
-#include "fcntl-safer.h"
+#include "dirname.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) */
@@ -205,12 +212,15 @@ 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 +249,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 +279,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 +290,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 }
 };
 
@@ -351,7 +364,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",
@@ -543,6 +558,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 +570,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;
@@ -633,7 +656,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++]);
@@ -644,6 +667,12 @@ int main (int argc, char **argv)
     if (list && !quiet && file_count > 1) {
         do_list(-1, -1); /* print totals */
     }
+    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 +703,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 +729,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 +756,7 @@ local void treat_stdin()
     clear_bufs(); /* clear input and output buffers */
     to_stdout = 1;
     part_nb = 0;
-    ifd = fileno(stdin);
+    ifd = STDIN_FILENO;
 
     if (decompress) {
         method = get_method(ifd);
@@ -743,7 +772,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 +799,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 +960,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 +998,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 +1068,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 +1088,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);
 
@@ -1106,13 +1198,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)
@@ -1133,7 +1227,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 +1282,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;
 
@@ -1218,7 +1323,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)
@@ -1548,7 +1653,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++;
         }
     }
@@ -1658,12 +1763,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 ("??? ?? ??:?? ");
@@ -1762,7 +1868,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();