diff options
Diffstat (limited to 'main.cc')
-rw-r--r-- | main.cc | 369 |
1 files changed, 369 insertions, 0 deletions
@@ -0,0 +1,369 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ +/* + 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 <cctype> +#include <cerrno> +#include <climits> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <string> +#include <vector> +#include <fcntl.h> +#include <stdint.h> +#include <unistd.h> +#include <sys/stat.h> +#include <grp.h> +#include <pwd.h> +#if defined(__OS2__) +#include <io.h> +#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=<dir> change to directory <dir>\n" + " -f, --file=<archive> use archive file <archive>\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=<group> use <group> name/id for added files\n" + " --owner=<owner> use <owner> 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 <http://gnu.org/licenses/gpl.html>\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 ); + } + } |