diff options
Diffstat (limited to 'main.cc')
-rw-r--r-- | main.cc | 154 |
1 files changed, 122 insertions, 32 deletions
@@ -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 |