summaryrefslogtreecommitdiffstats
path: root/rc.cc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--rc.cc457
1 files changed, 457 insertions, 0 deletions
diff --git a/rc.cc b/rc.cc
new file mode 100644
index 0000000..5a5c5b4
--- /dev/null
+++ b/rc.cc
@@ -0,0 +1,457 @@
+/* Zutils - Utilities dealing with compressed files
+ Copyright (C) 2009-2023 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/>.
+*/
+
+#define _FILE_OFFSET_BITS 64
+
+#include <cerrno>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <string>
+#include <vector>
+#include <unistd.h>
+#include <sys/wait.h>
+
+#include "arg_parser.h"
+#include "rc.h"
+
+
+const char * invocation_name = 0;
+const char * program_name = 0;
+int verbosity = 0;
+
+namespace {
+
+const char * const config_file_name = "zutils.conf";
+const char * const program_year = "2023";
+
+std::string compressor_names[num_formats] =
+ { "bzip2", "gzip", "lzip", "xz", "zstd" }; // default compressor names
+
+// args to compressors read from .conf or from options like --lz, maybe empty
+std::vector< std::string > compressor_args[num_formats];
+
+// vector of enabled formats plus [num_formats] for uncompressed.
+// empty means all enabled.
+std::vector< bool > enabled_formats;
+
+const struct { const char * from; const char * to; int format_index; }
+ known_extensions[] = {
+ { ".bz2", "", fmt_bz2 },
+ { ".tbz", ".tar", fmt_bz2 },
+ { ".tbz2", ".tar", fmt_bz2 },
+ { ".gz", "", fmt_gz },
+ { ".tgz", ".tar", fmt_gz },
+ { ".lz", "", fmt_lz },
+ { ".tlz", ".tar", fmt_lz },
+ { ".xz", "", fmt_xz },
+ { ".txz", ".tar", fmt_xz },
+ { ".zst", "", fmt_zst },
+ { ".tzst", ".tar", fmt_zst },
+ { ".Z", "", fmt_gz },
+ { 0, 0, -1 } };
+
+
+int my_fgetc( FILE * const f )
+ {
+ int ch;
+ bool comment = false;
+
+ do {
+ ch = std::fgetc( f );
+ if( ch == '#' ) comment = true;
+ else if( ch == '\n' || ch == EOF ) comment = false;
+ else if( ch == '\\' && comment )
+ {
+ const int c = std::fgetc( f );
+ if( c == '\n' ) { std::ungetc( c, f ); comment = false; }
+ }
+ }
+ while( comment );
+ return ch;
+ }
+
+
+// Return the parity of escapes (backslashes) at the end of a string.
+bool trailing_escape( const std::string & s )
+ {
+ unsigned len = s.size();
+ bool odd_escape = false;
+ while( len > 0 && s[--len] == '\\' ) odd_escape = !odd_escape;
+ return odd_escape;
+ }
+
+
+/* Read a line discarding comments, leading whitespace, and blank lines.
+ Escaped newlines are discarded.
+ Return the empty string if at EOF.
+*/
+const std::string & my_fgets( FILE * const f, int & linenum )
+ {
+ static std::string s;
+ bool strip = true; // strip leading whitespace
+ s.clear();
+
+ while( true )
+ {
+ int ch = my_fgetc( f );
+ if( strip )
+ {
+ strip = false;
+ while( std::isspace( ch ) )
+ { if( ch == '\n' ) { ++linenum; } ch = my_fgetc( f ); }
+ }
+ if( ch == EOF ) { if( s.size() ) { ++linenum; } break; }
+ else if( ch == '\n' )
+ {
+ ++linenum; strip = true;
+ if( trailing_escape( s ) ) s.erase( s.size() - 1 );
+ else if( s.size() ) break;
+ }
+ else s += ch;
+ }
+ return s;
+ }
+
+
+bool parse_compressor_command( const std::string & s, int i,
+ const int format_index )
+ {
+ const int len = s.size();
+ while( i < len && std::isspace( s[i] ) ) ++i; // strip spaces
+ int l = i;
+ while( i < len && !std::isspace( s[i] ) ) ++i;
+ if( l >= i || s[l] == '-' ) return false;
+ compressor_names[format_index].assign( s, l, i - l );
+
+ compressor_args[format_index].clear();
+ while( i < len )
+ {
+ while( i < len && std::isspace( s[i] ) ) ++i; // strip spaces
+ l = i;
+ while( i < len && !std::isspace( s[i] ) ) ++i;
+ if( l < i )
+ compressor_args[format_index].push_back( std::string( s, l, i - l ) );
+ }
+ return true;
+ }
+
+
+bool parse_rc_line( const std::string & line,
+ const char * const filename, const int linenum )
+ {
+ const int len = line.size();
+ int i = 0;
+ while( i < len && std::isspace( line[i] ) ) ++i; // strip spaces
+ int l = i;
+ while( i < len && line[i] != '=' && !std::isspace( line[i] ) ) ++i;
+ if( l >= i )
+ { if( verbosity >= 0 )
+ std::fprintf( stderr, "%s %d: missing format name.\n", filename, linenum );
+ return false; }
+ const std::string name( line, l, i - l );
+ int format_index = -1;
+ for( int j = 0; j < num_formats; ++j )
+ if( name == format_names[j] ) { format_index = j; break; }
+ if( format_index < 0 )
+ { if( verbosity >= 0 )
+ std::fprintf( stderr, "%s %d: bad format name '%s'\n",
+ filename, linenum, name.c_str() );
+ return false; }
+
+ while( i < len && std::isspace( line[i] ) ) ++i; // strip spaces
+ if( i <= 0 || i >= len || line[i] != '=' )
+ { if( verbosity >= 0 )
+ std::fprintf( stderr, "%s %d: missing '='\n", filename, linenum );
+ return false; }
+ ++i; // skip the '='
+ if( !parse_compressor_command( line, i, format_index ) )
+ {
+ if( verbosity >= 0 )
+ std::fprintf( stderr, "%s %d: missing compressor name.\n", filename, linenum );
+ return false;
+ }
+ return true;
+ }
+
+
+ // Return 0 if success, 1 if file not found, 2 if syntax or I/O error.
+int process_rcfile( const std::string & name )
+ {
+ FILE * const f = std::fopen( name.c_str(), "r" );
+ if( !f ) return 1;
+
+ int linenum = 0;
+ int retval = 0;
+
+ while( true )
+ {
+ const std::string & line = my_fgets( f, linenum );
+ if( line.empty() ) break; // EOF
+ if( !parse_rc_line( line, name.c_str(), linenum ) )
+ { retval = 2; break; }
+ }
+ if( std::fclose( f ) != 0 && retval == 0 )
+ { show_file_error( name.c_str(), "Error closing config file", errno );
+ retval = 2; }
+ return retval;
+ }
+
+
+void show_using_version( const char * const command )
+ {
+ FILE * const f = popen( command, "r" );
+ if( f )
+ {
+ char command_version[1024] = { 0 };
+ const int rd = std::fread( command_version, 1, sizeof command_version, f );
+ pclose( f );
+ int i = 0;
+ while( i + 1 < rd && command_version[i] != '\n' ) ++i;
+ command_version[i] = 0;
+ if( command_version[0] ) std::printf( "Using %s\n", command_version );
+ }
+ }
+
+} // end namespace
+
+
+bool enabled_format( const int format_index )
+ {
+ if( enabled_formats.size() <= num_formats ) return true; // all enabled
+ if( format_index < 0 || format_index >= num_formats )
+ return enabled_formats[num_formats]; // uncompressed
+ return enabled_formats[format_index];
+ }
+
+
+void parse_format_list( const std::string & arg, const char * const pn )
+ {
+ bool error = arg.empty();
+ enabled_formats.assign( num_formats + 1, false );
+
+ for( unsigned l = 0, r; l < arg.size(); l = r + 1 )
+ {
+ r = std::min( arg.find( ',', l ), arg.size() );
+ if( l >= r ) { error = true; break; } // empty format
+ int format_index = num_formats;
+ const std::string s( arg, l, r - l );
+ for( int i = 0; i < num_formats; ++i )
+ if( s == format_names[i] )
+ { format_index = i; break; }
+ if( format_index == num_formats && s != "un" ) // uncompressed
+ { error = true; break; }
+ enabled_formats[format_index] = true;
+ }
+ if( !error ) return;
+ show_option_error( arg.c_str(), "Invalid format in", pn );
+ std::exit( 1 );
+ }
+
+
+int parse_format_type( const std::string & arg, const char * const pn,
+ const bool allow_uncompressed )
+ {
+ for( int i = 0; i < num_formats; ++i )
+ if( arg == format_names[i] )
+ return i;
+ if( allow_uncompressed && arg == "un" ) return num_formats;
+ show_option_error( arg.c_str(), ( arg.find( ',' ) < arg.size() ) ?
+ "Too many formats in" : "Invalid format in", pn );
+ std::exit( 1 );
+ }
+
+
+int extension_index( const std::string & name )
+ {
+ for( int eindex = 0; known_extensions[eindex].from; ++eindex )
+ {
+ const std::string ext( known_extensions[eindex].from );
+ if( name.size() > ext.size() &&
+ name.compare( name.size() - ext.size(), ext.size(), ext ) == 0 )
+ return eindex;
+ }
+ return -1;
+ }
+
+int extension_format( const int eindex )
+ { return ( eindex >= 0 ) ? known_extensions[eindex].format_index : -1; }
+
+const char * extension_from( const int eindex )
+ { return known_extensions[eindex].from; }
+
+const char * extension_to( const int eindex )
+ { return known_extensions[eindex].to; }
+
+
+void maybe_process_config_file( const Arg_parser & parser )
+ {
+ for( int i = 0; i < parser.arguments(); ++i )
+ if( parser.code( i ) == 'N' ) return;
+ std::string name;
+ const char * p = std::getenv( "XDG_CONFIG_HOME" ); if( p ) name = p;
+ else { p = std::getenv( "HOME" ); if( p ) { name = p; name += "/.config"; } }
+ if( name.size() )
+ {
+ name += '/'; name += config_file_name;
+ const int retval = process_rcfile( name );
+ if( retval == 0 ) return;
+ if( retval == 2 ) std::exit( 2 );
+ }
+ name = SYSCONFDIR; name += '/'; name += config_file_name;
+ const int retval = process_rcfile( name );
+ if( retval == 2 ) std::exit( 2 );
+ }
+
+
+void parse_compressor( const std::string & arg, const char * const pn,
+ const int format_index, const int eretval )
+ {
+ if( !parse_compressor_command( arg, 0, format_index ) )
+ { show_option_error( arg.c_str(), "Invalid compressor command in", pn );
+ std::exit( eretval ); }
+ }
+
+
+const char * get_compressor_name( const int format_index )
+ {
+ if( format_index >= 0 && format_index < num_formats &&
+ compressor_names[format_index].size() )
+ return compressor_names[format_index].c_str();
+ return 0; // uncompressed/unknown
+ }
+
+
+const std::vector< std::string > & get_compressor_args( const int format_index )
+ {
+ return compressor_args[format_index];
+ }
+
+
+void show_help_addr()
+ {
+ std::printf( "\nReport bugs to zutils-bug@nongnu.org\n"
+ "Zutils home page: http://www.nongnu.org/zutils/zutils.html\n" );
+ }
+
+
+void show_version( const char * const command )
+ {
+ std::printf( "%s (zutils) %s\n", program_name, PROGVERSION );
+ std::printf( "Copyright (C) %s Antonio Diaz Diaz.\n", program_year );
+ if( command && verbosity >= 1 ) show_using_version( command );
+ if( verbosity >= 1 + ( command != 0 ) )
+ for( int format_index = 0; format_index < num_formats; ++format_index )
+ {
+ if( !enabled_format( format_index ) ) continue;
+ std::string compressor_command( compressor_names[format_index] );
+ if( compressor_command.empty() ) continue;
+ compressor_command += " -V 2> /dev/null";
+ show_using_version( compressor_command.c_str() );
+ }
+ 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" );
+ }
+
+
+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 );
+ }
+
+
+void show_option_error( const char * const arg, const char * const msg,
+ const char * const option_name )
+ {
+ if( verbosity >= 0 )
+ std::fprintf( stderr, "%s: '%s': %s option '%s'.\n",
+ program_name, arg, msg, option_name );
+ }
+
+
+void show_close_error( const char * const prog_name )
+ {
+ if( verbosity >= 0 )
+ std::fprintf( stderr, "%s: Error closing output of %s: %s\n",
+ program_name, prog_name, std::strerror( errno ) );
+ }
+
+
+void show_exec_error( const char * const prog_name )
+ {
+ if( verbosity >= 0 )
+ std::fprintf( stderr, "%s: Can't exec '%s': %s\n",
+ program_name, prog_name, std::strerror( errno ) );
+ }
+
+
+void show_fork_error( const char * const prog_name )
+ {
+ if( verbosity >= 0 )
+ std::fprintf( stderr, "%s: Can't fork '%s': %s\n",
+ program_name, prog_name, std::strerror( errno ) );
+ }
+
+
+int wait_for_child( const pid_t pid, const char * const name,
+ const int eretval, const bool isgzxz )
+ {
+ int status;
+ while( waitpid( pid, &status, 0 ) == -1 )
+ {
+ if( errno != EINTR )
+ {
+ if( verbosity >= 0 )
+ std::fprintf( stderr, "%s: Error waiting termination of '%s': %s\n",
+ program_name, name, std::strerror( errno ) );
+ _exit( eretval );
+ }
+ }
+ if( WIFEXITED( status ) )
+ {
+ const int tmp = WEXITSTATUS( status );
+ if( isgzxz && eretval == 1 && tmp == 1 ) return 2; // for ztest
+ return tmp;
+ }
+ return eretval;
+ }