summaryrefslogtreecommitdiffstats
path: root/main.cc
diff options
context:
space:
mode:
Diffstat (limited to 'main.cc')
-rw-r--r--main.cc154
1 files changed, 122 insertions, 32 deletions
diff --git a/main.cc b/main.cc
index 38bcf63..e04d37d 100644
--- a/main.cc
+++ b/main.cc
@@ -1,5 +1,5 @@
/* Tarlz - Archiver with multimember lzip compression
- Copyright (C) 2013-2021 Antonio Diaz Diaz.
+ Copyright (C) 2013-2022 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
@@ -56,7 +56,7 @@ const char * const program_name = "tarlz";
namespace {
-const char * const program_year = "2021";
+const char * const program_year = "2022";
const char * invocation_name = program_name; // default value
@@ -146,17 +146,44 @@ void show_version()
}
+int check_lzlib_ver() // <major>.<minor> or <major>.<minor>[a-z.-]*
+ {
+#if defined LZ_API_VERSION && LZ_API_VERSION >= 1012
+ const unsigned char * p = (unsigned char *)LZ_version_string;
+ unsigned major = 0, minor = 0;
+ while( major < 100000 && isdigit( *p ) )
+ { major *= 10; major += *p - '0'; ++p; }
+ if( *p == '.' ) ++p;
+ else
+out: { show_error( "Invalid LZ_version_string in lzlib.h" ); return 2; }
+ while( minor < 100 && isdigit( *p ) )
+ { minor *= 10; minor += *p - '0'; ++p; }
+ if( *p && *p != '-' && *p != '.' && !std::islower( *p ) ) goto out;
+ const unsigned version = major * 1000 + minor;
+ if( LZ_API_VERSION != version )
+ {
+ if( verbosity >= 0 )
+ std::fprintf( stderr, "%s: Version mismatch in lzlib.h: "
+ "LZ_API_VERSION = %u, should be %u.\n",
+ program_name, LZ_API_VERSION, version );
+ return 2;
+ }
+#endif
+ return 0;
+ }
+
+
int check_lib()
{
- bool warning = false;
+ int retval = check_lzlib_ver();
if( std::strcmp( LZ_version_string, LZ_version() ) != 0 )
- { warning = true;
+ { set_retval( retval, 1 );
if( verbosity >= 0 )
std::printf( "warning: LZ_version_string != LZ_version() (%s vs %s)\n",
LZ_version_string, LZ_version() ); }
#if defined LZ_API_VERSION && LZ_API_VERSION >= 1012
if( LZ_API_VERSION != LZ_api_version() )
- { warning = true;
+ { set_retval( retval, 1 );
if( verbosity >= 0 )
std::printf( "warning: LZ_API_VERSION != LZ_api_version() (%u vs %u)\n",
LZ_API_VERSION, LZ_api_version() ); }
@@ -173,20 +200,54 @@ int check_lib()
"Using an unknown LZ_API_VERSION\n", LZ_API_VERSION );
#endif
}
- return warning;
+ return retval;
+ }
+
+
+// separate large numbers >= 100_000 in groups of 3 digits using '_'
+const char * format_num3( unsigned long long num )
+ {
+ const char * const si_prefix = "kMGTPEZY";
+ const char * const binary_prefix = "KMGTPEZY";
+ enum { buffers = 8, bufsize = 4 * sizeof (long long) };
+ static char buffer[buffers][bufsize]; // circle of static buffers for printf
+ static int current = 0;
+
+ char * const buf = buffer[current++]; current %= buffers;
+ char * p = buf + bufsize - 1; // fill the buffer backwards
+ *p = 0; // terminator
+ char prefix = 0; // try binary first, then si
+ for( int i = 0; i < 8 && num >= 1024 && num % 1024 == 0; ++i )
+ { num /= 1024; prefix = binary_prefix[i]; }
+ if( prefix ) *(--p) = 'i';
+ else
+ for( int i = 0; i < 8 && num >= 1000 && num % 1000 == 0; ++i )
+ { num /= 1000; prefix = si_prefix[i]; }
+ if( prefix ) *(--p) = prefix;
+ const bool split = num >= 100000;
+
+ for( int i = 0; ; )
+ {
+ *(--p) = num % 10 + '0'; num /= 10; if( num == 0 ) break;
+ if( split && ++i >= 3 ) { i = 0; *(--p) = '_'; }
+ }
+ return p;
}
-unsigned long long getnum( const char * const ptr,
+unsigned long long getnum( const char * const arg,
+ const char * const option_name,
const unsigned long long llimit,
const unsigned long long ulimit )
{
char * tail;
errno = 0;
- unsigned long long result = strtoull( ptr, &tail, 0 );
- if( tail == ptr )
+ unsigned long long result = strtoull( arg, &tail, 0 );
+ if( tail == arg )
{
- show_error( "Bad or missing numerical argument.", 0, true );
+ if( verbosity >= 0 )
+ std::fprintf( stderr, "%s: Bad or missing numerical argument in "
+ "option '%s'.\n", program_name, option_name );
std::exit( 1 );
}
@@ -208,7 +269,9 @@ unsigned long long getnum( const char * const ptr,
}
if( exponent <= 0 )
{
- show_error( "Bad multiplier in numerical argument.", 0, true );
+ if( verbosity >= 0 )
+ std::fprintf( stderr, "%s: Bad multiplier in numerical argument of "
+ "option '%s'.\n", program_name, option_name );
std::exit( 1 );
}
for( int i = 0; i < exponent; ++i )
@@ -220,7 +283,10 @@ unsigned long long getnum( const char * const ptr,
if( !errno && ( result < llimit || result > ulimit ) ) errno = ERANGE;
if( errno )
{
- show_error( "Numerical argument out of limits." );
+ if( verbosity >= 0 )
+ std::fprintf( stderr, "%s: Numerical argument out of limits [%s,%s] "
+ "in option '%s'.\n", program_name, format_num3( llimit ),
+ format_num3( ulimit ), option_name );
std::exit( 1 );
}
return result;
@@ -249,10 +315,10 @@ void set_mode( Program_mode & program_mode, const Program_mode new_mode )
}
-void set_mtime( long long & mtime, const char * arg )
+void set_mtime( long long & mtime, const char * arg, const char * const pn )
{
if( *arg == '@' )
- { mtime = getnum( arg + 1, 0, ( 1ULL << 33 ) - 1 ); return; }
+ { mtime = getnum( arg + 1, pn, 0, ( 1ULL << 33 ) - 1 ); return; }
else if( *arg == '.' || *arg == '/' )
{
struct stat st;
@@ -276,22 +342,22 @@ void set_mtime( long long & mtime, const char * arg )
}
-void set_owner( int & owner, const char * const arg )
+void set_owner( int & owner, const char * const arg, const char * const pn )
{
const struct passwd * const pw = getpwnam( arg );
if( pw ) owner = pw->pw_uid;
else if( std::isdigit( (unsigned char)arg[0] ) )
- owner = getnum( arg, 0, INT_MAX );
+ owner = getnum( arg, pn, 0, INT_MAX );
else if( std::strcmp( arg, "root" ) == 0 ) owner = 0;
else { show_file_error( arg, "Invalid owner" ); std::exit( 1 ); }
}
-void set_group( int & group, const char * const arg )
+void set_group( int & group, const char * const arg, const char * const pn )
{
const struct group * const gr = getgrnam( arg );
if( gr ) group = gr->gr_gid;
else if( std::isdigit( (unsigned char)arg[0] ) )
- group = getnum( arg, 0, INT_MAX );
+ group = getnum( arg, pn, 0, INT_MAX );
else if( std::strcmp( arg, "root" ) == 0 ) group = 0;
else { show_file_error( arg, "Invalid group" ); std::exit( 1 ); }
}
@@ -308,7 +374,12 @@ int open_instream( const std::string & name )
{
const int infd = open( name.c_str(), O_RDONLY | O_BINARY );
if( infd < 0 )
- show_file_error( name.c_str(), "Can't open for reading", errno );
+ { show_file_error( name.c_str(), "Can't open for reading", errno );
+ return -1; }
+ struct stat st; // infd must not be a directory
+ if( fstat( infd, &st ) == 0 && S_ISDIR( st.st_mode ) )
+ { show_file_error( name.c_str(), "Is a directory." );
+ close( infd ); return -1; }
return infd;
}
@@ -460,6 +531,9 @@ int main( const int argc, const char * const argv[] )
if( max_workers < 1 || max_workers > INT_MAX / (int)sizeof (pthread_t) )
max_workers = INT_MAX / sizeof (pthread_t);
+ const char * f_pn = 0;
+ const char * o_pn = 0;
+ const char * z_pn = 0;
for( int argind = 0; argind < parser.arguments(); ++argind )
{
const int code = parser.code( argind );
@@ -470,6 +544,7 @@ int main( const int argc, const char * const argv[] )
if( parser.argument( argind ) != "-" ) cl_opts.filenames_given = true;
++cl_opts.num_files; continue;
}
+ const char * const pn = parser.parsed_name( argind ).c_str();
const std::string & sarg = parser.argument( argind );
const char * const arg = sarg.c_str();
switch( code )
@@ -478,16 +553,16 @@ int main( const int argc, const char * const argv[] )
case '5': case '6': case '7': case '8': case '9':
cl_opts.level = code - '0'; break;
case 'A': set_mode( cl_opts.program_mode, m_concatenate ); break;
- case 'B': cl_opts.data_size = getnum( arg, min_data_size, max_data_size );
- break;
+ case 'B': cl_opts.data_size =
+ getnum( arg, pn, min_data_size, max_data_size ); break;
case 'c': set_mode( cl_opts.program_mode, m_create ); break;
case 'C': break; // skip chdir
case 'd': set_mode( cl_opts.program_mode, m_diff ); break;
- case 'f': set_archive_name( cl_opts.archive_name, sarg ); break;
+ case 'f': set_archive_name( cl_opts.archive_name, sarg ); f_pn = pn; break;
case 'h': cl_opts.dereference = true; break;
case 'H': break; // ignore format
- case 'n': cl_opts.num_workers = getnum( arg, 0, max_workers ); break;
- case 'o': cl_opts.output_filename = sarg; break;
+ case 'n': cl_opts.num_workers = getnum( arg, pn, 0, max_workers ); break;
+ case 'o': cl_opts.output_filename = sarg; o_pn = pn; break;
case 'p': cl_opts.preserve_permissions = true; break;
case 'q': verbosity = -1; break;
case 'r': set_mode( cl_opts.program_mode, m_append ); break;
@@ -495,25 +570,25 @@ int main( const int argc, const char * const argv[] )
case 'v': if( verbosity < 4 ) ++verbosity; break;
case 'V': show_version(); return 0;
case 'x': set_mode( cl_opts.program_mode, m_extract ); break;
- case 'z': set_mode( cl_opts.program_mode, m_compress ); break;
- case opt_ano: set_owner( cl_opts.owner, "root" );
- set_group( cl_opts.group, "root" ); break;
+ case 'z': set_mode( cl_opts.program_mode, m_compress ); z_pn = pn; break;
+ case opt_ano: set_owner( cl_opts.owner, "root", pn );
+ set_group( cl_opts.group, "root", pn ); break;
case opt_aso: cl_opts.solidity = asolid; break;
case opt_bso: cl_opts.solidity = bsolid; break;
case opt_crc: cl_opts.missing_crc = true; break;
case opt_chk: return check_lib();
- case opt_dbg: cl_opts.debug_level = getnum( arg, 0, 3 ); break;
+ case opt_dbg: cl_opts.debug_level = getnum( arg, pn, 0, 3 ); break;
case opt_del: set_mode( cl_opts.program_mode, m_delete ); break;
case opt_dso: cl_opts.solidity = dsolid; break;
case opt_exc: Exclude::add_pattern( sarg ); break;
- case opt_grp: set_group( cl_opts.group, arg ); break;
+ case opt_grp: set_group( cl_opts.group, arg, pn ); break;
case opt_hlp: show_help( num_online ); return 0;
case opt_id: cl_opts.ignore_ids = true; break;
case opt_kd: cl_opts.keep_damaged = true; break;
- case opt_mti: set_mtime( cl_opts.mtime, arg ); break;
+ case opt_mti: set_mtime( cl_opts.mtime, arg, pn ); break;
case opt_nso: cl_opts.solidity = no_solid; break;
- case opt_out: cl_opts.out_slots = getnum( arg, 1, 1024 ); break;
- case opt_own: set_owner( cl_opts.owner, arg ); break;
+ case opt_out: cl_opts.out_slots = getnum( arg, pn, 1, 1024 ); break;
+ case opt_own: set_owner( cl_opts.owner, arg, pn ); break;
case opt_per: cl_opts.permissive = true; break;
case opt_sol: cl_opts.solidity = solid; break;
case opt_un: cl_opts.level = -1; break;
@@ -522,6 +597,21 @@ int main( const int argc, const char * const argv[] )
}
} // end process options
+ if( cl_opts.program_mode != m_compress && cl_opts.output_filename.size() )
+ {
+ if( verbosity >= 0 )
+ std::fprintf( stderr, "%s: Option '%s' can only be used with "
+ "'-z, --compress'.\n", program_name, o_pn );
+ return 1;
+ }
+ if( cl_opts.program_mode == m_compress && f_pn )
+ {
+ if( verbosity >= 0 )
+ std::fprintf( stderr, "%s: Option '%s' can't be used with '%s'.\n",
+ program_name, f_pn, z_pn );
+ return 1;
+ }
+
#if !defined LZ_API_VERSION || LZ_API_VERSION < 1012 // compile-time test
#error "lzlib 1.12 or newer needed."
#endif