summaryrefslogtreecommitdiffstats
path: root/extended.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2022-09-24 01:57:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2022-09-24 01:57:58 +0000
commitd35175c9e68a3ad252bfa22d266b8311df99a718 (patch)
tree0ed6c60ad70805dbda31e05d43ee1b967c478938 /extended.cc
parentReleasing debian version 0.22-4. (diff)
downloadtarlz-d35175c9e68a3ad252bfa22d266b8311df99a718.tar.xz
tarlz-d35175c9e68a3ad252bfa22d266b8311df99a718.zip
Merging upstream version 0.23.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'extended.cc')
-rw-r--r--extended.cc179
1 files changed, 141 insertions, 38 deletions
diff --git a/extended.cc b/extended.cc
index 721634a..f05d15f 100644
--- a/extended.cc
+++ b/extended.cc
@@ -18,6 +18,7 @@
#define _FILE_OFFSET_BITS 64
#include <cctype>
+#include <cerrno>
#include <cstdio>
#include <cstdlib>
@@ -29,21 +30,13 @@ const CRC32 crc32c( true );
namespace {
-unsigned decimal_digits( unsigned long long value )
- {
- unsigned digits = 1;
- while( value >= 10 ) { value /= 10; ++digits; }
- return digits;
- }
-
-
unsigned long long record_size( const unsigned keyword_size,
const unsigned long value_size )
{
- // size = ' ' + keyword + '=' + value + '\n'
+ /* length + ' ' + keyword + '=' + value + '\n'
+ minimize length; prefer "99<97_bytes>" to "100<97_bytes>" */
unsigned long long size = 1 + keyword_size + 1 + value_size + 1;
- const unsigned d1 = decimal_digits( size );
- size += decimal_digits( d1 + size );
+ size += decimal_digits( decimal_digits( size ) + size );
return size;
}
@@ -54,14 +47,14 @@ unsigned long long parse_decimal( const char * const ptr,
{
unsigned long long result = 0;
unsigned long long i = 0;
- while( i < size && std::isspace( ptr[i] ) ) ++i;
+ while( i < size && std::isspace( (unsigned char)ptr[i] ) ) ++i;
if( !std::isdigit( (unsigned char)ptr[i] ) )
{ if( tailp ) *tailp = ptr; return 0; }
for( ; i < size && std::isdigit( (unsigned char)ptr[i] ); ++i )
{
const unsigned long long prev = result;
result *= 10; result += ptr[i] - '0';
- if( result < prev || result > max_file_size ) // overflow
+ if( result < prev || result > LLONG_MAX ) // overflow
{ if( tailp ) *tailp = ptr; return 0; }
}
if( tailp ) *tailp = ptr + i;
@@ -92,21 +85,25 @@ unsigned char xdigit( const unsigned value )
}
void print_hex( char * const buf, int size, unsigned long long num )
- {
- while( --size >= 0 ) { buf[size] = xdigit( num & 0x0F ); num >>= 4; }
- }
+ { while( --size >= 0 ) { buf[size] = xdigit( num & 0x0F ); num >>= 4; } }
void print_decimal( char * const buf, int size, unsigned long long num )
- { while( --size >= 0 ) { buf[size] = '0' + ( num % 10 ); num /= 10; } }
-
+ { while( --size >= 0 ) { buf[size] = num % 10 + '0'; num /= 10; } }
-bool print_record( char * const buf, const unsigned long long size,
- const char * keyword, const std::string & value )
+unsigned long long print_size_keyword( char * const buf,
+ const unsigned long long size, const char * keyword )
{
// "size keyword=value\n"
unsigned long long pos = decimal_digits( size );
print_decimal( buf, pos, size ); buf[pos++] = ' ';
while( *keyword ) { buf[pos++] = *keyword; ++keyword; } buf[pos++] = '=';
+ return pos;
+ }
+
+bool print_record( char * const buf, const unsigned long long size,
+ const char * keyword, const std::string & value )
+ {
+ unsigned long long pos = print_size_keyword( buf, size, keyword );
std::memcpy( buf + pos, value.c_str(), value.size() );
pos += value.size(); buf[pos++] = '\n';
return pos == size;
@@ -115,18 +112,71 @@ bool print_record( char * const buf, const unsigned long long size,
bool print_record( char * const buf, const int size,
const char * keyword, const unsigned long long value )
{
- // "size keyword=value\n"
- int pos = decimal_digits( size );
- print_decimal( buf, pos, size ); buf[pos++] = ' ';
- while( *keyword ) { buf[pos++] = *keyword; ++keyword; } buf[pos++] = '=';
- const int vd = decimal_digits( value );
+ int pos = print_size_keyword( buf, size, keyword );
+ const int vd = decimal_digits( value );
print_decimal( buf + pos, vd, value ); pos += vd; buf[pos++] = '\n';
return pos == size;
}
+bool print_record( char * const buf, const int size,
+ const char * keyword, const Etime & value )
+ {
+ int pos = print_size_keyword( buf, size, keyword );
+ pos += value.print( buf + pos ); buf[pos++] = '\n';
+ return pos == size;
+ }
+
} // end namespace
+unsigned Etime::decimal_size() const
+ {
+ unsigned size = 1 + ( sec_ < 0 ); // first digit + negative sign
+ for( long long n = sec_; n >= 10 || n <= -10; n /= 10 ) ++size;
+ if( nsec_ > 0 && nsec_ <= 999999999 )
+ { size += 2; // decimal point + first fractional digit
+ for( int n = nsec_; n >= 10; n /= 10 ) ++size; }
+ return size;
+ }
+
+unsigned Etime::print( char * const buf ) const
+ {
+ int len = 0;
+ if( nsec_ > 0 && nsec_ <= 999999999 )
+ { for( int n = nsec_; n > 0; n /= 10 ) buf[len++] = n % 10 + '0';
+ buf[len++] = '.'; }
+ long long n = sec_;
+ do { long long on = n; n /= 10; buf[len++] = llabs( on - 10 * n ) + '0'; }
+ while( n != 0 );
+ if( sec_ < 0 ) buf[len++] = '-';
+ for( int i = 0; i < len / 2; ++i ) std::swap( buf[i], buf[len-i-1] );
+ return len;
+ }
+
+bool Etime::parse( const char * const ptr, const char ** const tailp,
+ const long long size )
+ {
+ char * tail;
+ errno = 0;
+ long long s = strtoll( ptr, &tail, 10 );
+ if( tail == ptr || errno ||
+ ( *tail != 0 && *tail != '\n' && *tail != '.' ) ) return false;
+ int ns = 0;
+ if( *tail == '.' ) // parse nanoseconds and any extra digits
+ {
+ ++tail;
+ if( tail - ptr >= size || !std::isdigit( (unsigned char)*tail ) )
+ return false;
+ for( int factor = 100000000;
+ tail - ptr < size && std::isdigit( (unsigned char)*tail );
+ ++tail, factor /= 10 )
+ ns += factor * ( *tail - '0' );
+ }
+ sec_ = s; nsec_ = ns; if( tailp ) *tailp = tail;
+ return true;
+ }
+
+
std::vector< std::string > Extended::unknown_keywords;
const std::string Extended::crc_record( "22 GNU.crc32=00000000\n" );
@@ -136,7 +186,14 @@ void Extended::calculate_sizes() const
path_recsize_ = path_.size() ? record_size( 4, path_.size() ) : 0;
file_size_recsize_ =
( file_size_ > 0 ) ? record_size( 4, decimal_digits( file_size_ ) ) : 0;
+ uid_recsize_ = ( uid_ >= 0 ) ? record_size( 3, decimal_digits( uid_ ) ) : 0;
+ gid_recsize_ = ( gid_ >= 0 ) ? record_size( 3, decimal_digits( gid_ ) ) : 0;
+ atime_recsize_ =
+ atime_.out_of_ustar_range() ? record_size( 5, atime_.decimal_size() ) : 0;
+ mtime_recsize_ =
+ mtime_.out_of_ustar_range() ? record_size( 5, mtime_.decimal_size() ) : 0;
edsize_ = linkpath_recsize_ + path_recsize_ + file_size_recsize_ +
+ uid_recsize_ + gid_recsize_ + atime_recsize_ + mtime_recsize_ +
crc_record.size();
padded_edsize_ = round_up( edsize_ );
full_size_ = header_size + padded_edsize_;
@@ -153,20 +210,19 @@ void Extended::unknown_keyword( const char * const buf,
for( unsigned i = 0; i < unknown_keywords.size(); ++i )
if( keyword == unknown_keywords[i] ) return;
unknown_keywords.push_back( keyword );
- std::string msg( "Ignoring unknown extended header keyword '" );
- msg += keyword; msg += '\'';
- show_error( msg.c_str() );
+ print_error( 0, "Ignoring unknown extended header keyword '%s'",
+ keyword.c_str() );
}
-// Return the size of the extended block, or -1 if error.
+// Return the size of the extended block, -1 if error, -2 if out of memory.
long long Extended::format_block( Resizable_buffer & rbuf ) const
{
if( empty() ) return 0; // no extended data
const unsigned long long bufsize = full_size(); // recalculate sizes
if( edsize_ <= 0 ) return 0; // no extended data
if( edsize_ >= 1LL << 33 ) return -1; // too much extended data
- if( !rbuf.resize( bufsize ) ) return -1; // extended block buffer
+ if( !rbuf.resize( bufsize ) ) return -2; // extended block buffer
uint8_t * const header = rbuf.u8(); // extended header
char * const buf = rbuf() + header_size; // extended records
init_tar_header( header );
@@ -185,13 +241,27 @@ long long Extended::format_block( Resizable_buffer & rbuf ) const
!print_record( buf + pos, file_size_recsize_, "size", file_size_ ) )
return -1;
pos += file_size_recsize_;
+ if( uid_recsize_ && !print_record( buf + pos, uid_recsize_, "uid", uid_ ) )
+ return -1;
+ pos += uid_recsize_;
+ if( gid_recsize_ && !print_record( buf + pos, gid_recsize_, "gid", gid_ ) )
+ return -1;
+ pos += gid_recsize_;
+ if( atime_recsize_ &&
+ !print_record( buf + pos, atime_recsize_, "atime", atime_ ) )
+ return -1;
+ pos += atime_recsize_;
+ if( mtime_recsize_ &&
+ !print_record( buf + pos, mtime_recsize_, "mtime", mtime_ ) )
+ return -1;
+ pos += mtime_recsize_;
const unsigned crc_size = Extended::crc_record.size();
std::memcpy( buf + pos, Extended::crc_record.c_str(), crc_size );
pos += crc_size;
if( pos != edsize_ ) return -1;
print_hex( buf + edsize_ - 9, 8,
crc32c.windowed_crc( (const uint8_t *)buf, edsize_ - 9, edsize_ ) );
- if( padded_edsize_ > edsize_ ) // wipe padding
+ if( padded_edsize_ > edsize_ ) // set padding to zero
std::memset( buf + edsize_, 0, padded_edsize_ - edsize_ );
crc_present_ = true;
return bufsize;
@@ -219,7 +289,7 @@ bool Extended::parse( const char * const buf, const unsigned long long edsize,
while( len > 1 && tail[5+len-1] == '/' ) --len; // trailing '/'
path_.assign( tail + 5, len );
// this also truncates path_ at the first embedded null character
- path_.assign( remove_leading_dotslash( path_.c_str() ) );
+ path_.assign( remove_leading_dotslash( path_.c_str(), &removed_prefix ) );
}
else if( rest > 9 && std::memcmp( tail, "linkpath=", 9 ) == 0 )
{
@@ -233,8 +303,34 @@ bool Extended::parse( const char * const buf, const unsigned long long edsize,
if( file_size_ != 0 && !permissive ) return false;
file_size_ = parse_decimal( tail + 5, &tail, rest - 5 );
// parse error or size fits in ustar header
- if( file_size_ < 1LL << 33 || tail != buf + ( pos + rsize - 1 ) )
- return false;
+ if( file_size_ < 1LL << 33 || file_size_ > max_file_size ||
+ tail != buf + ( pos + rsize - 1 ) ) return false;
+ }
+ else if( rest > 4 && std::memcmp( tail, "uid=", 4 ) == 0 )
+ {
+ if( uid_ >= 0 && !permissive ) return false;
+ uid_ = parse_decimal( tail + 4, &tail, rest - 4 );
+ // parse error or uid fits in ustar header
+ if( uid_ < 1 << 21 || tail != buf + ( pos + rsize - 1 ) ) return false;
+ }
+ else if( rest > 4 && std::memcmp( tail, "gid=", 4 ) == 0 )
+ {
+ if( gid_ >= 0 && !permissive ) return false;
+ gid_ = parse_decimal( tail + 4, &tail, rest - 4 );
+ // parse error or gid fits in ustar header
+ if( gid_ < 1 << 21 || tail != buf + ( pos + rsize - 1 ) ) return false;
+ }
+ else if( rest > 6 && std::memcmp( tail, "atime=", 6 ) == 0 )
+ {
+ if( atime_.isvalid() && !permissive ) return false;
+ if( !atime_.parse( tail + 6, &tail, rest - 6 ) || // parse error
+ tail != buf + ( pos + rsize - 1 ) ) return false;
+ }
+ else if( rest > 6 && std::memcmp( tail, "mtime=", 6 ) == 0 )
+ {
+ if( mtime_.isvalid() && !permissive ) return false;
+ if( !mtime_.parse( tail + 6, &tail, rest - 6 ) || // parse error
+ tail != buf + ( pos + rsize - 1 ) ) return false;
}
else if( rest > 10 && std::memcmp( tail, "GNU.crc32=", 10 ) == 0 )
{
@@ -259,7 +355,8 @@ bool Extended::parse( const char * const buf, const unsigned long long edsize,
}
-// if needed, copy linkpath, path and file_size from ustar header
+/* If not already initialized, copy linkpath, path, file_size, uid, gid,
+ atime, and mtime from ustar header. */
void Extended::fill_from_ustar( const Tar_header header )
{
if( linkpath_.empty() ) // copy linkpath from ustar header
@@ -275,7 +372,7 @@ void Extended::fill_from_ustar( const Tar_header header )
}
if( path_.empty() ) // copy path from ustar header
- {
+ { // the entire path may be in prefix
char stored_name[prefix_l+1+name_l+1];
int len = 0;
while( len < prefix_l && header[prefix_o+len] )
@@ -285,13 +382,19 @@ void Extended::fill_from_ustar( const Tar_header header )
{ stored_name[len] = header[name_o+i]; ++len; }
while( len > 0 && stored_name[len-1] == '/' ) --len; // trailing '/'
stored_name[len] = 0;
- path( remove_leading_dotslash( stored_name ) );
+ path( remove_leading_dotslash( stored_name, &removed_prefix ) );
}
const Typeflag typeflag = (Typeflag)header[typeflag_o];
if( file_size_ == 0 && // copy file_size from ustar header
( typeflag == tf_regular || typeflag == tf_hiperf ) )
file_size( parse_octal( header + size_o, size_l ) );
+ if( uid_ < 0 ) uid_ = parse_octal( header + uid_o, uid_l );
+ if( gid_ < 0 ) gid_ = parse_octal( header + gid_o, gid_l );
+ if( !atime_.isvalid() )
+ atime_.set( parse_octal( header + mtime_o, mtime_l ) ); // 33 bits
+ if( !mtime_.isvalid() )
+ mtime_.set( parse_octal( header + mtime_o, mtime_l ) ); // 33 bits
}
@@ -301,7 +404,7 @@ void Extended::fill_from_ustar( const Tar_header header )
long long Extended::get_file_size_and_reset( const Tar_header header )
{
const long long tmp = file_size_;
- file_size( 0 );
+ file_size( 0 ); // reset full_size_
const Typeflag typeflag = (Typeflag)header[typeflag_o];
if( typeflag == tf_regular || typeflag == tf_hiperf )
{