diff options
author | Lasse Collin <lasse.collin@tukaani.org> | 2008-11-19 20:46:52 +0200 |
---|---|---|
committer | Lasse Collin <lasse.collin@tukaani.org> | 2008-11-19 20:46:52 +0200 |
commit | e114502b2bc371e4a45449832cb69be036360722 (patch) | |
tree | 449c41d0408f99926de202611091747f1fbe2f85 /src/lzma/io.c | |
parent | Fixed the test that should have been fixed as part (diff) | |
download | xz-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.c | 757 |
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; } |