2025-02-17 21:04:20 +01:00
|
|
|
/* Tarlz - Archiver with multimember lzip compression
|
2025-02-17 21:10:53 +01:00
|
|
|
Copyright (C) 2013-2019 Antonio Diaz Diaz.
|
2025-02-17 21:04:20 +01:00
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 2 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define _FILE_OFFSET_BITS 64
|
|
|
|
|
|
|
|
#include <algorithm>
|
2025-02-17 21:10:53 +01:00
|
|
|
#include <cctype>
|
2025-02-17 21:04:20 +01:00
|
|
|
#include <cerrno>
|
2025-02-17 21:10:53 +01:00
|
|
|
#include <climits>
|
2025-02-17 21:04:20 +01:00
|
|
|
#include <cstdio>
|
|
|
|
#include <cstdlib>
|
|
|
|
#include <cstring>
|
|
|
|
#include <string>
|
|
|
|
#include <vector>
|
2025-02-17 21:12:33 +01:00
|
|
|
#include <pthread.h>
|
2025-02-17 21:04:20 +01:00
|
|
|
#include <stdint.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <utime.h>
|
|
|
|
#include <sys/stat.h>
|
2025-02-17 21:10:01 +01:00
|
|
|
#include <sys/types.h>
|
|
|
|
#if defined(__GNU_LIBRARY__)
|
2025-02-17 21:12:33 +01:00
|
|
|
#include <sys/sysmacros.h> // for major, minor, makedev
|
2025-02-17 21:10:01 +01:00
|
|
|
#endif
|
2025-02-17 21:04:20 +01:00
|
|
|
#include <lzlib.h>
|
|
|
|
|
|
|
|
#include "arg_parser.h"
|
2025-02-17 21:10:53 +01:00
|
|
|
#include "lzip_index.h"
|
2025-02-17 21:04:20 +01:00
|
|
|
#include "tarlz.h"
|
|
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
2025-02-17 21:10:53 +01:00
|
|
|
Resizable_buffer grbuf( initial_line_length );
|
2025-02-17 21:12:33 +01:00
|
|
|
bool archive_is_uncompressed_seekable = false;
|
2025-02-17 21:10:53 +01:00
|
|
|
bool has_lz_ext; // global var for archive_read
|
2025-02-17 21:10:01 +01:00
|
|
|
|
2025-02-17 21:12:33 +01:00
|
|
|
bool skip_warn( const bool reset = false ) // avoid duplicate warnings
|
2025-02-17 21:10:01 +01:00
|
|
|
{
|
|
|
|
static bool skipping = false;
|
|
|
|
|
|
|
|
if( reset ) skipping = false;
|
|
|
|
else if( !skipping )
|
2025-02-17 21:12:33 +01:00
|
|
|
{ skipping = true; show_error( "Skipping to next header." ); return true; }
|
|
|
|
return false;
|
2025-02-17 21:10:01 +01:00
|
|
|
}
|
|
|
|
|
2025-02-17 21:04:20 +01:00
|
|
|
|
|
|
|
bool make_path( const std::string & name )
|
|
|
|
{
|
|
|
|
const mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
|
|
|
|
unsigned end = name.size(); // first slash before last component
|
|
|
|
|
|
|
|
while( end > 0 && name[end-1] == '/' ) --end; // remove trailing slashes
|
|
|
|
while( end > 0 && name[end-1] != '/' ) --end; // remove last component
|
|
|
|
while( end > 0 && name[end-1] == '/' ) --end; // remove more slashes
|
|
|
|
|
|
|
|
unsigned index = 0;
|
|
|
|
while( index < end )
|
|
|
|
{
|
|
|
|
while( index < end && name[index] == '/' ) ++index;
|
|
|
|
unsigned first = index;
|
|
|
|
while( index < end && name[index] != '/' ) ++index;
|
|
|
|
if( first < index )
|
|
|
|
{
|
|
|
|
const std::string partial( name, 0, index );
|
|
|
|
struct stat st;
|
|
|
|
if( stat( partial.c_str(), &st ) == 0 )
|
|
|
|
{ if( !S_ISDIR( st.st_mode ) ) return false; }
|
|
|
|
else if( mkdir( partial.c_str(), mode ) != 0 )
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-17 21:10:01 +01:00
|
|
|
// Return value: 0 = OK, 1 = damaged member, 2 = fatal error.
|
|
|
|
// If sizep and error, return in *sizep the number of bytes read.
|
|
|
|
// The first 6 bytes of the archive must be intact for islz to be meaningful.
|
|
|
|
int archive_read( const int infd, uint8_t * const buf, const int size,
|
|
|
|
int * const sizep = 0 )
|
2025-02-17 21:04:20 +01:00
|
|
|
{
|
|
|
|
static LZ_Decoder * decoder = 0;
|
|
|
|
static bool at_eof = false;
|
2025-02-17 21:10:01 +01:00
|
|
|
static bool fatal = false;
|
|
|
|
static bool first_call = true;
|
2025-02-17 21:04:20 +01:00
|
|
|
|
2025-02-17 21:10:01 +01:00
|
|
|
if( sizep ) *sizep = 0;
|
|
|
|
if( fatal ) return 2;
|
2025-02-17 21:04:20 +01:00
|
|
|
if( first_call ) // check format
|
|
|
|
{
|
|
|
|
first_call = false;
|
|
|
|
if( size != header_size )
|
|
|
|
internal_error( "size != header_size on first call." );
|
2025-02-17 21:10:01 +01:00
|
|
|
const int rd = readblock( infd, buf, size );
|
|
|
|
if( sizep ) *sizep = rd;
|
2025-02-17 21:04:20 +01:00
|
|
|
if( rd != size && errno )
|
2025-02-17 21:10:01 +01:00
|
|
|
{ show_error( "Error reading archive", errno ); fatal = true; return 2; }
|
|
|
|
const Lzip_header & header = (*(const Lzip_header *)buf);
|
|
|
|
bool islz = ( rd >= min_member_size && header.verify_magic() &&
|
2025-02-17 21:10:53 +01:00
|
|
|
header.verify_version() &&
|
2025-02-17 21:10:01 +01:00
|
|
|
isvalid_ds( header.dictionary_size() ) );
|
2025-02-17 21:04:20 +01:00
|
|
|
const bool istar = ( rd == size && verify_ustar_chksum( buf ) );
|
|
|
|
const bool iseof =
|
|
|
|
( !islz && !istar && rd == size && block_is_zero( buf, size ) );
|
2025-02-17 21:10:01 +01:00
|
|
|
if( !islz && !istar && !iseof ) // corrupt or invalid format
|
2025-02-17 21:04:20 +01:00
|
|
|
{
|
2025-02-17 21:10:01 +01:00
|
|
|
show_error( "This does not look like a POSIX tar archive." );
|
2025-02-17 21:12:14 +01:00
|
|
|
if( has_lz_ext && rd >= min_member_size ) islz = true;
|
2025-02-17 21:10:01 +01:00
|
|
|
if( !islz ) return 1;
|
2025-02-17 21:04:20 +01:00
|
|
|
}
|
2025-02-17 21:10:01 +01:00
|
|
|
if( !islz ) // uncompressed
|
|
|
|
{ if( rd == size ) return 0; fatal = true; return 2; }
|
2025-02-17 21:12:33 +01:00
|
|
|
archive_is_uncompressed_seekable = false; // compressed
|
|
|
|
decoder = LZ_decompress_open();
|
2025-02-17 21:04:20 +01:00
|
|
|
if( !decoder || LZ_decompress_errno( decoder ) != LZ_ok )
|
2025-02-17 21:12:14 +01:00
|
|
|
{ show_error( mem_msg );
|
2025-02-17 21:10:01 +01:00
|
|
|
LZ_decompress_close( decoder ); fatal = true; return 2; }
|
2025-02-17 21:04:20 +01:00
|
|
|
if( LZ_decompress_write( decoder, buf, rd ) != rd )
|
|
|
|
internal_error( "library error (LZ_decompress_write)." );
|
2025-02-17 21:10:01 +01:00
|
|
|
const int res = archive_read( infd, buf, size, sizep );
|
|
|
|
if( res != 0 ) { if( res == 2 ) fatal = true; return res; }
|
|
|
|
if( verify_ustar_chksum( buf ) || block_is_zero( buf, size ) ) return 0;
|
|
|
|
show_error( "This does not look like a POSIX tar.lz archive." );
|
|
|
|
fatal = true; return 2;
|
2025-02-17 21:04:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if( !decoder ) // uncompressed
|
2025-02-17 21:10:01 +01:00
|
|
|
{
|
|
|
|
const int rd = readblock( infd, buf, size ); if( rd == size ) return 0;
|
|
|
|
if( sizep ) *sizep = rd;
|
|
|
|
show_error( "Archive ends unexpectedly." ); fatal = true; return 2;
|
|
|
|
}
|
2025-02-17 21:04:20 +01:00
|
|
|
const int ibuf_size = 16384;
|
|
|
|
uint8_t ibuf[ibuf_size];
|
|
|
|
int sz = 0;
|
|
|
|
while( sz < size )
|
|
|
|
{
|
2025-02-17 21:10:01 +01:00
|
|
|
const int rd = LZ_decompress_read( decoder, buf + sz, size - sz );
|
|
|
|
if( rd < 0 )
|
|
|
|
{
|
|
|
|
if( LZ_decompress_sync_to_member( decoder ) < 0 )
|
|
|
|
internal_error( "library error (LZ_decompress_sync_to_member)." );
|
2025-02-17 21:12:14 +01:00
|
|
|
skip_warn(); set_error_status( 2 ); return 1;
|
2025-02-17 21:10:01 +01:00
|
|
|
}
|
|
|
|
if( rd == 0 && LZ_decompress_finished( decoder ) == 1 )
|
2025-02-17 21:10:53 +01:00
|
|
|
{ LZ_decompress_close( decoder );
|
|
|
|
show_error( "Archive ends unexpectedly." ); fatal = true; return 2; }
|
2025-02-17 21:10:01 +01:00
|
|
|
sz += rd; if( sizep ) *sizep = sz;
|
|
|
|
if( sz == size && LZ_decompress_finished( decoder ) == 1 &&
|
|
|
|
LZ_decompress_close( decoder ) < 0 )
|
|
|
|
{ show_error( "LZ_decompress_close failed." ); fatal = true; return 2; }
|
|
|
|
if( sz < size && !at_eof && LZ_decompress_write_size( decoder ) > 0 )
|
2025-02-17 21:04:20 +01:00
|
|
|
{
|
|
|
|
const int rsize = std::min( ibuf_size, LZ_decompress_write_size( decoder ) );
|
|
|
|
const int rd = readblock( infd, ibuf, rsize );
|
|
|
|
if( LZ_decompress_write( decoder, ibuf, rd ) != rd )
|
|
|
|
internal_error( "library error (LZ_decompress_write)." );
|
|
|
|
if( rd < rsize )
|
|
|
|
{
|
|
|
|
at_eof = true; LZ_decompress_finish( decoder );
|
|
|
|
if( errno )
|
2025-02-17 21:10:01 +01:00
|
|
|
{ show_error( "Error reading archive", errno ); fatal = true;
|
|
|
|
return 2; }
|
2025-02-17 21:04:20 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-02-17 21:10:01 +01:00
|
|
|
return 0;
|
2025-02-17 21:04:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-17 21:10:53 +01:00
|
|
|
enum { mode_string_size = 10,
|
|
|
|
group_string_size = 1 + uname_l + 1 + gname_l + 1 }; // 67
|
|
|
|
|
|
|
|
void format_mode_string( const Tar_header header, char buf[mode_string_size] )
|
2025-02-17 21:04:20 +01:00
|
|
|
{
|
|
|
|
const Typeflag typeflag = (Typeflag)header[typeflag_o];
|
|
|
|
|
2025-02-17 21:10:53 +01:00
|
|
|
std::memcpy( buf, "----------", mode_string_size );
|
2025-02-17 21:04:20 +01:00
|
|
|
switch( typeflag )
|
|
|
|
{
|
|
|
|
case tf_regular: break;
|
|
|
|
case tf_link: buf[0] = 'h'; break;
|
|
|
|
case tf_symlink: buf[0] = 'l'; break;
|
|
|
|
case tf_chardev: buf[0] = 'c'; break;
|
|
|
|
case tf_blockdev: buf[0] = 'b'; break;
|
|
|
|
case tf_directory: buf[0] = 'd'; break;
|
|
|
|
case tf_fifo: buf[0] = 'p'; break;
|
|
|
|
case tf_hiperf: buf[0] = 'C'; break;
|
|
|
|
default: buf[0] = '?';
|
|
|
|
}
|
2025-02-17 21:10:53 +01:00
|
|
|
const mode_t mode = parse_octal( header + mode_o, mode_l ); // 12 bits
|
2025-02-17 21:04:20 +01:00
|
|
|
const bool setuid = mode & S_ISUID;
|
|
|
|
const bool setgid = mode & S_ISGID;
|
|
|
|
const bool sticky = mode & S_ISVTX;
|
|
|
|
if( mode & S_IRUSR ) buf[1] = 'r';
|
|
|
|
if( mode & S_IWUSR ) buf[2] = 'w';
|
|
|
|
if( mode & S_IXUSR ) buf[3] = setuid ? 's' : 'x';
|
|
|
|
else if( setuid ) buf[3] = 'S';
|
|
|
|
if( mode & S_IRGRP ) buf[4] = 'r';
|
|
|
|
if( mode & S_IWGRP ) buf[5] = 'w';
|
|
|
|
if( mode & S_IXGRP ) buf[6] = setgid ? 's' : 'x';
|
|
|
|
else if( setgid ) buf[6] = 'S';
|
|
|
|
if( mode & S_IROTH ) buf[7] = 'r';
|
|
|
|
if( mode & S_IWOTH ) buf[8] = 'w';
|
|
|
|
if( mode & S_IXOTH ) buf[9] = sticky ? 't' : 'x';
|
|
|
|
else if( sticky ) buf[9] = 'T';
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-17 21:10:53 +01:00
|
|
|
int format_user_group_string( const Tar_header header,
|
|
|
|
char buf[group_string_size] )
|
2025-02-17 21:04:20 +01:00
|
|
|
{
|
2025-02-17 21:10:53 +01:00
|
|
|
int len;
|
2025-02-17 21:04:20 +01:00
|
|
|
if( header[uname_o] && header[gname_o] )
|
2025-02-17 21:10:53 +01:00
|
|
|
len = snprintf( buf, group_string_size,
|
|
|
|
" %.32s/%.32s", header + uname_o, header + gname_o );
|
2025-02-17 21:04:20 +01:00
|
|
|
else
|
|
|
|
{
|
2025-02-17 21:10:53 +01:00
|
|
|
const unsigned uid = parse_octal( header + uid_o, uid_l );
|
|
|
|
const unsigned gid = parse_octal( header + gid_o, gid_l );
|
|
|
|
len = snprintf( buf, group_string_size, " %u/%u", uid, gid );
|
2025-02-17 21:04:20 +01:00
|
|
|
}
|
2025-02-17 21:10:53 +01:00
|
|
|
return len;
|
2025-02-17 21:04:20 +01:00
|
|
|
}
|
|
|
|
|
2025-02-17 21:10:53 +01:00
|
|
|
} // end namespace
|
2025-02-17 21:04:20 +01:00
|
|
|
|
2025-02-17 21:10:53 +01:00
|
|
|
bool block_is_zero( const uint8_t * const buf, const int size )
|
2025-02-17 21:04:20 +01:00
|
|
|
{
|
2025-02-17 21:10:53 +01:00
|
|
|
for( int i = 0; i < size; ++i ) if( buf[i] != 0 ) return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void format_member_name( const Extended & extended, const Tar_header header,
|
|
|
|
Resizable_buffer & rbuf, const bool long_format )
|
|
|
|
{
|
|
|
|
if( long_format )
|
2025-02-17 21:04:20 +01:00
|
|
|
{
|
2025-02-17 21:10:53 +01:00
|
|
|
format_mode_string( header, rbuf() );
|
|
|
|
const int group_string_len =
|
|
|
|
format_user_group_string( header, rbuf() + mode_string_size );
|
2025-02-17 21:12:33 +01:00
|
|
|
int offset = mode_string_size + group_string_len;
|
2025-02-17 21:10:53 +01:00
|
|
|
const time_t mtime = parse_octal( header + mtime_o, mtime_l ); // 33 bits
|
|
|
|
struct tm tms;
|
|
|
|
const struct tm * tm = localtime_r( &mtime, &tms );
|
|
|
|
if( !tm )
|
|
|
|
{ time_t z = 0; tm = localtime_r( &z, &tms ); if( !tm ) tm = &tms; }
|
2025-02-17 21:10:01 +01:00
|
|
|
const Typeflag typeflag = (Typeflag)header[typeflag_o];
|
|
|
|
const bool islink = ( typeflag == tf_link || typeflag == tf_symlink );
|
|
|
|
const char * const link_string = !islink ? "" :
|
|
|
|
( ( typeflag == tf_link ) ? " link to " : " -> " );
|
2025-02-17 21:12:33 +01:00
|
|
|
if( typeflag == tf_chardev || typeflag == tf_blockdev )
|
|
|
|
offset += snprintf( rbuf() + offset, rbuf.size() - offset, " %5u,%u",
|
|
|
|
(unsigned)parse_octal( header + devmajor_o, devmajor_l ),
|
|
|
|
(unsigned)parse_octal( header + devminor_o, devminor_l ) );
|
|
|
|
else
|
|
|
|
offset += snprintf( rbuf() + offset, rbuf.size() - offset, " %9llu",
|
|
|
|
extended.file_size() );
|
2025-02-17 21:10:53 +01:00
|
|
|
for( int i = 0; i < 2; ++i )
|
|
|
|
{
|
|
|
|
const int len = snprintf( rbuf() + offset, rbuf.size() - offset,
|
2025-02-17 21:12:33 +01:00
|
|
|
" %4d-%02u-%02u %02u:%02u %s%s%s\n",
|
|
|
|
1900 + tm->tm_year, 1 + tm->tm_mon, tm->tm_mday,
|
|
|
|
tm->tm_hour, tm->tm_min, extended.path().c_str(),
|
|
|
|
link_string, islink ? extended.linkpath().c_str() : "" );
|
2025-02-17 21:12:14 +01:00
|
|
|
if( (int)rbuf.size() > len + offset || !rbuf.resize( len + offset + 1 ) )
|
|
|
|
break;
|
2025-02-17 21:10:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2025-02-17 21:11:12 +01:00
|
|
|
if( rbuf.size() < extended.path().size() + 2 )
|
|
|
|
rbuf.resize( extended.path().size() + 2 );
|
|
|
|
snprintf( rbuf(), rbuf.size(), "%s\n", extended.path().c_str() );
|
2025-02-17 21:04:20 +01:00
|
|
|
}
|
2025-02-17 21:10:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
void show_member_name( const Extended & extended, const Tar_header header,
|
|
|
|
const int vlevel, Resizable_buffer & rbuf )
|
|
|
|
{
|
|
|
|
if( verbosity < vlevel ) return;
|
|
|
|
format_member_name( extended, header, rbuf, verbosity > vlevel );
|
|
|
|
std::fputs( rbuf(), stdout );
|
2025-02-17 21:04:20 +01:00
|
|
|
std::fflush( stdout );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-17 21:12:33 +01:00
|
|
|
int skip_member( const int infd, const Extended & extended )
|
2025-02-17 21:04:20 +01:00
|
|
|
{
|
2025-02-17 21:11:12 +01:00
|
|
|
unsigned long long rest = extended.file_size();
|
|
|
|
const int rem = rest % header_size;
|
2025-02-17 21:04:20 +01:00
|
|
|
const int padding = rem ? header_size - rem : 0;
|
2025-02-17 21:12:33 +01:00
|
|
|
if( archive_is_uncompressed_seekable &&
|
|
|
|
lseek( infd, rest + padding, SEEK_CUR ) > 0 ) return 0;
|
|
|
|
const unsigned bufsize = 32 * header_size;
|
|
|
|
uint8_t buf[bufsize];
|
2025-02-17 21:04:20 +01:00
|
|
|
while( rest > 0 )
|
|
|
|
{
|
|
|
|
const int rsize = ( rest >= bufsize ) ? bufsize : rest + padding;
|
2025-02-17 21:10:01 +01:00
|
|
|
const int ret = archive_read( infd, buf, rsize );
|
|
|
|
if( ret != 0 ) { if( ret == 2 ) return 2; else break; }
|
2025-02-17 21:04:20 +01:00
|
|
|
if( rest < bufsize ) break;
|
|
|
|
rest -= rsize;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-17 21:12:33 +01:00
|
|
|
void show_file_diff( const char * const filename, const char * const msg )
|
|
|
|
{
|
|
|
|
if( verbosity >= 0 ) std::fprintf( stderr, "%s: %s\n", filename, msg );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int compare_member( const int infd1, const Extended & extended,
|
|
|
|
const Tar_header header, const bool ignore_ids )
|
|
|
|
{
|
|
|
|
show_member_name( extended, header, 1, grbuf );
|
|
|
|
unsigned long long rest = extended.file_size();
|
|
|
|
const char * const filename = extended.path().c_str();
|
|
|
|
const Typeflag typeflag = (Typeflag)header[typeflag_o];
|
|
|
|
bool diff = false, size_differs = false, type_differs = true;
|
|
|
|
struct stat st;
|
|
|
|
if( lstat( filename, &st ) != 0 )
|
|
|
|
show_file_error( filename, "Warning: Can't stat", errno );
|
|
|
|
else if( ( typeflag == tf_regular || typeflag == tf_hiperf ) &&
|
|
|
|
!S_ISREG( st.st_mode ) )
|
|
|
|
show_file_diff( filename, "Is not a regular file" );
|
|
|
|
else if( typeflag == tf_symlink && !S_ISLNK( st.st_mode ) )
|
|
|
|
show_file_diff( filename, "Is not a symlink" );
|
|
|
|
else if( typeflag == tf_chardev && !S_ISCHR( st.st_mode ) )
|
|
|
|
show_file_diff( filename, "Is not a character device" );
|
|
|
|
else if( typeflag == tf_blockdev && !S_ISBLK( st.st_mode ) )
|
|
|
|
show_file_diff( filename, "Is not a block device" );
|
|
|
|
else if( typeflag == tf_directory && !S_ISDIR( st.st_mode ) )
|
|
|
|
show_file_diff( filename, "Is not a directory" );
|
|
|
|
else if( typeflag == tf_fifo && !S_ISFIFO( st.st_mode ) )
|
|
|
|
show_file_diff( filename, "Is not a FIFO" );
|
|
|
|
else
|
|
|
|
{
|
|
|
|
type_differs = false;
|
|
|
|
if( typeflag != tf_symlink )
|
|
|
|
{
|
|
|
|
const mode_t mode = parse_octal( header + mode_o, mode_l ); // 12 bits
|
|
|
|
if( mode != ( st.st_mode & ( S_ISUID | S_ISGID | S_ISVTX |
|
|
|
|
S_IRWXU | S_IRWXG | S_IRWXO ) ) )
|
|
|
|
{ show_file_diff( filename, "Mode differs" ); diff = true; }
|
|
|
|
}
|
|
|
|
if( !ignore_ids )
|
|
|
|
{
|
|
|
|
if( (uid_t)parse_octal( header + uid_o, uid_l ) != st.st_uid )
|
|
|
|
{ show_file_diff( filename, "Uid differs" ); diff = true; }
|
|
|
|
if( (gid_t)parse_octal( header + gid_o, gid_l ) != st.st_gid )
|
|
|
|
{ show_file_diff( filename, "Gid differs" ); diff = true; }
|
|
|
|
}
|
|
|
|
if( typeflag != tf_symlink )
|
|
|
|
{
|
2025-02-17 21:12:49 +01:00
|
|
|
if( typeflag != tf_directory )
|
|
|
|
{
|
|
|
|
const time_t mtime = parse_octal( header + mtime_o, mtime_l ); // 33 bits
|
|
|
|
if( mtime != st.st_mtime )
|
|
|
|
{ show_file_diff( filename, "Mod time differs" ); diff = true; }
|
|
|
|
}
|
2025-02-17 21:12:33 +01:00
|
|
|
if( ( typeflag == tf_regular || typeflag == tf_hiperf ) &&
|
|
|
|
(off_t)rest != st.st_size ) // don't compare contents
|
|
|
|
{ show_file_diff( filename, "Size differs" ); size_differs = true; }
|
|
|
|
if( ( typeflag == tf_chardev || typeflag == tf_blockdev ) &&
|
2025-02-17 21:12:49 +01:00
|
|
|
( parse_octal( header + devmajor_o, devmajor_l ) !=
|
|
|
|
(unsigned)major( st.st_rdev ) ||
|
|
|
|
parse_octal( header + devminor_o, devminor_l ) !=
|
|
|
|
(unsigned)minor( st.st_rdev ) ) )
|
2025-02-17 21:12:33 +01:00
|
|
|
{ show_file_diff( filename, "Device number differs" ); diff = true; }
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
char * const buf = new char[st.st_size+1];
|
|
|
|
long len = readlink( filename, buf, st.st_size );
|
|
|
|
bool e = ( len != st.st_size );
|
2025-02-17 21:12:49 +01:00
|
|
|
if( !e )
|
|
|
|
{
|
|
|
|
while( len > 1 && buf[len-1] == '/' ) --len; // trailing '/'
|
|
|
|
buf[len] = 0;
|
|
|
|
if( extended.linkpath() != buf ) e = true;
|
|
|
|
}
|
2025-02-17 21:12:33 +01:00
|
|
|
delete[] buf;
|
|
|
|
if( e ) { show_file_diff( filename, "Symlink differs" ); diff = true; }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if( diff || size_differs || type_differs )
|
|
|
|
{ diff = false; set_error_status( 1 ); }
|
|
|
|
if( rest == 0 ) return 0;
|
|
|
|
if( ( typeflag != tf_regular && typeflag != tf_hiperf ) ||
|
|
|
|
size_differs || type_differs ) return skip_member( infd1, extended );
|
|
|
|
// else compare file contents
|
|
|
|
const int rem = rest % header_size;
|
|
|
|
const int padding = rem ? header_size - rem : 0;
|
|
|
|
const unsigned bufsize = 32 * header_size;
|
|
|
|
uint8_t buf1[bufsize];
|
|
|
|
uint8_t buf2[bufsize];
|
|
|
|
const int infd2 = open_instream( filename );
|
|
|
|
if( infd2 < 0 )
|
|
|
|
{ set_error_status( 1 ); return skip_member( infd1, extended ); }
|
|
|
|
int retval = 0;
|
|
|
|
while( rest > 0 )
|
|
|
|
{
|
|
|
|
const int rsize1 = ( rest >= bufsize ) ? bufsize : rest + padding;
|
|
|
|
const int rsize2 = ( rest >= bufsize ) ? bufsize : rest;
|
|
|
|
const int ret = archive_read( infd1, buf1, rsize1 );
|
|
|
|
if( ret != 0 ) { if( ret == 2 ) retval = 2; diff = true; break; }
|
|
|
|
if( !diff )
|
|
|
|
{
|
|
|
|
const int rd = readblock( infd2, buf2, rsize2 );
|
|
|
|
if( rd != rsize2 )
|
|
|
|
{
|
|
|
|
if( errno ) show_file_error( filename, "Read error", errno );
|
|
|
|
else show_file_diff( filename, "EOF found in file" );
|
|
|
|
diff = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int i = 0; while( i < rsize2 && buf1[i] == buf2[i] ) ++i;
|
|
|
|
if( i < rsize2 )
|
|
|
|
{ show_file_diff( filename, "Contents differ" ); diff = true; }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if( rest < bufsize ) break;
|
|
|
|
rest -= rsize1;
|
|
|
|
}
|
|
|
|
if( diff ) set_error_status( 1 );
|
|
|
|
close( infd2 );
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int list_member( const int infd, const Extended & extended,
|
|
|
|
const Tar_header header )
|
|
|
|
{
|
|
|
|
show_member_name( extended, header, 0, grbuf );
|
|
|
|
return skip_member( infd, extended );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-17 21:04:20 +01:00
|
|
|
bool contains_dotdot( const char * const filename )
|
|
|
|
{
|
|
|
|
for( int i = 0; filename[i]; ++i )
|
2025-02-17 21:12:49 +01:00
|
|
|
if( dotdot_at_i( filename, i ) ) return true;
|
2025-02-17 21:04:20 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-17 21:10:01 +01:00
|
|
|
int extract_member( const int infd, const Extended & extended,
|
|
|
|
const Tar_header header, const bool keep_damaged )
|
2025-02-17 21:04:20 +01:00
|
|
|
{
|
2025-02-17 21:11:12 +01:00
|
|
|
const char * const filename = extended.path().c_str();
|
2025-02-17 21:04:20 +01:00
|
|
|
if( contains_dotdot( filename ) )
|
|
|
|
{
|
|
|
|
show_file_error( filename, "Contains a '..' component, skipping." );
|
2025-02-17 21:12:33 +01:00
|
|
|
return skip_member( infd, extended );
|
2025-02-17 21:04:20 +01:00
|
|
|
}
|
2025-02-17 21:10:53 +01:00
|
|
|
const mode_t mode = parse_octal( header + mode_o, mode_l ); // 12 bits
|
|
|
|
const time_t mtime = parse_octal( header + mtime_o, mtime_l ); // 33 bits
|
2025-02-17 21:04:20 +01:00
|
|
|
const Typeflag typeflag = (Typeflag)header[typeflag_o];
|
|
|
|
const bool islink = ( typeflag == tf_link || typeflag == tf_symlink );
|
|
|
|
int outfd = -1;
|
|
|
|
|
2025-02-17 21:10:53 +01:00
|
|
|
show_member_name( extended, header, 1, grbuf );
|
2025-02-17 21:04:20 +01:00
|
|
|
std::remove( filename );
|
|
|
|
make_path( filename );
|
|
|
|
switch( typeflag )
|
|
|
|
{
|
|
|
|
case tf_regular:
|
|
|
|
case tf_hiperf:
|
|
|
|
outfd = open_outstream( filename );
|
|
|
|
if( outfd < 0 ) return 2;
|
|
|
|
chmod( filename, mode ); // ignore errors
|
|
|
|
break;
|
|
|
|
case tf_link:
|
|
|
|
case tf_symlink:
|
|
|
|
{
|
2025-02-17 21:11:12 +01:00
|
|
|
const char * const linkname = extended.linkpath().c_str();
|
2025-02-17 21:04:20 +01:00
|
|
|
const bool hard = typeflag == tf_link;
|
|
|
|
if( ( hard && link( linkname, filename ) != 0 ) ||
|
|
|
|
( !hard && symlink( linkname, filename ) != 0 ) )
|
|
|
|
{
|
|
|
|
if( verbosity >= 0 )
|
|
|
|
std::fprintf( stderr, "Can't %slink file '%s' to '%s': %s.\n",
|
|
|
|
hard ? "" : "sym", linkname, filename,
|
|
|
|
std::strerror( errno ) );
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
} break;
|
|
|
|
case tf_directory:
|
|
|
|
if( mkdir( filename, mode ) != 0 && errno != EEXIST )
|
|
|
|
{
|
|
|
|
show_file_error( filename, "Can't create directory", errno );
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case tf_chardev:
|
|
|
|
case tf_blockdev:
|
|
|
|
{
|
2025-02-17 21:10:53 +01:00
|
|
|
const unsigned dev =
|
|
|
|
makedev( parse_octal( header + devmajor_o, devmajor_l ),
|
|
|
|
parse_octal( header + devminor_o, devminor_l ) );
|
2025-02-17 21:04:20 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case tf_fifo:
|
|
|
|
if( mkfifo( filename, mode ) != 0 && errno != EEXIST )
|
|
|
|
{
|
|
|
|
show_file_error( filename, "Can't create FIFO file", errno );
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
if( verbosity >= 0 )
|
|
|
|
std::fprintf( stderr, "File type '%c' not supported for file '%s'.\n",
|
|
|
|
typeflag, filename );
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
|
2025-02-17 21:10:53 +01:00
|
|
|
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 );
|
2025-02-17 21:04:20 +01:00
|
|
|
if( !islink && chown( filename, uid, gid ) != 0 &&
|
|
|
|
errno != EPERM && errno != EINVAL )
|
|
|
|
{
|
|
|
|
show_file_error( filename, "Can't change file owner", errno );
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
const unsigned bufsize = 32 * header_size;
|
|
|
|
uint8_t buf[bufsize];
|
2025-02-17 21:11:12 +01:00
|
|
|
unsigned long long rest = extended.file_size();
|
|
|
|
const int rem = rest % header_size;
|
2025-02-17 21:04:20 +01:00
|
|
|
const int padding = rem ? header_size - rem : 0;
|
|
|
|
while( rest > 0 )
|
|
|
|
{
|
|
|
|
const int rsize = ( rest >= bufsize ) ? bufsize : rest + padding;
|
2025-02-17 21:10:01 +01:00
|
|
|
int rd;
|
|
|
|
const int ret = archive_read( infd, buf, rsize, &rd );
|
|
|
|
if( ret != 0 )
|
|
|
|
{
|
|
|
|
if( outfd >= 0 )
|
|
|
|
{
|
|
|
|
if( keep_damaged )
|
|
|
|
{ writeblock( outfd, buf, std::min( rest, (unsigned long long)rd ) );
|
|
|
|
close( outfd ); }
|
|
|
|
else { close( outfd ); std::remove( filename ); }
|
|
|
|
}
|
|
|
|
if( ret == 2 ) return 2; else return 0;
|
|
|
|
}
|
2025-02-17 21:04:20 +01:00
|
|
|
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; }
|
|
|
|
if( !islink )
|
|
|
|
{
|
|
|
|
struct utimbuf t;
|
|
|
|
t.actime = mtime;
|
|
|
|
t.modtime = mtime;
|
|
|
|
utime( filename, &t ); // ignore errors
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2025-02-17 21:10:53 +01:00
|
|
|
} // end namespace
|
2025-02-17 21:04:20 +01:00
|
|
|
|
|
|
|
|
|
|
|
// return true if dir is a parent directory of name
|
|
|
|
bool compare_prefix_dir( const char * const dir, const char * const name )
|
|
|
|
{
|
|
|
|
int len = 0;
|
|
|
|
while( dir[len] && dir[len] == name[len] ) ++len;
|
|
|
|
return ( !dir[len] && len > 0 && ( dir[len-1] == '/' || name[len] == '/' ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// compare two file names ignoring trailing slashes
|
|
|
|
bool compare_tslash( const char * const name1, const char * const name2 )
|
|
|
|
{
|
|
|
|
const char * p = name1;
|
|
|
|
const char * q = name2;
|
|
|
|
while( *p && *p == *q ) { ++p; ++q; }
|
|
|
|
while( *p == '/' ) ++p;
|
|
|
|
while( *q == '/' ) ++q;
|
|
|
|
return ( !*p && !*q );
|
|
|
|
}
|
|
|
|
|
2025-02-17 21:10:53 +01:00
|
|
|
namespace {
|
2025-02-17 21:04:20 +01:00
|
|
|
|
2025-02-17 21:10:53 +01:00
|
|
|
bool parse_records( const int infd, Extended & extended,
|
|
|
|
const Tar_header header, const bool permissive )
|
|
|
|
{
|
|
|
|
const unsigned long long edsize = parse_octal( header + size_o, size_l );
|
2025-02-17 21:10:01 +01:00
|
|
|
const unsigned long long bufsize = round_up( edsize );
|
|
|
|
if( bufsize == 0 || edsize == 0 || edsize >= 1ULL << 33 )
|
|
|
|
return false; // overflow or no extended data
|
|
|
|
char * const buf = new char[bufsize]; // extended records buffer
|
2025-02-17 21:10:53 +01:00
|
|
|
const bool ret = ( archive_read( infd, (uint8_t *)buf, bufsize ) == 0 &&
|
|
|
|
extended.parse( buf, edsize, permissive ) );
|
|
|
|
delete[] buf;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // end namespace
|
|
|
|
|
|
|
|
|
|
|
|
/* Returns the number of bytes really read.
|
|
|
|
If (returned value < size) and (errno == 0), means EOF was reached.
|
|
|
|
*/
|
|
|
|
int readblock( const int fd, uint8_t * const buf, const int size )
|
|
|
|
{
|
|
|
|
int sz = 0;
|
|
|
|
errno = 0;
|
|
|
|
while( sz < size )
|
|
|
|
{
|
|
|
|
const int n = read( fd, buf + sz, size - sz );
|
|
|
|
if( n > 0 ) sz += n;
|
|
|
|
else if( n == 0 ) break; // EOF
|
|
|
|
else if( errno != EINTR ) break;
|
|
|
|
errno = 0;
|
|
|
|
}
|
|
|
|
return sz;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Returns the number of bytes really written.
|
|
|
|
If (returned value < size), it is always an error.
|
|
|
|
*/
|
|
|
|
int writeblock( const int fd, const uint8_t * const buf, const int size )
|
|
|
|
{
|
|
|
|
int sz = 0;
|
|
|
|
errno = 0;
|
|
|
|
while( sz < size )
|
|
|
|
{
|
|
|
|
const int n = write( fd, buf + sz, size - sz );
|
|
|
|
if( n > 0 ) sz += n;
|
|
|
|
else if( n < 0 && errno != EINTR ) break;
|
|
|
|
errno = 0;
|
|
|
|
}
|
|
|
|
return sz;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
unsigned long long parse_octal( const uint8_t * const ptr, const int size )
|
|
|
|
{
|
|
|
|
unsigned long long result = 0;
|
|
|
|
int i = 0;
|
|
|
|
while( i < size && std::isspace( ptr[i] ) ) ++i;
|
|
|
|
for( ; i < size && ptr[i] >= '0' && ptr[i] <= '7'; ++i )
|
|
|
|
{ result <<= 3; result += ptr[i] - '0'; }
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-17 21:04:20 +01:00
|
|
|
int decode( const std::string & archive_name, const Arg_parser & parser,
|
2025-02-17 21:10:53 +01:00
|
|
|
const int filenames, const int num_workers, const int debug_level,
|
2025-02-17 21:12:33 +01:00
|
|
|
const Program_mode program_mode, const bool ignore_ids,
|
|
|
|
const bool keep_damaged, const bool missing_crc,
|
2025-02-17 21:10:53 +01:00
|
|
|
const bool permissive )
|
2025-02-17 21:04:20 +01:00
|
|
|
{
|
|
|
|
const int infd = archive_name.size() ?
|
|
|
|
open_instream( archive_name ) : STDIN_FILENO;
|
|
|
|
if( infd < 0 ) return 1;
|
|
|
|
|
2025-02-17 21:12:33 +01:00
|
|
|
// Execute -C options and mark filenames to be compared, extracted or listed.
|
2025-02-17 21:10:53 +01:00
|
|
|
// name_pending is of type char instead of bool to allow concurrent update.
|
|
|
|
std::vector< char > name_pending( parser.arguments(), false );
|
2025-02-17 21:04:20 +01:00
|
|
|
for( int i = 0; i < parser.arguments(); ++i )
|
|
|
|
{
|
|
|
|
const int code = parser.code( i );
|
2025-02-17 21:12:33 +01:00
|
|
|
if( code == 'C' && program_mode != m_list )
|
2025-02-17 21:04:20 +01:00
|
|
|
{
|
2025-02-17 21:10:01 +01:00
|
|
|
const char * const dir = parser.argument( i ).c_str();
|
|
|
|
if( chdir( dir ) != 0 )
|
|
|
|
{ show_file_error( dir, "Error changing working directory", errno );
|
2025-02-17 21:04:20 +01:00
|
|
|
return 1; }
|
|
|
|
}
|
2025-02-17 21:12:14 +01:00
|
|
|
if( !code && parser.argument( i ).size() ) name_pending[i] = true;
|
2025-02-17 21:04:20 +01:00
|
|
|
}
|
|
|
|
|
2025-02-17 21:12:14 +01:00
|
|
|
// multi-threaded --list is faster even with 1 thread and 1 file in archive
|
2025-02-17 21:12:33 +01:00
|
|
|
if( program_mode == m_list && num_workers > 0 )
|
2025-02-17 21:10:53 +01:00
|
|
|
{
|
2025-02-17 21:12:14 +01:00
|
|
|
const Lzip_index lzip_index( infd, true, false ); // only regular files
|
2025-02-17 21:10:53 +01:00
|
|
|
const long members = lzip_index.members();
|
2025-02-17 21:12:14 +01:00
|
|
|
if( lzip_index.retval() == 0 && members >= 2 ) // one file + eof
|
|
|
|
{
|
|
|
|
// show_file_error( archive_name.c_str(), "Is compressed seekable" );
|
|
|
|
return list_lz( parser, name_pending, lzip_index, filenames, debug_level,
|
|
|
|
infd, std::min( (long)num_workers, members ),
|
|
|
|
missing_crc, permissive );
|
|
|
|
}
|
2025-02-17 21:12:33 +01:00
|
|
|
if( lseek( infd, 0, SEEK_SET ) == 0 && lzip_index.retval() != 0 &&
|
|
|
|
lzip_index.file_size() > 3 * header_size )
|
|
|
|
archive_is_uncompressed_seekable = true; // unless compressed corrupt
|
2025-02-17 21:10:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
has_lz_ext = // global var for archive_read
|
2025-02-17 21:10:01 +01:00
|
|
|
( archive_name.size() > 3 &&
|
|
|
|
archive_name.compare( archive_name.size() - 3, 3, ".lz" ) == 0 ) ||
|
|
|
|
( archive_name.size() > 4 &&
|
|
|
|
archive_name.compare( archive_name.size() - 4, 4, ".tlz" ) == 0 );
|
2025-02-17 21:10:53 +01:00
|
|
|
Extended extended; // metadata from extended records
|
2025-02-17 21:04:20 +01:00
|
|
|
int retval = 0;
|
2025-02-17 21:10:53 +01:00
|
|
|
bool prev_extended = false; // prev header was extended
|
|
|
|
while( true ) // process one tar member per iteration
|
2025-02-17 21:04:20 +01:00
|
|
|
{
|
2025-02-17 21:10:53 +01:00
|
|
|
Tar_header header;
|
|
|
|
const int ret = archive_read( infd, header, header_size );
|
2025-02-17 21:10:01 +01:00
|
|
|
if( ret == 2 ) return 2;
|
2025-02-17 21:10:53 +01:00
|
|
|
if( ret != 0 || !verify_ustar_chksum( header ) )
|
2025-02-17 21:04:20 +01:00
|
|
|
{
|
2025-02-17 21:10:53 +01:00
|
|
|
if( ret == 0 && block_is_zero( header, header_size ) ) break; // EOF
|
2025-02-17 21:12:33 +01:00
|
|
|
if( skip_warn() && verbosity >= 2 )
|
2025-02-17 21:12:14 +01:00
|
|
|
std::fprintf( stderr, "ustar chksum = %07o\n", ustar_chksum( header ) );
|
2025-02-17 21:12:33 +01:00
|
|
|
set_error_status( 2 ); continue;
|
2025-02-17 21:04:20 +01:00
|
|
|
}
|
2025-02-17 21:10:53 +01:00
|
|
|
skip_warn( true ); // reset warning
|
2025-02-17 21:04:20 +01:00
|
|
|
|
2025-02-17 21:10:01 +01:00
|
|
|
const Typeflag typeflag = (Typeflag)header[typeflag_o];
|
2025-02-17 21:10:53 +01:00
|
|
|
if( typeflag == tf_global )
|
|
|
|
{
|
|
|
|
if( prev_extended )
|
|
|
|
{ show_error( "Format violation: global header after extended header." );
|
|
|
|
return 2; }
|
|
|
|
Extended dummy; // global headers are parsed and ignored
|
|
|
|
if( !parse_records( infd, dummy, header, true ) )
|
|
|
|
{ show_error( "Error in global extended records. Skipping to next header." );
|
2025-02-17 21:12:14 +01:00
|
|
|
set_error_status( 2 ); }
|
2025-02-17 21:10:53 +01:00
|
|
|
continue;
|
|
|
|
}
|
2025-02-17 21:10:01 +01:00
|
|
|
if( typeflag == tf_extended )
|
|
|
|
{
|
|
|
|
if( prev_extended && !permissive )
|
|
|
|
{ show_error( "Format violation: consecutive extended headers found."
|
2025-02-17 21:10:53 +01:00
|
|
|
/*" Use --permissive.", 0, true*/ ); return 2; }
|
|
|
|
if( !parse_records( infd, extended, header, permissive ) )
|
2025-02-17 21:10:01 +01:00
|
|
|
{ show_error( "Error in extended records. Skipping to next header." );
|
2025-02-17 21:12:14 +01:00
|
|
|
extended.reset(); set_error_status( 2 ); }
|
2025-02-17 21:11:12 +01:00
|
|
|
else if( !extended.crc_present() && missing_crc )
|
2025-02-17 21:10:01 +01:00
|
|
|
{ show_error( "Missing CRC in extended records.", 0, true ); return 2; }
|
|
|
|
prev_extended = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
prev_extended = false;
|
|
|
|
|
2025-02-17 21:12:49 +01:00
|
|
|
extended.fill_from_ustar( header ); // copy metadata from header
|
2025-02-17 21:04:20 +01:00
|
|
|
|
2025-02-17 21:12:49 +01:00
|
|
|
const bool skip = check_skip_filename( parser, name_pending,
|
|
|
|
extended.path().c_str(), filenames );
|
2025-02-17 21:12:33 +01:00
|
|
|
if( skip )
|
|
|
|
retval = skip_member( infd, extended );
|
|
|
|
else if( program_mode == m_list )
|
|
|
|
retval = list_member( infd, extended, header );
|
|
|
|
else if( program_mode == m_diff )
|
|
|
|
retval = compare_member( infd, extended, header, ignore_ids );
|
2025-02-17 21:04:20 +01:00
|
|
|
else
|
2025-02-17 21:10:01 +01:00
|
|
|
retval = extract_member( infd, extended, header, keep_damaged );
|
|
|
|
extended.reset();
|
|
|
|
if( retval )
|
|
|
|
{ show_error( "Error is not recoverable: exiting now." );
|
|
|
|
return retval; }
|
2025-02-17 21:04:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for( int i = 0; i < parser.arguments(); ++i )
|
2025-02-17 21:12:14 +01:00
|
|
|
if( !parser.code( i ) && parser.argument( i ).size() && name_pending[i] )
|
2025-02-17 21:04:20 +01:00
|
|
|
{
|
|
|
|
show_file_error( parser.argument( i ).c_str(), "Not found in archive." );
|
2025-02-17 21:12:33 +01:00
|
|
|
retval = 1;
|
2025-02-17 21:04:20 +01:00
|
|
|
}
|
2025-02-17 21:12:33 +01:00
|
|
|
return final_exit_status( retval, program_mode != m_diff );
|
2025-02-17 21:04:20 +01:00
|
|
|
}
|