#include <sys/types.h>
#include <signal.h>
#include <stdbool.h>
+#include <stddef.h>
#include <sys/stat.h>
#include <errno.h>
#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"
/* configuration */
-#include <fcntl.h>
#include <limits.h>
#include <unistd.h>
#include <stdlib.h>
# 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
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) */
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[] =
{
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. */
{"-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 */
{"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 }
};
#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",
recursive = 1;
#endif
break;
+
+ case RSYNCABLE_OPTION:
+ case RSYNCABLE_OPTION + ENV_OPTION:
+ rsync = 1;
+ break;
case 'S':
#ifdef NO_MULTIPLE_DOTS
if (*optarg == '.') optarg++;
z_len = strlen(optarg);
z_suffix = optarg;
break;
+ case SYNCHRONOUS_OPTION:
+ synchronous = true;
+ break;
case 't':
test = decompress = to_stdout = 1;
break;
/* 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++]);
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 */
}
{
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:
}
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);
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);
/* 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;
}
}
+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
*/
* 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;
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)
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 (;;)
{
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);
}
-/* 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)
}
}
- 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;
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;
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)
inptr--;
last_member = 1;
if (imagic0 != EOF) {
- write_buf(fileno(stdout), magic, 1);
+ write_buf (STDOUT_FILENO, magic, 1);
bytes_out++;
}
}
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 ("??? ?? ??:?? ");
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();