1
0
mirror of https://git.tukaani.org/xz.git synced 2025-10-28 03:52:52 +00:00
xz/src/xz/process.c
Lasse Collin b0063023f8 Make the default memory usage limit 40 % of RAM for both
compressing and decompressing. This should be OK now that
xz automatically scales down the compression settings if
they would exceed the memory usage limit (earlier, the limit
for compression was increased to 90 % because low limit broke
scripts that used "xz -9" on systems with low RAM).

Support spcifying the memory usage limit as a percentage
of RAM (e.g. --memory=50%).

Support --threads=0 to reset the thread limit to the default
value (number of available CPU cores). Use UINT32_MAX instead
of SIZE_MAX as the maximum in args.c. hardware.c was already
expecting uint32_t value.

Cleaned up the output of --help and --long-help.
2009-05-22 11:29:50 +03:00

489 lines
13 KiB
C

///////////////////////////////////////////////////////////////////////////////
//
/// \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, _("%'" PRIu64 " MiB (%'" PRIu64 " B) of memory is "
"required per thread, "
"limit is %'" PRIu64 " MiB (%'" PRIu64 " B)"),
memory_usage >> 20, memory_usage,
memory_limit >> 20, memory_limit);
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 %'" PRIu32 " MiB to "
"%'" PRIu32 " MiB to not exceed "
"the memory usage limit of "
"%'" PRIu64 " MiB",
filters[i].id == LZMA_FILTER_LZMA2
? '2' : '1',
orig_dict_size >> 20,
opt->dict_size >> 20,
memory_limit >> 20);
}
/*
// 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_run(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 %'" PRIu64 " MiB, "
"but %'" PRIu64 " MiB would "
"have been needed"),
memlimit, memusage);
}
if (stop)
break;
}
// Show progress information under certain conditions.
message_progress_update();
}
message_progress_end(success);
return success;
}
extern void
process_file(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_run(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;
}