/* Tarlz - Archiver with multimember lzip compression Copyright (C) 2013-2018 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 2 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 . */ /* Exit status: 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 tarlz to panic. */ #define _FILE_OFFSET_BITS 64 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__OS2__) #include #endif #include "arg_parser.h" #include "tarlz.h" #ifndef O_BINARY #define O_BINARY 0 #endif #if CHAR_BIT != 8 #error "Environments where CHAR_BIT != 8 are not supported." #endif int verbosity = 0; namespace { const char * const Program_name = "Tarlz"; const char * const program_name = "tarlz"; const char * const program_year = "2018"; const char * invocation_name = 0; enum Mode { m_none, m_append, m_create, m_extract, m_list }; void show_help() { std::printf( "%s - Archiver with multimember lzip compression.\n", Program_name ); std::printf( "\nUsage: %s [options] [files]\n", invocation_name ); std::printf( "\nOptions:\n" " -h, --help display this help and exit\n" " -V, --version output version information and exit\n" " -c, --create create a new archive\n" " -C, --directory= change to directory \n" " -f, --file= use archive file \n" " -q, --quiet suppress all messages\n" " -r, --append append files to the end of an archive\n" " -t, --list list the contents of an archive\n" " -v, --verbose verbosely list files processed\n" " -x, --extract extract files from an archive\n" " -0 .. -9 set compression level [default 6]\n" " --asolid create solidly compressed appendable archive\n" " --dsolid create per-directory compressed archive\n" " --solid create solidly compressed archive\n" " --group= use name/id for added files\n" " --owner= use name/id for added files\n" " --uncompressed don't compress the created archive\n" "\nExit status: 0 for a normal exit, 1 for environmental problems (file\n" "not found, invalid flags, I/O errors, etc), 2 to indicate a corrupt or\n" "invalid input file, 3 for an internal consistency error (eg, bug) which\n" "caused tarlz to panic.\n" "\nReport bugs to lzip-bug@nongnu.org\n" "Tarlz home page: http://www.nongnu.org/lzip/tarlz.html\n" ); } void show_version() { std::printf( "%s %s\n", program_name, PROGVERSION ); std::printf( "Copyright (C) %s Antonio Diaz Diaz.\n", program_year ); std::printf( "License GPLv2+: GNU GPL version 2 or later \n" "This is free software: you are free to change and redistribute it.\n" "There is NO WARRANTY, to the extent permitted by law.\n" ); } unsigned long long getnum( const char * const ptr, 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 ) { show_error( "Bad or missing numerical argument.", 0, true ); std::exit( 1 ); } if( !errno && tail[0] ) { const unsigned factor = ( tail[1] == 'i' ) ? 1024 : 1000; int exponent = 0; // 0 = bad multiplier switch( tail[0] ) { 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; break; case 'k': if( factor == 1000 ) exponent = 1; break; } if( exponent <= 0 ) { show_error( "Bad multiplier in numerical argument.", 0, true ); std::exit( 1 ); } for( int i = 0; i < exponent; ++i ) { if( ulimit / factor >= 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; } void set_mode( Mode & program_mode, const Mode new_mode ) { if( program_mode != m_none && program_mode != new_mode ) { show_error( "Only one operation can be specified.", 0, true ); std::exit( 1 ); } program_mode = new_mode; } void set_owner( const char * const arg ) { const struct passwd * const pw = getpwnam( arg ); if( pw ) cl_owner = pw->pw_uid; else if( std::isdigit( arg[0] ) ) cl_owner = getnum( arg, 0, INT_MAX ); else { show_file_error( arg, "Invalid owner" ); std::exit( 1 ); } } void set_group( const char * const arg ) { const struct group * const gr = getgrnam( arg ); if( gr ) cl_group = gr->gr_gid; else if( std::isdigit( arg[0] ) ) cl_group = getnum( arg, 0, INT_MAX ); else { show_file_error( arg, "Invalid group" ); std::exit( 1 ); } } } // end namespace 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 ); return infd; } int open_outstream( const std::string & name, const bool create ) { const int flags = (create ? O_CREAT | O_WRONLY | O_TRUNC : O_RDWR) | O_BINARY; const mode_t outfd_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; const int outfd = open( name.c_str(), flags, outfd_mode ); if( outfd < 0 ) show_file_error( name.c_str(), create ? "Can't create file" : "Error opening file", errno ); return outfd; } /* Returns the number of bytes really read. If (returned value < size) and (errno == 0), means EOF was reached. */ int readblock( const int fd, uint8_t * const buf, const int size ) { int sz = 0; errno = 0; while( sz < size ) { const int n = read( fd, buf + sz, size - sz ); if( n > 0 ) sz += n; else if( n == 0 ) break; // EOF else if( errno != EINTR ) break; errno = 0; } return sz; } /* Returns the number of bytes really written. If (returned value < size), it is always an error. */ int writeblock( const int fd, const uint8_t * const buf, const int size ) { int sz = 0; errno = 0; while( sz < size ) { const int n = write( fd, buf + sz, size - sz ); if( n > 0 ) sz += n; else if( n < 0 && errno != EINTR ) break; errno = 0; } return sz; } void show_error( const char * const msg, const int errcode, const bool help ) { if( verbosity < 0 ) return; if( msg && msg[0] ) std::fprintf( stderr, "%s: %s%s%s\n", program_name, msg, ( errcode > 0 ) ? ": " : "", ( errcode > 0 ) ? std::strerror( errcode ) : "" ); if( help ) std::fprintf( stderr, "Try '%s --help' for more information.\n", invocation_name ); } void show_file_error( const char * const filename, const char * const msg, const int errcode ) { if( verbosity >= 0 ) std::fprintf( stderr, "%s: %s: %s%s%s\n", program_name, filename, msg, ( errcode > 0 ) ? ": " : "", ( errcode > 0 ) ? std::strerror( errcode ) : "" ); } void internal_error( const char * const msg ) { if( verbosity >= 0 ) std::fprintf( stderr, "%s: internal error: %s\n", program_name, msg ); std::exit( 3 ); } int main( const int argc, const char * const argv[] ) { std::string archive_name; Mode program_mode = m_none; int level = 6; // compression level, < 0 = uncompressed invocation_name = argv[0]; enum { opt_aso = 256, opt_dso, opt_grp, opt_own, opt_sol, opt_un }; const Arg_parser::Option options[] = { { '0', 0, Arg_parser::no }, { '1', 0, 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', 0, Arg_parser::no }, { 'c', "create", Arg_parser::no }, { 'C', "directory", Arg_parser::yes }, { 'f', "file", Arg_parser::yes }, { 'h', "help", Arg_parser::no }, { 'H', "format", Arg_parser::yes }, { 'q', "quiet", Arg_parser::no }, { 'r', "append", Arg_parser::no }, { 't', "list", Arg_parser::no }, { 'v', "verbose", Arg_parser::no }, { 'V', "version", Arg_parser::no }, { 'x', "extract", Arg_parser::no }, { opt_aso, "asolid", Arg_parser::no }, { opt_dso, "dsolid", Arg_parser::no }, { opt_grp, "group", Arg_parser::yes }, { opt_own, "owner", Arg_parser::yes }, { opt_sol, "solid", Arg_parser::no }, { opt_un, "uncompressed", Arg_parser::no }, { 0 , 0, Arg_parser::no } }; const Arg_parser parser( argc, argv, options, true ); if( parser.error().size() ) // bad option { show_error( parser.error().c_str(), 0, true ); return 1; } int filenames = 0; for( int argind = 0; argind < parser.arguments(); ++argind ) { const int code = parser.code( argind ); if( !code ) { ++filenames; continue; } // skip non-options const std::string & sarg = parser.argument( argind ); const char * const arg = sarg.c_str(); switch( code ) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': level = code - '0'; break; case 'c': set_mode( program_mode, m_create ); break; case 'C': break; // skip chdir case 'f': if( sarg != "-" ) archive_name = sarg; break; case 'h': show_help(); return 0; case 'H': break; // ignore format case 'q': verbosity = -1; break; case 'r': set_mode( program_mode, m_append ); break; case 't': set_mode( program_mode, m_list ); break; case 'v': if( verbosity < 4 ) ++verbosity; break; case 'V': show_version(); return 0; case 'x': set_mode( program_mode, m_extract ); break; case opt_aso: cl_solid = 2; break; case opt_dso: cl_solid = 1; break; case opt_grp: set_group( arg ); break; case opt_own: set_owner( arg ); break; case opt_sol: cl_solid = 3; break; case opt_un: level = -1; break; default : internal_error( "uncaught option" ); } } // end process options #if defined(__MSVCRT__) || defined(__OS2__) setmode( STDIN_FILENO, O_BINARY ); setmode( STDOUT_FILENO, O_BINARY ); #endif switch( program_mode ) { case m_none: show_error( "Missing operation.", 0, true ); return 2; case m_append: case m_create: return encode( archive_name, parser, filenames, level, program_mode == m_append ); case m_extract: case m_list: return decode( archive_name, parser, filenames, program_mode == m_list ); } }