/*  Zgrep - search compressed files for a regular expression
    Copyright (C) 2010, 2011 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 <http://www.gnu.org/licenses/>.
*/

void show_zgrep_help() throw()
  {
  std::printf( "Zgrep is a front end to the grep program that allows transparent search\n" );
  std::printf( "on any combination of compressed and non-compressed files. If any given\n" );
  std::printf( "file is compressed, its uncompressed content is used. If a given file\n" );
  std::printf( "does not exist, and its name does not end with one of the known\n" );
  std::printf( "extensions, zgrep tries the compressed file names corresponding to the\n" );
  std::printf( "supported compressors. If no files are specified, data is read from\n" );
  std::printf( "standard input, decompressed if needed, and fed to grep. Data read from\n" );
  std::printf( "standard input must be of the same type; all uncompressed or all\n" );
  std::printf( "compressed with the same compressor.\n" );
  std::printf( "The supported compressors are bzip2, gzip, lzip and xz.\n" );
  std::printf( "\nUsage: zgrep [options] <pattern> [files]\n" );
  std::printf( "\nExit status is 0 if match, 1 if no match, 2 if trouble.\n" );
  std::printf( "\nOptions:\n" );
  std::printf( "      --help                 display this help and exit\n" );
  std::printf( "  -V, --version              output version information and exit\n" );
  std::printf( "  -a, --text                 treat all files as text\n" );
  std::printf( "  -A, --after-context=<n>    print <n> lines of trailing context\n" );
  std::printf( "  -b, --byte-offset          print the byte offset of each line\n" );
  std::printf( "  -B, --before-context=<n>   print <n> lines of leading context\n" );
  std::printf( "  -c, --count                only print a count of matching lines per file\n" );
  std::printf( "  -C, --context=<n>          print <n> lines of output context\n" );
  std::printf( "  -e, --regexp=<pattern>     use <pattern> as the pattern to match\n" );
  std::printf( "  -E, --extended-regexp      <pattern> is an extended regular expression\n" );
  std::printf( "  -f, --file=<file>          obtain patterns from <file>\n" );
  std::printf( "  -F, --fixed-strings        <pattern> is a set of newline-separated strings\n" );
  std::printf( "  -h, --no-filename          suppress the prefixing filename on output\n" );
  std::printf( "  -H, --with-filename        print the filename for each match\n" );
  std::printf( "  -i, --ignore-case          ignore case distinctions\n" );
  std::printf( "  -I                         ignore binary files\n" );
  std::printf( "  -l, --files-with-matches   only print names of files containing matches\n" );
  std::printf( "  -L, --files-without-match  only print names of files containing no matches\n" );
  std::printf( "  -m, --max-count=<n>        stop after <n> matches\n" );
  std::printf( "  -n, --line-number          print the line number of each line\n" );
  std::printf( "  -o, --only-matching        show only the part of a line matching <pattern>\n" );
  std::printf( "  -q, --quiet                suppress all messages\n" );
  std::printf( "  -r, --recursive            operate recursively on directories\n" );
  std::printf( "  -s, --no-messages          suppress error messages\n" );
  std::printf( "  -v, --invert-match         select non-matching lines\n" );
  std::printf( "      --verbose              verbose mode (show error messages)\n" );
  std::printf( "  -w, --word-regexp          match only whole words\n" );
  std::printf( "  -x, --line-regexp          match only whole lines\n" );
  show_help_addr();
  }


int zgrep_stdin( int infd, const std::vector< const char * > & grep_args )
  {
  pid_t pid;
  if( !set_data_feeder( &infd, &pid ) ) return 2;
  const pid_t grep_pid = fork();
  if( grep_pid == 0 )			// child (grep)
    {
    if( dup2( infd, STDIN_FILENO ) >= 0 && close( infd ) == 0 )
      {
      const char ** const argv = new const char *[grep_args.size()+2];
      argv[0] = GREP;
      for( unsigned int i = 0; i < grep_args.size(); ++i )
        argv[i+1] = grep_args[i];
      argv[grep_args.size()+1] = 0;
      execvp( argv[0], (char **)argv );
      }
    show_exec_error( GREP );
    _exit( 2 );
    }
					// parent
  if( grep_pid < 0 )
    { show_fork_error( GREP ); return 2; }

  int retval = wait_for_child( grep_pid, GREP );
  if( retval != 1 )
    { if( pid ) kill( pid, SIGTERM ); }
  else
    if( pid && wait_for_child( pid, "data feeder" ) != 0 ) retval = 2;
  if( close( infd ) != 0 )
    { show_close_error( "data feeder" ); return 2; }
  return retval;
  }


int zgrep_file( int infd, const std::string & input_filename,
                const std::vector< const char * > & grep_args,
                const bool grep_list, const bool grep_show_name )
  {
  pid_t pid;
  if( !set_data_feeder( &infd, &pid ) ) return 2;
  int fda[2];				// pipe from grep
  if( pipe( fda ) < 0 )
    { show_error( "Can't create pipe", errno ); return 2; }
  const pid_t grep_pid = fork();
  if( grep_pid == 0 )			// child (grep)
    {
    if( dup2( infd, STDIN_FILENO ) >= 0 &&
        dup2( fda[1], STDOUT_FILENO ) >= 0 &&
        close( infd ) == 0 && close( fda[0] ) == 0 && close( fda[1] ) == 0 )
      {
      const char ** const argv = new const char *[grep_args.size()+2];
      argv[0] = GREP;
      for( unsigned int i = 0; i < grep_args.size(); ++i )
        argv[i+1] = grep_args[i];
      argv[grep_args.size()+1] = 0;
      execvp( argv[0], (char **)argv );
      }
    show_exec_error( GREP );
    _exit( 2 );
    }
					// parent
  close( fda[1] );
  if( grep_pid < 0 )
    { show_fork_error( GREP ); return 2; }
  enum { buffer_size = 256 };
  uint8_t buffer[buffer_size];
  bool line_begin = true;
  while( true )
    {
    const int size = readblock( fda[0], buffer, buffer_size );
    if( size != buffer_size && errno )
      { show_error( "Read error", errno ); return 2; }
    if( size > 0 && !grep_list )
      {
      if( grep_show_name )
        for( int i = 0; i < size; ++i )
          {
          if( line_begin )
            { line_begin = false; std::printf( "%s:", input_filename.c_str() ); }
          if( buffer[i] == '\n' ) line_begin = true;
          putchar( buffer[i] );
          }
      else if( std::fwrite( buffer, 1, size, stdout ) != (unsigned int)size )
        { show_error( "Write error", errno ); return 2; }
      }
    if( size < buffer_size ) break;
    }

  int retval = wait_for_child( grep_pid, GREP );
  if( retval != 1 )
    { if( pid ) kill( pid, SIGTERM ); }
  else
    if( pid && wait_for_child( pid, "data feeder" ) != 0 ) retval = 2;
  if( grep_list && retval == 0 )
    std::printf( "%s\n", input_filename.c_str() );
  if( close( infd ) != 0 )
    { show_close_error( "data feeder" ); return 2; }
  if( close( fda[0] ) != 0 )
    { show_close_error( GREP ); return 2; }
  return retval;
  }
