aboutsummaryrefslogtreecommitdiff
path: root/src/lzma/io.c
diff options
context:
space:
mode:
authorLasse Collin <lasse.collin@tukaani.org>2008-11-19 20:46:52 +0200
committerLasse Collin <lasse.collin@tukaani.org>2008-11-19 20:46:52 +0200
commite114502b2bc371e4a45449832cb69be036360722 (patch)
tree449c41d0408f99926de202611091747f1fbe2f85 /src/lzma/io.c
parentFixed the test that should have been fixed as part (diff)
downloadxz-e114502b2bc371e4a45449832cb69be036360722.tar.xz
Oh well, big messy commit again. Some highlights:
- Updated to the latest, probably final file format version. - Command line tool reworked to not use threads anymore. Threading will probably go into liblzma anyway. - Memory usage limit is now about 30 % for uncompression and about 90 % for compression. - Progress indicator with --verbose - Simplified --help and full --long-help - Upgraded to the last LGPLv2.1+ getopt_long from gnulib. - Some bug fixes
Diffstat (limited to 'src/lzma/io.c')
-rw-r--r--src/lzma/io.c757
1 files changed, 369 insertions, 388 deletions
diff --git a/src/lzma/io.c b/src/lzma/io.c
index b972099f..0ec63f03 100644
--- a/src/lzma/io.c
+++ b/src/lzma/io.c
@@ -19,131 +19,39 @@
#include "private.h"
-#if defined(HAVE_FUTIMES) || defined(HAVE_FUTIMESAT)
-# include <sys/time.h>
-#endif
+#include <fcntl.h>
-#ifndef O_SEARCH
-# define O_SEARCH O_RDONLY
+#if defined(HAVE_FUTIMES) || defined(HAVE_FUTIMESAT) || defined(HAVE_UTIMES)
+# include <sys/time.h>
+#elif defined(HAVE_UTIME)
+# include <utime.h>
#endif
-/// \brief Number of open file_pairs
-///
-/// Once the main() function has requested processing of all files,
-/// we wait that open_pairs drops back to zero. Then it is safe to
-/// exit from the program.
-static size_t open_pairs = 0;
-
-
-/// \brief mutex for file system operations
-///
-/// All file system operations are done via the functions in this file.
-/// They use fchdir() to avoid some race conditions (more portable than
-/// openat() & co.).
-///
-/// Synchronizing all file system operations shouldn't affect speed notably,
-/// since the actual reading from and writing to files is done in parallel.
-static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
-
-
-/// This condition is invoked when a file is closed and the value of
-/// the open_files variable has dropped to zero. The only listener for
-/// this condition is io_finish() which is called from main().
-static pthread_cond_t io_cond = PTHREAD_COND_INITIALIZER;
-
-
-/// True when stdout is being used by some thread
-static bool stdout_in_use = false;
-
-
-/// This condition is signalled when a thread releases stdout (no longer
-/// writes data to it).
-static pthread_cond_t stdout_cond = PTHREAD_COND_INITIALIZER;
-
-
-/// \brief Directory where we were started
-///
-/// This is needed when a new file, whose name was given on command line,
-/// is opened.
-static int start_dir;
-
-
-static uid_t uid;
-static gid_t gid;
-
-
-extern void
-io_init(void)
-{
- start_dir = open(".", O_SEARCH | O_NOCTTY);
- if (start_dir == -1) {
- errmsg(V_ERROR, _("Cannot get file descriptor of the current "
- "directory: %s"), strerror(errno));
- my_exit(ERROR);
- }
-
- uid = getuid();
- gid = getgid();
-
- return;
-}
-
-
-/// Waits until the number of open file_pairs has dropped to zero.
-extern void
-io_finish(void)
-{
- pthread_mutex_lock(&mutex);
-
- while (open_pairs != 0)
- pthread_cond_wait(&io_cond, &mutex);
-
- (void)close(start_dir);
-
- pthread_mutex_unlock(&mutex);
-
- return;
-}
-
-
/// \brief Unlinks a file
///
-/// \param dir_fd File descriptor of the directory containing the file
-/// \param name Name of the file with or without path
-///
-/// \return Zero on success. On error, -1 is returned and errno set.
-///
+/// This tries to verify that the file being unlinked really is the file that
+/// we want to unlink by verifying device and inode numbers. There's still
+/// a small unavoidable race, but this is much better than nothing (the file
+/// could have been moved/replaced even hours earlier).
static void
-io_unlink(int dir_fd, const char *name, ino_t ino)
+io_unlink(const char *name, const struct stat *known_st)
{
- const char *base = str_filename(name);
- if (base == NULL) {
- // This shouldn't happen.
- errmsg(V_ERROR, _("%s: Invalid filename"), name);
- return;
- }
+ struct stat new_st;
- pthread_mutex_lock(&mutex);
-
- if (fchdir(dir_fd)) {
- errmsg(V_ERROR, _("Cannot change directory: %s"),
- strerror(errno));
+ if (lstat(name, &new_st)
+ || new_st.st_dev != known_st->st_dev
+ || new_st.st_ino != known_st->st_ino) {
+ message_error(_("%s: File seems to be moved, not removing"),
+ name);
} else {
- struct stat st;
- if (lstat(base, &st) || st.st_ino != ino)
- errmsg(V_ERROR, _("%s: File seems to be moved, "
- "not removing"), name);
-
// There's a race condition between lstat() and unlink()
// but at least we have tried to avoid removing wrong file.
- else if (unlink(base))
- errmsg(V_ERROR, _("%s: Cannot remove: %s"),
+ if (unlink(name))
+ message_error(_("%s: Cannot remove: %s"),
name, strerror(errno));
}
- pthread_mutex_unlock(&mutex);
-
return;
}
@@ -160,14 +68,31 @@ io_copy_attrs(const file_pair *pair)
// destination file who didn't have permission to access the
// source file.
- if (uid == 0 && fchown(pair->dest_fd, pair->src_st.st_uid, -1))
- errmsg(V_WARNING, _("%s: Cannot set the file owner: %s"),
- pair->dest_name, strerror(errno));
+ // Simple cache to avoid repeated calls to geteuid().
+ static enum {
+ WARN_FCHOWN_UNKNOWN,
+ WARN_FCHOWN_NO,
+ WARN_FCHOWN_YES,
+ } warn_fchown = WARN_FCHOWN_UNKNOWN;
+
+ // Try changing the owner of the file. If we aren't root or the owner
+ // isn't already us, fchown() probably doesn't succeed. We warn
+ // about failing fchown() only if we are root.
+ if (fchown(pair->dest_fd, pair->src_st.st_uid, -1)
+ && warn_fchown != WARN_FCHOWN_NO) {
+ if (warn_fchown == WARN_FCHOWN_UNKNOWN)
+ warn_fchown = geteuid() == 0
+ ? WARN_FCHOWN_YES : WARN_FCHOWN_NO;
+
+ if (warn_fchown == WARN_FCHOWN_YES)
+ message_warning(_("%s: Cannot set the file owner: %s"),
+ pair->dest_name, strerror(errno));
+ }
mode_t mode;
if (fchown(pair->dest_fd, -1, pair->src_st.st_gid)) {
- errmsg(V_WARNING, _("%s: Cannot set the file group: %s"),
+ message_warning(_("%s: Cannot set the file group: %s"),
pair->dest_name, strerror(errno));
// We can still safely copy some additional permissions:
// `group' must be at least as strict as `other' and
@@ -186,192 +111,291 @@ io_copy_attrs(const file_pair *pair)
}
if (fchmod(pair->dest_fd, mode))
- errmsg(V_WARNING, _("%s: Cannot set the file permissions: %s"),
+ message_warning(_("%s: Cannot set the file permissions: %s"),
pair->dest_name, strerror(errno));
- // Copy the timestamps only if we have a secure function to do it.
-#if defined(HAVE_FUTIMES) || defined(HAVE_FUTIMESAT)
- struct timeval tv[2];
- tv[0].tv_sec = pair->src_st.st_atime;
- tv[1].tv_sec = pair->src_st.st_mtime;
+ // Copy the timestamps. We have several possible ways to do this, of
+ // which some are better in both security and precision.
+ //
+ // First, get the nanosecond part of the timestamps. As of writing,
+ // it's not standardized by POSIX, and there are several names for
+ // the same thing in struct stat.
+ long atime_nsec;
+ long mtime_nsec;
# if defined(HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC)
- tv[0].tv_usec = pair->src_st.st_atim.tv_nsec / 1000;
+ // GNU and Solaris
+ atime_nsec = pair->src_st.st_atim.tv_nsec;
+ mtime_nsec = pair->src_st.st_mtim.tv_nsec;
+
# elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC_TV_NSEC)
- tv[0].tv_usec = pair->src_st.st_atimespec.tv_nsec / 1000;
-# else
- tv[0].tv_usec = 0;
-# endif
+ // BSD
+ atime_nsec = pair->src_st.st_atimespec.tv_nsec;
+ mtime_nsec = pair->src_st.st_mtimespec.tv_nsec;
+
+# elif defined(HAVE_STRUCT_STAT_ST_ATIMENSEC)
+ // GNU and BSD without extensions
+ atime_nsec = pair->src_st.st_atimensec;
+ mtime_nsec = pair->src_st.st_mtimensec;
+
+# elif defined(HAVE_STRUCT_STAT_ST_UATIME)
+ // Tru64
+ atime_nsec = pair->src_st.st_uatime * 1000;
+ mtime_nsec = pair->src_st.st_umtime * 1000;
+
+# elif defined(HAVE_STRUCT_STAT_ST_ATIM_ST__TIM_TV_NSEC)
+ // UnixWare
+ atime_nsec = pair->src_st.st_atim.st__tim.tv_nsec;
+ mtime_nsec = pair->src_st.st_mtim.st__tim.tv_nsec;
-# if defined(HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC)
- tv[1].tv_usec = pair->src_st.st_mtim.tv_nsec / 1000;
-# elif defined(HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC)
- tv[1].tv_usec = pair->src_st.st_mtimespec.tv_nsec / 1000;
# else
- tv[1].tv_usec = 0;
+ // Safe fallback
+ atime_nsec = 0;
+ mtime_nsec = 0;
# endif
-# ifdef HAVE_FUTIMES
+ // Construct a structure to hold the timestamps and call appropriate
+ // function to set the timestamps.
+#if defined(HAVE_FUTIMENS)
+ // Use nanosecond precision.
+ struct timespec tv[2];
+ tv[0].tv_sec = pair->src_st.st_atime;
+ tv[0].tv_nsec = atime_nsec;
+ tv[1].tv_sec = pair->src_st.st_mtime;
+ tv[1].tv_nsec = mtime_nsec;
+
+ (void)futimens(pair->dest_fd, tv);
+
+#elif defined(HAVE_FUTIMES) || defined(HAVE_FUTIMESAT) || defined(HAVE_UTIMES)
+ // Use microsecond precision.
+ struct timeval tv[2];
+ tv[0].tv_sec = pair->src_st.st_atime;
+ tv[0].tv_usec = atime_nsec / 1000;
+ tv[1].tv_sec = pair->src_st.st_mtime;
+ tv[1].tv_usec = mtime_nsec / 1000;
+
+# if defined(HAVE_FUTIMES)
(void)futimes(pair->dest_fd, tv);
-# else
+# elif defined(HAVE_FUTIMESAT)
(void)futimesat(pair->dest_fd, NULL, tv);
+# else
+ // Argh, no function to use a file descriptor to set the timestamp.
+ (void)utimes(pair->src_name, tv);
# endif
+
+#elif defined(HAVE_UTIME)
+ // Use one-second precision. utime() doesn't support using file
+ // descriptor either.
+ const struct utimbuf buf = {
+ .actime = pair->src_st.st_atime;
+ .modtime = pair->src_st.st_mtime;
+ };
+
+ // Avoid warnings.
+ (void)atime_nsec;
+ (void)mtime_nsec;
+
+ (void)utime(pair->src_name, &buf);
#endif
return;
}
-/// Opens and changes into the directory containing the source file.
-static int
-io_open_dir(file_pair *pair)
+/// Opens the source file. Returns false on success, true on error.
+static bool
+io_open_src(file_pair *pair)
{
- if (pair->src_name == stdin_filename)
- return 0;
-
- if (fchdir(start_dir)) {
- errmsg(V_ERROR, _("Cannot change directory: %s"),
- strerror(errno));
- return -1;
+ // There's nothing to open when reading from stdin.
+ if (pair->src_name == stdin_filename) {
+ pair->src_fd = STDIN_FILENO;
+ return false;
}
- const char *split = strrchr(pair->src_name, '/');
- if (split == NULL) {
- pair->dir_fd = start_dir;
- } else {
- // Copy also the slash. It's needed to support filenames
- // like "/foo" (dirname being "/"), and it never hurts anyway.
- const size_t dirname_len = split - pair->src_name + 1;
- char dirname[dirname_len + 1];
- memcpy(dirname, pair->src_name, dirname_len);
- dirname[dirname_len] = '\0';
-
- // Open the directory and change into it.
- pair->dir_fd = open(dirname, O_SEARCH | O_NOCTTY);
- if (pair->dir_fd == -1 || fchdir(pair->dir_fd)) {
- errmsg(V_ERROR, _("%s: Cannot open the directory "
- "containing the file: %s"),
- pair->src_name, strerror(errno));
- (void)close(pair->dir_fd);
- return -1;
+ // We accept only regular files if we are writing the output
+ // to disk too, and if --force was not given.
+ const bool reg_files_only = !opt_stdout && !opt_force;
+
+ // Flags for open()
+ int flags = O_RDONLY | O_NOCTTY;
+
+ // If we accept only regular files, we need to be careful to avoid
+ // problems with special files like devices and FIFOs. O_NONBLOCK
+ // prevents blocking when opening such files. When we want to accept
+ // special files, we must not use O_NONBLOCK, or otherwise we won't
+ // block waiting e.g. FIFOs to become readable.
+ if (reg_files_only)
+ flags |= O_NONBLOCK;
+
+#ifdef O_NOFOLLOW
+ if (reg_files_only)
+ flags |= O_NOFOLLOW;
+#else
+ // Some POSIX-like systems lack O_NOFOLLOW (it's not required
+ // by POSIX). Check for symlinks with a separate lstat() on
+ // these systems.
+ if (reg_files_only) {
+ struct stat st;
+ if (lstat(pair->src_name, &st)) {
+ message_error("%s: %s", pair->src_name,
+ strerror(errno));
+ return true;
+
+ } else if (S_ISLNK(st.st_mode)) {
+ message_warning(_("%s: Is a symbolic link, "
+ "skipping"), pair->src_name);
+ return true;
}
}
+#endif
- return 0;
-}
+ // Try to open the file. If we are accepting non-regular files,
+ // unblock the caught signals so that open() can be interrupted
+ // if it blocks e.g. due to a FIFO file.
+ if (!reg_files_only)
+ signals_unblock();
+
+ // Maybe this wouldn't need a loop, since all the signal handlers for
+ // which we don't use SA_RESTART set user_abort to true. But it
+ // doesn't hurt to have it just in case.
+ do {
+ pair->src_fd = open(pair->src_name, flags);
+ } while (pair->src_fd == -1 && errno == EINTR && !user_abort);
+
+ if (!reg_files_only)
+ signals_block();
+
+ if (pair->src_fd == -1) {
+ // If we were interrupted, don't display any error message.
+ if (errno == EINTR) {
+ // All the signals that don't have SA_RESTART
+ // set user_abort.
+ assert(user_abort);
+ return true;
+ }
+#ifdef O_NOFOLLOW
+ // Give an understandable error message in if reason
+ // for failing was that the file was a symbolic link.
+ //
+ // Note that at least Linux, OpenBSD, Solaris, and Darwin
+ // use ELOOP to indicate if O_NOFOLLOW was the reason
+ // that open() failed. Because there may be
+ // directories in the pathname, ELOOP may occur also
+ // because of a symlink loop in the directory part.
+ // So ELOOP doesn't tell us what actually went wrong.
+ //
+ // FreeBSD associates EMLINK with O_NOFOLLOW and
+ // Tru64 uses ENOTSUP. We use these directly here
+ // and skip the lstat() call and the associated race.
+ // I want to hear if there are other kernels that
+ // fail with something else than ELOOP with O_NOFOLLOW.
+ bool was_symlink = false;
-static void
-io_close_dir(file_pair *pair)
-{
- if (pair->dir_fd != start_dir)
- (void)close(pair->dir_fd);
+# if defined(__FreeBSD__) || defined(__DragonFly__)
+ if (errno == EMLINK)
+ was_symlink = true;
- return;
-}
+# elif defined(__digital__) && defined(__unix__)
+ if (errno == ENOTSUP)
+ was_symlink = true;
+# else
+ if (errno == ELOOP && reg_files_only) {
+ const int saved_errno = errno;
+ struct stat st;
+ if (lstat(pair->src_name, &st) == 0
+ && S_ISLNK(st.st_mode))
+ was_symlink = true;
+
+ errno = saved_errno;
+ }
+# endif
-/// Opens the source file. The file is opened using the plain filename without
-/// path, thus the file must be in the current working directory. This is
-/// ensured because io_open_dir() is always called before this function.
-static int
-io_open_src(file_pair *pair)
-{
- if (pair->src_name == stdin_filename) {
- pair->src_fd = STDIN_FILENO;
- } else {
- // Strip the pathname. Thanks to io_open_dir(), the file
- // is now in the current working directory.
- const char *filename = str_filename(pair->src_name);
- if (filename == NULL)
- return -1;
-
- // Symlinks are followed if --stdout or --force has been
- // specified.
- const bool follow_symlinks = opt_stdout || opt_force;
- pair->src_fd = open(filename, O_RDONLY | O_NOCTTY
- | (follow_symlinks ? 0 : O_NOFOLLOW));
- if (pair->src_fd == -1) {
- // Give an understandable error message in if reason
- // for failing was that the file was a symbolic link.
- // - Linux, OpenBSD, Solaris: ELOOP
- // - FreeBSD: EMLINK
- // - Tru64: ENOTSUP
- // It seems to be safe to check for all these, since
- // those errno values aren't used for other purporses
- // on any of the listed operating system *when* the
- // above flags are used with open().
- if (!follow_symlinks
- && (errno == ELOOP
-#ifdef EMLINK
- || errno == EMLINK
-#endif
-#ifdef ENOTSUP
- || errno == ENOTSUP
+ if (was_symlink)
+ message_warning(_("%s: Is a symbolic link, "
+ "skipping"), pair->src_name);
+ else
#endif
- )) {
- errmsg(V_WARNING, _("%s: Is a symbolic link, "
- "skipping"), pair->src_name);
- } else {
- errmsg(V_ERROR, "%s: %s", pair->src_name,
- strerror(errno));
- }
+ // Something else than O_NOFOLLOW failing
+ // (assuming that the race conditions didn't
+ // confuse us).
+ message_error("%s: %s", pair->src_name,
+ strerror(errno));
- return -1;
- }
+ return true;
+ }
- if (fstat(pair->src_fd, &pair->src_st)) {
- errmsg(V_ERROR, "%s: %s", pair->src_name,
- strerror(errno));
+ // Drop O_NONBLOCK, which is used only when we are accepting only
+ // regular files. After the open() call, we want things to block
+ // instead of giving EAGAIN.
+ if (reg_files_only) {
+ flags = fcntl(pair->src_fd, F_GETFL);
+ if (flags == -1)
+ goto error_msg;
+
+ flags &= ~O_NONBLOCK;
+
+ if (fcntl(pair->src_fd, F_SETFL, flags))
+ goto error_msg;
+ }
+
+ // Stat the source file. We need the result also when we copy
+ // the permissions, and when unlinking.
+ if (fstat(pair->src_fd, &pair->src_st))
+ goto error_msg;
+
+ if (S_ISDIR(pair->src_st.st_mode)) {
+ message_warning(_("%s: Is a directory, skipping"),
+ pair->src_name);
+ goto error;
+ }
+
+ if (reg_files_only) {
+ if (!S_ISREG(pair->src_st.st_mode)) {
+ message_warning(_("%s: Not a regular file, "
+ "skipping"), pair->src_name);
goto error;
}
- if (S_ISDIR(pair->src_st.st_mode)) {
- errmsg(V_WARNING, _("%s: Is a directory, skipping"),
+ if (pair->src_st.st_mode & (S_ISUID | S_ISGID)) {
+ // gzip rejects setuid and setgid files even
+ // when --force was used. bzip2 doesn't check
+ // for them, but calls fchown() after fchmod(),
+ // and many systems automatically drop setuid
+ // and setgid bits there.
+ //
+ // We accept setuid and setgid files if
+ // --force was used. We drop these bits
+ // explicitly in io_copy_attr().
+ message_warning(_("%s: File has setuid or "
+ "setgid bit set, skipping"),
pair->src_name);
goto error;
}
- if (!opt_stdout) {
- if (!opt_force && !S_ISREG(pair->src_st.st_mode)) {
- errmsg(V_WARNING, _("%s: Not a regular file, "
- "skipping"), pair->src_name);
- goto error;
- }
-
- if (pair->src_st.st_mode & (S_ISUID | S_ISGID)) {
- // Setuid and setgid files are rejected even
- // with --force. This is good for security
- // (hopefully) but it's a bit weird to reject
- // file when --force was given. At least this
- // matches gzip's behavior.
- errmsg(V_WARNING, _("%s: File has setuid or "
- "setgid bit set, skipping"),
- pair->src_name);
- goto error;
- }
-
- if (!opt_force && (pair->src_st.st_mode & S_ISVTX)) {
- errmsg(V_WARNING, _("%s: File has sticky bit "
- "set, skipping"),
- pair->src_name);
- goto error;
- }
+ if (pair->src_st.st_mode & S_ISVTX) {
+ message_warning(_("%s: File has sticky bit "
+ "set, skipping"),
+ pair->src_name);
+ goto error;
+ }
- if (pair->src_st.st_nlink > 1) {
- errmsg(V_WARNING, _("%s: Input file has more "
- "than one hard link, "
- "skipping"), pair->src_name);
- goto error;
- }
+ if (pair->src_st.st_nlink > 1) {
+ message_warning(_("%s: Input file has more "
+ "than one hard link, "
+ "skipping"), pair->src_name);
+ goto error;
}
}
- return 0;
+ return false;
+error_msg:
+ message_error("%s: %s", pair->src_name, strerror(errno));
error:
(void)close(pair->src_fd);
- return -1;
+ return true;
}
@@ -383,65 +407,73 @@ error:
static void
io_close_src(file_pair *pair, bool success)
{
- if (pair->src_fd == STDIN_FILENO || pair->src_fd == -1)
- return;
-
- if (close(pair->src_fd)) {
- errmsg(V_ERROR, _("%s: Closing the file failed: %s"),
- pair->src_name, strerror(errno));
- } else if (success && !opt_keep_original) {
- io_unlink(pair->dir_fd, pair->src_name, pair->src_st.st_ino);
+ if (pair->src_fd != STDIN_FILENO && pair->src_fd != -1) {
+ // If we are going to unlink(), do it before closing the file.
+ // This way there's no risk that someone replaces the file and
+ // happens to get same inode number, which would make us
+ // unlink() wrong file.
+ if (success && !opt_keep_original)
+ io_unlink(pair->src_name, &pair->src_st);
+
+ (void)close(pair->src_fd);
}
return;
}
-static int
+static bool
io_open_dest(file_pair *pair)
{
if (opt_stdout || pair->src_fd == STDIN_FILENO) {
// We don't modify or free() this.
pair->dest_name = (char *)"(stdout)";
pair->dest_fd = STDOUT_FILENO;
+ return false;
+ }
- // Synchronize the order in which files get written to stdout.
- // Unlocking the mutex is safe, because opening the file_pair
- // can no longer fail.
- while (stdout_in_use)
- pthread_cond_wait(&stdout_cond, &mutex);
+ pair->dest_name = suffix_get_dest_name(pair->src_name);
+ if (pair->dest_name == NULL)
+ return true;
- stdout_in_use = true;
+ // If --force was used, unlink the target file first.
+ if (opt_force && unlink(pair->dest_name) && errno != ENOENT) {
+ message_error("%s: Cannot unlink: %s",
+ pair->dest_name, strerror(errno));
+ free(pair->dest_name);
+ return true;
+ }
- } else {
- pair->dest_name = get_dest_name(pair->src_name);
- if (pair->dest_name == NULL)
- return -1;
-
- // This cannot fail, because get_dest_name() doesn't return
- // invalid names.
- const char *filename = str_filename(pair->dest_name);
- assert(filename != NULL);
-
- pair->dest_fd = open(filename, O_WRONLY | O_NOCTTY | O_CREAT
- | (opt_force ? O_TRUNC : O_EXCL),
- S_IRUSR | S_IWUSR);
- if (pair->dest_fd == -1) {
- errmsg(V_ERROR, "%s: %s", pair->dest_name,
+ if (opt_force && unlink(pair->dest_name) && errno != ENOENT) {
+ message_error("%s: Cannot unlink: %s", pair->dest_name,
+ strerror(errno));
+ free(pair->dest_name);
+ return true;
+ }
+
+ // Open the file.
+ const int flags = O_WRONLY | O_NOCTTY | O_CREAT | O_EXCL;
+ const mode_t mode = S_IRUSR | S_IWUSR;
+ pair->dest_fd = open(pair->dest_name, flags, mode);
+
+ if (pair->dest_fd == -1) {
+ // Don't bother with error message if user requested
+ // us to exit anyway.
+ if (!user_abort)
+ message_error("%s: %s", pair->dest_name,
strerror(errno));
- free(pair->dest_name);
- return -1;
- }
- // If this really fails... well, we have a safe fallback.
- struct stat st;
- if (fstat(pair->dest_fd, &st))
- pair->dest_ino = 0;
- else
- pair->dest_ino = st.st_ino;
+ free(pair->dest_name);
+ return true;
}
- return 0;
+ // If this really fails... well, we have a safe fallback.
+ if (fstat(pair->dest_fd, &pair->dest_st)) {
+ pair->dest_st.st_dev = 0;
+ pair->dest_st.st_ino = 0;
+ }
+
+ return false;
}
@@ -455,22 +487,16 @@ io_open_dest(file_pair *pair)
static int
io_close_dest(file_pair *pair, bool success)
{
- if (pair->dest_fd == -1)
+ if (pair->dest_fd == -1 || pair->dest_fd == STDOUT_FILENO)
return 0;
- if (pair->dest_fd == STDOUT_FILENO) {
- stdout_in_use = false;
- pthread_cond_signal(&stdout_cond);
- return 0;
- }
-
if (close(pair->dest_fd)) {
- errmsg(V_ERROR, _("%s: Closing the file failed: %s"),
+ message_error(_("%s: Closing the file failed: %s"),
pair->dest_name, strerror(errno));
// Closing destination file failed, so we cannot trust its
// contents. Get rid of junk:
- io_unlink(pair->dir_fd, pair->dest_name, pair->dest_ino);
+ io_unlink(pair->dest_name, &pair->dest_st);
free(pair->dest_name);
return -1;
}
@@ -478,7 +504,7 @@ io_close_dest(file_pair *pair, bool success)
// If the operation using this file wasn't successful, we git rid
// of the junk file.
if (!success)
- io_unlink(pair->dir_fd, pair->dest_name, pair->dest_ino);
+ io_unlink(pair->dest_name, &pair->dest_st);
free(pair->dest_name);
@@ -492,98 +518,63 @@ io_open(const char *src_name)
if (is_empty_filename(src_name))
return NULL;
- file_pair *pair = malloc(sizeof(file_pair));
- if (pair == NULL) {
- out_of_memory();
- return NULL;
- }
+ // Since we have only one file open at a time, we can use
+ // a statically allocated structure.
+ static file_pair pair;
- *pair = (file_pair){
+ pair = (file_pair){
.src_name = src_name,
.dest_name = NULL,
- .dir_fd = -1,
.src_fd = -1,
.dest_fd = -1,
.src_eof = false,
};
- pthread_mutex_lock(&mutex);
-
- ++open_pairs;
-
- if (io_open_dir(pair))
- goto error_dir;
-
- if (io_open_src(pair))
- goto error_src;
-
- if (user_abort || io_open_dest(pair))
- goto error_dest;
-
- pthread_mutex_unlock(&mutex);
+ // Block the signals, for which we have a custom signal handler, so
+ // that we don't need to worry about EINTR.
+ signals_block();
+
+ file_pair *ret = NULL;
+ if (!io_open_src(&pair)) {
+ // io_open_src() may have unblocked the signals temporarily,
+ // and thus user_abort may have got set even if open()
+ // succeeded.
+ if (user_abort || io_open_dest(&pair))
+ io_close_src(&pair, false);
+ else
+ ret = &pair;
+ }
- return pair;
+ signals_unblock();
-error_dest:
- io_close_src(pair, false);
-error_src:
- io_close_dir(pair);
-error_dir:
- --open_pairs;
- pthread_mutex_unlock(&mutex);
- free(pair);
- return NULL;
+ return ret;
}
-/// \brief Closes the file descriptors and frees the structure
extern void
io_close(file_pair *pair, bool success)
{
+ signals_block();
+
if (success && pair->dest_fd != STDOUT_FILENO)
io_copy_attrs(pair);
// Close the destination first. If it fails, we must not remove
// the source file!
- if (!io_close_dest(pair, success)) {
- // Closing destination file succeeded. Remove the source file
- // if the operation using this file pair was successful
- // and we haven't been requested to keep the source file.
- io_close_src(pair, success);
- } else {
- // We don't care if operation using this file pair was
- // successful or not, since closing the destination file
- // failed. Don't remove the original file.
- io_close_src(pair, false);
- }
-
- io_close_dir(pair);
+ if (io_close_dest(pair, success))
+ success = false;
- free(pair);
-
- pthread_mutex_lock(&mutex);
-
- if (--open_pairs == 0)
- pthread_cond_signal(&io_cond);
+ // Close the source file, and unlink it if the operation using this
+ // file pair was successful and we haven't requested to keep the
+ // source file.
+ io_close_src(pair, success);
- pthread_mutex_unlock(&mutex);
+ signals_unblock();
return;
}
-/// \brief Reads from a file to a buffer
-///
-/// \param pair File pair having the sourcefile open for reading
-/// \param buf Destination buffer to hold the read data
-/// \param size Size of the buffer; assumed be smaller than SSIZE_MAX
-///
-/// \return On success, number of bytes read is returned. On end of
-/// file zero is returned and pair->src_eof set to true.
-/// On error, SIZE_MAX is returned and error message printed.
-///
-/// \note This does no locking, thus two threads must not read from
-/// the same file. This no problem in this program.
extern size_t
io_read(file_pair *pair, uint8_t *buf, size_t size)
{
@@ -608,7 +599,7 @@ io_read(file_pair *pair, uint8_t *buf, size_t size)
continue;
}
- errmsg(V_ERROR, _("%s: Read error: %s"),
+ message_error(_("%s: Read error: %s"),
pair->src_name, strerror(errno));
// FIXME Is this needed?
@@ -625,18 +616,7 @@ io_read(file_pair *pair, uint8_t *buf, size_t size)
}
-/// \brief Writes a buffer to a file
-///
-/// \param pair File pair having the destination file open for writing
-/// \param buf Buffer containing the data to be written
-/// \param size Size of the buffer; assumed be smaller than SSIZE_MAX
-///
-/// \return On success, zero is returned. On error, -1 is returned
-/// and error message printed.
-///
-/// \note This does no locking, thus two threads must not write to
-/// the same file. This no problem in this program.
-extern int
+extern bool
io_write(const file_pair *pair, const uint8_t *buf, size_t size)
{
assert(size < SSIZE_MAX);
@@ -660,18 +640,19 @@ io_write(const file_pair *pair, const uint8_t *buf, size_t size)
// GNU bash).
//
// We don't do anything special with --quiet, which
- // is what bzip2 does too. However, we print a
- // message if --verbose was used (or should that
- // only be with double --verbose i.e. debugging?).
- errmsg(errno == EPIPE ? V_VERBOSE : V_ERROR,
- _("%s: Write error: %s"),
+ // is what bzip2 does too. If we get SIGPIPE, we
+ // will handle it like other signals by setting
+ // user_abort, and get EPIPE here.
+ if (errno != EPIPE)
+ message_error(_("%s: Write error: %s"),
pair->dest_name, strerror(errno));
- return -1;
+
+ return true;
}
buf += (size_t)(amount);
size -= (size_t)(amount);
}
- return 0;
+ return false;
}