"Fossies" - the Fresh Open Source Software Archive 
Member "zutils-1.10/zupdate.cc" (5 Jan 2021, 16464 Bytes) of package /linux/privat/zutils-1.10.tar.lz:
1 /* Zupdate - recompress bzip2, gzip, xz files to lzip format
2 Copyright (C) 2013-2021 Antonio Diaz Diaz.
3
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 2 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #define _FILE_OFFSET_BITS 64
19
20 #include <cerrno>
21 #include <climits>
22 #include <csignal>
23 #include <cstdio>
24 #include <cstdlib>
25 #include <cstring>
26 #include <list>
27 #include <string>
28 #include <vector>
29 #include <dirent.h>
30 #include <fcntl.h>
31 #include <stdint.h>
32 #include <unistd.h>
33 #include <utime.h>
34 #include <sys/stat.h>
35 #include <sys/wait.h>
36 #if defined(__MSVCRT__) || defined(__OS2__)
37 #include <io.h>
38 #endif
39
40 #include "arg_parser.h"
41 #include "rc.h"
42
43 #ifndef O_BINARY
44 #define O_BINARY 0
45 #endif
46
47
48 namespace {
49
50 #include "recursive.cc"
51
52 void show_help()
53 {
54 std::printf( "zupdate recompresses files from bzip2, gzip, and xz formats to lzip\n"
55 "format. Each original is compared with the new file and then deleted.\n"
56 "Only regular files with standard file name extensions are recompressed,\n"
57 "other files are ignored. Compressed files are decompressed and then\n"
58 "recompressed on the fly; no temporary files are created. The lzip format\n"
59 "is chosen as destination because it is the most appropriate for\n"
60 "long-term data archiving.\n"
61 "\nIf no files are specified, recursive searches examine the current\n"
62 "working directory, and nonrecursive searches do nothing.\n"
63 "\nIf the lzip compressed version of a file already exists, the file is\n"
64 "skipped unless the option '--force' is given. In this case, if the\n"
65 "comparison with the existing lzip version fails, an error is returned\n"
66 "and the original file is not deleted. The operation of zupdate is meant\n"
67 "to be safe and not cause any data loss. Therefore, existing lzip\n"
68 "compressed files are never overwritten nor deleted.\n"
69 "\nThe names of the original files must have one of the following extensions:\n"
70 "'.bz2', '.gz', or '.xz', which are recompressed to '.lz';\n"
71 "'.tbz', '.tbz2', '.tgz', or '.txz', which are recompressed to '.tlz'.\n"
72 "\nUsage: zupdate [options] [files]\n"
73 "\nExit status is 0 if all the compressed files were successfully recompressed\n"
74 "(if needed), compared, and deleted (if requested). Non-zero otherwise.\n"
75 "\nOptions:\n"
76 " -h, --help display this help and exit\n"
77 " -V, --version output version information and exit\n"
78 " -f, --force don't skip a file even if the .lz exists\n"
79 " -k, --keep keep (don't delete) input files\n"
80 " -l, --lzip-verbose pass one option -v to the lzip compressor\n"
81 " -M, --format=<list> process only the formats in <list>\n"
82 " -N, --no-rcfile don't read runtime configuration file\n"
83 " -q, --quiet suppress all messages\n"
84 " -r, --recursive operate recursively on directories\n"
85 " -R, --dereference-recursive recursively follow symbolic links\n"
86 " -v, --verbose be verbose (a 2nd -v gives more)\n"
87 " -0 .. -9 set compression level [default 9]\n"
88 " --bz2=<command> set compressor and options for bzip2 format\n"
89 " --gz=<command> set compressor and options for gzip format\n"
90 " --lz=<command> set compressor and options for lzip format\n"
91 " --xz=<command> set compressor and options for xz format\n" );
92 show_help_addr();
93 }
94
95
96 int cant_execute( const std::string & command, const int status )
97 {
98 if( verbosity >= 0 )
99 {
100 if( WIFEXITED( status ) )
101 std::fprintf( stderr, "%s: Error executing '%s'. Exit status = %d\n",
102 program_name, command.c_str(), WEXITSTATUS( status ) );
103 else
104 std::fprintf( stderr, "%s: Can't execute '%s'\n",
105 program_name, command.c_str() );
106 }
107 return 1;
108 }
109
110
111 // Set permissions, owner, and times.
112 void set_permissions( const char * const rname, const struct stat & in_stats )
113 {
114 bool warning = false;
115 const mode_t mode = in_stats.st_mode;
116 // chown will in many cases return with EPERM, which can be safely ignored.
117 if( chown( rname, in_stats.st_uid, in_stats.st_gid ) == 0 )
118 { if( chmod( rname, mode ) != 0 ) warning = true; }
119 else
120 if( errno != EPERM ||
121 chmod( rname, mode & ~( S_ISUID | S_ISGID | S_ISVTX ) ) != 0 )
122 warning = true;
123 struct utimbuf t;
124 t.actime = in_stats.st_atime;
125 t.modtime = in_stats.st_mtime;
126 if( utime( rname, &t ) != 0 ) warning = true;
127 if( warning && verbosity >= 2 )
128 show_error( "Can't change output file attributes." );
129 }
130
131
132 // Returns 0 for success, -1 for file skipped, 1 for error.
133 int zupdate_file( const std::string & name, const char * const lzip_name,
134 const std::vector< std::string > & lzip_args2,
135 const bool force, const bool keep_input_files,
136 const bool no_rcfile )
137 {
138 static int disable_xz = -1; // tri-state bool
139 int format_index = -1;
140 std::string rname; // recompressed name
141
142 const int eindex = extension_index( name ); // search extension
143 if( eindex >= 0 )
144 {
145 format_index = extension_format( eindex );
146 if( format_index == fmt_lz )
147 {
148 if( verbosity >= 2 )
149 std::fprintf( stderr, "%s: Input file '%s' already has '%s' suffix.\n",
150 program_name, name.c_str(), extension_from( eindex ) );
151 return 0; // ignore this file
152 }
153 rname.assign( name, 0, name.size() - std::strlen( extension_from( eindex ) ) );
154 rname += ( std::strcmp( extension_to( eindex ), ".tar" ) == 0 ) ?
155 ".tlz" : ".lz"; // keep combined extension
156 }
157 const char * const compressor_name = get_compressor_name( format_index );
158 if( !compressor_name )
159 {
160 if( verbosity >= 2 )
161 std::fprintf( stderr, "%s: Unknown extension in file name '%s' -- ignored.\n",
162 program_name, name.c_str() );
163 return 0; // ignore this file
164 }
165
166 struct stat in_stats;
167 if( stat( name.c_str(), &in_stats ) != 0 ) // check input file
168 {
169 if( verbosity >= 0 )
170 std::fprintf( stderr, "%s: Can't stat input file '%s': %s\n",
171 program_name, name.c_str(), std::strerror( errno ) );
172 return 1;
173 }
174 if( !S_ISREG( in_stats.st_mode ) )
175 {
176 if( verbosity >= 0 )
177 std::fprintf( stderr, "%s: Input file '%s' is not a regular file.\n",
178 program_name, name.c_str() );
179 return 1;
180 }
181
182 struct stat st; // not used
183 const std::string rname2( rname + ".lz" ); // produced by lzip < 1.20
184 const bool lz_exists = ( stat( rname.c_str(), &st ) == 0 );
185 // don't modify an existing 'rname.lz'
186 const bool lz_lz_exists = ( stat( rname2.c_str(), &st ) == 0 );
187 if( lz_exists && !force )
188 {
189 if( verbosity >= 0 )
190 std::fprintf( stderr, "%s: Output file '%s' already exists, skipping.\n",
191 program_name, rname.c_str() );
192 return -1;
193 }
194
195 if( format_index == fmt_xz )
196 {
197 if( disable_xz < 0 )
198 {
199 std::string command( compressor_name ); command += " -V > /dev/null 2>&1";
200 disable_xz = ( std::system( command.c_str() ) != 0 );
201 }
202 if( disable_xz ) return 0; // ignore this file if no xz installed
203 }
204
205 if( !lz_exists ) // recompress
206 {
207 if( verbosity >= 1 )
208 std::fprintf( stderr, "Recompressing file '%s'\n", name.c_str() );
209 int fda[2]; // pipe between decompressor and compressor
210 if( pipe( fda ) < 0 )
211 { show_error( "Can't create pipe", errno ); return 1; }
212
213 const pid_t pid = fork();
214 if( pid == 0 ) // child1 (decompressor)
215 {
216 if( dup2( fda[1], STDOUT_FILENO ) >= 0 &&
217 close( fda[0] ) == 0 && close( fda[1] ) == 0 )
218 {
219 const std::vector< std::string > & compressor_args =
220 get_compressor_args( format_index );
221 const int size = compressor_args.size();
222 const char ** const argv = new const char *[size+5];
223 argv[0] = compressor_name;
224 for( int i = 0; i < size; ++i ) argv[i+1] = compressor_args[i].c_str();
225 argv[size+1] = "-cd";
226 argv[size+2] = "--";
227 argv[size+3] = name.c_str();
228 argv[size+4] = 0;
229 execvp( argv[0], (char **)argv );
230 }
231 show_exec_error( compressor_name );
232 _exit( 1 );
233 }
234 if( pid < 0 ) // parent
235 { show_fork_error( compressor_name ); return 1; }
236
237 const pid_t pid2 = fork();
238 if( pid2 == 0 ) // child2 (lzip compressor)
239 {
240 if( dup2( fda[0], STDIN_FILENO ) >= 0 &&
241 close( fda[0] ) == 0 && close( fda[1] ) == 0 )
242 {
243 const std::vector< std::string > & lzip_args =
244 get_compressor_args( fmt_lz );
245 const int size = lzip_args.size();
246 const int size2 = lzip_args2.size();
247 const char ** const argv = new const char *[size+size2+5];
248 argv[0] = lzip_name;
249 argv[1] = "-9";
250 for( int i = 0; i < size; ++i ) argv[i+2] = lzip_args[i].c_str();
251 for( int i = 0; i < size2; ++i ) argv[i+size+2] = lzip_args2[i].c_str();
252 argv[size+size2+2] = "-o";
253 argv[size+size2+3] = rname.c_str();
254 argv[size+size2+4] = 0;
255 execvp( argv[0], (char **)argv );
256 }
257 show_exec_error( lzip_name );
258 _exit( 1 );
259 }
260 if( pid2 < 0 ) // parent
261 { show_fork_error( lzip_name ); return 1; }
262
263 close( fda[0] ); close( fda[1] );
264 int retval = wait_for_child( pid, compressor_name );
265 int retval2 = wait_for_child( pid2, lzip_name );
266 if( retval || retval2 )
267 { if( !lz_lz_exists ) std::remove( rname2.c_str() ); // lzip < 1.20
268 std::remove( rname.c_str() ); return 1; }
269 if( stat( rname.c_str(), &st ) != 0 &&
270 ( lz_lz_exists || stat( rname2.c_str(), &st ) != 0 ||
271 std::rename( rname2.c_str(), rname.c_str() ) != 0 ) )
272 { show_file_error( rname.c_str(), "Error renaming output file", errno );
273 return 1; } // lzip < 1.11
274 set_permissions( rname.c_str(), in_stats );
275 }
276
277 {
278 if( lz_exists && verbosity >= 1 )
279 std::fprintf( stderr, "Comparing file '%s'\n", name.c_str() );
280 std::string zcmp_command( invocation_name );
281 unsigned i = zcmp_command.size();
282 while( i > 0 && zcmp_command[i-1] != '/' ) --i;
283 zcmp_command.resize( i ); zcmp_command.insert( zcmp_command.begin(), '\'' );
284 zcmp_command += "zcmp' "; // '[dir/]zcmp'
285 if( no_rcfile ) zcmp_command += "-N ";
286 if( verbosity < 0 ) zcmp_command += "-q ";
287 zcmp_command += '\''; zcmp_command += name;
288 zcmp_command += "' '"; zcmp_command += rname; zcmp_command += '\'';
289 int status = std::system( zcmp_command.c_str() );
290 if( status != 0 )
291 { if( !lz_exists ) std::remove( rname.c_str() );
292 return cant_execute( zcmp_command, status ); }
293 }
294
295 if( !keep_input_files && std::remove( name.c_str() ) != 0 && errno != ENOENT )
296 {
297 if( verbosity >= 0 )
298 std::fprintf( stderr, "%s: Can't delete input file '%s': %s\n",
299 program_name, name.c_str(), std::strerror( errno ) );
300 return 1;
301 }
302 return 0;
303 }
304
305 } // end namespace
306
307
308 int main( const int argc, const char * const argv[] )
309 {
310 enum { bz2_opt = 256, gz_opt, lz_opt, xz_opt };
311 int recursive = 0; // 1 = '-r', 2 = '-R'
312 std::list< std::string > filenames;
313 std::vector< std::string > lzip_args2; // args to lzip, maybe empty
314 bool force = false;
315 bool keep_input_files = false;
316 bool no_rcfile = false;
317 program_name = "zupdate";
318 invocation_name = ( argc > 0 ) ? argv[0] : program_name;
319
320 const Arg_parser::Option options[] =
321 {
322 { '0', 0, Arg_parser::no },
323 { '1', 0, Arg_parser::no },
324 { '2', 0, Arg_parser::no },
325 { '3', 0, Arg_parser::no },
326 { '4', 0, Arg_parser::no },
327 { '5', 0, Arg_parser::no },
328 { '6', 0, Arg_parser::no },
329 { '7', 0, Arg_parser::no },
330 { '8', 0, Arg_parser::no },
331 { '9', 0, Arg_parser::no },
332 { 'f', "force", Arg_parser::no },
333 { 'h', "help", Arg_parser::no },
334 { 'k', "keep", Arg_parser::no },
335 { 'l', "lzip-verbose", Arg_parser::no },
336 { 'M', "format", Arg_parser::yes },
337 { 'N', "no-rcfile", Arg_parser::no },
338 { 'q', "quiet", Arg_parser::no },
339 { 'r', "recursive", Arg_parser::no },
340 { 'R', "dereference-recursive", Arg_parser::no },
341 { 'v', "verbose", Arg_parser::no },
342 { 'V', "version", Arg_parser::no },
343 { bz2_opt, "bz2", Arg_parser::yes },
344 { gz_opt, "gz", Arg_parser::yes },
345 { lz_opt, "lz", Arg_parser::yes },
346 { xz_opt, "xz", Arg_parser::yes },
347 { 0 , 0, Arg_parser::no } };
348
349 const Arg_parser parser( argc, argv, options );
350 if( parser.error().size() ) // bad option
351 { show_error( parser.error().c_str(), 0, true ); return 1; }
352
353 maybe_process_config_file( parser );
354
355 int argind = 0;
356 for( ; argind < parser.arguments(); ++argind )
357 {
358 const int code = parser.code( argind );
359 if( !code ) break; // no more options
360 const std::string & arg = parser.argument( argind );
361 switch( code )
362 {
363 case '0': case '1': case '2': case '3': case '4':
364 case '5': case '6': case '7': case '8': case '9':
365 lzip_args2.push_back( "-" ); lzip_args2.back() += code; break;
366 case 'f': force = true; break;
367 case 'h': show_help(); return 0;
368 case 'k': keep_input_files = true; break;
369 case 'l': lzip_args2.push_back( "-v" ); break;
370 case 'M': parse_format_list( arg ); break;
371 case 'N': no_rcfile = true; break;
372 case 'q': verbosity = -1; lzip_args2.push_back( "-q" ); break;
373 case 'r': recursive = 1; break;
374 case 'R': recursive = 2; break;
375 case 'v': if( verbosity < 4 ) ++verbosity; break;
376 case 'V': show_version(); return 0;
377 case bz2_opt: parse_compressor( arg, fmt_bz2, 1 ); break;
378 case gz_opt: parse_compressor( arg, fmt_gz, 1 ); break;
379 case lz_opt: parse_compressor( arg, fmt_lz, 1 ); break;
380 case xz_opt: parse_compressor( arg, fmt_xz, 1 ); break;
381 default : internal_error( "uncaught option." );
382 }
383 } // end process options
384
385 #if defined(__MSVCRT__) || defined(__OS2__)
386 setmode( STDIN_FILENO, O_BINARY );
387 setmode( STDOUT_FILENO, O_BINARY );
388 #endif
389
390 const char * const lzip_name = get_compressor_name( fmt_lz );
391 if( !lzip_name )
392 { show_error( "Missing name of compressor for lzip format." ); return 1; }
393
394 for( ; argind < parser.arguments(); ++argind )
395 filenames.push_back( parser.argument( argind ) );
396
397 if( filenames.empty() && recursive ) filenames.push_back( "." );
398
399 std::string input_filename;
400 int retval = 0;
401 bool error = false;
402 while( next_filename( filenames, input_filename, error, recursive, true ) )
403 {
404 int tmp = zupdate_file( input_filename, lzip_name, lzip_args2, force,
405 keep_input_files, no_rcfile );
406 if( tmp < 0 ) error = true;
407 if( tmp > retval ) retval = tmp;
408 if( tmp > 0 ) break;
409 }
410 if( error && retval == 0 ) retval = 1;
411 return retval;
412 }