/* Plzip - A parallel version of the lzip data compressor
Copyright (C) 2009 Laszlo Ersek.
Copyright (C) 2009 Antonio Diaz Diaz.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
/*
Return values: 0 for a normal exit, 1 for environmental problems
(file not found, invalid flags, I/O errors, etc), 2 to indicate a
corrupt or invalid input file, 3 for an internal consistency error
(eg, bug) which caused plzip to panic.
*/
#define _FILE_OFFSET_BITS 64
#include
#include
#include
#include
#include
#include /* va_list */
#include
#include /* flockfile() */
#include
#include /* strerror() */
#include
#include
#include /* open() */
#include
#include
#include
#include
#include
#include
#if CHAR_BIT != 8
# error "Environments where CHAR_BIT != 8 are not supported."
#endif
#include "arg_parser.h"
#include "main.h"
#include "plzip.h"
#ifndef LLONG_MAX
#define LLONG_MAX 0x7FFFFFFFFFFFFFFFLL
#endif
#ifndef LLONG_MIN
#define LLONG_MIN (-LLONG_MAX - 1LL)
#endif
#ifndef ULLONG_MAX
#define ULLONG_MAX 0xFFFFFFFFFFFFFFFFULL
#endif
void internal_error( const char * msg );
int readblock( const int fd, char * buf, const int size ) throw();
int writeblock( const int fd, const char * buf, const int size ) throw();
namespace {
const char * invocation_name = 0;
const char * const Program_name = "Plzip";
const char * const program_name = "plzip";
const char * const program_year = "2009";
struct { const char * from; const char * to; } const known_extensions[] = {
{ ".lz", "" },
{ ".tlz", ".tar" },
{ 0, 0 } };
struct lzma_options
{
int dictionary_size; // 4KiB..512MiB
int match_len_limit; // 5..273
};
enum Mode { m_compress = 0, m_decompress, m_test };
std::string output_filename;
int outhandle = -1;
int verbosity = 0;
bool delete_output_on_interrupt = false;
class Pretty_print
{
const char * const stdin_name;
const unsigned int stdin_name_len;
unsigned int longest_name;
std::string name_;
mutable bool first_post;
public:
Pretty_print( const std::vector< std::string > & filenames )
: stdin_name( "(stdin)" ), stdin_name_len( std::strlen( stdin_name ) ),
longest_name( 0 ), first_post( false )
{
for( unsigned int i = 0; i < filenames.size(); ++i )
{
const std::string & s = filenames[i];
const unsigned int len = ( ( s == "-" ) ? stdin_name_len : s.size() );
if( len > longest_name ) longest_name = len;
}
if( longest_name == 0 ) longest_name = stdin_name_len;
}
void set_name( const std::string & filename )
{
if( filename.size() && filename != "-" ) name_ = filename;
else name_ = stdin_name;
first_post = true;
}
void reset() const throw() { if( name_.size() ) first_post = true; }
const char * name() const throw() { return name_.c_str(); }
void operator()( const char * const msg = 0 ) const throw();
};
void show_help() throw()
{
std::printf( "%s - A parallel version of the lzip data compressor.\n", Program_name );
std::printf( "\nUsage: %s [options] [files]\n", invocation_name );
std::printf( "\nOptions:\n" );
std::printf( " -h, --help display this help and exit\n" );
std::printf( " -V, --version output version information and exit\n" );
// std::printf( " -b, --member-size= set member size limit in bytes\n" );
std::printf( " -c, --stdout send output to standard output\n" );
std::printf( " -d, --decompress decompress\n" );
// std::printf( " -f, --force overwrite existing output files\n" );
// std::printf( " -k, --keep keep (don't delete) input files\n" );
// std::printf( " -m, --match-length= set match length limit in bytes [80]\n" );
std::printf( " -n, --threads= set the number of (de)compression threads\n" );
// std::printf( " -o, --output= if reading stdin, place the output into \n" );
std::printf( " -q, --quiet suppress all messages\n" );
// std::printf( " -s, --dictionary-size= set dictionary size limit in bytes [8MiB]\n" );
// std::printf( " -S, --volume-size= set volume size limit in bytes\n" );
std::printf( " -t, --test test compressed file integrity\n" );
std::printf( " -v, --verbose be verbose (a 2nd -v gives more)\n" );
std::printf( " -1 .. -9 set compression level [default 6]\n" );
std::printf( " --fast alias for -1\n" );
std::printf( " --best alias for -9\n" );
if( verbosity > 0 )
{
std::printf( " -D, --debug= (0-3) print debug statistics to stderr\n" );
}
std::printf( "If no file names are given, %s compresses or decompresses\n", program_name );
std::printf( "from standard input to standard output.\n" );
std::printf( "Numbers may be followed by a multiplier: k = kB = 10^3 = 1000,\n" );
std::printf( "Ki = KiB = 2^10 = 1024, M = 10^6, Mi = 2^20, G = 10^9, Gi = 2^30, etc...\n" );
std::printf( "\nReport bugs to lzip-bug@nongnu.org\n" );
std::printf( "Lzip home page: http://www.nongnu.org/lzip/lzip.html\n" );
}
void show_version() throw()
{
std::printf( "%s %s\n", Program_name, PROGVERSION );
std::printf( "Copyright (C) 2009 Laszlo Ersek.\n" );
std::printf( "Copyright (C) %s Antonio Diaz Diaz.\n", program_year );
std::printf( "License GPLv3+: GNU GPL version 3 or later \n" );
std::printf( "This is free software: you are free to change and redistribute it.\n" );
std::printf( "There is NO WARRANTY, to the extent permitted by law.\n" );
}
long long getnum( const char * ptr, const int bs = 0,
const long long llimit = LLONG_MIN + 1,
const long long ulimit = LLONG_MAX ) throw()
{
errno = 0;
char *tail;
long long result = strtoll( ptr, &tail, 0 );
if( tail == ptr )
{
show_error( "bad or missing numerical argument", 0, true );
std::exit( 1 );
}
if( !errno && tail[0] )
{
int factor = ( tail[1] == 'i' ) ? 1024 : 1000;
int exponent = 0;
bool bad_multiplier = false;
switch( tail[0] )
{
case ' ': break;
case 'b': if( bs > 0 ) { factor = bs; exponent = 1; }
else bad_multiplier = true;
break;
case 'Y': exponent = 8; break;
case 'Z': exponent = 7; break;
case 'E': exponent = 6; break;
case 'P': exponent = 5; break;
case 'T': exponent = 4; break;
case 'G': exponent = 3; break;
case 'M': exponent = 2; break;
case 'K': if( factor == 1024 ) exponent = 1; else bad_multiplier = true;
break;
case 'k': if( factor == 1000 ) exponent = 1; else bad_multiplier = true;
break;
default: bad_multiplier = true;
}
if( bad_multiplier )
{
show_error( "bad multiplier in numerical argument", 0, true );
std::exit( 1 );
}
for( int i = 0; i < exponent; ++i )
{
if( LLONG_MAX / factor >= llabs( result ) ) result *= factor;
else { errno = ERANGE; break; }
}
}
if( !errno && ( result < llimit || result > ulimit ) ) errno = ERANGE;
if( errno )
{
show_error( "numerical argument out of limits" );
std::exit( 1 );
}
return result;
}
int get_dict_size( const char * arg ) throw()
{
char *tail;
int bits = std::strtol( arg, &tail, 0 );
if( bits >= min_dictionary_bits && bits <= max_dictionary_bits && *tail == 0 )
return ( 1 << bits );
return getnum( arg, 0, min_dictionary_size, max_dictionary_size );
}
int extension_index( const std::string & name ) throw()
{
for( int i = 0; known_extensions[i].from; ++i )
{
const std::string ext( known_extensions[i].from );
if( name.size() > ext.size() &&
name.compare( name.size() - ext.size(), ext.size(), ext ) == 0 )
return i;
}
return -1;
}
int open_instream( const std::string & name, struct stat * in_statsp,
const Mode program_mode, const int eindex,
const bool force, const bool to_stdout ) throw()
{
int inhandle = -1;
if( program_mode == m_compress && !force && eindex >= 0 )
{
if( verbosity >= 0 )
std::fprintf( stderr, "%s: input file `%s' already has `%s' suffix.\n",
program_name, name.c_str(),
known_extensions[eindex].from );
}
else
{
inhandle = open( name.c_str(), O_RDONLY );
if( inhandle < 0 )
{
if( verbosity >= 0 )
std::fprintf( stderr, "%s: Can't open input file `%s': %s.\n",
program_name, name.c_str(), std::strerror( errno ) );
}
else
{
const int i = fstat( inhandle, in_statsp );
const mode_t & mode = in_statsp->st_mode;
if( i < 0 || !( S_ISREG( mode ) || ( to_stdout &&
( S_ISFIFO( mode ) || S_ISSOCK( mode ) ||
S_ISBLK( mode ) || S_ISCHR( mode ) ) ) ) )
{
if( verbosity >= 0 )
std::fprintf( stderr, "%s: input file `%s' is not a regular file%s.\n",
program_name, name.c_str(),
to_stdout ? "" : " and `--stdout' was not specified" );
close( inhandle );
inhandle = -1;
}
}
}
return inhandle;
}
void set_c_outname( const std::string & name ) throw()
{
output_filename = name;
output_filename += known_extensions[0].from;
}
void set_d_outname( const std::string & name, const int i ) throw()
{
if( i >= 0 )
{
const std::string from( known_extensions[i].from );
if( name.size() > from.size() )
{
output_filename.assign( name, 0, name.size() - from.size() );
output_filename += known_extensions[i].to;
return;
}
}
output_filename = name; output_filename += ".out";
if( verbosity >= 0 )
std::fprintf( stderr, "%s: can't guess original name for `%s' -- using `%s'.\n",
program_name, name.c_str(), output_filename.c_str() );
}
bool open_outstream( const bool force ) throw()
{
if( force )
outhandle = open( output_filename.c_str(), O_CREAT | O_TRUNC | O_WRONLY,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH );
else outhandle = open( output_filename.c_str(), O_CREAT | O_EXCL | O_WRONLY,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH );
if( outhandle < 0 )
{
if( errno == EEXIST ) outhandle = -2; else outhandle = -1;
if( verbosity >= 0 )
{
if( outhandle == -2 )
std::fprintf( stderr, "%s: Output file %s already exists, skipping.\n",
program_name, output_filename.c_str() );
else
std::fprintf( stderr, "%s: Can't create output file `%s': %s.\n",
program_name, output_filename.c_str(), std::strerror( errno ) );
}
}
return ( outhandle >= 0 );
}
bool check_tty( const int inhandle, const Mode program_mode ) throw()
{
if( program_mode == m_compress && isatty( outhandle ) )
{
show_error( "I won't write compressed data to a terminal.", 0, true );
return false;
}
if( ( program_mode == m_decompress || program_mode == m_test ) &&
isatty( inhandle ) )
{
show_error( "I won't read compressed data from a terminal.", 0, true );
return false;
}
return true;
}
void cleanup_and_fail( const int retval ) throw()
{
if( delete_output_on_interrupt )
{
if( verbosity >= 0 )
std::fprintf( stderr, "%s: Deleting output file `%s', if it exists.\n",
program_name, output_filename.c_str() );
if( outhandle >= 0 ) { close( outhandle ); outhandle = -1; }
if( std::remove( output_filename.c_str() ) != 0 )
show_error( "WARNING: deletion of output file (apparently) failed." );
}
std::exit( retval );
}
// Set permissions, owner and times.
void close_and_set_permissions( const struct stat * in_statsp, int * retvalp )
{
int tmp = 0;
if( in_statsp )
{
if( fchmod( outhandle, in_statsp->st_mode ) != 0 ) tmp = 1;
if( !tmp ) (void)fchown( outhandle, in_statsp->st_uid, in_statsp->st_gid );
// fchown will in many cases return with EPERM, which can be safely ignored.
}
if( close( outhandle ) == 0 ) outhandle = -1;
else cleanup_and_fail( 1 );
delete_output_on_interrupt = false;
if( !in_statsp ) return;
if( !tmp )
{
struct utimbuf t;
t.actime = in_statsp->st_atime;
t.modtime = in_statsp->st_mtime;
if( utime( output_filename.c_str(), &t ) != 0 ) tmp = 1;
}
if( tmp )
{
if( tmp > *retvalp ) *retvalp = tmp;
show_error( "I can't change output file attributes." );
cleanup_and_fail( *retvalp );
}
}
int decompress( const int inhandle, const Pretty_print & pp,
const bool testing )
{
void * decoder = LZ_decompress_open();
if( !decoder || LZ_decompress_errno( decoder ) != LZ_ok )
{
LZ_decompress_close( decoder );
pp( "not enough memory. Find a machine with more memory" );
return 1;
}
if( verbosity >= 1 ) pp();
const int in_buffer_size = 65536, out_buffer_size = 8 * in_buffer_size;
uint8_t in_buffer[in_buffer_size], out_buffer[out_buffer_size];
int in_pos = 0, in_stream_pos = 0;
bool finished = false;
while( true )
{
int in_size = 0;
if( !finished )
{
if( in_stream_pos == 0 )
in_stream_pos = readblock( inhandle, (char *)in_buffer, in_buffer_size );
if( in_pos < in_stream_pos )
{
in_size = LZ_decompress_write( decoder, in_buffer + in_pos, in_stream_pos - in_pos );
in_pos += in_size;
}
if( in_pos >= in_stream_pos )
{
if( in_stream_pos < in_buffer_size )
{ finished = true; LZ_decompress_finish( decoder ); }
in_stream_pos = 0; in_pos = 0;
}
}
int out_size = LZ_decompress_read( decoder, out_buffer, out_buffer_size );
// std::fprintf( stderr, "%5d in_size, %6d out_size.\n", in_size, out_size );
if( out_size < 0 )
{
const LZ_errno lz_errno = LZ_decompress_errno( decoder );
if( lz_errno == LZ_header_error )
{
if( LZ_decompress_total_out_size( decoder ) > 0 )
break; // trailing garbage
pp( "error reading member header" );
return 1;
}
if( lz_errno == LZ_mem_error )
{
pp( "not enough memory. Find a machine with more memory" );
return 1;
}
if( lz_errno == LZ_unexpected_eof )
{
if( verbosity >= 0 )
{ pp();
std::fprintf( stderr, "file ends unexpectedly at pos %lld\n",
LZ_decompress_total_in_size( decoder ) ); }
return 2;
}
pp(); show_error( "read error", errno ); return 1;
}
else if( out_size > 0 && outhandle >= 0 )
{
const int wr = writeblock( outhandle, (char *)out_buffer, out_size );
if( wr != out_size )
{ pp(); show_error( "write error", errno ); return 1; }
}
if( LZ_decompress_finished( decoder ) == 1 ) break;
if( finished && in_size == 0 && out_size == 0 )
internal_error( "library error (LZ_decompress_read)" );
}
if( verbosity >= 1 )
{ if( testing ) std::fprintf( stderr, "ok\n" );
else std::fprintf( stderr, "done\n" ); }
LZ_decompress_close( decoder );
return 0;
}
extern "C" void signal_handler( int ) throw()
{
show_error( "Control-C or similar caught, quitting." );
cleanup_and_fail( 1 );
}
void set_signals() throw()
{
signal( SIGHUP, signal_handler );
signal( SIGINT, signal_handler );
signal( SIGTERM, signal_handler );
}
} // end namespace
void Pretty_print::operator()( const char * const msg ) const throw()
{
if( verbosity >= 0 )
{
if( first_post )
{
first_post = false;
std::fprintf( stderr, " %s: ", name_.c_str() );
for( unsigned int i = 0; i < longest_name - name_.size(); ++i )
std::fprintf( stderr, " " );
if( !msg ) std::fflush( stderr );
}
if( msg ) std::fprintf( stderr, "%s.\n", msg );
}
}
/* Private stuff needed by fatal(). */
static pthread_t main_thread;
static pid_t pid;
/* Public utility variables and functions. */
void *(*mallocf)(size_t size);
void (*freef)(void *ptr);
/*
This can be called from any thread, main thread or sub-threads alike, since
they all call common helper functions that call fatal() in case of an error.
*/
void
fatal(void)
{
if( pthread_equal(pthread_self(), main_thread) )
cleanup_and_fail( 1 );
else
{
if (0 == kill(pid, SIGUSR1))
pthread_exit(0);
}
_exit(EXIT_FAILURE);
}
void
fail(const char *fmt, int err, ...)
{
va_list args;
/* Locking stderr should also protect strerror(). */
flockfile(stderr);
(void)fprintf(stderr, "%s: ", program_name);
va_start(args, err);
(void)vfprintf(stderr, fmt, args);
va_end(args);
(void)fprintf(stderr, ": %s\n", strerror(err));
funlockfile(stderr);
/* Stream stderr is never fully buffered originally. */
fatal();
}
void *
xalloc(size_t size)
{
void *ret = (*mallocf)(size);
if (0 == ret) {
fail("(*mallocf)()", errno);
}
return ret;
}
void
xinit(Cond *cond)
{
int ret;
pthread_mutexattr_t attr;
ret = pthread_mutexattr_init(&attr);
if (0 != ret) {
fail("pthread_mutexattr_init()", ret);
}
ret = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
if (0 != ret) {
fail("pthread_mutexattr_settype()", ret);
}
ret = pthread_mutex_init(&cond->lock, &attr);
if (0 != ret) {
fail("pthread_mutex_init()", ret);
}
ret = pthread_mutexattr_destroy(&attr);
if (0 != ret) {
fail("pthread_mutexattr_destroy()", ret);
}
ret = pthread_cond_init(&cond->cond, 0);
if (0 != ret) {
fail("pthread_cond_init()", ret);
}
cond->ccount = 0lu;
cond->wcount = 0lu;
}
void
xdestroy(Cond *cond)
{
int ret;
ret = pthread_cond_destroy(&cond->cond);
if (0 != ret) {
fail("pthread_cond_destroy()", ret);
}
ret = pthread_mutex_destroy(&cond->lock);
if (0 != ret) {
fail("pthread_mutex_destroy()", ret);
}
}
void
xlock(Cond *cond)
{
int ret;
ret = pthread_mutex_lock(&cond->lock);
if (0 != ret) {
fail("pthread_mutex_lock()", ret);
}
}
void
xlock_pred(Cond *cond)
{
xlock(cond);
++cond->ccount;
}
void
xunlock(Cond *cond)
{
int ret;
ret = pthread_mutex_unlock(&cond->lock);
if (0 != ret) {
fail("pthread_mutex_unlock()", ret);
}
}
void
xwait(Cond *cond)
{
int ret;
++cond->wcount;
ret = pthread_cond_wait(&cond->cond, &cond->lock);
if (0 != ret) {
fail("pthread_cond_wait()", ret);
}
++cond->ccount;
}
void
xsignal(Cond *cond)
{
int ret;
ret = pthread_cond_signal(&cond->cond);
if (0 != ret) {
fail("pthread_cond_signal()", ret);
}
}
void
xbroadcast(Cond *cond)
{
int ret;
ret = pthread_cond_broadcast(&cond->cond);
if (0 != ret) {
fail("pthread_cond_broadcast()", ret);
}
}
void
xcreate(pthread_t *thread, void *(*routine)(void *), void *arg)
{
int ret;
ret = pthread_create(thread, 0, routine, arg);
if (0 != ret) {
fail("pthread_create()", ret);
}
}
void
xjoin(pthread_t thread)
{
int ret;
ret = pthread_join(thread, 0);
if (0 != ret) {
fail("pthread_join()", ret);
}
}
void
xraise(int sig)
{
if (-1 == kill(pid, sig)) {
fail("kill()", errno);
}
}
/* Private stuff part 2. */
struct Opts
{
unsigned num_worker; /* Start this many worker threads. */
unsigned clidx; /* Compression level index in [0u .. 8u]. */
int print_cctrs; /* Print condition variable counters. */
};
/* Backlog factor for all workers together. */
static const unsigned blf = 3u;
/* Separator characters in environment variable values. No escaping. */
static const char envsep[] = " \t";
static void *
trace_malloc(size_t size)
{
void *ret;
int save_errno = 0;
ret = malloc(size);
if (0 == ret) {
save_errno = errno;
}
if (0 > fprintf(stderr, "%lu: malloc(%lu) == %p\n", (long unsigned)pid,
(long unsigned)size, ret)
) {
fatal();
}
if (0 == ret) {
errno = save_errno;
}
return ret;
}
static void
trace_free(void *ptr)
{
if (0 > fprintf(stderr, "%lu: free(%p)\n", (long unsigned)pid, ptr)) {
fatal();
}
free(ptr);
}
static void
xsigemptyset(sigset_t *set)
{
if (-1 == sigemptyset(set)) {
fail("sigemptyset()", errno);
}
}
static void
xsigaddset(sigset_t *set, int signo)
{
if (-1 == sigaddset(set, signo)) {
fail("sigaddset()", errno);
}
}
static void
xsigmask(int how, const sigset_t *set, sigset_t *oset)
{
int ret;
ret = pthread_sigmask(how, set, oset);
if (0 != ret) {
fail("pthread_sigmask()", ret);
}
}
static void
xsigaction(int sig, void (*handler)(int))
{
struct sigaction act;
act.sa_handler = handler;
xsigemptyset(&act.sa_mask);
act.sa_flags = 0;
if (-1 == sigaction(sig, &act, 0)) {
fail("sigaction()", errno);
}
}
enum caught_sig
{
CS_INT = 1,
CS_TERM,
CS_USR1,
CS_USR2
};
static volatile sig_atomic_t caught_sig;
static void
sighandler(int sig)
{
/* sig_atomic_t is nowhere required to be able to hold signal values. */
switch (sig) {
case SIGINT:
caught_sig = CS_INT;
break;
case SIGTERM:
caught_sig = CS_TERM;
break;
case SIGUSR1:
caught_sig = CS_USR1;
break;
case SIGUSR2:
caught_sig = CS_USR2;
break;
default:
assert(0);
}
}
static void
process( const Opts *opts, unsigned num_slot,
int infd, int outfd, const Pretty_print & pp, const sigset_t *unblocked )
{
/*
We could wait for signals with either sigwait() or sigsuspend(). SUSv2
states about sigwait() that its effect on signal actions is unspecified.
SUSv3 still claims the same.
The SUSv2 description of sigsuspend() talks about both the thread and the
whole process being suspended until a signal arrives, although thread
suspension seems much more likely from the wording. They note that they
filed a clarification request for this. SUSv3 cleans this up and chooses
thread suspension which was more logical anyway.
I favor sigsuspend() because I need to re-raise SIGTERM and SIGINT, and
unspecified action behavior with sigwait() seems messy.
13-OCT-2009 lacos
*/
Plzip_arg muxer_arg;
pthread_t muxer;
if( verbosity >= 1 ) pp( "" );
muxer_arg.num_worker = opts->num_worker;
muxer_arg.num_slot = num_slot;
muxer_arg.clidx = opts->clidx;
muxer_arg.print_cctrs = opts->print_cctrs;
muxer_arg.infd = infd;
muxer_arg.outfd = outfd;
xcreate(&muxer, plzip_wrap, &muxer_arg);
/* Unblock signals, wait for them, then block them again. */
{
int ret = sigsuspend(unblocked);
assert(-1 == ret && EINTR == errno);
}
switch (caught_sig) {
case CS_INT:
case CS_TERM: // FIXME remove output file
{
int sig;
sigset_t mask;
sig = (CS_INT == caught_sig) ? SIGINT : SIGTERM;
/*
We might have inherited a SIG_IGN from the parent, but that would
make no sense here. 24-OCT-2009 lacos
*/
xsigaction(sig, SIG_DFL);
xraise(sig);
xsigemptyset(&mask);
xsigaddset(&mask, sig);
xsigmask(SIG_UNBLOCK, &mask, 0);
}
/*
We shouldn't reach this point, but if we do for some reason, fall
through.
*/
case CS_USR1:
/* Error from a non-main thread via fatal(). */
fatal();
case CS_USR2:
/* Muxer thread joined other sub-threads and finished successfully. */
break;
default:
assert(0);
}
xjoin(muxer);
}
static void
sigs_mod(int block_n_catch, sigset_t *oset)
{
void (*handler)(int);
if (block_n_catch) {
sigset_t mask;
xsigemptyset(&mask);
xsigaddset(&mask, SIGINT);
xsigaddset(&mask, SIGTERM);
xsigaddset(&mask, SIGUSR1);
xsigaddset(&mask, SIGUSR2);
xsigmask(SIG_BLOCK, &mask, oset);
handler = sighandler;
}
else {
handler = SIG_DFL;
}
xsigaction(SIGINT, handler);
xsigaction(SIGTERM, handler);
xsigaction(SIGUSR1, handler);
xsigaction(SIGUSR2, handler);
if (!block_n_catch) {
xsigmask(SIG_SETMASK, oset, 0);
}
}
void show_error( const char * msg, const int errcode, const bool help ) throw()
{
if( verbosity >= 0 )
{
if( msg && msg[0] != 0 )
{
std::fprintf( stderr, "%s: %s", program_name, msg );
if( errcode > 0 ) std::fprintf( stderr, ": %s", std::strerror( errcode ) );
std::fprintf( stderr, "\n" );
}
if( help && invocation_name && invocation_name[0] != 0 )
std::fprintf( stderr, "Try `%s --help' for more information.\n", invocation_name );
}
}
void internal_error( const char * msg )
{
std::string s( "internal error: " ); s += msg;
show_error( s.c_str() );
std::exit( 3 );
}
// Returns the number of bytes really read.
// If (returned value < size) and (errno == 0), means EOF was reached.
//
int readblock( const int fd, char * buf, const int size ) throw()
{
int rest = size;
errno = 0;
while( rest > 0 )
{
errno = 0;
const int n = read( fd, buf + size - rest, rest );
if( n > 0 ) rest -= n;
else if( n == 0 ) break;
else if( errno != EINTR && errno != EAGAIN ) break;
}
return ( rest > 0 ) ? size - rest : size;
}
// Returns the number of bytes really written.
// If (returned value < size), it is always an error.
//
int writeblock( const int fd, const char * buf, const int size ) throw()
{
int rest = size;
errno = 0;
while( rest > 0 )
{
errno = 0;
const int n = write( fd, buf + size - rest, rest );
if( n > 0 ) rest -= n;
else if( errno && errno != EINTR && errno != EAGAIN ) break;
}
return ( rest > 0 ) ? size - rest : size;
}
int main( const int argc, const char * argv[] )
{
Opts opts;
// Mapping from gzip/bzip2 style 1..9 compression modes
// to the corresponding LZMA compression modes.
const lzma_options option_mapping[] =
{
{ 1 << 20, 10 }, // -1
{ 1 << 20, 12 }, // -2
{ 1 << 20, 17 }, // -3
{ 1 << 21, 26 }, // -4
{ 1 << 22, 44 }, // -5
{ 1 << 23, 80 }, // -6
{ 1 << 24, 108 }, // -7
{ 1 << 24, 163 }, // -8
{ 1 << 25, 273 } }; // -9
lzma_options encoder_options = option_mapping[5]; // default = "-6"
int inhandle = -1;
Mode program_mode = m_compress;
bool force = false;
bool keep_input_files = false;
bool to_stdout = false;
std::string input_filename;
std::string default_output_filename;
std::vector< std::string > filenames;
invocation_name = argv[0];
if( LZ_version()[0] != LZ_version_string[0] )
internal_error( "bad library version" );
main_thread = pthread_self();
pid = getpid();
mallocf = malloc;
freef = free;
xsigaction(SIGPIPE, SIG_IGN);
xsigaction(SIGXFSZ, SIG_IGN);
/* Effectuate option defaults. */
long max_worker = sysconf(_SC_THREAD_THREADS_MAX);
if (-1L == max_worker) {
max_worker = LONG_MAX;
}
if (UINT_MAX < (long unsigned)max_worker) {
max_worker = UINT_MAX;
}
if (UINT_MAX / blf < (unsigned)max_worker) {
max_worker = UINT_MAX / blf;
}
if ((size_t)-1 / sizeof(pthread_t) < (unsigned)max_worker) {
max_worker = (size_t)-1 / sizeof(pthread_t);
}
opts.num_worker = 0u;
opts.clidx = 5u;
opts.print_cctrs = 0;
const Arg_parser::Option options[] =
{
{ '1', "fast", Arg_parser::no },
{ '2', 0, Arg_parser::no },
{ '3', 0, Arg_parser::no },
{ '4', 0, Arg_parser::no },
{ '5', 0, Arg_parser::no },
{ '6', 0, Arg_parser::no },
{ '7', 0, Arg_parser::no },
{ '8', 0, Arg_parser::no },
{ '9', "best", Arg_parser::no },
{ 'b', "member-size", Arg_parser::yes },
{ 'c', "stdout", Arg_parser::no },
{ 'd', "decompress", Arg_parser::no },
{ 'D', "debug", Arg_parser::yes },
{ 'f', "force", Arg_parser::no },
{ 'h', "help", Arg_parser::no },
{ 'k', "keep", Arg_parser::no },
{ 'm', "match-length", Arg_parser::yes },
{ 'n', "threads", Arg_parser::yes },
{ 'o', "output", Arg_parser::yes },
{ 'q', "quiet", Arg_parser::no },
{ 's', "dictionary-size", Arg_parser::yes },
{ 'S', "volume-size", Arg_parser::yes },
{ 't', "test", Arg_parser::no },
{ 'v', "verbose", Arg_parser::no },
{ 'V', "version", Arg_parser::no },
{ 0 , 0, Arg_parser::no } };
Arg_parser parser( argc, argv, options );
if( parser.error().size() ) // bad option
{ show_error( parser.error().c_str(), 0, true ); return 1; }
int argind = 0;
for( ; argind < parser.arguments(); ++argind )
{
const int code = parser.code( argind );
if( !code ) break; // no more options
const char * arg = parser.argument( argind ).c_str();
switch( code )
{
case '1': case '2': case '3':
case '4': case '5': case '6':
case '7': case '8': case '9':
opts.clidx = code - '1';
encoder_options = option_mapping[code-'1']; break;
case 'c': to_stdout = true; break;
case 'd': program_mode = m_decompress; break;
case 'D': { int debug_level = getnum( arg, 0, 0, 3 );
if( debug_level & 1 ) opts.print_cctrs = 1;
if( debug_level & 2 )
{ mallocf = trace_malloc; freef = trace_free; } }
break;
case 'f': force = true; break;
case 'h': show_help(); return 0;
case 'k': keep_input_files = true; break;
case 'm': encoder_options.match_len_limit =
getnum( arg, 0, 5, 273 ); break;
case 'o': default_output_filename = arg; break;
case 'n': opts.num_worker = getnum( arg, 0, 1, max_worker ); break;
case 'q': verbosity = -1; break;
case 's': encoder_options.dictionary_size = get_dict_size( arg );
break;
case 't': program_mode = m_test; break;
case 'v': if( verbosity < 4 ) ++verbosity; break;
case 'V': show_version(); return 0;
default : internal_error( "uncaught option" );
}
}
if( opts.num_worker <= 0 )
{
#ifdef _SC_NPROCESSORS_ONLN
long num_online = sysconf( _SC_NPROCESSORS_ONLN );
if( num_online <= 0 )
{
show_error( "number of online processors unavailable.", 0, true );
fatal();
}
opts.num_worker = std::min( num_online, max_worker );
#else
show_error( "number of (de)compression threads not set.", 0, true );
fatal();
#endif
}
assert(UINT_MAX / blf >= opts.num_worker);
unsigned num_slot = opts.num_worker * blf;
bool filenames_given = false;
for( ; argind < parser.arguments(); ++argind )
{
if( parser.argument( argind ) != "-" ) filenames_given = true;
filenames.push_back( parser.argument( argind ) );
}
if( filenames.empty() ) filenames.push_back("-");
if( filenames_given && program_mode != m_compress ) set_signals();
Pretty_print pp( filenames );
if( program_mode == m_test )
outhandle = -1;
int retval = 0;
for( unsigned int i = 0; i < filenames.size(); ++i )
{
struct stat in_stats;
output_filename.clear();
if( !filenames[i].size() || filenames[i] == "-" )
{
input_filename.clear();
inhandle = STDIN_FILENO;
if( program_mode != m_test )
{
if( to_stdout || !default_output_filename.size() )
outhandle = STDOUT_FILENO;
else
{
if( program_mode == m_compress )
set_c_outname( default_output_filename );
else output_filename = default_output_filename;
if( !open_outstream( force ) )
{
if( outhandle == -1 && retval < 1 ) retval = 1;
close( inhandle ); inhandle = -1;
continue;
}
}
}
}
else
{
input_filename = filenames[i];
const int eindex = extension_index( input_filename );
inhandle = open_instream( input_filename, &in_stats, program_mode,
eindex, force, to_stdout );
if( inhandle < 0 ) { if( retval < 1 ) retval = 1; continue; }
if( program_mode != m_test )
{
if( to_stdout ) outhandle = STDOUT_FILENO;
else
{
if( program_mode == m_compress )
set_c_outname( input_filename );
else set_d_outname( input_filename, eindex );
if( !open_outstream( force ) )
{
if( outhandle == -1 && retval < 1 ) retval = 1;
close( inhandle ); inhandle = -1;
continue;
}
}
}
}
if( !check_tty( inhandle, program_mode ) ) return 1;
if( output_filename.size() && !to_stdout && program_mode != m_test )
delete_output_on_interrupt = true;
const struct stat * in_statsp = input_filename.size() ? &in_stats : 0;
pp.set_name( input_filename );
int tmp = 0;
if( program_mode == m_compress )
{
sigset_t unblocked;
sigs_mod(1, &unblocked);
process( &opts, num_slot, inhandle, outhandle, pp, &unblocked );
sigs_mod(0, &unblocked);
}
else
tmp = decompress( inhandle, pp, program_mode == m_test );
if( tmp > retval ) retval = tmp;
if( tmp && program_mode != m_test ) cleanup_and_fail( retval );
if( delete_output_on_interrupt )
close_and_set_permissions( in_statsp, &retval );
if( input_filename.size() )
{
close( inhandle ); inhandle = -1;
if( !keep_input_files && !to_stdout && program_mode != m_test )
std::remove( input_filename.c_str() );
}
}
if( outhandle >= 0 && close( outhandle ) != 0 )
{
if( verbosity >= 0 )
std::fprintf( stderr, "%s: Can't close stdout: %s.\n",
program_name, std::strerror( errno ) );
if( retval < 1 ) retval = 1;
}
return retval;
}