diff options
Diffstat (limited to 'rc.cc')
-rw-r--r-- | rc.cc | 457 |
1 files changed, 457 insertions, 0 deletions
@@ -0,0 +1,457 @@ +/* Zutils - Utilities dealing with compressed files + Copyright (C) 2009-2024 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 = "2024"; + +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 or incomplete (size <= num_formats) 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 ( eindex >= 0 ) ? 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; + } |