summaryrefslogtreecommitdiffstats
path: root/decode.cc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--decode.cc226
1 files changed, 121 insertions, 105 deletions
diff --git a/decode.cc b/decode.cc
index 18e7c4b..a45a1fd 100644
--- a/decode.cc
+++ b/decode.cc
@@ -36,6 +36,7 @@
#include "arg_parser.h"
#include "lzip_index.h"
#include "archive_reader.h"
+#include "decode.h"
namespace {
@@ -60,10 +61,12 @@ void read_error( const Archive_reader & ar )
}
-int skip_member( Archive_reader & ar, const Extended & extended )
+int skip_member( Archive_reader & ar, const Extended & extended,
+ const Typeflag typeflag )
{
- const int ret = ar.skip_member( extended );
- if( ret != 0 ) { read_error( ar ); if( ret == 2 ) return 2; }
+ if( data_may_follow( typeflag ) )
+ { const int ret = ar.skip_member( extended );
+ if( ret != 0 ) { read_error( ar ); if( ar.fatal() ) return ret; } }
return 0;
}
@@ -80,15 +83,15 @@ int compare_member( const Cl_options & cl_opts, Archive_reader & ar,
if( extended.file_size() <= 0 ) return 0;
const Typeflag typeflag = (Typeflag)header[typeflag_o];
if( ( typeflag != tf_regular && typeflag != tf_hiperf ) || stat_differs )
- return skip_member( ar, extended );
+ return skip_member( ar, extended, typeflag );
// else compare file contents
const char * const filename = extended.path().c_str();
const int infd2 = open_instream( filename );
if( infd2 < 0 )
- { set_error_status( 1 ); return skip_member( ar, extended ); }
+ { set_error_status( 1 ); return skip_member( ar, extended, typeflag ); }
int retval = compare_file_contents( estr, ostr, ar, extended.file_size(),
filename, infd2 );
- if( retval ) { read_error( ar ); if( retval != 2 ) retval = 0; }
+ if( retval ) { read_error( ar ); if( !ar.fatal() ) retval = 0; }
else { if( estr.size() ) std::fputs( estr.c_str(), stderr );
if( ostr.size() )
{ std::fputs( ostr.c_str(), stdout ); std::fflush( stdout ); } }
@@ -100,7 +103,7 @@ int list_member( Archive_reader & ar,
const Extended & extended, const Tar_header header )
{
if( !show_member_name( extended, header, 0, grbuf ) ) return 1;
- return skip_member( ar, extended );
+ return skip_member( ar, extended, (Typeflag)header[typeflag_o] );
}
@@ -108,27 +111,33 @@ int extract_member( const Cl_options & cl_opts, Archive_reader & ar,
const Extended & extended, const Tar_header header )
{
const char * const filename = extended.path().c_str();
+ const Typeflag typeflag = (Typeflag)header[typeflag_o];
if( contains_dotdot( filename ) )
{
- show_file_error( filename, "Contains a '..' component, skipping." );
- return skip_member( ar, extended );
+ show_file_error( filename, dotdot_msg );
+ return skip_member( ar, extended, typeflag );
}
mode_t mode = parse_octal( header + mode_o, mode_l ); // 12 bits
if( geteuid() != 0 && !cl_opts.preserve_permissions ) mode &= ~get_umask();
- const time_t mtime = parse_octal( header + mtime_o, mtime_l ); // 33 bits
- const Typeflag typeflag = (Typeflag)header[typeflag_o];
- const bool islink = ( typeflag == tf_link || typeflag == tf_symlink );
int outfd = -1;
if( !show_member_name( extended, header, 1, grbuf ) ) return 1;
+ // remove file (or empty dir) before extraction to prevent following links
std::remove( filename );
- make_path( filename );
+ if( !make_path( filename ) )
+ {
+ show_file_error( filename, intdir_msg, errno );
+ set_error_status( 1 );
+ return skip_member( ar, extended, typeflag );
+ }
+
switch( typeflag )
{
case tf_regular:
case tf_hiperf:
outfd = open_outstream( filename );
- if( outfd < 0 ) return 2;
+ if( outfd < 0 )
+ { set_error_status( 1 ); return skip_member( ar, extended, typeflag ); }
break;
case tf_link:
case tf_symlink:
@@ -138,18 +147,15 @@ int extract_member( const Cl_options & cl_opts, Archive_reader & ar,
if( ( hard && link( linkname, filename ) != 0 ) ||
( !hard && symlink( linkname, filename ) != 0 ) )
{
- if( verbosity >= 0 )
- std::fprintf( stderr, "Can't %slink '%s' to '%s': %s.\n",
- hard ? "" : "sym", linkname, filename,
- std::strerror( errno ) );
- return 2;
+ print_error( errno, cantln_msg, hard ? "" : "sym", linkname, filename );
+ set_error_status( 1 );
}
} break;
case tf_directory:
if( mkdir( filename, mode ) != 0 && errno != EEXIST )
{
- show_file_error( filename, "Can't create directory", errno );
- return 2;
+ show_file_error( filename, mkdir_msg, errno );
+ set_error_status( 1 );
}
break;
case tf_chardev:
@@ -161,70 +167,74 @@ int extract_member( const Cl_options & cl_opts, Archive_reader & ar,
const int dmode = ( typeflag == tf_chardev ? S_IFCHR : S_IFBLK ) | mode;
if( mknod( filename, dmode, dev ) != 0 )
{
- show_file_error( filename, "Can't create device node", errno );
- return 2;
+ show_file_error( filename, mknod_msg, errno );
+ set_error_status( 1 );
}
break;
}
case tf_fifo:
- if( mkfifo( filename, mode ) != 0 && errno != EEXIST )
+ if( mkfifo( filename, mode ) != 0 )
{
- show_file_error( filename, "Can't create FIFO file", errno );
- return 2;
+ show_file_error( filename, mkfifo_msg, errno );
+ set_error_status( 1 );
}
break;
default:
- if( verbosity >= 0 )
- std::fprintf( stderr, "File type '%c' not supported for file '%s'.\n",
- typeflag, filename );
- return 2;
+ print_error( 0, uftype_msg, filename, typeflag );
+ set_error_status( 2 );
+ return skip_member( ar, extended, typeflag );
}
- const uid_t uid = (uid_t)parse_octal( header + uid_o, uid_l );
- const gid_t gid = (gid_t)parse_octal( header + gid_o, gid_l );
- if( !islink && chown( filename, uid, gid ) != 0 &&
- errno != EPERM && errno != EINVAL )
+ const bool islink = ( typeflag == tf_link || typeflag == tf_symlink );
+ errno = 0;
+ if( !islink &&
+ ( !uid_gid_in_range( extended.get_uid(), extended.get_gid() ) ||
+ chown( filename, extended.get_uid(), extended.get_gid() ) != 0 ) )
{
- show_file_error( filename, "Can't change file owner", errno );
- return 2;
+ if( outfd >= 0 ) mode &= ~( S_ISUID | S_ISGID | S_ISVTX );
+ // chown will in many cases return with EPERM, which can be safely ignored.
+ if( errno != EPERM && errno != EINVAL )
+ { show_file_error( filename, chown_msg, errno ); set_error_status( 1 ); }
}
- if( typeflag == tf_regular || typeflag == tf_hiperf )
- fchmod( outfd, mode ); // ignore errors
+ if( outfd >= 0 ) fchmod( outfd, mode ); // ignore errors
- const int bufsize = 32 * header_size;
- uint8_t buf[bufsize];
- long long rest = extended.file_size();
- const int rem = rest % header_size;
- const int padding = rem ? header_size - rem : 0;
- while( rest > 0 )
+ if( data_may_follow( typeflag ) )
{
- const int rsize = ( rest >= bufsize ) ? bufsize : rest + padding;
- const int ret = ar.read( buf, rsize );
- if( ret != 0 )
+ const int bufsize = 32 * header_size;
+ uint8_t buf[bufsize];
+ long long rest = extended.file_size();
+ const int rem = rest % header_size;
+ const int padding = rem ? header_size - rem : 0;
+ while( rest > 0 )
{
- read_error( ar );
- if( outfd >= 0 )
+ const int rsize = ( rest >= bufsize ) ? bufsize : rest + padding;
+ const int ret = ar.read( buf, rsize );
+ if( ret != 0 )
{
- if( cl_opts.keep_damaged )
- { writeblock( outfd, buf, std::min( rest, (long long)ar.e_size() ) );
- close( outfd ); }
- else { close( outfd ); std::remove( filename ); }
+ read_error( ar );
+ if( outfd >= 0 )
+ {
+ if( cl_opts.keep_damaged )
+ { writeblock( outfd, buf, std::min( rest, (long long)ar.e_size() ) );
+ close( outfd ); }
+ else { close( outfd ); std::remove( filename ); }
+ }
+ if( ar.fatal() ) return ret; else return 0;
}
- if( ret == 2 ) return 2; else return 0;
+ const int wsize = ( rest >= bufsize ) ? bufsize : rest;
+ if( outfd >= 0 && writeblock( outfd, buf, wsize ) != wsize )
+ { show_file_error( filename, werr_msg, errno ); return 1; }
+ rest -= wsize;
}
- const int wsize = ( rest >= bufsize ) ? bufsize : rest;
- if( outfd >= 0 && writeblock( outfd, buf, wsize ) != wsize )
- { show_file_error( filename, "Error writing file", errno ); return 2; }
- rest -= wsize;
}
if( outfd >= 0 && close( outfd ) != 0 )
- { show_file_error( filename, "Error closing file", errno ); return 2; }
+ { show_file_error( filename, eclosf_msg, errno ); return 1; }
if( !islink )
{
struct utimbuf t;
- t.actime = mtime;
- t.modtime = mtime;
+ t.actime = extended.atime().sec();
+ t.modtime = extended.mtime().sec();
utime( filename, &t ); // ignore errors
}
return 0;
@@ -233,10 +243,8 @@ int extract_member( const Cl_options & cl_opts, Archive_reader & ar,
void format_file_diff( std::string & ostr, const char * const filename,
const char * const msg )
- {
- if( verbosity < 0 ) return;
- { ostr += filename; ostr += ": "; ostr += msg; ostr += '\n'; }
- }
+ { if( verbosity >= 0 )
+ { ostr += filename; ostr += ": "; ostr += msg; ostr += '\n'; } }
} // end namespace
@@ -276,18 +284,21 @@ bool compare_file_type( std::string & estr, std::string & ostr,
}
if( !cl_opts.ignore_ids )
{
- if( (uid_t)parse_octal( header + uid_o, uid_l ) != st.st_uid )
+ if( extended.get_uid() != (long long)st.st_uid )
{ format_file_diff( ostr, filename, "Uid differs" ); diff = true; }
- if( (gid_t)parse_octal( header + gid_o, gid_l ) != st.st_gid )
+ if( extended.get_gid() != (long long)st.st_gid )
{ format_file_diff( ostr, filename, "Gid differs" ); diff = true; }
}
if( typeflag != tf_symlink )
{
- if( typeflag != tf_directory )
+ if( typeflag != tf_directory &&
+ extended.mtime().sec() != (long long)st.st_mtime )
{
- const time_t mtime = parse_octal( header + mtime_o, mtime_l ); // 33 bits
- if( mtime != st.st_mtime )
- { format_file_diff( ostr, filename, "Mod time differs" ); diff = true; }
+ if( (time_t)extended.mtime().sec() == st.st_mtime )
+ { if( !cl_opts.ignore_overflow ) { diff = true;
+ format_file_diff( ostr, filename, "Mod time overflow" ); } }
+ else { diff = true;
+ format_file_diff( ostr, filename, "Mod time differs" ); }
}
if( ( typeflag == tf_regular || typeflag == tf_hiperf ) &&
extended.file_size() != st.st_size ) // don't compare contents
@@ -364,6 +375,7 @@ bool compare_file_contents( std::string & estr, std::string & ostr,
int decode( const Cl_options & cl_opts )
{
+ if( !grbuf.size() ) { show_error( mem_msg ); return 1; }
// open archive before changing working directory
const Archive_descriptor ad( cl_opts.archive_name );
if( ad.infd < 0 ) return 1;
@@ -378,8 +390,7 @@ int decode( const Cl_options & cl_opts )
{
const char * const dir = cl_opts.parser.argument( i ).c_str();
if( chdir( dir ) != 0 )
- { show_file_error( dir, "Error changing working directory", errno );
- return 1; }
+ { show_file_error( dir, chdir_msg, errno ); return 1; }
}
if( !code && cl_opts.parser.argument( i ).size() &&
!Exclude::excluded( cl_opts.parser.argument( i ).c_str() ) )
@@ -389,16 +400,14 @@ int decode( const Cl_options & cl_opts )
// multi-threaded --list is faster even with 1 thread and 1 file in archive
// but multi-threaded --diff and --extract probably need at least 2 of each
if( ( cl_opts.program_mode == m_diff || cl_opts.program_mode == m_list ||
- cl_opts.program_mode == m_extract ) && cl_opts.num_workers > 0 )
+ cl_opts.program_mode == m_extract ) && cl_opts.num_workers > 0 &&
+ ad.indexed && ad.lzip_index.members() >= 2 ) // one file + EOA
{
- if( ad.indexed && ad.lzip_index.members() >= 2 ) // one file + eof
- {
- // show_file_error( ad.namep, "Is compressed seekable" );
- return decode_lz( cl_opts, ad, name_pending );
- }
+ // show_file_error( ad.namep, "Is compressed seekable" );
+ return decode_lz( cl_opts, ad, name_pending );
}
- Archive_reader ar( ad );
+ Archive_reader ar( ad ); // serial reader
Extended extended; // metadata from extended records
int retval = 0;
bool prev_extended = false; // prev header was extended
@@ -406,12 +415,12 @@ int decode( const Cl_options & cl_opts )
{
Tar_header header;
const int ret = ar.read( header, header_size );
- if( ret != 0 ) { read_error( ar ); if( ret == 2 ) { retval = 2; break; } }
- if( ret != 0 || !verify_ustar_chksum( header ) )
+ if( ret != 0 ) { read_error( ar ); if( ar.fatal() ) { retval = ret; break; } }
+ if( ret != 0 || !verify_ustar_chksum( header ) ) // error or EOA
{
- if( ret == 0 && block_is_zero( header, header_size ) )
+ if( ret == 0 && block_is_zero( header, header_size ) ) // EOA
{
- if( !prev_extended || cl_opts.permissive ) break; // EOF
+ if( !prev_extended || cl_opts.permissive ) break;
show_file_error( ad.namep, fv_msg1 );
retval = 2; break;
}
@@ -427,23 +436,26 @@ int decode( const Cl_options & cl_opts )
if( prev_extended && !cl_opts.permissive )
{ show_file_error( ad.namep, fv_msg2 ); retval = 2; break; }
Extended dummy; // global headers are parsed and ignored
- const int ret = ar.parse_records( dummy, header, grbuf, true );
- if( ret != 0 ) { show_file_error( ad.namep, gblrec_msg ); skip_warn();
- set_error_status( ret ); }
+ const int ret = ar.parse_records( dummy, header, grbuf, gblrec_msg, true );
+ if( ret != 0 )
+ { show_file_error( ad.namep, ar.e_msg(), ar.e_code() );
+ if( ar.fatal() ) { retval = ret; break; }
+ skip_warn(); set_error_status( ret ); }
continue;
}
if( typeflag == tf_extended )
{
if( prev_extended && !cl_opts.permissive )
{ show_file_error( ad.namep, fv_msg3 ); retval = 2; break; }
- const int ret = ar.parse_records( extended, header, grbuf,
+ const int ret = ar.parse_records( extended, header, grbuf, extrec_msg,
cl_opts.permissive );
- if( ret != 0 ) { show_file_error( ad.namep, extrec_msg ); skip_warn();
- extended.reset(); set_error_status( ret ); }
+ if( ret != 0 )
+ { show_file_error( ad.namep, ar.e_msg(), ar.e_code() );
+ if( ar.fatal() ) { retval = ret; break; }
+ skip_warn(); extended.reset(); set_error_status( ret ); }
else if( !extended.crc_present() && cl_opts.missing_crc )
- { show_file_error( ad.namep, mcrc_msg ); retval = 2; break; }
- prev_extended = true;
- continue;
+ { show_file_error( ad.namep, miscrc_msg ); retval = 2; break; }
+ prev_extended = true; continue;
}
prev_extended = false;
@@ -451,26 +463,30 @@ int decode( const Cl_options & cl_opts )
// members without name are skipped except when listing
if( check_skip_filename( cl_opts, name_pending, extended.path().c_str() ) )
- retval = skip_member( ar, extended );
- else if( cl_opts.program_mode == m_list )
- retval = list_member( ar, extended, header );
- else if( extended.path().empty() ) retval = skip_member( ar, extended );
- else if( cl_opts.program_mode == m_diff )
- retval = compare_member( cl_opts, ar, extended, header );
- else retval = extract_member( cl_opts, ar, extended, header );
+ retval = skip_member( ar, extended, typeflag );
+ else
+ {
+ print_removed_prefix( extended.removed_prefix );
+ if( cl_opts.program_mode == m_list )
+ retval = list_member( ar, extended, header );
+ else if( extended.path().empty() )
+ retval = skip_member( ar, extended, typeflag );
+ else if( cl_opts.program_mode == m_diff )
+ retval = compare_member( cl_opts, ar, extended, header );
+ else retval = extract_member( cl_opts, ar, extended, header );
+ }
extended.reset();
if( retval )
{ show_error( "Error is not recoverable: exiting now." ); break; }
}
- if( close( ad.infd ) != 0 && !retval )
- { show_file_error( ad.namep, "Error closing archive", errno );
- retval = 1; }
+ if( close( ad.infd ) != 0 && retval == 0 )
+ { show_file_error( ad.namep, eclosa_msg, errno ); retval = 1; }
if( retval == 0 )
for( int i = 0; i < cl_opts.parser.arguments(); ++i )
if( nonempty_arg( cl_opts.parser, i ) && name_pending[i] )
- { show_file_error( cl_opts.parser.argument( i ).c_str(),
- "Not found in archive." ); retval = 1; }
+ { show_file_error( cl_opts.parser.argument( i ).c_str(), nfound_msg );
+ retval = 1; }
return final_exit_status( retval, cl_opts.program_mode != m_diff );
}