summaryrefslogtreecommitdiffstats
path: root/zupdate.cc
diff options
context:
space:
mode:
Diffstat (limited to 'zupdate.cc')
-rw-r--r--zupdate.cc157
1 files changed, 111 insertions, 46 deletions
diff --git a/zupdate.cc b/zupdate.cc
index fce00b3..ca5b9d3 100644
--- a/zupdate.cc
+++ b/zupdate.cc
@@ -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;
}