diff options
Diffstat (limited to '')
-rw-r--r-- | zupdate.cc | 157 |
1 files changed, 111 insertions, 46 deletions
@@ -73,11 +73,12 @@ void show_help() "\nExit status is 0 if all the compressed files were successfully recompressed\n" "(if needed), compared, and deleted (if requested). 1 if a non-fatal error\n" "occurred (file not found or not regular, or has invalid format, or can't be\n" - "deleted). 2 if a fatal error occurred (compressor can't be run, or\n" - "comparison fails).\n" + "deleted). 2 if a fatal error occurred (invalid command line options,\n" + "compressor can't be run, or comparison fails).\n" "\nOptions:\n" " -h, --help display this help and exit\n" " -V, --version output version information and exit\n" + " -d, --destdir=<dir> write recompressed files into <dir>\n" " -e, --expand-extensions expand combined extensions; tgz -> tar.lz\n" " -f, --force don't skip a file even if the .lz exists\n" " -i, --ignore-errors ignore non-fatal errors\n" @@ -94,11 +95,60 @@ void show_help() " --gz=<command> set compressor and options for gzip format\n" " --lz=<command> set compressor and options for lzip format\n" " --xz=<command> set compressor and options for xz format\n" - " --zst=<command> set compressor and options for zstd format\n" ); + " --zst=<command> set compressor and options for zstd format\n" + "\nValid formats for option '-M' are 'bz2', 'gz', 'lz', 'xz', and 'zst'.\n" ); show_help_addr(); } +void extract_srcdir_name( const std::string & name, std::string & srcdir ) + { + if( name.empty() || name == "." ) return; // leave srcdir empty + if( name[name.size()-1] == '/' ) // remove last slash + { srcdir.assign( name, 0, name.size() - 1 ); return; } + struct stat st; + if( stat( name.c_str(), &st ) == 0 && S_ISDIR( st.st_mode ) ) + { srcdir = name; return; } + + unsigned size = 0; // size of srcdir without last slash nor basename + for( unsigned i = name.size(); i > 0; --i ) + if( name[i-1] == '/' ) { size = i - 1; break; } + if( size > 0 ) srcdir.assign( name, 0, size ); + } + + +bool make_dirs( const std::string & name ) + { + static std::string cached_dirname; + unsigned dirsize = name.size(); // size of dirname without last slash + + for( unsigned i = name.size(); i > 0; --i ) + if( name[i-1] == '/' ) { dirsize = i - 1; break; } + if( dirsize >= name.size() ) return true; // no dirname + if( dirsize == 0 ) return true; // dirname is '/' + if( cached_dirname.size() == dirsize && + cached_dirname.compare( 0, dirsize, name ) == 0 ) return true; + + for( unsigned i = 0; i < dirsize; ) + { + while( i < dirsize && name[i] == '/' ) ++i; + const unsigned first = i; + while( i < dirsize && name[i] != '/' ) ++i; + if( first < i ) + { + std::string partial( name, 0, i ); + struct stat st; + if( stat( partial.c_str(), &st ) == 0 ) + { if( !S_ISDIR( st.st_mode ) ) return false; } + else if( mkdir( partial.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | + S_IXOTH ) != 0 && errno != EEXIST ) return false; + } + } + cached_dirname.assign( name, 0, dirsize ); + return true; + } + + void cant_execute( const std::string & command, const int status ) { if( verbosity >= 0 ) @@ -137,13 +187,14 @@ void set_permissions( const char * const rname, const struct stat & in_stats ) // Return value: 0 = success, -1 = file skipped, 1 = error, 2 = fatal error. int zupdate_file( const std::string & name, const char * const lzip_name, const std::vector< std::string > & lzip_args2, + const std::string & srcdir, const std::string & destdir, const bool expand, const bool force, const bool keep_input_files, const bool no_rcfile ) { // bzip2, gzip, and lzip are the primary formats. xz and zstd are optional. static int disable_xz = -1; // tri-state bool static int disable_zst = -1; // tri-state bool - int format_index = -1; + int format_index = -1; // undefined std::string rname; // recompressed name const int eindex = extension_index( name ); // search extension @@ -157,7 +208,18 @@ int zupdate_file( const std::string & name, const char * const lzip_name, program_name, name.c_str(), extension_from( eindex ) ); return 0; // ignore this file } - rname.assign( name, 0, name.size() - std::strlen( extension_from( eindex ) ) ); + if( destdir.size() ) + { + if( srcdir.size() && name.compare( 0, srcdir.size(), srcdir ) != 0 ) + internal_error( "srcdir mismatch." ); + rname = destdir; + if( rname[rname.size()-1] != '/' && name[srcdir.size()] != '/' ) + rname += '/'; + rname.append( name, srcdir.size(), name.size() - srcdir.size() - + std::strlen( extension_from( eindex ) ) ); + } + else + rname.assign( name, 0, name.size() - std::strlen( extension_from( eindex ) ) ); rname += ( std::strcmp( extension_to( eindex ), ".tar" ) == 0 ) ? ( expand ? ".tar.lz" : ".tlz" ) : ".lz"; } @@ -172,19 +234,11 @@ int zupdate_file( const std::string & name, const char * const lzip_name, struct stat in_stats; if( stat( name.c_str(), &in_stats ) != 0 ) // check input file - { - if( verbosity >= 0 ) - std::fprintf( stderr, "%s: Can't stat input file '%s': %s\n", - program_name, name.c_str(), std::strerror( errno ) ); - return 1; - } + { show_file_error( name.c_str(), "Can't stat input file", errno ); + return 1; } if( !S_ISREG( in_stats.st_mode ) ) - { - if( verbosity >= 0 ) - std::fprintf( stderr, "%s: Input file '%s' is not a regular file.\n", - program_name, name.c_str() ); - return 1; - } + { show_file_error( name.c_str(), "Input file is not a regular file." ); + return 1; } struct stat st; // not used const std::string rname2( rname + ".lz" ); // produced by lzip < 1.20 @@ -206,8 +260,8 @@ int zupdate_file( const std::string & name, const char * const lzip_name, std::string command( compressor_name ); command += " -V > /dev/null 2>&1"; disable_xz = ( std::system( command.c_str() ) != 0 ); if( disable_xz && verbosity >= 2 ) - std::fprintf( stderr, "%s: '%s' not found. Ignoring xz files.\n", - program_name, compressor_name ); + show_file_error( compressor_name, + "Xz decompressor not found. Ignoring xz files." ); } if( disable_xz ) return 0; // ignore this file if no xz installed } @@ -218,8 +272,8 @@ int zupdate_file( const std::string & name, const char * const lzip_name, std::string command( compressor_name ); command += " -V > /dev/null 2>&1"; disable_zst = ( std::system( command.c_str() ) != 0 ); if( disable_zst && verbosity >= 2 ) - std::fprintf( stderr, "%s: '%s' not found. Ignoring zstd files.\n", - program_name, compressor_name ); + show_file_error( compressor_name, + "Zstd decompressor not found. Ignoring zstd files." ); } if( disable_zst ) return 0; // ignore this file if no zstd installed } @@ -228,6 +282,9 @@ int zupdate_file( const std::string & name, const char * const lzip_name, { if( verbosity >= 1 ) std::fprintf( stderr, "Recompressing file '%s'\n", name.c_str() ); + if( destdir.size() && !make_dirs( rname ) ) + { show_file_error( rname.c_str(), "Error creating intermediate directory." ); + return 2; } int fda[2]; // pipe between decompressor and compressor if( pipe( fda ) < 0 ) { show_error( "Can't create pipe", errno ); return 2; } @@ -264,8 +321,8 @@ int zupdate_file( const std::string & name, const char * const lzip_name, { const std::vector< std::string > & lzip_args = get_compressor_args( fmt_lz ); - const int size = lzip_args.size(); - const int size2 = lzip_args2.size(); + const int size = lzip_args.size(); // from .conf or --lz + const int size2 = lzip_args2.size(); // from command line const char ** const argv = new const char *[size+size2+5]; argv[0] = lzip_name; argv[1] = "-9"; @@ -299,13 +356,14 @@ int zupdate_file( const std::string & name, const char * const lzip_name, { if( lz_exists && verbosity >= 1 ) std::fprintf( stderr, "Comparing file '%s'\n", name.c_str() ); + // Quote names in zcmp_command to allow file/dir names with spaces. std::string zcmp_command( invocation_name ); unsigned i = zcmp_command.size(); while( i > 0 && zcmp_command[i-1] != '/' ) --i; // strip "zupdate" zcmp_command.resize( i ); zcmp_command.insert( zcmp_command.begin(), '\'' ); zcmp_command += "zcmp' "; // '[dir/]zcmp' if( no_rcfile ) zcmp_command += "-N "; - if( verbosity < 0 ) zcmp_command += "-q "; + if( verbosity < 0 ) zcmp_command += "-q -s "; zcmp_command += '\''; zcmp_command += name; zcmp_command += "' '"; zcmp_command += rname; zcmp_command += '\''; int status = std::system( zcmp_command.c_str() ); @@ -315,12 +373,8 @@ int zupdate_file( const std::string & name, const char * const lzip_name, } if( !keep_input_files && std::remove( name.c_str() ) != 0 && errno != ENOENT ) - { - if( verbosity >= 0 ) - std::fprintf( stderr, "%s: Can't delete input file '%s': %s\n", - program_name, name.c_str(), std::strerror( errno ) ); - return 1; - } + { show_file_error( name.c_str(), "Can't delete input file", errno ); + return 1; } return 0; } @@ -331,7 +385,7 @@ int main( const int argc, const char * const argv[] ) { enum { bz2_opt = 256, gz_opt, lz_opt, xz_opt, zst_opt }; int recursive = 0; // 1 = '-r', 2 = '-R' - std::list< std::string > filenames; + std::string destdir; // write recompressed files here std::vector< std::string > lzip_args2; // args to lzip, maybe empty bool expand = false; bool force = false; @@ -353,6 +407,7 @@ int main( const int argc, const char * const argv[] ) { '7', 0, Arg_parser::no }, { '8', 0, Arg_parser::no }, { '9', 0, Arg_parser::no }, + { 'd', "destdir", Arg_parser::yes }, { 'e', "expand-extensions", Arg_parser::no }, { 'f', "force", Arg_parser::no }, { 'h', "help", Arg_parser::no }, @@ -391,6 +446,7 @@ int main( const int argc, const char * const argv[] ) case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': lzip_args2.push_back( "-" ); lzip_args2.back() += code; break; + case 'd': destdir = arg; break; case 'e': expand = true; break; case 'f': force = true; break; case 'h': show_help(); return 0; @@ -404,11 +460,11 @@ int main( const int argc, const char * const argv[] ) case 'R': recursive = 2; break; case 'v': if( verbosity < 4 ) ++verbosity; break; case 'V': show_version(); return 0; - case bz2_opt: parse_compressor( arg, fmt_bz2, 1 ); break; - case gz_opt: parse_compressor( arg, fmt_gz, 1 ); break; - case lz_opt: parse_compressor( arg, fmt_lz, 1 ); break; - case xz_opt: parse_compressor( arg, fmt_xz, 1 ); break; - case zst_opt: parse_compressor( arg, fmt_zst, 1 ); break; + case bz2_opt: parse_compressor( arg, pn, fmt_bz2, 1 ); break; + case gz_opt: parse_compressor( arg, pn, fmt_gz, 1 ); break; + case lz_opt: parse_compressor( arg, pn, fmt_lz, 1 ); break; + case xz_opt: parse_compressor( arg, pn, fmt_xz, 1 ); break; + case zst_opt: parse_compressor( arg, pn, fmt_zst, 1 ); break; default : internal_error( "uncaught option." ); } } // end process options @@ -422,22 +478,31 @@ int main( const int argc, const char * const argv[] ) if( !lzip_name ) { show_error( "Missing name of compressor for lzip format." ); return 2; } - for( ; argind < parser.arguments(); ++argind ) - filenames.push_back( parser.argument( argind ) ); - - if( filenames.empty() && recursive ) filenames.push_back( "." ); + std::list< std::string > filenames; + if( argind < parser.arguments() ) + filenames.push_back( parser.argument( argind++ ) ); // first argument + else if( recursive ) filenames.push_back( "." ); + else return 0; // nothing to do std::string input_filename; int retval = 0; bool error = false; - while( next_filename( filenames, input_filename, error, recursive, true ) ) + while( true ) { - int tmp = zupdate_file( input_filename, lzip_name, lzip_args2, expand, - force, keep_input_files, no_rcfile ); - if( tmp < 0 ) error = true; - if( tmp > retval ) retval = tmp; - if( tmp >= 2 || ( tmp == 1 && !ignore_errors ) ) break; + std::string srcdir; // dirname to be replaced by destdir + if( destdir.size() ) extract_srcdir_name( filenames.front(), srcdir ); + while( next_filename( filenames, input_filename, error, recursive, true ) ) + { + int tmp = zupdate_file( input_filename, lzip_name, lzip_args2, srcdir, + destdir, expand, force, keep_input_files, no_rcfile ); + if( tmp < 0 ) error = true; // file skipped + if( tmp > retval ) retval = tmp; + if( tmp >= 2 || ( tmp == 1 && !ignore_errors ) ) goto out; + } + if( argind >= parser.arguments() ) break; + filenames.push_back( parser.argument( argind++ ) ); } +out: if( error && retval == 0 ) retval = 1; return retval; } |