diff options
Diffstat (limited to '')
-rw-r--r-- | decode.cc | 226 |
1 files changed, 121 insertions, 105 deletions
@@ -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 ); } |