bool opt_stdout = false;
bool opt_force = false;
bool opt_keep_original = false;
+bool opt_synchronous = true;
bool opt_robot = false;
bool opt_ignore_check = false;
OPT_LZMA1,
OPT_LZMA2,
+ OPT_NO_SYNC,
OPT_SINGLE_STREAM,
OPT_NO_SPARSE,
OPT_FILES,
{ "force", no_argument, NULL, 'f' },
{ "stdout", no_argument, NULL, 'c' },
{ "to-stdout", no_argument, NULL, 'c' },
+ { "no-sync", no_argument, NULL, OPT_NO_SYNC },
{ "single-stream", no_argument, NULL, OPT_SINGLE_STREAM },
{ "no-sparse", no_argument, NULL, OPT_NO_SPARSE },
{ "suffix", required_argument, NULL, 'S' },
optarg, 0, UINT64_MAX);
break;
+ case OPT_NO_SYNC:
+ opt_synchronous = false;
+ break;
+
default:
message_try_help();
tuklib_exit(E_ERROR, E_ERROR, false);
opt_stdout = true;
}
+ // Don't use fsync() if --keep is specified or implied.
+ // However, don't document this as "--keep implies --no-sync"
+ // because if syncing support was added to --flush-timeout,
+ // it would sync even if --keep was specified.
+ if (opt_keep_original)
+ opt_synchronous = false;
+
// When compressing, if no --format flag was used, or it
// was --format=auto, we compress to the .xz format.
if (opt_mode == MODE_COMPRESS && opt_format == FORMAT_AUTO)
# include <io.h>
#else
# include <poll.h>
+# include <libgen.h>
static bool warn_fchown;
#endif
# define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG)
#endif
+#if defined(_WIN32) && !defined(__CYGWIN__)
+# define fsync _commit
+#endif
+
#ifndef O_BINARY
# define O_BINARY 0
#endif
# define O_NOCTTY 0
#endif
+#ifndef O_SEARCH
+# define O_SEARCH O_RDONLY
+#endif
+
+#ifndef O_DIRECTORY
+# define O_DIRECTORY 0
+#endif
+
// Using this macro to silence a warning from gcc -Wlogical-op.
#if EAGAIN == EWOULDBLOCK
# define IS_EAGAIN_OR_EWOULDBLOCK(e) ((e) == EAGAIN)
}
+/// \brief Synchronizes the destination file to permanent storage
+///
+/// \param pair File pair having the destination file open for writing
+///
+/// \return On success, false is returned. On error, error message
+/// is printed and true is returned.
+static bool
+io_sync_dest(file_pair *pair)
+{
+ assert(pair->dest_fd != -1);
+ assert(pair->dest_fd != STDOUT_FILENO);
+
+ if (fsync(pair->dest_fd)) {
+ message_error(_("%s: Synchronizing the file failed: %s"),
+ tuklib_mask_nonprint(pair->dest_name),
+ strerror(errno));
+ return true;
+ }
+
+#ifndef TUKLIB_DOSLIKE
+ if (fsync(pair->dir_fd)) {
+ message_error(_("%s: Synchronizing the directory of "
+ "the file failed: %s"),
+ tuklib_mask_nonprint(pair->dest_name),
+ strerror(errno));
+ return true;
+ }
+#endif
+
+ return false;
+}
+
+
/// Opens the source file. Returns false on success, true on error.
static bool
io_open_src_real(file_pair *pair)
.dest_name = NULL,
.src_fd = -1,
.dest_fd = -1,
+#ifndef TUKLIB_DOSLIKE
+ .dir_fd = -1,
+#endif
.src_eof = false,
.src_has_seen_input = false,
.flush_needed = false,
if (pair->dest_name == NULL)
return true;
+#ifndef TUKLIB_DOSLIKE
+ if (opt_synchronous) {
+ // Open the directory where the destination file will
+ // be created (the file descriptor is needed for
+ // fsync()). Do this before creating the destination
+ // file:
+ //
+ // - We currently have no files to clean up if
+ // opening the directory fails. (We aren't
+ // reading from stdin so there are no stdin_flags
+ // to restore either.)
+ //
+ // - Allocating memory with xstrdup() is safe only
+ // when we have nothing to clean up.
+ char *buf = xstrdup(pair->dest_name);
+ const char *dir_name = dirname(buf);
+
+ // O_NOCTTY and O_NONBLOCK are there in case
+ // O_DIRECTORY is 0 and dir_name doesn't refer
+ // to a directory. (We opened the source file
+ // already but directories might have been renamed
+ // after the source file was opened.)
+ pair->dir_fd = open(dir_name, O_SEARCH | O_DIRECTORY
+ | O_NOCTTY | O_NONBLOCK);
+ if (pair->dir_fd == -1) {
+ // Since we did open the source file
+ // successfully, we should rarely get here.
+ // Perhaps something has been renamed or
+ // had its permissions changed.
+ //
+ // In an odd case, the directory has write
+ // and search permissions but not read
+ // permission (d-wx------), and O_SEARCH is
+ // actually O_RDONLY. Then we would be able
+ // to create a new file and only the directory
+ // syncing would be impossible. But let's be
+ // strict about syncing and require users to
+ // explicitly disable it if they don't want it.
+ message_error(_("%s: Opening the directory "
+ "failed: %s"),
+ tuklib_mask_nonprint(dir_name),
+ strerror(errno));
+ free(buf);
+ goto error;
+ }
+
+ free(buf);
+ }
+#endif
+
#ifdef __DJGPP__
struct stat st;
if (stat(pair->dest_name, &st) == 0) {
strerror(errno));
goto error;
}
+
+ // We could sync dir_fd now and close it. However, performance
+ // can be better if this is delayed until dest_fd has been
+ // synced in io_sync_dest().
}
if (fstat(pair->dest_fd, &pair->dest_st)) {
return false;
error:
+#ifndef TUKLIB_DOSLIKE
+ // io_close() closes pair->dir_fd but let's do it here anyway.
+ if (pair->dir_fd != -1) {
+ (void)close(pair->dir_fd);
+ pair->dir_fd = -1;
+ }
+#endif
+
free(pair->dest_name);
return true;
}
if (pair->dest_fd == -1 || pair->dest_fd == STDOUT_FILENO)
return false;
+#ifndef TUKLIB_DOSLIKE
+ // dir_fd was only used for syncing the directory.
+ // Error checking was done when syncing.
+ if (pair->dir_fd != -1)
+ (void)close(pair->dir_fd);
+#endif
+
if (close(pair->dest_fd)) {
message_error(_("%s: Closing the file failed: %s"),
tuklib_mask_nonprint(pair->dest_name),
signals_block();
- // Copy the file attributes. We need to skip this if destination
- // file isn't open or it is standard output.
- if (success && pair->dest_fd != -1 && pair->dest_fd != STDOUT_FILENO)
+ if (success && pair->dest_fd != -1 && pair->dest_fd != STDOUT_FILENO) {
+ // Copy the file attributes. This may produce warnings but
+ // not errors so "success" isn't affected.
io_copy_attrs(pair);
+ // Synchronize the file and its directory if needed.
+ if (opt_synchronous)
+ success = !io_sync_dest(pair);
+ }
+
// Close the destination first. If it fails, we must not remove
// the source file!
if (io_close_dest(pair, success))
.\" Authors: Lasse Collin
.\" Jia Tan
.\"
-.TH XZ 1 "2025-01-04" "Tukaani" "XZ Utils"
+.TH XZ 1 "2025-01-05" "Tukaani" "XZ Utils"
.
.SH NAME
xz, unxz, xzcat, lzma, unlzma, lzcat \- Compress or decompress .xz and .lzma files
.B xz
does buffering.
.TP
+.B \-\-no\-sync
+Do not synchronize the target file and its directory
+to the storage device before removing the source file.
+This can improve performance if compressing or decompressing
+many small files.
+However, if the system crashes soon after the deletion,
+it is possible that the target file was not written
+to the storage device but the delete operation was.
+In that case neither the original source file
+nor the target file is available.
+.IP ""
+This option has an effect only when
+.B xz
+is going to remove the source file.
+In other cases synchronization is never done.
+.IP ""
+The synchronization and
+.B \-\-no\-sync
+were added in
+.B xz
+5.7.1alpha.
+.TP
.BI \-\-memlimit\-compress= limit
Set a memory usage limit for compression.
If this option is specified multiple times,