/* Zutils - Utilities dealing with compressed files
Copyright (C) 2009, 2010, 2011, 2012, 2013 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 .
*/
#define _FILE_OFFSET_BITS 64
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#if defined(__MSVCRT__) || defined(__OS2__)
#include
#endif
#include "arg_parser.h"
#include "zutils.h"
#if CHAR_BIT != 8
#error "Environments where CHAR_BIT != 8 are not supported."
#endif
namespace {
#ifdef O_BINARY
const int o_binary = O_BINARY;
#else
const int o_binary = 0;
#endif
enum Mode { m_none, m_zcat, m_zgrep, m_ztest };
void show_help()
{
std::printf( "Zutils is a collection of utilities able to deal with any combination of\n"
"compressed and non-compressed files transparently. If any given file,\n"
"including standard input, is compressed, its uncompressed content is used.\n"
"\nThe supported formats are bzip2, gzip, lzip and xz.\n"
"\nUsage: %s [options] [files]\n", invocation_name );
std::printf( "\nTry '%s --help' for more specific help.\n", invocation_name );
std::printf( "\nOperations:\n"
" -h, --help display this help and exit\n"
" -V, --version output version information and exit\n"
" --zcat zcat operation\n"
" --zgrep zgrep operation\n"
" --ztest ztest operation\n" );
show_help_addr();
}
int simple_extension_index( const std::string & name )
{
for( int i = 0; i < num_formats; ++i )
{
const std::string ext( simple_extensions[i] );
if( name.size() > ext.size() &&
name.compare( name.size() - ext.size(), ext.size(), ext ) == 0 )
return i;
}
return -1;
}
int open_instream( std::string & input_filename, const Mode program_mode,
const bool search )
{
int infd = open( input_filename.c_str(), O_RDONLY | o_binary );
if( infd < 0 )
{
if( search && ( program_mode == m_zcat || program_mode == m_zgrep ) &&
simple_extension_index( input_filename ) < 0 )
{
for( int i = 0; i < num_formats; ++i )
{
const std::string name( input_filename +
simple_extensions[format_order[i]] );
infd = open( name.c_str(), O_RDONLY | o_binary );
if( infd >= 0 ) { input_filename = name; break; }
}
}
if( infd < 0 )
show_error2( "Can't open input file", input_filename.c_str() );
}
return infd;
}
#include "zcat.cc"
#include "zgrep.cc"
#include "ztest.cc"
} // end namespace
int main( const int argc, const char * const argv[] )
{
enum { format_opt = 256, help_opt, verbose_opt,
zcat_opt, zgrep_opt, ztest_opt };
const Arg_parser::Option * options = 0;
int infd = -1;
int format_type = -1;
Mode program_mode = m_none;
bool recursive = false;
std::string input_filename;
std::list< std::string > filenames;
Cat_options cat_options;
std::vector< const char * > grep_args; // args to grep, maybe empty
std::vector< const char * > ztest_args; // args to ztest, maybe empty
invocation_name = argv[0];
const Arg_parser::Option m_zcat_options[] =
{
{ 'A', "show-all", Arg_parser::no }, // cat
{ 'b', "number-nonblank", Arg_parser::no }, // cat
{ 'c', "stdout", Arg_parser::no }, // gzip
{ 'd', "decompress", Arg_parser::no }, // gzip
{ 'e', 0, Arg_parser::no }, // cat
{ 'E', "show-ends", Arg_parser::no }, // cat
{ 'f', "force", Arg_parser::no }, // gzip
{ 'h', "help", Arg_parser::no },
{ 'l', "list", Arg_parser::no }, // gzip
{ 'L', "license", Arg_parser::no }, // gzip
{ 'n', "number", Arg_parser::no }, // cat
{ 'q', "quiet", Arg_parser::no },
{ 'r', "recursive", Arg_parser::no },
{ 's', "squeeze-blank", Arg_parser::no }, // cat
{ 't', 0, Arg_parser::no }, // cat
{ 'T', "show-tabs", Arg_parser::no }, // cat
{ 'v', "show-nonprinting", Arg_parser::no }, // cat
{ 'V', "version", Arg_parser::no },
{ format_opt, "format", Arg_parser::yes },
{ verbose_opt, "verbose", Arg_parser::no },
{ zcat_opt, "zcat", Arg_parser::no },
{ 0 , 0, Arg_parser::no } };
const Arg_parser::Option m_zgrep_options[] =
{
{ 'a', "text", Arg_parser::no }, // grep GNU
{ 'A', "after-context", Arg_parser::yes }, // grep GNU
{ 'b', "byte-offset", Arg_parser::no }, // grep GNU
{ 'B', "before-context", Arg_parser::yes }, // grep GNU
{ 'c', "count", Arg_parser::no }, // grep
{ 'C', "context", Arg_parser::yes }, // grep GNU
{ 'e', "regexp", Arg_parser::yes }, // grep
{ 'E', "extended-regexp", Arg_parser::no }, // grep
{ 'f', "file ", Arg_parser::yes }, // grep
{ 'F', "fixed-strings", Arg_parser::no }, // grep
{ 'h', "no-filename", Arg_parser::no }, // grep GNU
{ 'H', "with-filename", Arg_parser::no }, // grep GNU
{ 'i', "ignore-case", Arg_parser::no }, // grep
{ 'I', 0, Arg_parser::no }, // grep GNU
{ 'l', "files-with-matches", Arg_parser::no }, // grep
{ 'L', "files-without-match", Arg_parser::no }, // grep GNU
{ 'm', "max-count", Arg_parser::yes }, // grep GNU
{ 'n', "line-number", Arg_parser::no }, // grep
{ 'o', "only-matching", Arg_parser::no }, // grep
{ 'q', "quiet", Arg_parser::no },
{ 'r', "recursive", Arg_parser::no },
{ 's', "no-messages", Arg_parser::no }, // grep
{ 'v', "invert-match", Arg_parser::no }, // grep
{ 'V', "version", Arg_parser::no },
{ 'w', "word-regexp", Arg_parser::no }, // grep GNU
{ 'x', "line-regexp", Arg_parser::no }, // grep
{ format_opt, "format", Arg_parser::yes },
{ help_opt, "help", Arg_parser::no },
{ verbose_opt, "verbose", Arg_parser::no },
{ zgrep_opt, "zgrep", Arg_parser::no },
{ 0 , 0, Arg_parser::no } };
const Arg_parser::Option m_ztest_options[] =
{
{ 'h', "help", Arg_parser::no },
{ 'q', "quiet", Arg_parser::no },
{ 'r', "recursive", Arg_parser::no },
{ 'v', "verbose", Arg_parser::no },
{ 'V', "version", Arg_parser::no },
{ format_opt, "format", Arg_parser::yes },
{ ztest_opt, "ztest", Arg_parser::no },
{ 0 , 0, Arg_parser::no } };
{ // parse operation
const Arg_parser::Option operations[] =
{
{ 'h', "help", Arg_parser::no },
{ 'V', "version", Arg_parser::no },
{ zcat_opt, "zcat", Arg_parser::no },
{ zgrep_opt, "zgrep", Arg_parser::no },
{ ztest_opt, "ztest", Arg_parser::no },
{ 0 , 0, Arg_parser::no } };
const Arg_parser parser( argv[1], ( argc > 2 ) ? argv[2] : 0, operations );
if( parser.error().size() ) // bad operation
{ show_error( parser.error().c_str(), 0, true ); return 1; }
if( parser.arguments() > 0 )
{
switch( parser.code( 0 ) )
{
case 0 : break;
case 'h': show_help(); return 0;
case 'V': show_version(); return 0;
case zcat_opt : program_mode = m_zcat; options = m_zcat_options;
util_name = "zcat"; break;
case zgrep_opt : program_mode = m_zgrep; options = m_zgrep_options;
util_name = "zgrep"; break;
case ztest_opt : program_mode = m_ztest; options = m_ztest_options;
util_name = "ztest"; break;
default : internal_error( "uncaught option" );
}
}
#if defined(__MSVCRT__) || defined(__OS2__)
setmode( STDIN_FILENO, O_BINARY );
setmode( STDOUT_FILENO, O_BINARY );
#endif
if( program_mode == m_none )
{
show_error( "You must specify the operation to be performed.", 0, true );
return 1;
}
} // end parse operation
const Arg_parser parser( argc, argv, options );
if( parser.error().size() ) // bad option
{ show_error( parser.error().c_str(), 0, true );
return ( program_mode == m_zcat || program_mode == m_ztest ) ? 1 : 2; }
int argind = 0;
int grep_show_name = -1;
bool grep_list = false;
bool grep_pattern_found = false;
for( ; argind < parser.arguments(); ++argind )
{
const int code = parser.code( argind );
const char * arg = parser.argument( argind ).c_str();
if( !code )
{
if( program_mode == m_zgrep && !grep_pattern_found )
{ grep_args.push_back( arg ); grep_pattern_found = true; continue; }
else break; // no more options
}
if( code == format_opt )
{
format_type = get_format_type( arg ); continue;
}
switch( program_mode )
{
case m_none: internal_error( "invalid operation" ); break;
case m_zcat:
switch( code )
{
case 'A': cat_options.show_ends = true;
cat_options.show_nonprinting = true;
cat_options.show_tabs = true; break;
case 'b': cat_options.number_lines = 1; break;
case 'c': break;
case 'd': break;
case 'e': cat_options.show_nonprinting = true; // fall through
case 'E': cat_options.show_ends = true; break;
case 'f': break;
case 'h': show_zcat_help(); return 0;
case 'l': break;
case 'L': break;
case 'n': if( cat_options.number_lines == 0 )
{ cat_options.number_lines = 2; } break;
case 'q': verbosity = -1; break;
case 'r': recursive = true; break;
case 's': cat_options.squeeze_blank = true; break;
case 't': cat_options.show_nonprinting = true; // fall through
case 'T': cat_options.show_tabs = true; break;
case 'v': cat_options.show_nonprinting = true; break;
case 'V': show_version( "Zcat" ); return 0;
case verbose_opt : if( verbosity < 4 ) ++verbosity; break;
case zcat_opt : break;
default : internal_error( "uncaught option" );
} break;
case m_zgrep:
switch( code )
{
case 'a': grep_args.push_back( "-a" ); break;
case 'A': grep_args.push_back( "-A" ); grep_args.push_back( arg ); break;
case 'b': grep_args.push_back( "-b" ); break;
case 'B': grep_args.push_back( "-B" ); grep_args.push_back( arg ); break;
case 'c': grep_args.push_back( "-c" ); break;
case 'C': grep_args.push_back( "-C" ); grep_args.push_back( arg ); break;
case 'e': grep_args.push_back( "-e" ); grep_args.push_back( arg );
grep_pattern_found = true; break;
case 'E': grep_args.push_back( "-E" ); break;
case 'f': grep_args.push_back( "-f" ); grep_args.push_back( arg );
grep_pattern_found = true; break;
case 'F': grep_args.push_back( "-F" ); break;
case 'h': grep_show_name = false; break;
case 'H': grep_show_name = true; break;
case 'i': grep_args.push_back( "-i" ); break;
case 'I': grep_args.push_back( "-I" ); break;
case 'l': grep_args.push_back( "-l" ); grep_list = true; break;
case 'L': grep_args.push_back( "-L" ); grep_list = true; break;
case 'm': grep_args.push_back( "-m" ); grep_args.push_back( arg ); break;
case 'n': grep_args.push_back( "-n" ); break;
case 'o': grep_args.push_back( "-o" ); break;
case 'q': grep_args.push_back( "-q" ); verbosity = -1; break;
case 'r': recursive = true; break;
case 's': grep_args.push_back( "-s" ); verbosity = -1; break;
case 'v': grep_args.push_back( "-v" ); break;
case 'V': show_version( "Zgrep" ); return 0;
case 'w': grep_args.push_back( "-w" ); break;
case 'x': grep_args.push_back( "-x" ); break;
case help_opt : show_zgrep_help(); return 0;
case verbose_opt : if( verbosity < 4 ) ++verbosity; break;
case zgrep_opt : break;
default : internal_error( "uncaught option" );
} break;
case m_ztest:
switch( code )
{
case 'h': show_ztest_help(); return 0;
case 'q': verbosity = -1; ztest_args.push_back( "-q" ); break;
case 'r': recursive = true; break;
case 'v': if( verbosity < 4 ) ++verbosity;
ztest_args.push_back( "-v" ); break;
case 'V': show_version( "Ztest" ); return 0;
case ztest_opt : break;
default : internal_error( "uncaught option" );
} break;
}
} // end process options
if( program_mode == m_zgrep && !grep_pattern_found )
{ show_error( "Pattern not found." ); return 2; }
for( ; argind < parser.arguments(); ++argind )
filenames.push_back( parser.argument( argind ) );
if( filenames.empty() ) filenames.push_back("-");
if( grep_show_name < 0 )
grep_show_name = ( filenames.size() != 1 || recursive );
int retval = ( ( program_mode == m_zgrep ) ? 1 : 0 );
while( !filenames.empty() )
{
input_filename = filenames.front();
filenames.pop_front();
if( !input_filename.size() || input_filename == "-" )
{
input_filename.clear();
infd = STDIN_FILENO;
}
else
{
if( recursive )
{
struct stat st;
if( !stat( input_filename.c_str(), &st ) && S_ISDIR( st.st_mode ) )
{
DIR * const dirp = opendir( input_filename.c_str() );
if( !dirp )
{
show_error2( "Can't open directory", input_filename.c_str() );
if( retval < 1 ) retval = 1; continue;
}
std::list< std::string > tmp_list;
while( true )
{
const struct dirent * const entryp = readdir( dirp );
if( !entryp ) { closedir( dirp ); break; }
std::string tmp_name( entryp->d_name );
if( tmp_name != "." && tmp_name != ".." )
tmp_list.push_back( input_filename + "/" + tmp_name );
}
filenames.splice( filenames.begin(), tmp_list );
continue;
}
}
infd = open_instream( input_filename, program_mode, format_type < 0 );
if( infd < 0 ) { if( retval < 1 ) retval = 1; continue; }
}
int tmp = 0;
switch( program_mode )
{
case m_none:
break;
case m_zcat:
tmp = cat( infd, format_type, input_filename, cat_options );
break;
case m_zgrep:
if( infd == STDIN_FILENO )
tmp = zgrep_stdin( infd, format_type, grep_args );
else tmp = zgrep_file( infd, format_type, input_filename, grep_args,
grep_list, grep_show_name );
break;
case m_ztest:
if( infd == STDIN_FILENO )
tmp = ztest_stdin( infd, format_type, ztest_args );
else tmp = ztest_file( infd, format_type, input_filename, ztest_args );
break;
}
if( program_mode == m_zgrep )
{ if( tmp == 0 || ( tmp == 2 && retval == 1 ) ) retval = tmp; }
else if( tmp > retval ) retval = tmp;
if( input_filename.size() )
{ close( infd ); infd = -1; }
}
if( std::fclose( stdout ) != 0 )
{
show_error( "Can't close stdout", errno );
switch( program_mode )
{
case m_none: break;
case m_zcat: retval = 1; break;
case m_zgrep: if( retval != 0 || verbosity >= 0 ) retval = 2; break;
case m_ztest: if( retval == 0 ) retval = 1; break;
}
}
return retval;
}