aboutsummaryrefslogtreecommitdiff
path: root/src/lzma/io.c
diff options
context:
space:
mode:
authorLasse Collin <lasse.collin@tukaani.org>2007-12-09 00:42:33 +0200
committerLasse Collin <lasse.collin@tukaani.org>2007-12-09 00:42:33 +0200
commit5d018dc03549c1ee4958364712fb0c94e1bf2741 (patch)
tree1b211911fb33fddb3f04b77f99e81df23623ffc4 /src/lzma/io.c
downloadxz-5d018dc03549c1ee4958364712fb0c94e1bf2741.tar.xz
Imported to git.
Diffstat (limited to 'src/lzma/io.c')
-rw-r--r--src/lzma/io.c664
1 files changed, 664 insertions, 0 deletions
diff --git a/src/lzma/io.c b/src/lzma/io.c
new file mode 100644
index 00000000..a7683fcc
--- /dev/null
+++ b/src/lzma/io.c
@@ -0,0 +1,664 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file io.c
+/// \brief File opening, unlinking, and closing
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "private.h"
+
+#if defined(HAVE_FUTIMES) || defined(HAVE_FUTIMESAT)
+# include <sys/time.h>
+#endif
+
+#ifndef O_SEARCH
+# define O_SEARCH O_RDONLY
+#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.
+///
+static void
+io_unlink(int dir_fd, const char *name, ino_t ino)
+{
+ const char *base = str_filename(name);
+ if (base == NULL) {
+ // This shouldn't happen.
+ errmsg(V_ERROR, _("%s: Invalid filename"), name);
+ return;
+ }
+
+ pthread_mutex_lock(&mutex);
+
+ if (fchdir(dir_fd)) {
+ errmsg(V_ERROR, _("Cannot change directory: %s"),
+ strerror(errno));
+ } 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"),
+ name, strerror(errno));
+ }
+
+ pthread_mutex_unlock(&mutex);
+
+ return;
+}
+
+
+/// \brief Copies owner/group and permissions
+///
+/// \todo ACL and EA support
+///
+static void
+io_copy_attrs(const file_pair *pair)
+{
+ // This function is more tricky than you may think at first.
+ // Blindly copying permissions may permit users to access the
+ // 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));
+
+ mode_t mode;
+
+ if (fchown(pair->dest_fd, -1, pair->src_st.st_gid)) {
+ errmsg(V_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
+ // also vice versa.
+ //
+ // NOTE: After this, the owner of the source file may
+ // get additional permissions. This shouldn't be too bad,
+ // because the owner would have had permission to chmod
+ // the original file anyway.
+ mode = ((pair->src_st.st_mode & 0070) >> 3)
+ & (pair->src_st.st_mode & 0007);
+ mode = (pair->src_st.st_mode & 0700) | (mode << 3) | mode;
+ } else {
+ // Drop the setuid, setgid, and sticky bits.
+ mode = pair->src_st.st_mode & 0777;
+ }
+
+ if (fchmod(pair->dest_fd, mode))
+ errmsg(V_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;
+
+# if defined(HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC)
+ tv[0].tv_usec = pair->src_st.st_atim.tv_nsec / 1000;
+# 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
+
+# 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;
+# endif
+
+# ifdef HAVE_FUTIMES
+ (void)futimes(pair->dest_fd, tv);
+# else
+ (void)futimesat(pair->dest_fd, NULL, tv);
+# endif
+#endif
+
+ return;
+}
+
+
+/// Opens and changes into the directory containing the source file.
+static int
+io_open_dir(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;
+ }
+
+ 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;
+ }
+ }
+
+ return 0;
+}
+
+
+static void
+io_close_dir(file_pair *pair)
+{
+ if (pair->dir_fd != start_dir)
+ (void)close(pair->dir_fd);
+
+ return;
+}
+
+
+/// 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
+#endif
+ )) {
+ errmsg(V_WARNING, _("%s: Is a symbolic link, "
+ "skipping"), pair->src_name);
+ } else {
+ errmsg(V_ERROR, "%s: %s", pair->src_name,
+ strerror(errno));
+ }
+
+ return -1;
+ }
+
+ if (fstat(pair->src_fd, &pair->src_st)) {
+ errmsg(V_ERROR, "%s: %s", pair->src_name,
+ strerror(errno));
+ goto error;
+ }
+
+ if (S_ISDIR(pair->src_st.st_mode)) {
+ errmsg(V_WARNING, _("%s: Is a directory, 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_nlink > 1) {
+ errmsg(V_WARNING, _("%s: Input file has more "
+ "than one hard link, "
+ "skipping"), pair->src_name);
+ goto error;
+ }
+ }
+ }
+
+ return 0;
+
+error:
+ (void)close(pair->src_fd);
+ return -1;
+}
+
+
+/// \brief Closes source file of the file_pair structure
+///
+/// \param pair File whose src_fd should be closed
+/// \param success If true, the file will be removed from the disk if
+/// closing succeeds and --keep hasn't been used.
+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);
+ }
+
+ return;
+}
+
+
+static int
+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;
+
+ // 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);
+
+ stdout_in_use = 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,
+ 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;
+ }
+
+ return 0;
+}
+
+
+/// \brief Closes destination file of the file_pair structure
+///
+/// \param pair File whose dest_fd should be closed
+/// \param success If false, the file will be removed from the disk.
+///
+/// \return Zero if closing succeeds. On error, -1 is returned and
+/// error message printed.
+static int
+io_close_dest(file_pair *pair, bool success)
+{
+ if (pair->dest_fd == -1)
+ 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"),
+ 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);
+ free(pair->dest_name);
+ return -1;
+ }
+
+ // 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);
+
+ free(pair->dest_name);
+
+ return 0;
+}
+
+
+extern file_pair *
+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;
+ }
+
+ *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);
+
+ return pair;
+
+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;
+}
+
+
+/// \brief Closes the file descriptors and frees the structure
+extern void
+io_close(file_pair *pair, bool success)
+{
+ 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);
+
+ free(pair);
+
+ pthread_mutex_lock(&mutex);
+
+ if (--open_pairs == 0)
+ pthread_cond_signal(&io_cond);
+
+ pthread_mutex_unlock(&mutex);
+
+ 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)
+{
+ // We use small buffers here.
+ assert(size < SSIZE_MAX);
+
+ size_t left = size;
+
+ while (left > 0) {
+ const ssize_t amount = read(pair->src_fd, buf, left);
+
+ if (amount == 0) {
+ pair->src_eof = true;
+ break;
+ }
+
+ if (amount == -1) {
+ if (errno == EINTR) {
+ if (user_abort)
+ return SIZE_MAX;
+
+ continue;
+ }
+
+ errmsg(V_ERROR, _("%s: Read error: %s"),
+ pair->src_name, strerror(errno));
+
+ // FIXME Is this needed?
+ pair->src_eof = true;
+
+ return SIZE_MAX;
+ }
+
+ buf += (size_t)(amount);
+ left -= (size_t)(amount);
+ }
+
+ return size - left;
+}
+
+
+/// \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
+io_write(const file_pair *pair, const uint8_t *buf, size_t size)
+{
+ assert(size < SSIZE_MAX);
+
+ while (size > 0) {
+ const ssize_t amount = write(pair->dest_fd, buf, size);
+ if (amount == -1) {
+ if (errno == EINTR) {
+ if (user_abort)
+ return -1;
+
+ continue;
+ }
+
+ errmsg(V_ERROR, _("%s: Write error: %s"),
+ pair->dest_name, strerror(errno));
+ return -1;
+ }
+
+ buf += (size_t)(amount);
+ size -= (size_t)(amount);
+ }
+
+ return 0;
+}