1 /* Zutils - Utilities dealing with compressed files
2    Copyright (C) 2009-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 <cstdio>
22 #include <cstdlib>
23 #include <cstring>
24 #include <string>
25 #include <vector>
26 #include <unistd.h>
27 #include <sys/wait.h>
28 
29 #include "arg_parser.h"
30 #include "rc.h"
31 
32 
33 const char * invocation_name = 0;
34 const char * program_name = 0;
35 int verbosity = 0;
36 
37 namespace {
38 
39 const char * const config_file_name = "zutilsrc";
40 const char * const     program_year = "2021";
41 
42 std::string compressor_names[num_formats] =
43   { "bzip2", "gzip", "lzip", "xz" };		// default compressor names
44 
45 // args to compressors read from rc or from options --[bglx]z, maybe empty
46 std::vector< std::string > compressor_args[num_formats];
47 
48 // vector of enabled formats plus [num_formats] for uncompressed.
49 // empty means all enabled.
50 std::vector< bool > enabled_formats;
51 
52 const struct { const char * from; const char * to; int format_index; }
53   known_extensions[] = {
54   { ".bz2",  "",     fmt_bz2 },
55   { ".tbz",  ".tar", fmt_bz2 },
56   { ".tbz2", ".tar", fmt_bz2 },
57   { ".gz",   "",     fmt_gz },
58   { ".tgz",  ".tar", fmt_gz },
59   { ".lz",   "",     fmt_lz },
60   { ".tlz",  ".tar", fmt_lz },
61   { ".xz",   "",     fmt_xz },
62   { ".txz",  ".tar", fmt_xz },
63   { 0,       0,      -1 } };
64 
65 
my_fgetc(FILE * const f)66 int my_fgetc( FILE * const f )
67   {
68   int ch;
69   bool comment = false;
70 
71   do {
72     ch = std::fgetc( f );
73     if( ch == '#' ) comment = true;
74     else if( ch == '\n' || ch == EOF ) comment = false;
75     else if( ch == '\\' && comment )
76       {
77       const int c = std::fgetc( f );
78       if( c == '\n' ) { std::ungetc( c, f ); comment = false; }
79       }
80     }
81   while( comment );
82   return ch;
83   }
84 
85 
86 // Returns the parity of escapes (backslashes) at the end of a string.
trailing_escape(const std::string & s)87 bool trailing_escape( const std::string & s )
88   {
89   unsigned len = s.size();
90   bool odd_escape = false;
91   while( len > 0 && s[--len] == '\\' ) odd_escape = !odd_escape;
92   return odd_escape;
93   }
94 
95 
96 /* Read a line discarding comments, leading whitespace, and blank lines.
97    Escaped newlines are discarded.
98    Returns the empty string if at EOF.
99 */
my_fgets(FILE * const f,int & linenum)100 const std::string & my_fgets( FILE * const f, int & linenum )
101   {
102   static std::string s;
103   bool strip = true;			// strip leading whitespace
104   s.clear();
105 
106   while( true )
107     {
108     int ch = my_fgetc( f );
109     if( strip )
110       {
111       strip = false;
112       while( std::isspace( ch ) )
113         { if( ch == '\n' ) { ++linenum; } ch = my_fgetc( f ); }
114       }
115     if( ch == EOF ) { if( s.size() ) { ++linenum; } break; }
116     else if( ch == '\n' )
117       {
118       ++linenum; strip = true;
119       if( trailing_escape( s ) ) s.erase( s.size() - 1 );
120       else if( s.size() ) break;
121       }
122     else s += ch;
123     }
124   return s;
125   }
126 
127 
parse_compressor_command(const std::string & s,int i,const int format_index)128 bool parse_compressor_command( const std::string & s, int i,
129                                const int format_index )
130   {
131   const int len = s.size();
132   while( i < len && std::isspace( s[i] ) ) ++i;		// strip spaces
133   int l = i;
134   while( i < len && !std::isspace( s[i] ) ) ++i;
135   if( l >= i || s[l] == '-' ) return false;
136   compressor_names[format_index].assign( s, l, i - l );
137 
138   compressor_args[format_index].clear();
139   while( i < len )
140     {
141     while( i < len && std::isspace( s[i] ) ) ++i;	// strip spaces
142     l = i;
143     while( i < len && !std::isspace( s[i] ) ) ++i;
144     if( l < i )
145       compressor_args[format_index].push_back( std::string( s, l, i - l ) );
146     }
147   return true;
148   }
149 
150 
parse_rc_line(const std::string & line,const char * const filename,const int linenum)151 bool parse_rc_line( const std::string & line,
152                     const char * const filename, const int linenum )
153   {
154   const int len = line.size();
155   int i = 0;
156   while( i < len && std::isspace( line[i] ) ) ++i;	// strip spaces
157   int l = i;
158   while( i < len && line[i] != '=' && !std::isspace( line[i] ) ) ++i;
159   if( l >= i )
160     { if( verbosity >= 0 )
161         std::fprintf( stderr, "%s %d: missing format name.\n", filename, linenum );
162       return false; }
163   const std::string name( line, l, i - l );
164   int format_index = -1;
165   for( int j = 0; j < num_formats; ++j )
166     if( name == format_names[j] ) { format_index = j; break; }
167   if( format_index < 0 )
168     { if( verbosity >= 0 )
169         std::fprintf( stderr, "%s %d: bad format name '%s'\n",
170                       filename, linenum, name.c_str() );
171       return false; }
172 
173   while( i < len && std::isspace( line[i] ) ) ++i;	// strip spaces
174   if( i <= 0 || i >= len || line[i] != '=' )
175     { if( verbosity >= 0 )
176         std::fprintf( stderr, "%s %d: missing '='\n", filename, linenum );
177       return false; }
178   ++i;							// skip the '='
179   if( !parse_compressor_command( line, i, format_index ) )
180     {
181     if( verbosity >= 0 )
182       std::fprintf( stderr, "%s %d: missing compressor name.\n", filename, linenum );
183     return false;
184     }
185   return true;
186   }
187 
188 
189     // Returns 0 for success, 1 for file not found, 2 for syntax error.
process_rcfile(const std::string & name)190 int process_rcfile( const std::string & name )
191   {
192   FILE * const f = std::fopen( name.c_str(), "r" );
193   if( !f ) return 1;
194 
195   int linenum = 0;
196   int retval = 0;
197 
198   while( true )
199     {
200     const std::string & line = my_fgets( f, linenum );
201     if( line.empty() ) break;				// EOF
202     if( !parse_rc_line( line, name.c_str(), linenum ) )
203       { retval = 2; break; }
204     }
205   std::fclose( f );
206   return retval;
207   }
208 
209 } // end namespace
210 
211 
enabled_format(const int format_index)212 bool enabled_format( const int format_index )
213   {
214   if( enabled_formats.size() <= num_formats ) return true;	// all enabled
215   if( format_index < 0 ) return enabled_formats[num_formats];	// uncompressed
216   return enabled_formats[format_index];
217   }
218 
219 
parse_format_list(const std::string & arg)220 void parse_format_list( const std::string & arg )
221   {
222   const std::string un( "uncompressed" );
223   bool error = arg.empty();
224   enabled_formats.assign( num_formats + 1, false );
225 
226   for( unsigned l = 0, r; l < arg.size(); l = r + 1 )
227     {
228     r = std::min( arg.find( ',', l ), arg.size() );
229     if( l >= r ) { error = true; break; }		// empty format
230     int format_index = num_formats;
231     const std::string s( arg, l, r - l );
232     for( int i = 0; i < num_formats; ++i )
233       if( s == format_names[i] )
234         { format_index = i; break; }
235     if( format_index == num_formats && un.find( s ) != 0 )
236       { error = true; break; }
237     enabled_formats[format_index] = true;
238     }
239   if( error )
240     { show_error( "Bad argument for option '--format'." ); std::exit( 1 ); }
241   }
242 
243 
parse_format_type(const std::string & arg)244 int parse_format_type( const std::string & arg )
245   {
246   for( int i = 0; i < num_formats; ++i )
247     if( arg == format_names[i] )
248       return i;
249   show_error( "Bad argument for option '--force-format'." );
250   std::exit( 1 );
251   }
252 
253 
extension_index(const std::string & name)254 int extension_index( const std::string & name )
255   {
256   for( int eindex = 0; known_extensions[eindex].from; ++eindex )
257     {
258     const std::string ext( known_extensions[eindex].from );
259     if( name.size() > ext.size() &&
260         name.compare( name.size() - ext.size(), ext.size(), ext ) == 0 )
261       return eindex;
262     }
263   return -1;
264   }
265 
extension_format(const int eindex)266 int extension_format( const int eindex )
267   { return ( eindex >= 0 ) ? known_extensions[eindex].format_index : -1; }
268 
extension_from(const int eindex)269 const char * extension_from( const int eindex )
270   { return known_extensions[eindex].from; }
271 
extension_to(const int eindex)272 const char * extension_to( const int eindex )
273   { return known_extensions[eindex].to; }
274 
275 
maybe_process_config_file(const Arg_parser & parser)276 void maybe_process_config_file( const Arg_parser & parser )
277   {
278   for( int i = 0; i < parser.arguments(); ++i )
279     if( parser.code( i ) == 'N' ) return;
280   std::string name;
281   const char * p = std::getenv( "HOME" ); if( p ) name = p;
282   if( name.size() )
283     {
284     name += "/."; name += config_file_name;
285     const int retval = process_rcfile( name );
286     if( retval == 0 ) return;
287     if( retval == 2 ) std::exit( 2 );
288     }
289   name = SYSCONFDIR; name += '/'; name += config_file_name;
290   const int retval = process_rcfile( name );
291   if( retval == 2 ) std::exit( 2 );
292   }
293 
294 
parse_compressor(const std::string & arg,const int format_index,const int eretval)295 void parse_compressor( const std::string & arg, const int format_index,
296                        const int eretval )
297   {
298   if( !parse_compressor_command( arg, 0, format_index ) )
299     { show_error( "Missing compressor name." ); std::exit( eretval ); }
300   }
301 
302 
get_compressor_name(const int format_index)303 const char * get_compressor_name( const int format_index )
304   {
305   if( format_index >= 0 && format_index < num_formats &&
306       compressor_names[format_index].size() )
307     return compressor_names[format_index].c_str();
308   return 0;
309   }
310 
311 
get_compressor_args(const int format_index)312 const std::vector< std::string > & get_compressor_args( const int format_index )
313   {
314   return compressor_args[format_index];
315   }
316 
317 
show_help_addr()318 void show_help_addr()
319   {
320   std::printf( "\nReport bugs to zutils-bug@nongnu.org\n"
321                "Zutils home page: http://www.nongnu.org/zutils/zutils.html\n" );
322   }
323 
324 
show_version()325 void show_version()
326   {
327   std::printf( "%s (zutils) %s\n", program_name, PROGVERSION );
328   std::printf( "Copyright (C) %s Antonio Diaz Diaz.\n", program_year );
329   std::printf( "License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html>\n"
330                "This is free software: you are free to change and redistribute it.\n"
331                "There is NO WARRANTY, to the extent permitted by law.\n" );
332   }
333 
334 
show_error(const char * const msg,const int errcode,const bool help)335 void show_error( const char * const msg, const int errcode, const bool help )
336   {
337   if( verbosity < 0 ) return;
338   if( msg && msg[0] )
339     std::fprintf( stderr, "%s: %s%s%s\n", program_name, msg,
340                   ( errcode > 0 ) ? ": " : "",
341                   ( errcode > 0 ) ? std::strerror( errcode ) : "" );
342   if( help )
343     std::fprintf( stderr, "Try '%s --help' for more information.\n",
344                   invocation_name );
345   }
346 
347 
show_file_error(const char * const filename,const char * const msg,const int errcode)348 void show_file_error( const char * const filename, const char * const msg,
349                       const int errcode )
350   {
351   if( verbosity >= 0 )
352     std::fprintf( stderr, "%s: %s: %s%s%s\n", program_name, filename, msg,
353                   ( errcode > 0 ) ? ": " : "",
354                   ( errcode > 0 ) ? std::strerror( errcode ) : "" );
355   }
356 
357 
internal_error(const char * const msg)358 void internal_error( const char * const msg )
359   {
360   if( verbosity >= 0 )
361     std::fprintf( stderr, "%s: internal error: %s\n", program_name, msg );
362   std::exit( 3 );
363   }
364 
365 
show_close_error(const char * const prog_name)366 void show_close_error( const char * const prog_name )
367   {
368   if( verbosity >= 0 )
369     std::fprintf( stderr, "%s: Error closing output of %s: %s\n",
370                   program_name, prog_name, std::strerror( errno ) );
371   }
372 
373 
show_exec_error(const char * const prog_name)374 void show_exec_error( const char * const prog_name )
375   {
376   if( verbosity >= 0 )
377     std::fprintf( stderr, "%s: Can't exec '%s': %s\n",
378                   program_name, prog_name, std::strerror( errno ) );
379   }
380 
381 
show_fork_error(const char * const prog_name)382 void show_fork_error( const char * const prog_name )
383   {
384   if( verbosity >= 0 )
385     std::fprintf( stderr, "%s: Can't fork '%s': %s\n",
386                   program_name, prog_name, std::strerror( errno ) );
387   }
388 
389 
wait_for_child(const pid_t pid,const char * const name,const int eretval,const bool isgzxz)390 int wait_for_child( const pid_t pid, const char * const name,
391                     const int eretval, const bool isgzxz )
392   {
393   int status;
394   while( waitpid( pid, &status, 0 ) == -1 )
395     {
396     if( errno != EINTR )
397       {
398       if( verbosity >= 0 )
399         std::fprintf( stderr, "%s: Error waiting termination of '%s': %s\n",
400                       program_name, name, std::strerror( errno ) );
401       _exit( eretval );
402       }
403     }
404   if( WIFEXITED( status ) )
405     {
406     const int tmp = WEXITSTATUS( status );
407     if( isgzxz && eretval == 1 && tmp == 1 ) return 2;		// for ztest
408     return tmp;
409     }
410   return eretval;
411   }
412