From 65014fd211dfbd4be48685998cb5a12aaa29c8d2 Mon Sep 17 00:00:00 2001 From: Lasse Collin Date: Fri, 26 Jun 2009 20:49:54 +0300 Subject: Rename process.[hc] to coder.[hc] and io.[hc] to file_io.[hc] to avoid problems on systems with system headers with those names. --- src/xz/Makefile.am | 8 +- src/xz/coder.c | 488 ++++++++++++++++++++++++++++++++++++ src/xz/coder.h | 57 +++++ src/xz/file_io.c | 716 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/xz/file_io.h | 86 +++++++ src/xz/io.c | 716 ----------------------------------------------------- src/xz/io.h | 86 ------- src/xz/private.h | 4 +- src/xz/process.c | 488 ------------------------------------ src/xz/process.h | 57 ----- 10 files changed, 1353 insertions(+), 1353 deletions(-) create mode 100644 src/xz/coder.c create mode 100644 src/xz/coder.h create mode 100644 src/xz/file_io.c create mode 100644 src/xz/file_io.h delete mode 100644 src/xz/io.c delete mode 100644 src/xz/io.h delete mode 100644 src/xz/process.c delete mode 100644 src/xz/process.h (limited to 'src/xz') diff --git a/src/xz/Makefile.am b/src/xz/Makefile.am index ff88a839..121a2d1c 100644 --- a/src/xz/Makefile.am +++ b/src/xz/Makefile.am @@ -10,10 +10,12 @@ bin_PROGRAMS = xz xz_SOURCES = \ args.c \ args.h \ + coder.c \ + coder.h \ + file_io.c \ + file_io.h \ hardware.c \ hardware.h \ - io.c \ - io.h \ main.c \ main.h \ message.c \ @@ -21,8 +23,6 @@ xz_SOURCES = \ options.c \ options.h \ private.h \ - process.c \ - process.h \ signals.c \ signals.h \ suffix.c \ diff --git a/src/xz/coder.c b/src/xz/coder.c new file mode 100644 index 00000000..1c904343 --- /dev/null +++ b/src/xz/coder.c @@ -0,0 +1,488 @@ +/////////////////////////////////////////////////////////////////////////////// +// +/// \file process.c +/// \brief Compresses or uncompresses a file +// +// Author: Lasse Collin +// +// This file has been put into the public domain. +// You can do whatever you want with this file. +// +/////////////////////////////////////////////////////////////////////////////// + +#include "private.h" + + +enum operation_mode opt_mode = MODE_COMPRESS; + +enum format_type opt_format = FORMAT_AUTO; + + +/// Stream used to communicate with liblzma +static lzma_stream strm = LZMA_STREAM_INIT; + +/// Filters needed for all encoding all formats, and also decoding in raw data +static lzma_filter filters[LZMA_FILTERS_MAX + 1]; + +/// Number of filters. Zero indicates that we are using a preset. +static size_t filters_count = 0; + +/// Number of the preset (0-9) +static size_t preset_number = 6; + +/// True if we should auto-adjust the compression settings to use less memory +/// if memory usage limit is too low for the original settings. +static bool auto_adjust = true; + +/// Indicate if no preset has been explicitly given. In that case, if we need +/// to auto-adjust for lower memory usage, we won't print a warning. +static bool preset_default = true; + +/// If a preset is used (no custom filter chain) and preset_extreme is true, +/// a significantly slower compression is used to achieve slightly better +/// compression ratio. +static bool preset_extreme = false; + +/// Integrity check type +#ifdef HAVE_CHECK_CRC64 +static lzma_check check = LZMA_CHECK_CRC64; +#else +static lzma_check check = LZMA_CHECK_CRC32; +#endif + + +extern void +coder_set_check(lzma_check new_check) +{ + check = new_check; + return; +} + + +extern void +coder_set_preset(size_t new_preset) +{ + preset_number = new_preset; + preset_default = false; + return; +} + + +extern void +coder_set_extreme(void) +{ + preset_extreme = true; + return; +} + + +extern void +coder_add_filter(lzma_vli id, void *options) +{ + if (filters_count == LZMA_FILTERS_MAX) + message_fatal(_("Maximum number of filters is four")); + + filters[filters_count].id = id; + filters[filters_count].options = options; + ++filters_count; + + return; +} + + +static void lzma_attribute((noreturn)) +memlimit_too_small(uint64_t memory_usage, uint64_t memory_limit) +{ + message_fatal(_("Memory usage limit (%" PRIu64 " MiB) is too small " + "for the given filter setup (%" PRIu64 " MiB)"), + memory_limit >> 20, memory_usage >> 20); +} + + +extern void +coder_set_compression_settings(void) +{ + // Options for LZMA1 or LZMA2 in case we are using a preset. + static lzma_options_lzma opt_lzma; + + if (filters_count == 0) { + // We are using a preset. This is not a good idea in raw mode + // except when playing around with things. Different versions + // of this software may use different options in presets, and + // thus make uncompressing the raw data difficult. + if (opt_format == FORMAT_RAW) { + // The message is shown only if warnings are allowed + // but the exit status isn't changed. + message(V_WARNING, _("Using a preset in raw mode " + "is discouraged.")); + message(V_WARNING, _("The exact options of the " + "presets may vary between software " + "versions.")); + } + + // Get the preset for LZMA1 or LZMA2. + if (preset_extreme) + preset_number |= LZMA_PRESET_EXTREME; + + if (lzma_lzma_preset(&opt_lzma, preset_number)) + message_bug(); + + // Use LZMA2 except with --format=lzma we use LZMA1. + filters[0].id = opt_format == FORMAT_LZMA + ? LZMA_FILTER_LZMA1 : LZMA_FILTER_LZMA2; + filters[0].options = &opt_lzma; + filters_count = 1; + } else { + preset_default = false; + } + + // Terminate the filter options array. + filters[filters_count].id = LZMA_VLI_UNKNOWN; + + // If we are using the LZMA_Alone format, allow exactly one filter + // which has to be LZMA. + if (opt_format == FORMAT_LZMA && (filters_count != 1 + || filters[0].id != LZMA_FILTER_LZMA1)) + message_fatal(_("With --format=lzma only the LZMA1 filter " + "is supported")); + + // Print the selected filter chain. + message_filters(V_DEBUG, filters); + + // If using --format=raw, we can be decoding. The memusage function + // also validates the filter chain and the options used for the + // filters. + const uint64_t memory_limit = hardware_memlimit_get(); + uint64_t memory_usage; + if (opt_mode == MODE_COMPRESS) + memory_usage = lzma_raw_encoder_memusage(filters); + else + memory_usage = lzma_raw_decoder_memusage(filters); + + if (memory_usage == UINT64_MAX) + message_fatal("Unsupported filter chain or filter options"); + + // Print memory usage info. + message(V_DEBUG, _("%s MiB (%s B) of memory is required per thread, " + "limit is %s MiB (%s B)"), + uint64_to_str(memory_usage >> 20, 0), + uint64_to_str(memory_usage, 1), + uint64_to_str(memory_limit >> 20, 2), + uint64_to_str(memory_limit, 3)); + + if (memory_usage > memory_limit) { + // If --no-auto-adjust was used or we didn't find LZMA1 or + // LZMA2 as the last filter, give an error immediatelly. + // --format=raw implies --no-auto-adjust. + if (!auto_adjust || opt_format == FORMAT_RAW) + memlimit_too_small(memory_usage, memory_limit); + + assert(opt_mode == MODE_COMPRESS); + + // Look for the last filter if it is LZMA2 or LZMA1, so + // we can make it use less RAM. With other filters we don't + // know what to do. + size_t i = 0; + while (filters[i].id != LZMA_FILTER_LZMA2 + && filters[i].id != LZMA_FILTER_LZMA1) { + if (filters[i].id == LZMA_VLI_UNKNOWN) + memlimit_too_small(memory_usage, memory_limit); + + ++i; + } + + // Decrease the dictionary size until we meet the memory + // usage limit. First round down to full mebibytes. + lzma_options_lzma *opt = filters[i].options; + const uint32_t orig_dict_size = opt->dict_size; + opt->dict_size &= ~((UINT32_C(1) << 20) - 1); + while (true) { + // If it is below 1 MiB, auto-adjusting failed. We + // could be more sophisticated and scale it down even + // more, but let's see if many complain about this + // version. + // + // FIXME: Displays the scaled memory usage instead + // of the original. + if (opt->dict_size < (UINT32_C(1) << 20)) + memlimit_too_small(memory_usage, memory_limit); + + memory_usage = lzma_raw_encoder_memusage(filters); + if (memory_usage == UINT64_MAX) + message_bug(); + + // Accept it if it is low enough. + if (memory_usage <= memory_limit) + break; + + // Otherwise 1 MiB down and try again. I hope this + // isn't too slow method for cases where the original + // dict_size is very big. + opt->dict_size -= UINT32_C(1) << 20; + } + + // Tell the user that we decreased the dictionary size. + // However, omit the message if no preset or custom chain + // was given. FIXME: Always warn? + if (!preset_default) + message(V_WARNING, "Adjusted LZMA%c dictionary size " + "from %s MiB to %s MiB to not exceed " + "the memory usage limit of %s MiB", + filters[i].id == LZMA_FILTER_LZMA2 + ? '2' : '1', + uint64_to_str(orig_dict_size >> 20, 0), + uint64_to_str(opt->dict_size >> 20, 1), + uint64_to_str(memory_limit >> 20, 2)); + } + +/* + // Limit the number of worker threads so that memory usage + // limit isn't exceeded. + assert(memory_usage > 0); + size_t thread_limit = memory_limit / memory_usage; + if (thread_limit == 0) + thread_limit = 1; + + if (opt_threads > thread_limit) + opt_threads = thread_limit; +*/ + + return; +} + + +static bool +coder_init(void) +{ + lzma_ret ret = LZMA_PROG_ERROR; + + if (opt_mode == MODE_COMPRESS) { + switch (opt_format) { + case FORMAT_AUTO: + // args.c ensures this. + assert(0); + break; + + case FORMAT_XZ: + ret = lzma_stream_encoder(&strm, filters, check); + break; + + case FORMAT_LZMA: + ret = lzma_alone_encoder(&strm, filters[0].options); + break; + + case FORMAT_RAW: + ret = lzma_raw_encoder(&strm, filters); + break; + } + } else { + const uint32_t flags = LZMA_TELL_UNSUPPORTED_CHECK + | LZMA_CONCATENATED; + + switch (opt_format) { + case FORMAT_AUTO: + ret = lzma_auto_decoder(&strm, + hardware_memlimit_get(), flags); + break; + + case FORMAT_XZ: + ret = lzma_stream_decoder(&strm, + hardware_memlimit_get(), flags); + break; + + case FORMAT_LZMA: + ret = lzma_alone_decoder(&strm, + hardware_memlimit_get()); + break; + + case FORMAT_RAW: + // Memory usage has already been checked in + // coder_set_compression_settings(). + ret = lzma_raw_decoder(&strm, filters); + break; + } + } + + if (ret != LZMA_OK) { + if (ret == LZMA_MEM_ERROR) + message_error("%s", message_strm(LZMA_MEM_ERROR)); + else + message_bug(); + + return true; + } + + return false; +} + + +static bool +coder_main(file_pair *pair) +{ + // Buffers to hold input and output data. + uint8_t in_buf[IO_BUFFER_SIZE]; + uint8_t out_buf[IO_BUFFER_SIZE]; + + // Initialize the progress indicator. + const uint64_t in_size = pair->src_st.st_size <= (off_t)(0) + ? 0 : (uint64_t)(pair->src_st.st_size); + message_progress_start(&strm, pair->src_name, in_size); + + lzma_action action = LZMA_RUN; + lzma_ret ret; + bool success = false; // Assume that something goes wrong. + + strm.avail_in = 0; + strm.next_out = out_buf; + strm.avail_out = IO_BUFFER_SIZE; + + while (!user_abort) { + // Fill the input buffer if it is empty and we haven't reached + // end of file yet. + if (strm.avail_in == 0 && !pair->src_eof) { + strm.next_in = in_buf; + strm.avail_in = io_read(pair, in_buf, IO_BUFFER_SIZE); + + if (strm.avail_in == SIZE_MAX) + break; + + // Encoder needs to know when we have given all the + // input to it. The decoders need to know it too when + // we are using LZMA_CONCATENATED. + if (pair->src_eof) + action = LZMA_FINISH; + } + + // Let liblzma do the actual work. + ret = lzma_code(&strm, action); + + // Write out if the output buffer became full. + if (strm.avail_out == 0) { + if (opt_mode != MODE_TEST && io_write(pair, out_buf, + IO_BUFFER_SIZE - strm.avail_out)) + break; + + strm.next_out = out_buf; + strm.avail_out = IO_BUFFER_SIZE; + } + + if (ret != LZMA_OK) { + // Determine if the return value indicates that we + // won't continue coding. + const bool stop = ret != LZMA_NO_CHECK + && ret != LZMA_UNSUPPORTED_CHECK; + + if (stop) { + // Write the remaining bytes even if something + // went wrong, because that way the user gets + // as much data as possible, which can be good + // when trying to get at least some useful + // data out of damaged files. + if (opt_mode != MODE_TEST && io_write(pair, + out_buf, IO_BUFFER_SIZE + - strm.avail_out)) + break; + } + + if (ret == LZMA_STREAM_END) { + // Check that there is no trailing garbage. + // This is needed for LZMA_Alone and raw + // streams. + if (strm.avail_in == 0 && !pair->src_eof) { + // Try reading one more byte. + // Hopefully we don't get any more + // input, and thus pair->src_eof + // becomes true. + strm.avail_in = io_read( + pair, in_buf, 1); + if (strm.avail_in == SIZE_MAX) + break; + + assert(strm.avail_in == 0 + || strm.avail_in == 1); + } + + if (strm.avail_in == 0) { + assert(pair->src_eof); + success = true; + break; + } + + // We hadn't reached the end of the file. + ret = LZMA_DATA_ERROR; + assert(stop); + } + + // If we get here and stop is true, something went + // wrong and we print an error. Otherwise it's just + // a warning and coding can continue. + if (stop) { + message_error("%s: %s", pair->src_name, + message_strm(ret)); + } else { + message_warning("%s: %s", pair->src_name, + message_strm(ret)); + + // When compressing, all possible errors set + // stop to true. + assert(opt_mode != MODE_COMPRESS); + } + + if (ret == LZMA_MEMLIMIT_ERROR) { + // Figure out how much memory it would have + // actually needed. + uint64_t memusage = lzma_memusage(&strm); + uint64_t memlimit = hardware_memlimit_get(); + + // Round the memory limit down and usage up. + // This way we don't display a ridiculous + // message like "Limit was 9 MiB, but 9 MiB + // would have been needed". + memusage = (memusage + 1024 * 1024 - 1) + / (1024 * 1024); + memlimit /= 1024 * 1024; + + message_error(_("Limit was %s MiB, " + "but %s MiB would " + "have been needed"), + uint64_to_str(memlimit, 0), + uint64_to_str(memusage, 1)); + } + + if (stop) + break; + } + + // Show progress information under certain conditions. + message_progress_update(); + } + + message_progress_end(success); + + return success; +} + + +extern void +coder_run(const char *filename) +{ + // First try initializing the coder. If it fails, it's useless to try + // opening the file. Check also for user_abort just in case if we had + // got a signal while initializing the coder. + if (coder_init() || user_abort) + return; + + // Try to open the input and output files. + file_pair *pair = io_open(filename); + if (pair == NULL) + return; + + // Do the actual coding. + const bool success = coder_main(pair); + + // Close the file pair. It needs to know if coding was successful to + // know if the source or target file should be unlinked. + io_close(pair, success); + + return; +} diff --git a/src/xz/coder.h b/src/xz/coder.h new file mode 100644 index 00000000..aa8a4d83 --- /dev/null +++ b/src/xz/coder.h @@ -0,0 +1,57 @@ +/////////////////////////////////////////////////////////////////////////////// +// +/// \file process.h +/// \brief Compresses or uncompresses a file +// +// Author: Lasse Collin +// +// This file has been put into the public domain. +// You can do whatever you want with this file. +// +/////////////////////////////////////////////////////////////////////////////// + +enum operation_mode { + MODE_COMPRESS, + MODE_DECOMPRESS, + MODE_TEST, + MODE_LIST, +}; + + +// NOTE: The order of these is significant in suffix.c. +enum format_type { + FORMAT_AUTO, + FORMAT_XZ, + FORMAT_LZMA, + // HEADER_GZIP, + FORMAT_RAW, +}; + + +/// Operation mode of the command line tool. This is set in args.c and read +/// in several files. +extern enum operation_mode opt_mode; + +/// File format to use when encoding or what format(s) to accept when +/// decoding. This is a global because it's needed also in suffix.c. +/// This is set in args.c. +extern enum format_type opt_format; + + +/// Set the integrity check type used when compressing +extern void coder_set_check(lzma_check check); + +/// Set preset number +extern void coder_set_preset(size_t new_preset); + +/// Enable extreme mode +extern void coder_set_extreme(void); + +/// Add a filter to the custom filter chain +extern void coder_add_filter(lzma_vli id, void *options); + +/// +extern void coder_set_compression_settings(void); + +/// Compress or decompress the given file +extern void coder_run(const char *filename); diff --git a/src/xz/file_io.c b/src/xz/file_io.c new file mode 100644 index 00000000..d8eb5f06 --- /dev/null +++ b/src/xz/file_io.c @@ -0,0 +1,716 @@ +/////////////////////////////////////////////////////////////////////////////// +// +/// \file io.c +/// \brief File opening, unlinking, and closing +// +// Author: Lasse Collin +// +// This file has been put into the public domain. +// You can do whatever you want with this file. +// +/////////////////////////////////////////////////////////////////////////////// + +#include "private.h" + +#include + +#ifdef DOSLIKE +# include +#endif + +#if defined(HAVE_FUTIMES) || defined(HAVE_FUTIMESAT) || defined(HAVE_UTIMES) +# include +#elif defined(HAVE_UTIME) +# include +#endif + +#ifndef O_BINARY +# define O_BINARY 0 +#endif + +#ifndef O_NOCTTY +# define O_NOCTTY 0 +#endif + +#ifndef DOSLIKE +# include "open_stdxxx.h" +static bool warn_fchown; +#endif + + +extern void +io_init(void) +{ +#ifndef DOSLIKE + // Make sure that stdin, stdout, and and stderr are connected to + // a valid file descriptor. Exit immediatelly with exit code ERROR + // if we cannot make the file descriptors valid. Maybe we should + // print an error message, but our stderr could be screwed anyway. + open_stdxxx(E_ERROR); + + // If fchown() fails setting the owner, we warn about it only if + // we are root. + warn_fchown = geteuid() == 0; +#endif + +#ifdef __DJGPP__ + // Avoid doing useless things when statting files. + // This isn't important but doesn't hurt. + _djstat_flags = _STAT_INODE | _STAT_EXEC_EXT + | _STAT_EXEC_MAGIC | _STAT_DIRSIZE; +#endif + + return; +} + + +/// \brief Unlinks a file +/// +/// 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(const char *name, const struct stat *known_st) +{ + // On Windows, st_ino is meaningless, so don't bother testing it. +#ifndef DOSLIKE + struct stat new_st; + + 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 +#endif + // There's a race condition between lstat() and unlink() + // but at least we have tried to avoid removing wrong file. + if (unlink(name)) + message_error(_("%s: Cannot remove: %s"), + name, strerror(errno)); + + return; +} + + +/// \brief Copies owner/group and permissions +/// +/// \todo ACL and EA support +/// +static void +io_copy_attrs(const file_pair *pair) +{ + // Skip chown and chmod on Windows. +#ifndef DOSLIKE + // 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. + + // 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) + 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)) { + 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 + // 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)) + message_warning(_("%s: Cannot set the file permissions: %s"), + pair->dest_name, strerror(errno)); +#endif + + // 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) + // 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) + // 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; + +# else + // Safe fallback + atime_nsec = 0; + mtime_nsec = 0; +# endif + + // 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); +# 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->dest_name, tv); +# endif + +#elif defined(HAVE_UTIME) + // Use one-second precision. utime() doesn't support using file + // descriptor either. Some systems have broken utime() prototype + // so don't make this 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->dest_name, &buf); +#endif + + return; +} + + +/// Opens the source file. Returns false on success, true on error. +static bool +io_open_src(file_pair *pair) +{ + // There's nothing to open when reading from stdin. + if (pair->src_name == stdin_filename) { + pair->src_fd = STDIN_FILENO; +#ifdef DOSLIKE + setmode(STDIN_FILENO, O_BINARY); +#endif + return false; + } + + // 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_BINARY | O_NOCTTY; + +#ifndef DOSLIKE + // 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; +#endif + +#if defined(O_NOFOLLOW) + if (reg_files_only) + flags |= O_NOFOLLOW; +#elif !defined(DOSLIKE) + // 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 + + // 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; + +# if defined(__FreeBSD__) || defined(__DragonFly__) + if (errno == EMLINK) + was_symlink = true; + +# elif defined(__digital__) && defined(__unix__) + if (errno == ENOTSUP) + was_symlink = true; + +# elif defined(__NetBSD__) + // FIXME? As of 2008-11-20, NetBSD doesn't document what + // errno is used with O_NOFOLLOW. It seems to be EFTYPE, + // but since it isn't documented, it may be wrong to rely + // on it here. + if (errno == EFTYPE) + 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 + + if (was_symlink) + message_warning(_("%s: Is a symbolic link, " + "skipping"), pair->src_name); + else +#endif + // 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 true; + } + +#ifndef DOSLIKE + // 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; + } +#endif + + // 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; + } + + // These are meaningless on Windows. +#ifndef DOSLIKE + 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 (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) { + message_warning(_("%s: Input file has more " + "than one hard link, " + "skipping"), pair->src_name); + goto error; + } +#endif + } + + return false; + +error_msg: + message_error("%s: %s", pair->src_name, strerror(errno)); +error: + (void)close(pair->src_fd); + return true; +} + + +/// \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) { +#ifdef DOSLIKE + (void)close(pair->src_fd); +#endif + + // 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. + // + // NOTE: DOS-like systems are an exception to this, because + // they don't allow unlinking files that are open. *sigh* + if (success && !opt_keep_original) + io_unlink(pair->src_name, &pair->src_st); + +#ifndef DOSLIKE + (void)close(pair->src_fd); +#endif + } + + return; +} + + +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; +#ifdef DOSLIKE + setmode(STDOUT_FILENO, O_BINARY); +#endif + return false; + } + + pair->dest_name = suffix_get_dest_name(pair->src_name); + if (pair->dest_name == NULL) + return 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; + } + + 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_BINARY | 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 true; + } + + // 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; +} + + +/// \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 || pair->dest_fd == STDOUT_FILENO) + return 0; + + if (close(pair->dest_fd)) { + 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->dest_name, &pair->dest_st); + 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->dest_name, &pair->dest_st); + + free(pair->dest_name); + + return 0; +} + + +extern file_pair * +io_open(const char *src_name) +{ + if (is_empty_filename(src_name)) + 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){ + .src_name = src_name, + .dest_name = NULL, + .src_fd = -1, + .dest_fd = -1, + .src_eof = false, + }; + + // 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; + } + + signals_unblock(); + + return ret; +} + + +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)) + success = false; + + // 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); + + signals_unblock(); + + return; +} + + +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; + } + + message_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; +} + + +extern bool +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; + } + + // Handle broken pipe specially. gzip and bzip2 + // don't print anything on SIGPIPE. In addition, + // gzip --quiet uses exit status 2 (warning) on + // broken pipe instead of whatever raise(SIGPIPE) + // would make it return. It is there to hide "Broken + // pipe" message on some old shells (probably old + // GNU bash). + // + // We don't do anything special with --quiet, which + // 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 true; + } + + buf += (size_t)(amount); + size -= (size_t)(amount); + } + + return false; +} diff --git a/src/xz/file_io.h b/src/xz/file_io.h new file mode 100644 index 00000000..c10554ad --- /dev/null +++ b/src/xz/file_io.h @@ -0,0 +1,86 @@ +/////////////////////////////////////////////////////////////////////////////// +// +/// \file io.h +/// \brief I/O types and functions +// +// Author: Lasse Collin +// +// This file has been put into the public domain. +// You can do whatever you want with this file. +// +/////////////////////////////////////////////////////////////////////////////// + +// Some systems have suboptimal BUFSIZ. Use a bit bigger value on them. +#if BUFSIZ <= 1024 +# define IO_BUFFER_SIZE 8192 +#else +# define IO_BUFFER_SIZE BUFSIZ +#endif + + +typedef struct { + /// Name of the source filename (as given on the command line) or + /// pointer to static "(stdin)" when reading from standard input. + const char *src_name; + + /// Destination filename converted from src_name or pointer to static + /// "(stdout)" when writing to standard output. + char *dest_name; + + /// File descriptor of the source file + int src_fd; + + /// File descriptor of the target file + int dest_fd; + + /// Stat of the source file. + struct stat src_st; + + /// Stat of the destination file. + struct stat dest_st; + + /// True once end of the source file has been detected. + bool src_eof; + +} file_pair; + + +/// \brief Initialize the I/O module +extern void io_init(void); + + +/// \brief Opens a file pair +extern file_pair *io_open(const char *src_name); + + +/// \brief Closes the file descriptors and frees possible allocated memory +/// +/// The success argument determines if source or destination file gets +/// unlinked: +/// - false: The destination file is unlinked. +/// - true: The source file is unlinked unless writing to stdout or --keep +/// was used. +extern void io_close(file_pair *pair, bool success); + + +/// \brief Reads from the source file to a buffer +/// +/// \param pair File pair having the source file 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. +extern size_t io_read(file_pair *pair, uint8_t *buf, size_t size); + + +/// \brief Writes a buffer to the destination 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. +extern bool io_write(const file_pair *pair, const uint8_t *buf, size_t size); diff --git a/src/xz/io.c b/src/xz/io.c deleted file mode 100644 index d8eb5f06..00000000 --- a/src/xz/io.c +++ /dev/null @@ -1,716 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// -/// \file io.c -/// \brief File opening, unlinking, and closing -// -// Author: Lasse Collin -// -// This file has been put into the public domain. -// You can do whatever you want with this file. -// -/////////////////////////////////////////////////////////////////////////////// - -#include "private.h" - -#include - -#ifdef DOSLIKE -# include -#endif - -#if defined(HAVE_FUTIMES) || defined(HAVE_FUTIMESAT) || defined(HAVE_UTIMES) -# include -#elif defined(HAVE_UTIME) -# include -#endif - -#ifndef O_BINARY -# define O_BINARY 0 -#endif - -#ifndef O_NOCTTY -# define O_NOCTTY 0 -#endif - -#ifndef DOSLIKE -# include "open_stdxxx.h" -static bool warn_fchown; -#endif - - -extern void -io_init(void) -{ -#ifndef DOSLIKE - // Make sure that stdin, stdout, and and stderr are connected to - // a valid file descriptor. Exit immediatelly with exit code ERROR - // if we cannot make the file descriptors valid. Maybe we should - // print an error message, but our stderr could be screwed anyway. - open_stdxxx(E_ERROR); - - // If fchown() fails setting the owner, we warn about it only if - // we are root. - warn_fchown = geteuid() == 0; -#endif - -#ifdef __DJGPP__ - // Avoid doing useless things when statting files. - // This isn't important but doesn't hurt. - _djstat_flags = _STAT_INODE | _STAT_EXEC_EXT - | _STAT_EXEC_MAGIC | _STAT_DIRSIZE; -#endif - - return; -} - - -/// \brief Unlinks a file -/// -/// 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(const char *name, const struct stat *known_st) -{ - // On Windows, st_ino is meaningless, so don't bother testing it. -#ifndef DOSLIKE - struct stat new_st; - - 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 -#endif - // There's a race condition between lstat() and unlink() - // but at least we have tried to avoid removing wrong file. - if (unlink(name)) - message_error(_("%s: Cannot remove: %s"), - name, strerror(errno)); - - return; -} - - -/// \brief Copies owner/group and permissions -/// -/// \todo ACL and EA support -/// -static void -io_copy_attrs(const file_pair *pair) -{ - // Skip chown and chmod on Windows. -#ifndef DOSLIKE - // 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. - - // 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) - 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)) { - 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 - // 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)) - message_warning(_("%s: Cannot set the file permissions: %s"), - pair->dest_name, strerror(errno)); -#endif - - // 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) - // 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) - // 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; - -# else - // Safe fallback - atime_nsec = 0; - mtime_nsec = 0; -# endif - - // 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); -# 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->dest_name, tv); -# endif - -#elif defined(HAVE_UTIME) - // Use one-second precision. utime() doesn't support using file - // descriptor either. Some systems have broken utime() prototype - // so don't make this 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->dest_name, &buf); -#endif - - return; -} - - -/// Opens the source file. Returns false on success, true on error. -static bool -io_open_src(file_pair *pair) -{ - // There's nothing to open when reading from stdin. - if (pair->src_name == stdin_filename) { - pair->src_fd = STDIN_FILENO; -#ifdef DOSLIKE - setmode(STDIN_FILENO, O_BINARY); -#endif - return false; - } - - // 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_BINARY | O_NOCTTY; - -#ifndef DOSLIKE - // 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; -#endif - -#if defined(O_NOFOLLOW) - if (reg_files_only) - flags |= O_NOFOLLOW; -#elif !defined(DOSLIKE) - // 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 - - // 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; - -# if defined(__FreeBSD__) || defined(__DragonFly__) - if (errno == EMLINK) - was_symlink = true; - -# elif defined(__digital__) && defined(__unix__) - if (errno == ENOTSUP) - was_symlink = true; - -# elif defined(__NetBSD__) - // FIXME? As of 2008-11-20, NetBSD doesn't document what - // errno is used with O_NOFOLLOW. It seems to be EFTYPE, - // but since it isn't documented, it may be wrong to rely - // on it here. - if (errno == EFTYPE) - 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 - - if (was_symlink) - message_warning(_("%s: Is a symbolic link, " - "skipping"), pair->src_name); - else -#endif - // 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 true; - } - -#ifndef DOSLIKE - // 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; - } -#endif - - // 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; - } - - // These are meaningless on Windows. -#ifndef DOSLIKE - 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 (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) { - message_warning(_("%s: Input file has more " - "than one hard link, " - "skipping"), pair->src_name); - goto error; - } -#endif - } - - return false; - -error_msg: - message_error("%s: %s", pair->src_name, strerror(errno)); -error: - (void)close(pair->src_fd); - return true; -} - - -/// \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) { -#ifdef DOSLIKE - (void)close(pair->src_fd); -#endif - - // 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. - // - // NOTE: DOS-like systems are an exception to this, because - // they don't allow unlinking files that are open. *sigh* - if (success && !opt_keep_original) - io_unlink(pair->src_name, &pair->src_st); - -#ifndef DOSLIKE - (void)close(pair->src_fd); -#endif - } - - return; -} - - -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; -#ifdef DOSLIKE - setmode(STDOUT_FILENO, O_BINARY); -#endif - return false; - } - - pair->dest_name = suffix_get_dest_name(pair->src_name); - if (pair->dest_name == NULL) - return 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; - } - - 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_BINARY | 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 true; - } - - // 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; -} - - -/// \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 || pair->dest_fd == STDOUT_FILENO) - return 0; - - if (close(pair->dest_fd)) { - 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->dest_name, &pair->dest_st); - 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->dest_name, &pair->dest_st); - - free(pair->dest_name); - - return 0; -} - - -extern file_pair * -io_open(const char *src_name) -{ - if (is_empty_filename(src_name)) - 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){ - .src_name = src_name, - .dest_name = NULL, - .src_fd = -1, - .dest_fd = -1, - .src_eof = false, - }; - - // 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; - } - - signals_unblock(); - - return ret; -} - - -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)) - success = false; - - // 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); - - signals_unblock(); - - return; -} - - -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; - } - - message_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; -} - - -extern bool -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; - } - - // Handle broken pipe specially. gzip and bzip2 - // don't print anything on SIGPIPE. In addition, - // gzip --quiet uses exit status 2 (warning) on - // broken pipe instead of whatever raise(SIGPIPE) - // would make it return. It is there to hide "Broken - // pipe" message on some old shells (probably old - // GNU bash). - // - // We don't do anything special with --quiet, which - // 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 true; - } - - buf += (size_t)(amount); - size -= (size_t)(amount); - } - - return false; -} diff --git a/src/xz/io.h b/src/xz/io.h deleted file mode 100644 index c10554ad..00000000 --- a/src/xz/io.h +++ /dev/null @@ -1,86 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// -/// \file io.h -/// \brief I/O types and functions -// -// Author: Lasse Collin -// -// This file has been put into the public domain. -// You can do whatever you want with this file. -// -/////////////////////////////////////////////////////////////////////////////// - -// Some systems have suboptimal BUFSIZ. Use a bit bigger value on them. -#if BUFSIZ <= 1024 -# define IO_BUFFER_SIZE 8192 -#else -# define IO_BUFFER_SIZE BUFSIZ -#endif - - -typedef struct { - /// Name of the source filename (as given on the command line) or - /// pointer to static "(stdin)" when reading from standard input. - const char *src_name; - - /// Destination filename converted from src_name or pointer to static - /// "(stdout)" when writing to standard output. - char *dest_name; - - /// File descriptor of the source file - int src_fd; - - /// File descriptor of the target file - int dest_fd; - - /// Stat of the source file. - struct stat src_st; - - /// Stat of the destination file. - struct stat dest_st; - - /// True once end of the source file has been detected. - bool src_eof; - -} file_pair; - - -/// \brief Initialize the I/O module -extern void io_init(void); - - -/// \brief Opens a file pair -extern file_pair *io_open(const char *src_name); - - -/// \brief Closes the file descriptors and frees possible allocated memory -/// -/// The success argument determines if source or destination file gets -/// unlinked: -/// - false: The destination file is unlinked. -/// - true: The source file is unlinked unless writing to stdout or --keep -/// was used. -extern void io_close(file_pair *pair, bool success); - - -/// \brief Reads from the source file to a buffer -/// -/// \param pair File pair having the source file 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. -extern size_t io_read(file_pair *pair, uint8_t *buf, size_t size); - - -/// \brief Writes a buffer to the destination 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. -extern bool io_write(const file_pair *pair, const uint8_t *buf, size_t size); diff --git a/src/xz/private.h b/src/xz/private.h index 1ed37cd0..6141b88c 100644 --- a/src/xz/private.h +++ b/src/xz/private.h @@ -44,11 +44,11 @@ #endif #include "main.h" -#include "process.h" +#include "coder.h" #include "message.h" #include "args.h" #include "hardware.h" -#include "io.h" +#include "file_io.h" #include "options.h" #include "signals.h" #include "suffix.h" diff --git a/src/xz/process.c b/src/xz/process.c deleted file mode 100644 index 1c904343..00000000 --- a/src/xz/process.c +++ /dev/null @@ -1,488 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// -/// \file process.c -/// \brief Compresses or uncompresses a file -// -// Author: Lasse Collin -// -// This file has been put into the public domain. -// You can do whatever you want with this file. -// -/////////////////////////////////////////////////////////////////////////////// - -#include "private.h" - - -enum operation_mode opt_mode = MODE_COMPRESS; - -enum format_type opt_format = FORMAT_AUTO; - - -/// Stream used to communicate with liblzma -static lzma_stream strm = LZMA_STREAM_INIT; - -/// Filters needed for all encoding all formats, and also decoding in raw data -static lzma_filter filters[LZMA_FILTERS_MAX + 1]; - -/// Number of filters. Zero indicates that we are using a preset. -static size_t filters_count = 0; - -/// Number of the preset (0-9) -static size_t preset_number = 6; - -/// True if we should auto-adjust the compression settings to use less memory -/// if memory usage limit is too low for the original settings. -static bool auto_adjust = true; - -/// Indicate if no preset has been explicitly given. In that case, if we need -/// to auto-adjust for lower memory usage, we won't print a warning. -static bool preset_default = true; - -/// If a preset is used (no custom filter chain) and preset_extreme is true, -/// a significantly slower compression is used to achieve slightly better -/// compression ratio. -static bool preset_extreme = false; - -/// Integrity check type -#ifdef HAVE_CHECK_CRC64 -static lzma_check check = LZMA_CHECK_CRC64; -#else -static lzma_check check = LZMA_CHECK_CRC32; -#endif - - -extern void -coder_set_check(lzma_check new_check) -{ - check = new_check; - return; -} - - -extern void -coder_set_preset(size_t new_preset) -{ - preset_number = new_preset; - preset_default = false; - return; -} - - -extern void -coder_set_extreme(void) -{ - preset_extreme = true; - return; -} - - -extern void -coder_add_filter(lzma_vli id, void *options) -{ - if (filters_count == LZMA_FILTERS_MAX) - message_fatal(_("Maximum number of filters is four")); - - filters[filters_count].id = id; - filters[filters_count].options = options; - ++filters_count; - - return; -} - - -static void lzma_attribute((noreturn)) -memlimit_too_small(uint64_t memory_usage, uint64_t memory_limit) -{ - message_fatal(_("Memory usage limit (%" PRIu64 " MiB) is too small " - "for the given filter setup (%" PRIu64 " MiB)"), - memory_limit >> 20, memory_usage >> 20); -} - - -extern void -coder_set_compression_settings(void) -{ - // Options for LZMA1 or LZMA2 in case we are using a preset. - static lzma_options_lzma opt_lzma; - - if (filters_count == 0) { - // We are using a preset. This is not a good idea in raw mode - // except when playing around with things. Different versions - // of this software may use different options in presets, and - // thus make uncompressing the raw data difficult. - if (opt_format == FORMAT_RAW) { - // The message is shown only if warnings are allowed - // but the exit status isn't changed. - message(V_WARNING, _("Using a preset in raw mode " - "is discouraged.")); - message(V_WARNING, _("The exact options of the " - "presets may vary between software " - "versions.")); - } - - // Get the preset for LZMA1 or LZMA2. - if (preset_extreme) - preset_number |= LZMA_PRESET_EXTREME; - - if (lzma_lzma_preset(&opt_lzma, preset_number)) - message_bug(); - - // Use LZMA2 except with --format=lzma we use LZMA1. - filters[0].id = opt_format == FORMAT_LZMA - ? LZMA_FILTER_LZMA1 : LZMA_FILTER_LZMA2; - filters[0].options = &opt_lzma; - filters_count = 1; - } else { - preset_default = false; - } - - // Terminate the filter options array. - filters[filters_count].id = LZMA_VLI_UNKNOWN; - - // If we are using the LZMA_Alone format, allow exactly one filter - // which has to be LZMA. - if (opt_format == FORMAT_LZMA && (filters_count != 1 - || filters[0].id != LZMA_FILTER_LZMA1)) - message_fatal(_("With --format=lzma only the LZMA1 filter " - "is supported")); - - // Print the selected filter chain. - message_filters(V_DEBUG, filters); - - // If using --format=raw, we can be decoding. The memusage function - // also validates the filter chain and the options used for the - // filters. - const uint64_t memory_limit = hardware_memlimit_get(); - uint64_t memory_usage; - if (opt_mode == MODE_COMPRESS) - memory_usage = lzma_raw_encoder_memusage(filters); - else - memory_usage = lzma_raw_decoder_memusage(filters); - - if (memory_usage == UINT64_MAX) - message_fatal("Unsupported filter chain or filter options"); - - // Print memory usage info. - message(V_DEBUG, _("%s MiB (%s B) of memory is required per thread, " - "limit is %s MiB (%s B)"), - uint64_to_str(memory_usage >> 20, 0), - uint64_to_str(memory_usage, 1), - uint64_to_str(memory_limit >> 20, 2), - uint64_to_str(memory_limit, 3)); - - if (memory_usage > memory_limit) { - // If --no-auto-adjust was used or we didn't find LZMA1 or - // LZMA2 as the last filter, give an error immediatelly. - // --format=raw implies --no-auto-adjust. - if (!auto_adjust || opt_format == FORMAT_RAW) - memlimit_too_small(memory_usage, memory_limit); - - assert(opt_mode == MODE_COMPRESS); - - // Look for the last filter if it is LZMA2 or LZMA1, so - // we can make it use less RAM. With other filters we don't - // know what to do. - size_t i = 0; - while (filters[i].id != LZMA_FILTER_LZMA2 - && filters[i].id != LZMA_FILTER_LZMA1) { - if (filters[i].id == LZMA_VLI_UNKNOWN) - memlimit_too_small(memory_usage, memory_limit); - - ++i; - } - - // Decrease the dictionary size until we meet the memory - // usage limit. First round down to full mebibytes. - lzma_options_lzma *opt = filters[i].options; - const uint32_t orig_dict_size = opt->dict_size; - opt->dict_size &= ~((UINT32_C(1) << 20) - 1); - while (true) { - // If it is below 1 MiB, auto-adjusting failed. We - // could be more sophisticated and scale it down even - // more, but let's see if many complain about this - // version. - // - // FIXME: Displays the scaled memory usage instead - // of the original. - if (opt->dict_size < (UINT32_C(1) << 20)) - memlimit_too_small(memory_usage, memory_limit); - - memory_usage = lzma_raw_encoder_memusage(filters); - if (memory_usage == UINT64_MAX) - message_bug(); - - // Accept it if it is low enough. - if (memory_usage <= memory_limit) - break; - - // Otherwise 1 MiB down and try again. I hope this - // isn't too slow method for cases where the original - // dict_size is very big. - opt->dict_size -= UINT32_C(1) << 20; - } - - // Tell the user that we decreased the dictionary size. - // However, omit the message if no preset or custom chain - // was given. FIXME: Always warn? - if (!preset_default) - message(V_WARNING, "Adjusted LZMA%c dictionary size " - "from %s MiB to %s MiB to not exceed " - "the memory usage limit of %s MiB", - filters[i].id == LZMA_FILTER_LZMA2 - ? '2' : '1', - uint64_to_str(orig_dict_size >> 20, 0), - uint64_to_str(opt->dict_size >> 20, 1), - uint64_to_str(memory_limit >> 20, 2)); - } - -/* - // Limit the number of worker threads so that memory usage - // limit isn't exceeded. - assert(memory_usage > 0); - size_t thread_limit = memory_limit / memory_usage; - if (thread_limit == 0) - thread_limit = 1; - - if (opt_threads > thread_limit) - opt_threads = thread_limit; -*/ - - return; -} - - -static bool -coder_init(void) -{ - lzma_ret ret = LZMA_PROG_ERROR; - - if (opt_mode == MODE_COMPRESS) { - switch (opt_format) { - case FORMAT_AUTO: - // args.c ensures this. - assert(0); - break; - - case FORMAT_XZ: - ret = lzma_stream_encoder(&strm, filters, check); - break; - - case FORMAT_LZMA: - ret = lzma_alone_encoder(&strm, filters[0].options); - break; - - case FORMAT_RAW: - ret = lzma_raw_encoder(&strm, filters); - break; - } - } else { - const uint32_t flags = LZMA_TELL_UNSUPPORTED_CHECK - | LZMA_CONCATENATED; - - switch (opt_format) { - case FORMAT_AUTO: - ret = lzma_auto_decoder(&strm, - hardware_memlimit_get(), flags); - break; - - case FORMAT_XZ: - ret = lzma_stream_decoder(&strm, - hardware_memlimit_get(), flags); - break; - - case FORMAT_LZMA: - ret = lzma_alone_decoder(&strm, - hardware_memlimit_get()); - break; - - case FORMAT_RAW: - // Memory usage has already been checked in - // coder_set_compression_settings(). - ret = lzma_raw_decoder(&strm, filters); - break; - } - } - - if (ret != LZMA_OK) { - if (ret == LZMA_MEM_ERROR) - message_error("%s", message_strm(LZMA_MEM_ERROR)); - else - message_bug(); - - return true; - } - - return false; -} - - -static bool -coder_main(file_pair *pair) -{ - // Buffers to hold input and output data. - uint8_t in_buf[IO_BUFFER_SIZE]; - uint8_t out_buf[IO_BUFFER_SIZE]; - - // Initialize the progress indicator. - const uint64_t in_size = pair->src_st.st_size <= (off_t)(0) - ? 0 : (uint64_t)(pair->src_st.st_size); - message_progress_start(&strm, pair->src_name, in_size); - - lzma_action action = LZMA_RUN; - lzma_ret ret; - bool success = false; // Assume that something goes wrong. - - strm.avail_in = 0; - strm.next_out = out_buf; - strm.avail_out = IO_BUFFER_SIZE; - - while (!user_abort) { - // Fill the input buffer if it is empty and we haven't reached - // end of file yet. - if (strm.avail_in == 0 && !pair->src_eof) { - strm.next_in = in_buf; - strm.avail_in = io_read(pair, in_buf, IO_BUFFER_SIZE); - - if (strm.avail_in == SIZE_MAX) - break; - - // Encoder needs to know when we have given all the - // input to it. The decoders need to know it too when - // we are using LZMA_CONCATENATED. - if (pair->src_eof) - action = LZMA_FINISH; - } - - // Let liblzma do the actual work. - ret = lzma_code(&strm, action); - - // Write out if the output buffer became full. - if (strm.avail_out == 0) { - if (opt_mode != MODE_TEST && io_write(pair, out_buf, - IO_BUFFER_SIZE - strm.avail_out)) - break; - - strm.next_out = out_buf; - strm.avail_out = IO_BUFFER_SIZE; - } - - if (ret != LZMA_OK) { - // Determine if the return value indicates that we - // won't continue coding. - const bool stop = ret != LZMA_NO_CHECK - && ret != LZMA_UNSUPPORTED_CHECK; - - if (stop) { - // Write the remaining bytes even if something - // went wrong, because that way the user gets - // as much data as possible, which can be good - // when trying to get at least some useful - // data out of damaged files. - if (opt_mode != MODE_TEST && io_write(pair, - out_buf, IO_BUFFER_SIZE - - strm.avail_out)) - break; - } - - if (ret == LZMA_STREAM_END) { - // Check that there is no trailing garbage. - // This is needed for LZMA_Alone and raw - // streams. - if (strm.avail_in == 0 && !pair->src_eof) { - // Try reading one more byte. - // Hopefully we don't get any more - // input, and thus pair->src_eof - // becomes true. - strm.avail_in = io_read( - pair, in_buf, 1); - if (strm.avail_in == SIZE_MAX) - break; - - assert(strm.avail_in == 0 - || strm.avail_in == 1); - } - - if (strm.avail_in == 0) { - assert(pair->src_eof); - success = true; - break; - } - - // We hadn't reached the end of the file. - ret = LZMA_DATA_ERROR; - assert(stop); - } - - // If we get here and stop is true, something went - // wrong and we print an error. Otherwise it's just - // a warning and coding can continue. - if (stop) { - message_error("%s: %s", pair->src_name, - message_strm(ret)); - } else { - message_warning("%s: %s", pair->src_name, - message_strm(ret)); - - // When compressing, all possible errors set - // stop to true. - assert(opt_mode != MODE_COMPRESS); - } - - if (ret == LZMA_MEMLIMIT_ERROR) { - // Figure out how much memory it would have - // actually needed. - uint64_t memusage = lzma_memusage(&strm); - uint64_t memlimit = hardware_memlimit_get(); - - // Round the memory limit down and usage up. - // This way we don't display a ridiculous - // message like "Limit was 9 MiB, but 9 MiB - // would have been needed". - memusage = (memusage + 1024 * 1024 - 1) - / (1024 * 1024); - memlimit /= 1024 * 1024; - - message_error(_("Limit was %s MiB, " - "but %s MiB would " - "have been needed"), - uint64_to_str(memlimit, 0), - uint64_to_str(memusage, 1)); - } - - if (stop) - break; - } - - // Show progress information under certain conditions. - message_progress_update(); - } - - message_progress_end(success); - - return success; -} - - -extern void -coder_run(const char *filename) -{ - // First try initializing the coder. If it fails, it's useless to try - // opening the file. Check also for user_abort just in case if we had - // got a signal while initializing the coder. - if (coder_init() || user_abort) - return; - - // Try to open the input and output files. - file_pair *pair = io_open(filename); - if (pair == NULL) - return; - - // Do the actual coding. - const bool success = coder_main(pair); - - // Close the file pair. It needs to know if coding was successful to - // know if the source or target file should be unlinked. - io_close(pair, success); - - return; -} diff --git a/src/xz/process.h b/src/xz/process.h deleted file mode 100644 index aa8a4d83..00000000 --- a/src/xz/process.h +++ /dev/null @@ -1,57 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// -/// \file process.h -/// \brief Compresses or uncompresses a file -// -// Author: Lasse Collin -// -// This file has been put into the public domain. -// You can do whatever you want with this file. -// -/////////////////////////////////////////////////////////////////////////////// - -enum operation_mode { - MODE_COMPRESS, - MODE_DECOMPRESS, - MODE_TEST, - MODE_LIST, -}; - - -// NOTE: The order of these is significant in suffix.c. -enum format_type { - FORMAT_AUTO, - FORMAT_XZ, - FORMAT_LZMA, - // HEADER_GZIP, - FORMAT_RAW, -}; - - -/// Operation mode of the command line tool. This is set in args.c and read -/// in several files. -extern enum operation_mode opt_mode; - -/// File format to use when encoding or what format(s) to accept when -/// decoding. This is a global because it's needed also in suffix.c. -/// This is set in args.c. -extern enum format_type opt_format; - - -/// Set the integrity check type used when compressing -extern void coder_set_check(lzma_check check); - -/// Set preset number -extern void coder_set_preset(size_t new_preset); - -/// Enable extreme mode -extern void coder_set_extreme(void); - -/// Add a filter to the custom filter chain -extern void coder_add_filter(lzma_vli id, void *options); - -/// -extern void coder_set_compression_settings(void); - -/// Compress or decompress the given file -extern void coder_run(const char *filename); -- cgit v1.2.3