1
0
Fork 0

Merging upstream version 0.14.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-02-17 21:13:25 +01:00
parent 29b7e49778
commit fc346f9fb9
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
21 changed files with 795 additions and 265 deletions

View file

@ -1,3 +1,13 @@
2019-03-12 Antonio Diaz Diaz <antonio@gnu.org>
* Version 0.14 released.
* Added new option '--exclude'.
* Added new option '-h, --dereference'.
* Short option name '-h' no longer means '--help'.
* create.cc: Implemented '-A, --concatenate', '-r, --append' to
uncompressed archives and to standard output.
* main.cc: Ported option '--out-slots' from plzip.
2019-02-27 Antonio Diaz Diaz <antonio@gnu.org> 2019-02-27 Antonio Diaz Diaz <antonio@gnu.org>
* Version 0.13 released. * Version 0.13 released.

View file

@ -8,8 +8,8 @@ LIBS = -llz -lpthread
SHELL = /bin/sh SHELL = /bin/sh
CAN_RUN_INSTALLINFO = $(SHELL) -c "install-info --version" > /dev/null 2>&1 CAN_RUN_INSTALLINFO = $(SHELL) -c "install-info --version" > /dev/null 2>&1
objs = arg_parser.o lzip_index.o create.o create_lz.o extended.o extract.o \ objs = arg_parser.o lzip_index.o create.o create_lz.o exclude.o extended.o \
list_lz.o main.o extract.o list_lz.o main.o
.PHONY : all install install-bin install-info install-man \ .PHONY : all install install-bin install-info install-man \
@ -33,6 +33,7 @@ $(objs) : Makefile
arg_parser.o : arg_parser.h arg_parser.o : arg_parser.h
create.o : arg_parser.h tarlz.h create.o : arg_parser.h tarlz.h
create_lz.o : arg_parser.h tarlz.h create_lz.o : arg_parser.h tarlz.h
exclude.o : tarlz.h
extended.o : tarlz.h extended.o : tarlz.h
extract.o : arg_parser.h lzip_index.h tarlz.h extract.o : arg_parser.h lzip_index.h tarlz.h
list_lz.o : arg_parser.h lzip_index.h tarlz.h list_lz.o : arg_parser.h lzip_index.h tarlz.h
@ -131,6 +132,7 @@ dist : doc
$(DISTNAME)/testsuite/rbaz \ $(DISTNAME)/testsuite/rbaz \
$(DISTNAME)/testsuite/test3.tar \ $(DISTNAME)/testsuite/test3.tar \
$(DISTNAME)/testsuite/test3_bad[1-5].tar \ $(DISTNAME)/testsuite/test3_bad[1-5].tar \
$(DISTNAME)/testsuite/eof.tar \
$(DISTNAME)/testsuite/test.txt.lz \ $(DISTNAME)/testsuite/test.txt.lz \
$(DISTNAME)/testsuite/test.txt.tar.lz \ $(DISTNAME)/testsuite/test.txt.tar.lz \
$(DISTNAME)/testsuite/test_bad[12].txt.tar.lz \ $(DISTNAME)/testsuite/test_bad[12].txt.tar.lz \

22
NEWS
View file

@ -1,11 +1,17 @@
Changes in version 0.13: Changes in version 0.14:
Skipping of unreadable files during multi-threaded archive creation with The new option '--exclude', which excludes files matching a shell pattern,
per-file compression has been fixed. Tarlz did produce empty lzip members, has been added.
and sometines left the last files out of the archive.
Multi-threaded listing of tar.lz archives containing empty lzip members has The new option '-h, --dereference', which instructs tarlz to follow symbolic
been fixed. It listed members out of order and sometimes hung. links during archive creation, appending or comparison, has been added.
(The short option name '-h' no longer means '--help').
When creating an archive, negative modification times are now stored as cero Concatenation and appending to uncompressed archives and to standard output
(1970-01-01 00:00:00 UTC). Negative times are not portable. have been implemented.
The new option '--out-slots', setting the number of output packets buffered
per worker thread during multi-threaded creation and appending to compressed
archives, has been added. Increasing the number of packets may increase
compression speed if the files being archived are larger than 64 MiB
compressed, but requires more memory.

3
README
View file

@ -11,7 +11,8 @@ it like any other tar.lz archive. Tarlz can append files to the end of such
compressed archives. compressed archives.
Tarlz can create tar archives with five levels of compression granularity; Tarlz can create tar archives with five levels of compression granularity;
per file, per block (default), per directory, appendable solid, and solid. per file (--no-solid), per block (--bsolid, default), per directory
(--dsolid), appendable solid (--asolid), and solid (--solid).
Of course, compressing each file (or each directory) individually can't Of course, compressing each file (or each directory) individually can't
achieve a compression ratio as high as compressing solidly the whole tar achieve a compression ratio as high as compressing solidly the whole tar

2
configure vendored
View file

@ -6,7 +6,7 @@
# to copy, distribute and modify it. # to copy, distribute and modify it.
pkgname=tarlz pkgname=tarlz
pkgversion=0.13 pkgversion=0.14
progname=tarlz progname=tarlz
srctrigger=doc/${pkgname}.texi srctrigger=doc/${pkgname}.texi

247
create.cc
View file

@ -54,7 +54,7 @@ namespace {
LZ_Encoder * encoder = 0; // local vars needed by add_member LZ_Encoder * encoder = 0; // local vars needed by add_member
const char * archive_namep = 0; const char * archive_namep = 0;
unsigned long long partial_data_size = 0; // size of current block unsigned long long partial_data_size = 0; // size of current block
Resizable_buffer grbuf( 2 * header_size ); // extended header + data Resizable_buffer grbuf; // extended header + data
int goutfd = -1; int goutfd = -1;
int error_status = 0; int error_status = 0;
@ -123,56 +123,108 @@ bool copy_file( const int infd, const int outfd, const long long max_size = -1 )
} }
/* Check archive type. If success, leave fd file pos at 0. /* Check archive type. Return position of EOF blocks or -1 if failure.
If remove_eof, leave fd file pos at beginning of the EOF blocks. */ If remove_eof, leave fd file pos at beginning of the EOF blocks.
bool check_appendable( const int fd, const bool remove_eof ) Else, leave fd file pos at 0. */
long long check_appendable( const int fd, const bool remove_eof )
{ {
struct stat st; struct stat st; // fd must be regular
if( fstat( fd, &st ) != 0 || !S_ISREG( st.st_mode ) ) return false; if( fstat( fd, &st ) != 0 || !S_ISREG( st.st_mode ) ) return -1;
if( lseek( fd, 0, SEEK_SET ) != 0 ) return false; if( lseek( fd, 0, SEEK_SET ) != 0 ) return -1;
enum { bufsize = header_size + ( header_size / 8 ) }; enum { bufsize = header_size + ( header_size / 8 ) };
uint8_t buf[bufsize]; uint8_t buf[bufsize];
int rd = readblock( fd, buf, bufsize ); const int rd = readblock( fd, buf, bufsize );
if( rd == 0 && errno == 0 ) return true; // append to empty archive if( rd == 0 && errno == 0 ) return 0; // append to empty archive
if( rd < min_member_size || ( rd != bufsize && errno ) ) return false; if( rd < min_member_size || ( rd != bufsize && errno ) ) return -1;
const Lzip_header * const p = (const Lzip_header *)buf; // shut up gcc const Lzip_header * const p = (const Lzip_header *)buf; // shut up gcc
if( !p->verify_magic() || !p->verify_version() ) return false; if( !p->verify_magic() || !p->verify_version() ) return -1;
LZ_Decoder * decoder = LZ_decompress_open(); // decompress first header LZ_Decoder * decoder = LZ_decompress_open(); // decompress first header
if( !decoder || LZ_decompress_errno( decoder ) != LZ_ok || if( !decoder || LZ_decompress_errno( decoder ) != LZ_ok ||
LZ_decompress_write( decoder, buf, rd ) != rd || LZ_decompress_write( decoder, buf, rd ) != rd ||
( rd = LZ_decompress_read( decoder, buf, header_size ) ) != header_size ) LZ_decompress_read( decoder, buf, header_size ) != header_size )
{ LZ_decompress_close( decoder ); return false; } { LZ_decompress_close( decoder ); return -1; }
LZ_decompress_close( decoder ); LZ_decompress_close( decoder );
const bool maybe_eof = ( buf[0] == 0 ); const bool maybe_eof = block_is_zero( buf, header_size );
if( !verify_ustar_chksum( buf ) && !maybe_eof ) return false; if( !verify_ustar_chksum( buf ) && !maybe_eof ) return -1;
const long long end = lseek( fd, 0, SEEK_END ); const long long end = lseek( fd, 0, SEEK_END );
if( end < min_member_size ) return false; if( end < min_member_size ) return -1;
Lzip_trailer trailer; Lzip_trailer trailer;
if( seek_read( fd, trailer.data, Lzip_trailer::size, if( seek_read( fd, trailer.data, Lzip_trailer::size,
end - Lzip_trailer::size ) != Lzip_trailer::size ) end - Lzip_trailer::size ) != Lzip_trailer::size ) return -1;
return false;
const long long member_size = trailer.member_size(); const long long member_size = trailer.member_size();
if( member_size < min_member_size || member_size > end || if( member_size < min_member_size || member_size > end ||
( maybe_eof && member_size != end ) ) return false; ( maybe_eof && member_size != end ) ) return -1;
Lzip_header header; Lzip_header header;
if( seek_read( fd, header.data, Lzip_header::size, if( seek_read( fd, header.data, Lzip_header::size,
end - member_size ) != Lzip_header::size ) return false; end - member_size ) != Lzip_header::size ) return -1;
if( !header.verify_magic() || !header.verify_version() || if( !header.verify_magic() || !header.verify_version() ||
!isvalid_ds( header.dictionary_size() ) ) return false; !isvalid_ds( header.dictionary_size() ) ) return -1;
const unsigned long long data_size = trailer.data_size(); const unsigned long long data_size = trailer.data_size();
if( data_size < header_size || data_size > 32256 ) return false; if( data_size < header_size || data_size > 32256 ) return -1;
const unsigned data_crc = trailer.data_crc(); const unsigned data_crc = trailer.data_crc();
const CRC32 crc32; const CRC32 crc32;
uint32_t crc = 0xFFFFFFFFU; uint32_t crc = 0xFFFFFFFFU;
for( unsigned i = 0; i < data_size; ++i ) crc32.update_byte( crc, 0 ); for( unsigned i = 0; i < data_size; ++i ) crc32.update_byte( crc, 0 );
crc ^= 0xFFFFFFFFU; crc ^= 0xFFFFFFFFU;
if( crc != data_crc ) return false; if( crc != data_crc ) return -1;
const long long pos = remove_eof ? end - member_size : 0; const long long pos = remove_eof ? end - member_size : 0;
return ( lseek( fd, pos, SEEK_SET ) == pos ); if( lseek( fd, pos, SEEK_SET ) != pos ) return -1;
return end - member_size;
}
/* Skip all tar headers. Return position of EOF blocks or -1 if failure.
If remove_eof, leave fd file pos at beginning of the EOF blocks.
Else, leave fd file pos at 0. */
long long check_uncompressed_appendable( const int fd, const bool remove_eof )
{
struct stat st; // fd must be regular
if( fstat( fd, &st ) != 0 || !S_ISREG( st.st_mode ) ) return -1;
if( lseek( fd, 0, SEEK_SET ) != 0 ) return -1;
if( st.st_size == 0 ) return 0; // append to empty archive
long long eof_pos = 0;
Extended extended; // metadata from extended records
Resizable_buffer rbuf; // extended records buffer
bool prev_extended = false; // prev header was extended
while( true ) // process one tar member per iteration
{
Tar_header header;
const int rd = readblock( fd, header, header_size );
if( rd == 0 && errno == 0 ) break; // missing EOF blocks
if( rd != header_size ) return -1;
if( !verify_ustar_chksum( header ) ) // maybe EOF
{ if( block_is_zero( header, header_size ) ) break; else return -1; }
const Typeflag typeflag = (Typeflag)header[typeflag_o];
if( typeflag == tf_extended || typeflag == tf_global )
{
if( prev_extended ) return -1;
const unsigned long long edsize = parse_octal( header + size_o, size_l );
const unsigned long long bufsize = round_up( edsize );
if( edsize == 0 || edsize >= 1ULL << 33 || bufsize >= INT_MAX )
return -1; // overflow or no extended data
if( !rbuf.resize( bufsize ) ) return -1;
if( readblock( fd, (uint8_t *)rbuf(), bufsize ) != (int)bufsize )
return -1;
if( typeflag == tf_extended )
{ if( !extended.parse( rbuf(), edsize, false ) ) return -1;
prev_extended = true; }
continue;
}
prev_extended = false;
eof_pos = lseek( fd, round_up( extended.get_file_size_and_reset( header ) ),
SEEK_CUR );
if( eof_pos <= 0 ) return -1;
}
if( prev_extended ) return -1;
const long long pos = remove_eof ? eof_pos : 0;
if( lseek( fd, pos, SEEK_SET ) != pos ) return -1;
return eof_pos;
} }
@ -251,6 +303,7 @@ bool store_name( const char * const filename, Extended & extended,
int add_member( const char * const filename, const struct stat *, int add_member( const char * const filename, const struct stat *,
const int flag, struct FTW * ) const int flag, struct FTW * )
{ {
if( Exclude::excluded( filename ) ) return 0; // skip excluded
unsigned long long file_size = 0; unsigned long long file_size = 0;
Extended extended; // metadata for extended records Extended extended; // metadata for extended records
Tar_header header; Tar_header header;
@ -310,6 +363,26 @@ bool writeblock_wrapper( const int outfd, const uint8_t * const buffer,
} }
// write End-Of-Archive records
bool write_eof_records( const int outfd, const bool compressed )
{
if( compressed )
{
enum { eof_member_size = 44 };
const uint8_t eof_member[eof_member_size] = {
0x4C, 0x5A, 0x49, 0x50, 0x01, 0x0C, 0x00, 0x00, 0x6F, 0xFD, 0xFF, 0xFF,
0xA3, 0xB7, 0x80, 0x0C, 0x82, 0xDB, 0xFF, 0xFF, 0x9F, 0xF0, 0x00, 0x00,
0x2E, 0xAF, 0xB5, 0xEF, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
return writeblock_wrapper( outfd, eof_member, eof_member_size );
}
enum { bufsize = 2 * header_size };
uint8_t buf[bufsize];
std::memset( buf, 0, bufsize );
return writeblock_wrapper( outfd, buf, bufsize );
}
/* Removes any amount of leading "./" and '/' strings from filename. /* Removes any amount of leading "./" and '/' strings from filename.
Optionally also removes prefixes containing a ".." component. */ Optionally also removes prefixes containing a ".." component. */
const char * remove_leading_dotslash( const char * const filename, const char * remove_leading_dotslash( const char * const filename,
@ -348,7 +421,7 @@ bool fill_headers( const char * const filename, Extended & extended,
const int flag ) const int flag )
{ {
struct stat st; struct stat st;
if( lstat( filename, &st ) != 0 ) if( hstat( filename, &st ) != 0 )
{ show_file_error( filename, "Can't stat input file", errno ); { show_file_error( filename, "Can't stat input file", errno );
set_error_status( 1 ); return false; } set_error_status( 1 ); return false; }
if( file_is_the_archive( st ) ) if( file_is_the_archive( st ) )
@ -492,52 +565,95 @@ bool verify_ustar_chksum( const uint8_t * const header )
ustar_chksum( header ) == parse_octal( header + chksum_o, chksum_l ) ); } ustar_chksum( header ) == parse_octal( header + chksum_o, chksum_l ) ); }
int concatenate( const std::string & archive_name, const Arg_parser & parser, bool has_lz_ext( const std::string & name )
{
return ( name.size() > 3 &&
name.compare( name.size() - 3, 3, ".lz" ) == 0 ) ||
( name.size() > 4 &&
name.compare( name.size() - 4, 4, ".tlz" ) == 0 );
}
int concatenate( std::string archive_name, const Arg_parser & parser,
const int filenames ) const int filenames )
{ {
if( !filenames ) if( !filenames )
{ if( verbosity >= 1 ) show_error( "Nothing to concatenate." ); return 0; } { if( verbosity >= 1 ) show_error( "Nothing to concatenate." ); return 0; }
if( archive_name.empty() ) const bool to_stdout = archive_name.empty();
{ show_error( "'--concatenate' is incompatible with '-f -'.", 0, true ); const int outfd =
return 1; } to_stdout ? STDOUT_FILENO : open_outstream( archive_name, false );
const int outfd = open_outstream( archive_name, false );
if( outfd < 0 ) return 1; if( outfd < 0 ) return 1;
if( !file_is_the_archive.init( outfd ) ) if( to_stdout ) archive_name = "(stdout)";
else if( !file_is_the_archive.init( outfd ) )
{ show_file_error( archive_name.c_str(), "Can't stat", errno ); return 1; } { show_file_error( archive_name.c_str(), "Can't stat", errno ); return 1; }
int compressed; // tri-state bool
if( to_stdout ) compressed = -1; // unknown
else
{
compressed = has_lz_ext( archive_name ); // default value
long long pos = check_appendable( outfd, true );
if( pos > 0 ) compressed = true;
else if( pos < 0 )
{
pos = check_uncompressed_appendable( outfd, true );
if( pos > 0 ) compressed = false;
else if( pos < 0 )
{ show_file_error( archive_name.c_str(), compressed ?
"This does not look like an appendable tar.lz archive." :
"This does not look like an appendable tar archive." );
return 2; }
}
}
int retval = 0; int retval = 0;
bool eof_pending = false;
for( int i = 0; i < parser.arguments(); ++i ) // copy archives for( int i = 0; i < parser.arguments(); ++i ) // copy archives
{ {
if( parser.code( i ) ) continue; // skip options if( parser.code( i ) ) continue; // skip options
if( parser.argument( i ).empty() ) continue; // skip empty names if( parser.argument( i ).empty() ) continue; // skip empty names
const char * const filename = parser.argument( i ).c_str(); const char * const filename = parser.argument( i ).c_str();
if( Exclude::excluded( filename ) ) continue; // skip excluded
const int infd = open_instream( filename ); const int infd = open_instream( filename );
if( infd < 0 ) { retval = 1; break; } if( infd < 0 ) { retval = 1; break; }
if( !check_appendable( infd, false ) )
{ show_file_error( filename, "Not an appendable tar.lz archive." );
close( infd ); retval = 2; break; }
struct stat st; struct stat st;
if( fstat( infd, &st ) == 0 && file_is_the_archive( st ) ) if( !to_stdout && fstat( infd, &st ) == 0 && file_is_the_archive( st ) )
{ show_file_error( filename, "File is the archive; not concatenated." ); { show_file_error( filename, "File is the archive; not concatenated." );
close( infd ); continue; } close( infd ); continue; }
if( !check_appendable( outfd, true ) ) long long size;
{ show_error( "This does not look like an appendable tar.lz archive." ); if( compressed < 0 ) // not initialized yet
{
if( ( size = check_appendable( infd, false ) ) > 0 ) compressed = true;
else if( ( size = check_uncompressed_appendable( infd, false ) ) > 0 )
compressed = false;
else { size = -1 ; compressed = has_lz_ext( filename ); }
}
else size = compressed ? check_appendable( infd, false ) :
check_uncompressed_appendable( infd, false );
if( size < 0 )
{ show_file_error( filename, compressed ?
"Not an appendable tar.lz archive." :
"Not an appendable tar archive." );
close( infd ); retval = 2; break; } close( infd ); retval = 2; break; }
if( !copy_file( infd, outfd ) || close( infd ) != 0 ) if( !copy_file( infd, outfd, size ) || close( infd ) != 0 )
{ show_file_error( filename, "Error copying archive", errno ); { show_file_error( filename, "Error copying archive", errno );
retval = 1; break; } eof_pending = false; retval = 1; break; }
eof_pending = true;
if( verbosity >= 1 ) std::fprintf( stderr, "%s\n", filename ); if( verbosity >= 1 ) std::fprintf( stderr, "%s\n", filename );
} }
if( eof_pending && !write_eof_records( outfd, compressed ) && !retval )
retval = 1;
if( close( outfd ) != 0 && !retval ) if( close( outfd ) != 0 && !retval )
{ show_error( "Error closing archive", errno ); retval = 1; } { show_file_error( archive_name.c_str(), "Error closing archive", errno );
retval = 1; }
return retval; return retval;
} }
int encode( const std::string & archive_name, const Arg_parser & parser, int encode( const std::string & archive_name, const Arg_parser & parser,
const int filenames, const int level, const int num_workers, const int filenames, const int level, const int num_workers,
const int debug_level, const bool append ) const int out_slots, const int debug_level, const bool append,
const bool dereference )
{ {
struct Lzma_options struct Lzma_options
{ {
@ -558,28 +674,32 @@ int encode( const std::string & archive_name, const Arg_parser & parser,
{ 1 << 25, 273 } }; // -9 { 1 << 25, 273 } }; // -9
const bool compressed = ( level >= 0 && level <= 9 ); const bool compressed = ( level >= 0 && level <= 9 );
if( !append ) if( archive_name.size() && !compressed && has_lz_ext( archive_name ) )
{ show_file_error( archive_name.c_str(),
"Uncompressed mode incompatible with .lz extension." ); return 2; }
if( !filenames )
{ {
if( !filenames ) if( !append && archive_name.size() ) // create archive
{ show_error( "Cowardly refusing to create an empty archive.", 0, true ); { show_error( "Cowardly refusing to create an empty archive.", 0, true );
return 1; } return 1; }
if( archive_name.empty() ) goutfd = STDOUT_FILENO; else // create/append to stdout or append to archive
else if( ( goutfd = open_outstream( archive_name ) ) < 0 ) return 1;
}
else
{
if( !filenames )
{ if( verbosity >= 1 ) show_error( "Nothing to append." ); return 0; } { if( verbosity >= 1 ) show_error( "Nothing to append." ); return 0; }
if( archive_name.empty() ) }
{ show_error( "'--append' is incompatible with '-f -'.", 0, true );
return 1; } if( archive_name.empty() ) // create/append to stdout
if( !compressed ) goutfd = STDOUT_FILENO;
{ show_error( "'--append' is incompatible with '--uncompressed'.", 0, true ); else if( !append ) // create archive
return 1; } { if( ( goutfd = open_outstream( archive_name ) ) < 0 ) return 1; }
else // append to archive
{
if( ( goutfd = open_outstream( archive_name, false ) ) < 0 ) return 1; if( ( goutfd = open_outstream( archive_name, false ) ) < 0 ) return 1;
if( !check_appendable( goutfd, true ) ) if( compressed && check_appendable( goutfd, true ) < 0 )
{ show_error( "This does not look like an appendable tar.lz archive." ); { show_file_error( archive_name.c_str(),
return 2; } "This does not look like an appendable tar.lz archive." ); return 2; }
if( !compressed && check_uncompressed_appendable( goutfd, true ) < 0 )
{ show_file_error( archive_name.c_str(),
"This does not look like an appendable tar archive." ); return 2; }
} }
archive_namep = archive_name.size() ? archive_name.c_str() : "(stdout)"; archive_namep = archive_name.size() ? archive_name.c_str() : "(stdout)";
@ -602,7 +722,7 @@ int encode( const std::string & archive_name, const Arg_parser & parser,
// show_file_error( archive_namep, "Multi-threaded --create" ); // show_file_error( archive_namep, "Multi-threaded --create" );
return encode_lz( parser, dictionary_size, return encode_lz( parser, dictionary_size,
option_mapping[level].match_len_limit, num_workers, option_mapping[level].match_len_limit, num_workers,
goutfd, debug_level ); goutfd, out_slots, debug_level, dereference );
} }
encoder = LZ_compress_open( dictionary_size, encoder = LZ_compress_open( dictionary_size,
option_mapping[level].match_len_limit, LLONG_MAX ); option_mapping[level].match_len_limit, LLONG_MAX );
@ -632,14 +752,16 @@ int encode( const std::string & archive_name, const Arg_parser & parser,
while( len > 1 && arg[len-1] == '/' ) --len; while( len > 1 && arg[len-1] == '/' ) --len;
if( len < arg.size() ) if( len < arg.size() )
{ deslashed.assign( arg, 0, len ); filename = deslashed.c_str(); } { deslashed.assign( arg, 0, len ); filename = deslashed.c_str(); }
if( Exclude::excluded( filename ) ) continue; // skip excluded
struct stat st; struct stat st;
if( lstat( filename, &st ) != 0 ) // filename from command line if( lstat( filename, &st ) != 0 ) // filename from command line
{ show_file_error( filename, "Can't stat input file", errno ); { show_file_error( filename, "Can't stat input file", errno );
set_error_status( 1 ); } set_error_status( 1 ); }
else if( ( retval = nftw( filename, add_member, 16, FTW_PHYS ) ) != 0 ) else if( ( retval = nftw( filename, add_member, 16,
dereference ? 0 : FTW_PHYS ) ) != 0 )
break; // write error break; // write error
else if( encoder && solidity == dsolid && !archive_write( 0, 0 ) ) else if( encoder && solidity == dsolid && !archive_write( 0, 0 ) )
retval = 1; { retval = 1; break; }
} }
if( !retval ) // write End-Of-Archive records if( !retval ) // write End-Of-Archive records
@ -656,6 +778,7 @@ int encode( const std::string & archive_name, const Arg_parser & parser,
if( encoder && LZ_compress_close( encoder ) < 0 ) if( encoder && LZ_compress_close( encoder ) < 0 )
{ show_error( "LZ_compress_close failed." ); retval = 1; } { show_error( "LZ_compress_close failed." ); retval = 1; }
if( close( goutfd ) != 0 && !retval ) if( close( goutfd ) != 0 && !retval )
{ show_error( "Error closing archive", errno ); retval = 1; } { show_file_error( archive_name.c_str(), "Error closing archive", errno );
retval = 1; }
return final_exit_status( retval ); return final_exit_status( retval );
} }

View file

@ -41,7 +41,7 @@ namespace {
enum { max_packet_size = 1 << 20 }; enum { max_packet_size = 1 << 20 };
class Packet_courier; class Packet_courier;
Packet_courier * courierp = 0; // local vars needed by add_member Packet_courier * courierp = 0; // local vars needed by add_member_lz
unsigned long long partial_data_size = 0; // size of current block unsigned long long partial_data_size = 0; // size of current block
@ -257,9 +257,10 @@ public:
// send one ipacket with tar member metadata to courier // send one ipacket with tar member metadata to courier
int add_member( const char * const filename, const struct stat *, int add_member_lz( const char * const filename, const struct stat *,
const int flag, struct FTW * ) const int flag, struct FTW * )
{ {
if( Exclude::excluded( filename ) ) return 0; // skip excluded
unsigned long long file_size = 0; unsigned long long file_size = 0;
// metadata for extended records // metadata for extended records
Extended * const extended = new( std::nothrow ) Extended; Extended * const extended = new( std::nothrow ) Extended;
@ -286,6 +287,7 @@ struct Grouper_arg
{ {
Packet_courier * courier; Packet_courier * courier;
const Arg_parser * parser; const Arg_parser * parser;
bool dereference;
}; };
@ -296,6 +298,7 @@ extern "C" void * grouper( void * arg )
const Grouper_arg & tmp = *(const Grouper_arg *)arg; const Grouper_arg & tmp = *(const Grouper_arg *)arg;
Packet_courier & courier = *tmp.courier; Packet_courier & courier = *tmp.courier;
const Arg_parser & parser = *tmp.parser; const Arg_parser & parser = *tmp.parser;
const bool dereference = tmp.dereference;
for( int i = 0; i < parser.arguments(); ++i ) // parse command line for( int i = 0; i < parser.arguments(); ++i ) // parse command line
{ {
@ -312,11 +315,13 @@ extern "C" void * grouper( void * arg )
while( len > 1 && arg[len-1] == '/' ) --len; while( len > 1 && arg[len-1] == '/' ) --len;
if( len < arg.size() ) if( len < arg.size() )
{ deslashed.assign( arg, 0, len ); filename = deslashed.c_str(); } { deslashed.assign( arg, 0, len ); filename = deslashed.c_str(); }
if( Exclude::excluded( filename ) ) continue; // skip excluded
struct stat st; struct stat st;
if( lstat( filename, &st ) != 0 ) // filename from command line if( lstat( filename, &st ) != 0 ) // filename from command line
{ show_file_error( filename, "Can't stat input file", errno ); { show_file_error( filename, "Can't stat input file", errno );
set_error_status( 1 ); } set_error_status( 1 ); }
else if( nftw( filename, add_member, 16, FTW_PHYS ) != 0 ) else if( nftw( filename, add_member_lz, 16,
dereference ? 0 : FTW_PHYS ) != 0 )
cleanup_and_fail(); // write error or oom cleanup_and_fail(); // write error or oom
else if( solidity == dsolid ) // end of group else if( solidity == dsolid ) // end of group
courier.receive_packet( new Ipacket ); courier.receive_packet( new Ipacket );
@ -401,7 +406,7 @@ extern "C" void * cworker( void * arg )
LZ_Encoder * encoder = 0; LZ_Encoder * encoder = 0;
uint8_t * data = 0; uint8_t * data = 0;
Resizable_buffer rbuf( 2 * header_size ); // extended header + data Resizable_buffer rbuf; // extended header + data
if( !rbuf.size() ) { show_error( mem_msg2 ); cleanup_and_fail(); } if( !rbuf.size() ) { show_error( mem_msg2 ); cleanup_and_fail(); }
int opos = 0; int opos = 0;
@ -518,21 +523,22 @@ void muxer( Packet_courier & courier, const int outfd )
// init the courier, then start the grouper and the workers and call the muxer // init the courier, then start the grouper and the workers and call the muxer
int encode_lz( const Arg_parser & parser, const int dictionary_size, int encode_lz( const Arg_parser & parser, const int dictionary_size,
const int match_len_limit, const int num_workers, const int match_len_limit, const int num_workers,
const int outfd, const int debug_level ) const int outfd, const int out_slots, const int debug_level,
const bool dereference )
{ {
const int in_slots = 65536; // max small files (<=512B) in 64 MiB const int in_slots = 65536; // max small files (<=512B) in 64 MiB
const int total_in_slots = ( INT_MAX / num_workers >= in_slots ) ? const int total_in_slots = ( INT_MAX / num_workers >= in_slots ) ?
num_workers * in_slots : INT_MAX; num_workers * in_slots : INT_MAX;
const int out_slots = 64;
/* If an error happens after any threads have been started, exit must be /* If an error happens after any threads have been started, exit must be
called before courier goes out of scope. */ called before courier goes out of scope. */
Packet_courier courier( num_workers, total_in_slots, out_slots ); Packet_courier courier( num_workers, total_in_slots, out_slots );
courierp = &courier; // needed by add_member courierp = &courier; // needed by add_member_lz
Grouper_arg grouper_arg; Grouper_arg grouper_arg;
grouper_arg.courier = &courier; grouper_arg.courier = &courier;
grouper_arg.parser = &parser; grouper_arg.parser = &parser;
grouper_arg.dereference = dereference;
pthread_t grouper_thread; pthread_t grouper_thread;
int errcode = pthread_create( &grouper_thread, 0, grouper, &grouper_arg ); int errcode = pthread_create( &grouper_thread, 0, grouper, &grouper_arg );
@ -570,14 +576,7 @@ int encode_lz( const Arg_parser & parser, const int dictionary_size,
{ show_error( "Can't join grouper thread", errcode ); cleanup_and_fail(); } { show_error( "Can't join grouper thread", errcode ); cleanup_and_fail(); }
// write End-Of-Archive records // write End-Of-Archive records
int retval = 0; int retval = !write_eof_records( outfd, true );
enum { eof_member_size = 44 };
const uint8_t eof_member[eof_member_size] = {
0x4C, 0x5A, 0x49, 0x50, 0x01, 0x0C, 0x00, 0x00, 0x6F, 0xFD, 0xFF, 0xFF,
0xA3, 0xB7, 0x80, 0x0C, 0x82, 0xDB, 0xFF, 0xFF, 0x9F, 0xF0, 0x00, 0x00,
0x2E, 0xAF, 0xB5, 0xEF, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
if( !writeblock_wrapper( outfd, eof_member, eof_member_size ) ) retval = 1;
if( close( outfd ) != 0 && !retval ) if( close( outfd ) != 0 && !retval )
{ show_error( "Error closing archive", errno ); retval = 1; } { show_error( "Error closing archive", errno ); retval = 1; }

View file

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.46.1. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.46.1.
.TH TARLZ "1" "February 2019" "tarlz 0.13" "User Commands" .TH TARLZ "1" "March 2019" "tarlz 0.14" "User Commands"
.SH NAME .SH NAME
tarlz \- creates tar archives with multimember lzip compression tarlz \- creates tar archives with multimember lzip compression
.SH SYNOPSIS .SH SYNOPSIS
@ -24,7 +24,7 @@ recover as much data as possible from each damaged member, and lziprecover
can be used to recover some of the damaged members. can be used to recover some of the damaged members.
.SH OPTIONS .SH OPTIONS
.TP .TP
\fB\-h\fR, \fB\-\-help\fR \fB\-\-help\fR
display this help and exit display this help and exit
.TP .TP
\fB\-V\fR, \fB\-\-version\fR \fB\-V\fR, \fB\-\-version\fR
@ -48,9 +48,15 @@ find differences between archive and file system
\fB\-\-ignore\-ids\fR \fB\-\-ignore\-ids\fR
ignore differences in owner and group IDs ignore differences in owner and group IDs
.TP .TP
\fB\-\-exclude=\fR<pattern>
exclude files matching a shell pattern
.TP
\fB\-f\fR, \fB\-\-file=\fR<archive> \fB\-f\fR, \fB\-\-file=\fR<archive>
use archive file <archive> use archive file <archive>
.TP .TP
\fB\-h\fR, \fB\-\-dereference\fR
follow symlinks; archive the files they point to
.TP
\fB\-n\fR, \fB\-\-threads=\fR<n> \fB\-n\fR, \fB\-\-threads=\fR<n>
set number of (de)compression threads [2] set number of (de)compression threads [2]
.TP .TP
@ -104,6 +110,9 @@ don't delete partially extracted files
.TP .TP
\fB\-\-missing\-crc\fR \fB\-\-missing\-crc\fR
exit with error status if missing extended CRC exit with error status if missing extended CRC
.TP
\fB\-\-out\-slots=\fR<n>
number of 1 MiB output packets buffered [64]
.PP .PP
Exit status: 0 for a normal exit, 1 for environmental problems (file Exit status: 0 for a normal exit, 1 for environmental problems (file
not found, invalid flags, I/O errors, etc), 2 to indicate a corrupt or not found, invalid flags, I/O errors, etc), 2 to indicate a corrupt or

View file

@ -11,7 +11,7 @@ File: tarlz.info, Node: Top, Next: Introduction, Up: (dir)
Tarlz Manual Tarlz Manual
************ ************
This manual is for Tarlz (version 0.13, 27 February 2019). This manual is for Tarlz (version 0.14, 12 March 2019).
* Menu: * Menu:
@ -48,8 +48,8 @@ tar tools like GNU tar, which treat it like any other tar.lz archive.
Tarlz can append files to the end of such compressed archives. Tarlz can append files to the end of such compressed archives.
Tarlz can create tar archives with five levels of compression Tarlz can create tar archives with five levels of compression
granularity; per file, per block (default), per directory, appendable granularity; per file (--no-solid), per block (--bsolid, default), per
solid, and solid. directory (--dsolid), appendable solid (--asolid), and solid (--solid).
Of course, compressing each file (or each directory) individually can't Of course, compressing each file (or each directory) individually can't
achieve a compression ratio as high as compressing solidly the whole tar achieve a compression ratio as high as compressing solidly the whole tar
@ -107,7 +107,6 @@ equivalent to '-1 --solid'
tarlz supports the following options: tarlz supports the following options:
'-h'
'--help' '--help'
Print an informative help message describing the options and exit. Print an informative help message describing the options and exit.
@ -118,14 +117,17 @@ equivalent to '-1 --solid'
'-A' '-A'
'--concatenate' '--concatenate'
Append tar.lz archives to the end of a tar.lz archive. All the Append one or more archives to the end of an archive. All the
archives involved must be regular (seekable) files compressed as archives involved must be regular (seekable) files, and must be
multimember lzip files, and the two end-of-file blocks plus any either all compressed or all uncompressed. Compressed and
zero padding must be contained in the last lzip member of each uncompressed archives can't be mixed. Compressed archives must be
archive. The intermediate end-of-file blocks are removed as each multimember lzip files with the two end-of-file blocks plus any
new archive is concatenated. Exit with status 0 without modifying zero padding contained in the last lzip member of each archive.
the archive if no FILES have been specified. Tarlz can't The intermediate end-of-file blocks are removed as each new archive
concatenate uncompressed tar archives. is concatenated. If the archive is uncompressed, tarlz parses and
skips tar headers until it finds the end-of-file blocks. Exit with
status 0 without modifying the archive if no FILES have been
specified.
'-B BYTES' '-B BYTES'
'--data-size=BYTES' '--data-size=BYTES'
@ -158,21 +160,38 @@ equivalent to '-1 --solid'
'--diff' '--diff'
Find differences between archive and file system. For each tar Find differences between archive and file system. For each tar
member in the archive, verify that the corresponding file exists member in the archive, verify that the corresponding file exists
and is of the same type (regular file, directory, etc). Report the and is of the same type (regular file, directory, etc). Report on
differences found in type, mode (permissions), owner and group standard output the differences found in type, mode (permissions),
IDs, modification time, file size, file contents (of regular owner and group IDs, modification time, file size, file contents
files), target (of symlinks) and device number (of block/character (of regular files), target (of symlinks) and device number (of
special files). block/character special files).
As tarlz removes leading slashes from member names, the '-C'
option may be used in combination with '--diff' when absolute
filenames were used on archive creation: 'tarlz -C / -d'.
Alternatively, tarlz may be run from the root directory to perform
the comparison.
'--ignore-ids' '--ignore-ids'
Make '--diff' ignore differences in owner and group IDs. This Make '--diff' ignore differences in owner and group IDs. This
option is useful when comparing an '--anonymous' archive. option is useful when comparing an '--anonymous' archive.
'--exclude=PATTERN'
Exclude files matching a shell pattern like '*.o'. A file is
considered to match if any component of the filename matches. For
example, '*.o' matches 'foo.o', 'foo.o/bar' and 'foo/bar.o'.
'-f ARCHIVE' '-f ARCHIVE'
'--file=ARCHIVE' '--file=ARCHIVE'
Use archive file ARCHIVE. '-' used as an ARCHIVE argument reads Use archive file ARCHIVE. '-' used as an ARCHIVE argument reads
from standard input or writes to standard output. from standard input or writes to standard output.
'-h'
'--dereference'
Follow symbolic links during archive creation, appending or
comparison. Archive or compare the files they point to instead of
the links themselves.
'-n N' '-n N'
'--threads=N' '--threads=N'
Set the number of (de)compression threads, overriding the system's Set the number of (de)compression threads, overriding the system's
@ -197,14 +216,18 @@ equivalent to '-1 --solid'
'-r' '-r'
'--append' '--append'
Append files to the end of a tar.lz archive. The archive must be a Append files to the end of an archive. The archive must be a
regular (seekable) file compressed as a multimember lzip file, and regular (seekable) file either compressed or uncompressed.
the two end-of-file blocks plus any zero padding must be contained Compressed members can't be appended to an uncompressed archive,
in the last lzip member of the archive. First this last member is nor vice versa. If the archive is compressed, it must be a
removed, then the new members are appended, and then a new multimember lzip file with the two end-of-file blocks plus any
end-of-file member is appended to the archive. Exit with status 0 zero padding contained in the last lzip member of the archive.
without modifying the archive if no FILES have been specified. Appending works as follows; first the end-of-file blocks are
Tarlz can't append files to an uncompressed tar archive. removed, then the new members are appended, and finally two new
end-of-file blocks are appended to the archive. If the archive is
uncompressed, tarlz parses and skips tar headers until it finds
the end-of-file blocks. Exit with status 0 without modifying the
archive if no FILES have been specified.
'-t' '-t'
'--list' '--list'
@ -221,10 +244,10 @@ equivalent to '-1 --solid'
the FILES given. Else extract all the files in the archive. the FILES given. Else extract all the files in the archive.
'-0 .. -9' '-0 .. -9'
Set the compression level. The default compression level is '-6'. Set the compression level for '--create' and '--append'. The
Like lzip, tarlz also minimizes the dictionary size of the lzip default compression level is '-6'. Like lzip, tarlz also minimizes
members it creates, reducing the amount of memory required for the dictionary size of the lzip members it creates, reducing the
decompression. amount of memory required for decompression.
Level Dictionary size Match length limit Level Dictionary size Match length limit
-0 64 KiB 16 bytes -0 64 KiB 16 bytes
@ -239,8 +262,10 @@ equivalent to '-1 --solid'
-9 32 MiB 273 bytes -9 32 MiB 273 bytes
'--uncompressed' '--uncompressed'
With '--create', don't compress the created tar archive. Create an With '--create', don't compress the tar archive created. Create an
uncompressed tar archive instead. uncompressed tar archive instead. With '--append', don't compress
the new members appended to the tar archive. Compressed members
can't be appended to an uncompressed archive, nor vice versa.
'--asolid' '--asolid'
When creating or appending to a compressed archive, use appendable When creating or appending to a compressed archive, use appendable
@ -314,6 +339,14 @@ equivalent to '-1 --solid'
the posix pax format; i.e., the lack of a mandatory check sequence the posix pax format; i.e., the lack of a mandatory check sequence
in the extended records. *Note crc32::. in the extended records. *Note crc32::.
'--out-slots=N'
Number of 1 MiB output packets buffered per worker thread during
multi-threaded creation or appending to compressed archives.
Increasing the number of packets may increase compression speed if
the files being archived are larger than 64 MiB compressed, but
requires more memory. Valid values range from 1 to 1024. The
default value is 64.
Exit status: 0 for a normal exit, 1 for environmental problems (file Exit status: 0 for a normal exit, 1 for environmental problems (file
not found, invalid flags, I/O errors, etc), 2 to indicate a corrupt or not found, invalid flags, I/O errors, etc), 2 to indicate a corrupt or
@ -345,7 +378,7 @@ sets). The members simply appear one after another in the file, with no
additional information before, between, or after them. additional information before, between, or after them.
Each lzip member contains one or more tar members in a simplified Each lzip member contains one or more tar members in a simplified
posix pax interchange format; the only pax typeflag value supported by posix pax interchange format. The only pax typeflag value supported by
tarlz (in addition to the typeflag values defined by the ustar format) tarlz (in addition to the typeflag values defined by the ustar format)
is 'x'. The pax format is an extension on top of the ustar format that is 'x'. The pax format is an extension on top of the ustar format that
removes the size limitations of the ustar format. removes the size limitations of the ustar format.
@ -714,7 +747,7 @@ speed by the number of available processors), the uncompressed archive
must be at least as large as the number of worker threads times the must be at least as large as the number of worker threads times the
block size (*note --data-size::). Else some processors will not get any block size (*note --data-size::). Else some processors will not get any
data to compress, and compression will be proportionally slower. The data to compress, and compression will be proportionally slower. The
maximum speed increase achievable on a given file is limited by the maximum speed increase achievable on a given archive is limited by the
ratio (uncompressed_size / data_size). For example, a tarball the size ratio (uncompressed_size / data_size). For example, a tarball the size
of gcc or linux will scale up to 10 or 12 processors at level -9. of gcc or linux will scale up to 10 or 12 processors at level -9.
@ -835,20 +868,20 @@ Concept index
 
Tag Table: Tag Table:
Node: Top223 Node: Top223
Node: Introduction1089 Node: Introduction1086
Node: Invoking tarlz3228 Node: Invoking tarlz3280
Ref: --data-size5107 Ref: --data-size5339
Ref: --bsolid10054 Ref: --bsolid11442
Node: File format13298 Node: File format15072
Ref: key_crc3218118 Ref: key_crc3219892
Node: Amendments to pax format23535 Node: Amendments to pax format25309
Ref: crc3224059 Ref: crc3225833
Ref: flawed-compat25084 Ref: flawed-compat26858
Node: Multi-threaded tar27451 Node: Multi-threaded tar29225
Node: Minimum archive sizes29990 Node: Minimum archive sizes31764
Node: Examples32120 Node: Examples33897
Node: Problems33789 Node: Problems35566
Node: Concept index34315 Node: Concept index36092
 
End Tag Table End Tag Table

View file

@ -6,8 +6,8 @@
@finalout @finalout
@c %**end of header @c %**end of header
@set UPDATED 27 February 2019 @set UPDATED 12 March 2019
@set VERSION 0.13 @set VERSION 0.14
@dircategory Data Compression @dircategory Data Compression
@direntry @direntry
@ -69,7 +69,8 @@ tar, which treat it like any other tar.lz archive. Tarlz can append files to
the end of such compressed archives. the end of such compressed archives.
Tarlz can create tar archives with five levels of compression granularity; Tarlz can create tar archives with five levels of compression granularity;
per file, per block (default), per directory, appendable solid, and solid. per file (---no-solid), per block (---bsolid, default), per directory
(---dsolid), appendable solid (---asolid), and solid (---solid).
@noindent @noindent
Of course, compressing each file (or each directory) individually can't Of course, compressing each file (or each directory) individually can't
@ -140,8 +141,7 @@ equivalent to @samp{-1 --solid}
tarlz supports the following options: tarlz supports the following options:
@table @code @table @code
@item -h @item --help
@itemx --help
Print an informative help message describing the options and exit. Print an informative help message describing the options and exit.
@item -V @item -V
@ -151,13 +151,15 @@ This version number should be included in all bug reports.
@item -A @item -A
@itemx --concatenate @itemx --concatenate
Append tar.lz archives to the end of a tar.lz archive. All the archives Append one or more archives to the end of an archive. All the archives
involved must be regular (seekable) files compressed as multimember lzip involved must be regular (seekable) files, and must be either all compressed
files, and the two end-of-file blocks plus any zero padding must be or all uncompressed. Compressed and uncompressed archives can't be mixed.
contained in the last lzip member of each archive. The intermediate Compressed archives must be multimember lzip files with the two end-of-file
end-of-file blocks are removed as each new archive is concatenated. Exit blocks plus any zero padding contained in the last lzip member of each
with status 0 without modifying the archive if no @var{files} have been archive. The intermediate end-of-file blocks are removed as each new archive
specified. Tarlz can't concatenate uncompressed tar archives. is concatenated. If the archive is uncompressed, tarlz parses and skips tar
headers until it finds the end-of-file blocks. Exit with status 0 without
modifying the archive if no @var{files} have been specified.
@anchor{--data-size} @anchor{--data-size}
@item -B @var{bytes} @item -B @var{bytes}
@ -190,19 +192,34 @@ option appears after a relative filename in the command line.
@itemx --diff @itemx --diff
Find differences between archive and file system. For each tar member in the Find differences between archive and file system. For each tar member in the
archive, verify that the corresponding file exists and is of the same type archive, verify that the corresponding file exists and is of the same type
(regular file, directory, etc). Report the differences found in type, mode (regular file, directory, etc). Report on standard output the differences
(permissions), owner and group IDs, modification time, file size, file found in type, mode (permissions), owner and group IDs, modification time,
contents (of regular files), target (of symlinks) and device number (of file size, file contents (of regular files), target (of symlinks) and device
block/character special files). number (of block/character special files).
As tarlz removes leading slashes from member names, the @samp{-C} option may
be used in combination with @samp{--diff} when absolute filenames were used
on archive creation: @w{@samp{tarlz -C / -d}}. Alternatively, tarlz may be
run from the root directory to perform the comparison.
@item --ignore-ids @item --ignore-ids
Make @samp{--diff} ignore differences in owner and group IDs. This option is Make @samp{--diff} ignore differences in owner and group IDs. This option is
useful when comparing an @samp{--anonymous} archive. useful when comparing an @samp{--anonymous} archive.
@item --exclude=@var{pattern}
Exclude files matching a shell pattern like @samp{*.o}. A file is considered
to match if any component of the filename matches. For example, @samp{*.o}
matches @samp{foo.o}, @samp{foo.o/bar} and @samp{foo/bar.o}.
@item -f @var{archive} @item -f @var{archive}
@itemx --file=@var{archive} @itemx --file=@var{archive}
Use archive file @var{archive}. @samp{-} used as an @var{archive} Use archive file @var{archive}. @samp{-} used as an @var{archive} argument
argument reads from standard input or writes to standard output. reads from standard input or writes to standard output.
@item -h
@itemx --dereference
Follow symbolic links during archive creation, appending or comparison.
Archive or compare the files they point to instead of the links themselves.
@item -n @var{n} @item -n @var{n}
@itemx --threads=@var{n} @itemx --threads=@var{n}
@ -226,14 +243,17 @@ Quiet operation. Suppress all messages.
@item -r @item -r
@itemx --append @itemx --append
Append files to the end of a tar.lz archive. The archive must be a Append files to the end of an archive. The archive must be a regular
regular (seekable) file compressed as a multimember lzip file, and the (seekable) file either compressed or uncompressed. Compressed members can't
two end-of-file blocks plus any zero padding must be contained in the be appended to an uncompressed archive, nor vice versa. If the archive is
last lzip member of the archive. First this last member is removed, then compressed, it must be a multimember lzip file with the two end-of-file
the new members are appended, and then a new end-of-file member is blocks plus any zero padding contained in the last lzip member of the
appended to the archive. Exit with status 0 without modifying the archive. Appending works as follows; first the end-of-file blocks are
archive if no @var{files} have been specified. Tarlz can't append files removed, then the new members are appended, and finally two new end-of-file
to an uncompressed tar archive. blocks are appended to the archive. If the archive is uncompressed, tarlz
parses and skips tar headers until it finds the end-of-file blocks. Exit
with status 0 without modifying the archive if no @var{files} have been
specified.
@item -t @item -t
@itemx --list @itemx --list
@ -250,9 +270,10 @@ Extract files from an archive. If @var{files} are given, extract only
the @var{files} given. Else extract all the files in the archive. the @var{files} given. Else extract all the files in the archive.
@item -0 .. -9 @item -0 .. -9
Set the compression level. The default compression level is @samp{-6}. Set the compression level for @samp{--create} and @samp{--append}. The
Like lzip, tarlz also minimizes the dictionary size of the lzip members default compression level is @samp{-6}. Like lzip, tarlz also minimizes the
it creates, reducing the amount of memory required for decompression. dictionary size of the lzip members it creates, reducing the amount of
memory required for decompression.
@multitable {Level} {Dictionary size} {Match length limit} @multitable {Level} {Dictionary size} {Match length limit}
@item Level @tab Dictionary size @tab Match length limit @item Level @tab Dictionary size @tab Match length limit
@ -269,8 +290,10 @@ it creates, reducing the amount of memory required for decompression.
@end multitable @end multitable
@item --uncompressed @item --uncompressed
With @samp{--create}, don't compress the created tar archive. Create an With @samp{--create}, don't compress the tar archive created. Create an
uncompressed tar archive instead. uncompressed tar archive instead. With @samp{--append}, don't compress the
new members appended to the tar archive. Compressed members can't be
appended to an uncompressed archive, nor vice versa.
@item --asolid @item --asolid
When creating or appending to a compressed archive, use appendable solid When creating or appending to a compressed archive, use appendable solid
@ -340,6 +363,13 @@ missing CRC instead of as a corrupt record. This misleading
format; i.e., the lack of a mandatory check sequence in the extended format; i.e., the lack of a mandatory check sequence in the extended
records. @xref{crc32}. records. @xref{crc32}.
@item --out-slots=@var{n}
Number of @w{1 MiB} output packets buffered per worker thread during
multi-threaded creation or appending to compressed archives. Increasing the
number of packets may increase compression speed if the files being archived
are larger than @w{64 MiB} compressed, but requires more memory. Valid
values range from 1 to 1024. The default value is 64.
@ignore @ignore
@item --permissive @item --permissive
Allow some violations of the archive format, like consecutive extended Allow some violations of the archive format, like consecutive extended
@ -382,7 +412,7 @@ The members simply appear one after another in the file, with no
additional information before, between, or after them. additional information before, between, or after them.
Each lzip member contains one or more tar members in a simplified posix Each lzip member contains one or more tar members in a simplified posix
pax interchange format; the only pax typeflag value supported by tarlz pax interchange format. The only pax typeflag value supported by tarlz
(in addition to the typeflag values defined by the ustar format) is (in addition to the typeflag values defined by the ustar format) is
@samp{x}. The pax format is an extension on top of the ustar format that @samp{x}. The pax format is an extension on top of the ustar format that
removes the size limitations of the ustar format. removes the size limitations of the ustar format.
@ -766,7 +796,7 @@ the number of available processors), the uncompressed archive must be at
least as large as the number of worker threads times the block size least as large as the number of worker threads times the block size
(@pxref{--data-size}). Else some processors will not get any data to (@pxref{--data-size}). Else some processors will not get any data to
compress, and compression will be proportionally slower. The maximum speed compress, and compression will be proportionally slower. The maximum speed
increase achievable on a given file is limited by the ratio increase achievable on a given archive is limited by the ratio
@w{(uncompressed_size / data_size)}. For example, a tarball the size of gcc @w{(uncompressed_size / data_size)}. For example, a tarball the size of gcc
or linux will scale up to 10 or 12 processors at level -9. or linux will scale up to 10 or 12 processors at level -9.

55
exclude.cc Normal file
View file

@ -0,0 +1,55 @@
/* Tarlz - Archiver with multimember lzip compression
Copyright (C) 2013-2019 Antonio Diaz Diaz.
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 <climits>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#include <fnmatch.h>
#include <pthread.h>
#include <stdint.h>
#include "tarlz.h"
namespace Exclude {
std::vector< std::string > patterns; // list of patterns
} // end namespace Exclude
void Exclude::add_pattern( const std::string & arg )
{ patterns.push_back( arg ); }
bool Exclude::excluded( const char * const filename )
{
if( patterns.empty() ) return false;
const char * p = filename;
while( *p )
{
for( unsigned i = 0; i < patterns.size(); ++i )
if( fnmatch( patterns[i].c_str(), p, FNM_LEADING_DIR ) == 0 ) return true;
while( *p && *p != '/' ) ++p; // skip component
while( *p == '/' ) ++p; // skip slashes
}
return false;
}

View file

@ -201,7 +201,9 @@ bool Extended::parse( const char * const buf, const unsigned long long edsize,
if( rest > 5 && std::memcmp( tail, "path=", 5 ) == 0 ) if( rest > 5 && std::memcmp( tail, "path=", 5 ) == 0 )
{ {
if( path_.size() && !permissive ) return false; if( path_.size() && !permissive ) return false;
path_.assign( tail + 5, rest - 5 ); unsigned long long len = rest - 5;
while( len > 1 && tail[5+len-1] == '/' ) --len; // trailing '/'
path_.assign( tail + 5, len );
// this also truncates path_ at the first embedded null character // 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() ) );
} }
@ -275,3 +277,19 @@ void Extended::fill_from_ustar( const Tar_header header )
( typeflag == tf_regular || typeflag == tf_hiperf ) ) ( typeflag == tf_regular || typeflag == tf_hiperf ) )
file_size( parse_octal( header + size_o, size_l ) ); file_size( parse_octal( header + size_o, size_l ) );
} }
/* Returns file size from record or from ustar header, and resets file_size_.
Used for fast parsing of headers in uncompressed archives. */
unsigned long long Extended::get_file_size_and_reset( const Tar_header header )
{
const unsigned long long tmp = file_size_;
file_size( 0 );
const Typeflag typeflag = (Typeflag)header[typeflag_o];
if( typeflag == tf_regular || typeflag == tf_hiperf )
{
if( tmp == 0 ) return parse_octal( header + size_o, size_l );
else return tmp;
}
return 0;
}

View file

@ -44,9 +44,9 @@
namespace { namespace {
Resizable_buffer grbuf( initial_line_length ); Resizable_buffer grbuf;
bool archive_is_uncompressed_seekable = false; bool archive_is_uncompressed_seekable = false;
bool has_lz_ext; // global var for archive_read bool archive_has_lz_ext; // local var for archive_read
bool skip_warn( const bool reset = false ) // avoid duplicate warnings bool skip_warn( const bool reset = false ) // avoid duplicate warnings
{ {
@ -120,7 +120,7 @@ int archive_read( const int infd, uint8_t * const buf, const int size,
if( !islz && !istar && !iseof ) // corrupt or invalid format if( !islz && !istar && !iseof ) // corrupt or invalid format
{ {
show_error( "This does not look like a POSIX tar archive." ); show_error( "This does not look like a POSIX tar archive." );
if( has_lz_ext && rd >= min_member_size ) islz = true; if( archive_has_lz_ext && rd >= min_member_size ) islz = true;
if( !islz ) return 1; if( !islz ) return 1;
} }
if( !islz ) // uncompressed if( !islz ) // uncompressed
@ -247,7 +247,7 @@ bool block_is_zero( const uint8_t * const buf, const int size )
} }
void format_member_name( const Extended & extended, const Tar_header header, bool format_member_name( const Extended & extended, const Tar_header header,
Resizable_buffer & rbuf, const bool long_format ) Resizable_buffer & rbuf, const bool long_format )
{ {
if( long_format ) if( long_format )
@ -279,27 +279,32 @@ void format_member_name( const Extended & extended, const Tar_header header,
1900 + tm->tm_year, 1 + tm->tm_mon, tm->tm_mday, 1900 + tm->tm_year, 1 + tm->tm_mon, tm->tm_mday,
tm->tm_hour, tm->tm_min, extended.path().c_str(), tm->tm_hour, tm->tm_min, extended.path().c_str(),
link_string, islink ? extended.linkpath().c_str() : "" ); link_string, islink ? extended.linkpath().c_str() : "" );
if( (int)rbuf.size() > len + offset || !rbuf.resize( len + offset + 1 ) ) if( (int)rbuf.size() > len + offset ) break;
break; if( !rbuf.resize( len + offset + 1 ) ) return false;
} }
} }
else else
{ {
if( rbuf.size() < extended.path().size() + 2 ) if( rbuf.size() < extended.path().size() + 2 &&
rbuf.resize( extended.path().size() + 2 ); !rbuf.resize( extended.path().size() + 2 ) ) return false;
snprintf( rbuf(), rbuf.size(), "%s\n", extended.path().c_str() ); snprintf( rbuf(), rbuf.size(), "%s\n", extended.path().c_str() );
} }
return true;
} }
namespace { namespace {
void show_member_name( const Extended & extended, const Tar_header header, bool show_member_name( const Extended & extended, const Tar_header header,
const int vlevel, Resizable_buffer & rbuf ) const int vlevel, Resizable_buffer & rbuf )
{ {
if( verbosity < vlevel ) return; if( verbosity >= vlevel )
format_member_name( extended, header, rbuf, verbosity > vlevel ); {
std::fputs( rbuf(), stdout ); if( !format_member_name( extended, header, rbuf, verbosity > vlevel ) )
std::fflush( stdout ); { show_error( mem_msg ); return false; }
std::fputs( rbuf(), stdout );
std::fflush( stdout );
}
return true;
} }
@ -326,20 +331,21 @@ int skip_member( const int infd, const Extended & extended )
void show_file_diff( const char * const filename, const char * const msg ) void show_file_diff( const char * const filename, const char * const msg )
{ {
if( verbosity >= 0 ) std::fprintf( stderr, "%s: %s\n", filename, msg ); if( verbosity >= 0 )
{ std::printf( "%s: %s\n", filename, msg ); std::fflush( stdout ); }
} }
int compare_member( const int infd1, const Extended & extended, int compare_member( const int infd1, const Extended & extended,
const Tar_header header, const bool ignore_ids ) const Tar_header header, const bool ignore_ids )
{ {
show_member_name( extended, header, 1, grbuf ); if( !show_member_name( extended, header, 1, grbuf ) ) return 1;
unsigned long long rest = extended.file_size(); unsigned long long rest = extended.file_size();
const char * const filename = extended.path().c_str(); const char * const filename = extended.path().c_str();
const Typeflag typeflag = (Typeflag)header[typeflag_o]; const Typeflag typeflag = (Typeflag)header[typeflag_o];
bool diff = false, size_differs = false, type_differs = true; bool diff = false, size_differs = false, type_differs = true;
struct stat st; struct stat st;
if( lstat( filename, &st ) != 0 ) if( hstat( filename, &st ) != 0 )
show_file_error( filename, "Warning: Can't stat", errno ); show_file_error( filename, "Warning: Can't stat", errno );
else if( ( typeflag == tf_regular || typeflag == tf_hiperf ) && else if( ( typeflag == tf_regular || typeflag == tf_hiperf ) &&
!S_ISREG( st.st_mode ) ) !S_ISREG( st.st_mode ) )
@ -453,7 +459,7 @@ int compare_member( const int infd1, const Extended & extended,
int list_member( const int infd, const Extended & extended, int list_member( const int infd, const Extended & extended,
const Tar_header header ) const Tar_header header )
{ {
show_member_name( extended, header, 0, grbuf ); if( !show_member_name( extended, header, 0, grbuf ) ) return 1;
return skip_member( infd, extended ); return skip_member( infd, extended );
} }
@ -481,7 +487,7 @@ int extract_member( const int infd, const Extended & extended,
const bool islink = ( typeflag == tf_link || typeflag == tf_symlink ); const bool islink = ( typeflag == tf_link || typeflag == tf_symlink );
int outfd = -1; int outfd = -1;
show_member_name( extended, header, 1, grbuf ); if( !show_member_name( extended, header, 1, grbuf ) ) return 1;
std::remove( filename ); std::remove( filename );
make_path( filename ); make_path( filename );
switch( typeflag ) switch( typeflag )
@ -615,17 +621,16 @@ bool compare_tslash( const char * const name1, const char * const name2 )
namespace { namespace {
bool parse_records( const int infd, Extended & extended, bool parse_records( const int infd, Extended & extended,
const Tar_header header, const bool permissive ) const Tar_header header, Resizable_buffer & rbuf,
const bool permissive )
{ {
const unsigned long long edsize = parse_octal( header + size_o, size_l ); const unsigned long long edsize = parse_octal( header + size_o, size_l );
const unsigned long long bufsize = round_up( edsize ); const unsigned long long bufsize = round_up( edsize );
if( bufsize == 0 || edsize == 0 || edsize >= 1ULL << 33 ) if( edsize == 0 || edsize >= 1ULL << 33 || bufsize == 0 || bufsize >= INT_MAX )
return false; // overflow or no extended data return false; // overflow or no extended data
char * const buf = new char[bufsize]; // extended records buffer if( !rbuf.resize( bufsize ) ) return false; // extended records buffer
const bool ret = ( archive_read( infd, (uint8_t *)buf, bufsize ) == 0 && return ( archive_read( infd, (uint8_t *)rbuf(), bufsize ) == 0 &&
extended.parse( buf, edsize, permissive ) ); extended.parse( rbuf(), edsize, permissive ) );
delete[] buf;
return ret;
} }
} // end namespace } // end namespace
@ -702,7 +707,9 @@ int decode( const std::string & archive_name, const Arg_parser & parser,
{ show_file_error( dir, "Error changing working directory", errno ); { show_file_error( dir, "Error changing working directory", errno );
return 1; } return 1; }
} }
if( !code && parser.argument( i ).size() ) name_pending[i] = true; if( !code && parser.argument( i ).size() &&
!Exclude::excluded( parser.argument( i ).c_str() ) )
name_pending[i] = true;
} }
// multi-threaded --list is faster even with 1 thread and 1 file in archive // multi-threaded --list is faster even with 1 thread and 1 file in archive
@ -722,11 +729,7 @@ int decode( const std::string & archive_name, const Arg_parser & parser,
archive_is_uncompressed_seekable = true; // unless compressed corrupt archive_is_uncompressed_seekable = true; // unless compressed corrupt
} }
has_lz_ext = // global var for archive_read archive_has_lz_ext = has_lz_ext( archive_name ); // var for archive_read
( 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 );
Extended extended; // metadata from extended records Extended extended; // metadata from extended records
int retval = 0; int retval = 0;
bool prev_extended = false; // prev header was extended bool prev_extended = false; // prev header was extended
@ -737,35 +740,46 @@ int decode( const std::string & archive_name, const Arg_parser & parser,
if( ret == 2 ) return 2; if( ret == 2 ) return 2;
if( ret != 0 || !verify_ustar_chksum( header ) ) if( ret != 0 || !verify_ustar_chksum( header ) )
{ {
if( ret == 0 && block_is_zero( header, header_size ) ) break; // EOF if( ret == 0 && block_is_zero( header, header_size ) )
{
if( !prev_extended ) break; // EOF
show_file_error( archive_name.c_str(),
"Format violation: extended header followed by EOF blocks." );
return 2;
}
if( skip_warn() && verbosity >= 2 ) if( skip_warn() && verbosity >= 2 )
std::fprintf( stderr, "ustar chksum = %07o\n", ustar_chksum( header ) ); std::fprintf( stderr, "ustar chksum = %07o\n", ustar_chksum( header ) );
set_error_status( 2 ); continue; set_error_status( 2 ); continue;
} }
skip_warn( true ); // reset warning skip_warn( true ); // reset warning
const Typeflag typeflag = (Typeflag)header[typeflag_o]; const Typeflag typeflag = (Typeflag)header[typeflag_o];
if( typeflag == tf_global ) if( typeflag == tf_global )
{ {
if( prev_extended ) if( prev_extended )
{ show_error( "Format violation: global header after extended header." ); { show_file_error( archive_name.c_str(),
"Format violation: extended header followed by global header." );
return 2; } return 2; }
Extended dummy; // global headers are parsed and ignored Extended dummy; // global headers are parsed and ignored
if( !parse_records( infd, dummy, header, true ) ) if( !parse_records( infd, dummy, header, grbuf, true ) )
{ show_error( "Error in global extended records. Skipping to next header." ); { show_file_error( archive_name.c_str(),
"Error in global extended records. Skipping to next header." );
set_error_status( 2 ); } set_error_status( 2 ); }
continue; continue;
} }
if( typeflag == tf_extended ) if( typeflag == tf_extended )
{ {
if( prev_extended && !permissive ) if( prev_extended && !permissive )
{ show_error( "Format violation: consecutive extended headers found." { show_file_error( archive_name.c_str(),
"Format violation: consecutive extended headers found."
/*" Use --permissive.", 0, true*/ ); return 2; } /*" Use --permissive.", 0, true*/ ); return 2; }
if( !parse_records( infd, extended, header, permissive ) ) if( !parse_records( infd, extended, header, grbuf, permissive ) )
{ show_error( "Error in extended records. Skipping to next header." ); { show_file_error( archive_name.c_str(),
"Error in extended records. Skipping to next header." );
extended.reset(); set_error_status( 2 ); } extended.reset(); set_error_status( 2 ); }
else if( !extended.crc_present() && missing_crc ) else if( !extended.crc_present() && missing_crc )
{ show_error( "Missing CRC in extended records.", 0, true ); return 2; } { show_file_error( archive_name.c_str(),
"Missing CRC in extended records." ); return 2; }
prev_extended = true; prev_extended = true;
continue; continue;
} }

View file

@ -149,6 +149,7 @@ bool check_skip_filename( const Arg_parser & parser,
std::vector< char > & name_pending, std::vector< char > & name_pending,
const char * const filename, const int filenames ) const char * const filename, const int filenames )
{ {
if( Exclude::excluded( filename ) ) return true; // skip excluded
bool skip = filenames > 0; bool skip = filenames > 0;
if( skip ) if( skip )
for( int i = 0; i < parser.arguments(); ++i ) for( int i = 0; i < parser.arguments(); ++i )
@ -377,7 +378,8 @@ int list_member_lz( LZ_Decoder * const decoder, const int infd,
} }
if( verbosity < 0 || skip ) rbuf()[0] = 0; if( verbosity < 0 || skip ) rbuf()[0] = 0;
else format_member_name( extended, header, rbuf, verbosity > 0 ); else if( !format_member_name( extended, header, rbuf, verbosity > 0 ) )
{ *msg = mem_msg; return 1; }
const Packet * const opacket = new Packet( member_id, rbuf(), const Packet * const opacket = new Packet( member_id, rbuf(),
data_rest ? Packet::ok : Packet::member_done ); data_rest ? Packet::ok : Packet::member_done );
if( !courier.collect_packet( opacket, worker_id ) ) if( !courier.collect_packet( opacket, worker_id ) )
@ -404,19 +406,19 @@ int parse_records_lz( LZ_Decoder * const decoder, const int infd,
long long & file_pos, const long long member_end, long long & file_pos, const long long member_end,
const long long cdata_size, long long & data_pos, const long long cdata_size, long long & data_pos,
Extended & extended, const Tar_header header, Extended & extended, const Tar_header header,
const char ** msg, const bool permissive ) Resizable_buffer & rbuf, const char ** msg,
const bool permissive )
{ {
const unsigned long long edsize = parse_octal( header + size_o, size_l ); const unsigned long long edsize = parse_octal( header + size_o, size_l );
const unsigned long long bufsize = round_up( edsize ); const unsigned long long bufsize = round_up( edsize );
if( bufsize == 0 || edsize == 0 || edsize >= 1ULL << 33 ) if( edsize == 0 || edsize >= 1ULL << 33 || bufsize == 0 || bufsize >= INT_MAX )
return false; // overflow or no extended data return 1; // overflow or no extended data
char * const buf = new char[bufsize]; // extended records buffer if( !rbuf.resize( bufsize ) ) return 1; // extended records buffer
int retval = archive_read_lz( decoder, infd, file_pos, member_end, int retval = archive_read_lz( decoder, infd, file_pos, member_end,
cdata_size, (uint8_t *)buf, bufsize, msg ); cdata_size, (uint8_t *)rbuf(), bufsize, msg );
if( retval == 0 ) if( retval == 0 )
{ if( extended.parse( buf, edsize, permissive ) ) data_pos += bufsize; { if( extended.parse( rbuf(), edsize, permissive ) ) data_pos += bufsize;
else retval = 1; } else retval = 1; }
delete[] buf;
return retval; return retval;
} }
@ -452,7 +454,7 @@ extern "C" void * tworker( void * arg )
const int missing_crc = tmp.missing_crc; const int missing_crc = tmp.missing_crc;
const bool permissive = tmp.permissive; const bool permissive = tmp.permissive;
Resizable_buffer rbuf( initial_line_length ); Resizable_buffer rbuf;
LZ_Decoder * const decoder = LZ_decompress_open(); LZ_Decoder * const decoder = LZ_decompress_open();
if( !rbuf.size() || !decoder || LZ_decompress_errno( decoder ) != LZ_ok ) if( !rbuf.size() || !decoder || LZ_decompress_errno( decoder ) != LZ_ok )
{ show_error( mem_msg ); cleanup_and_fail(); } { show_error( mem_msg ); cleanup_and_fail(); }
@ -518,7 +520,8 @@ extern "C" void * tworker( void * arg )
cleanup_and_fail( 2 ); } cleanup_and_fail( 2 ); }
Extended dummy; // global headers are parsed and ignored Extended dummy; // global headers are parsed and ignored
const int ret = parse_records_lz( decoder, infd, file_pos, member_end, const int ret = parse_records_lz( decoder, infd, file_pos, member_end,
cdata_size, data_pos, dummy, header, &msg, true ); cdata_size, data_pos, dummy, header,
rbuf, &msg, true );
if( ret != 0 ) if( ret != 0 )
{ {
if( !courier.request_mastership( i, worker_id ) ) goto done; if( !courier.request_mastership( i, worker_id ) ) goto done;
@ -542,7 +545,8 @@ extern "C" void * tworker( void * arg )
{ msg = "Format violation: consecutive extended headers found."; { msg = "Format violation: consecutive extended headers found.";
ret = 2; } ret = 2; }
else ret = parse_records_lz( decoder, infd, file_pos, member_end, else ret = parse_records_lz( decoder, infd, file_pos, member_end,
cdata_size, data_pos, extended, header, &msg, permissive ); cdata_size, data_pos, extended, header,
rbuf, &msg, permissive );
if( ret == 0 && !extended.crc_present() && missing_crc ) if( ret == 0 && !extended.crc_present() && missing_crc )
{ msg = "Missing CRC in extended records."; ret = 2; } { msg = "Missing CRC in extended records."; ret = 2; }
if( ret != 0 ) if( ret != 0 )
@ -588,7 +592,7 @@ done:
if( LZ_decompress_close( decoder ) < 0 ) if( LZ_decompress_close( decoder ) < 0 )
{ {
const Packet * const opacket = new Packet( lzip_index.members(), const Packet * const opacket = new Packet( lzip_index.members(),
"LZ_decompress_close failed.", Packet::error ); "LZ_decompress_close failed.", Packet::error );
courier.collect_packet( opacket, worker_id ); courier.collect_packet( opacket, worker_id );
} }
courier.worker_finished(); courier.worker_finished();

31
main.cc
View file

@ -61,6 +61,7 @@ namespace {
const char * const program_name = "tarlz"; const char * const program_name = "tarlz";
const char * const program_year = "2019"; const char * const program_year = "2019";
const char * invocation_name = 0; const char * invocation_name = 0;
bool dereference = false;
void show_help( const long num_online ) void show_help( const long num_online )
@ -82,7 +83,7 @@ void show_help( const long num_online )
"can be used to recover some of the damaged members.\n" "can be used to recover some of the damaged members.\n"
"\nUsage: %s [options] [files]\n", invocation_name ); "\nUsage: %s [options] [files]\n", invocation_name );
std::printf( "\nOptions:\n" std::printf( "\nOptions:\n"
" -h, --help display this help and exit\n" " --help display this help and exit\n"
" -V, --version output version information and exit\n" " -V, --version output version information and exit\n"
" -A, --concatenate append tar.lz archives to the end of an archive\n" " -A, --concatenate append tar.lz archives to the end of an archive\n"
" -B, --data-size=<bytes> set target size of input data blocks [2x8=16 MiB]\n" " -B, --data-size=<bytes> set target size of input data blocks [2x8=16 MiB]\n"
@ -90,7 +91,9 @@ void show_help( const long num_online )
" -C, --directory=<dir> change to directory <dir>\n" " -C, --directory=<dir> change to directory <dir>\n"
" -d, --diff find differences between archive and file system\n" " -d, --diff find differences between archive and file system\n"
" --ignore-ids ignore differences in owner and group IDs\n" " --ignore-ids ignore differences in owner and group IDs\n"
" --exclude=<pattern> exclude files matching a shell pattern\n"
" -f, --file=<archive> use archive file <archive>\n" " -f, --file=<archive> use archive file <archive>\n"
" -h, --dereference follow symlinks; archive the files they point to\n"
" -n, --threads=<n> set number of (de)compression threads [%ld]\n" " -n, --threads=<n> set number of (de)compression threads [%ld]\n"
" -q, --quiet suppress all messages\n" " -q, --quiet suppress all messages\n"
" -r, --append append files to the end of an archive\n" " -r, --append append files to the end of an archive\n"
@ -109,6 +112,7 @@ void show_help( const long num_online )
" --group=<group> use <group> name/ID for files added\n" " --group=<group> use <group> name/ID for files added\n"
" --keep-damaged don't delete partially extracted files\n" " --keep-damaged don't delete partially extracted files\n"
" --missing-crc exit with error status if missing extended CRC\n" " --missing-crc exit with error status if missing extended CRC\n"
" --out-slots=<n> number of 1 MiB output packets buffered [64]\n"
/* " --permissive allow repeated extended headers and records\n"*/, /* " --permissive allow repeated extended headers and records\n"*/,
num_online ); num_online );
if( verbosity >= 1 ) if( verbosity >= 1 )
@ -217,6 +221,10 @@ void set_group( const char * const arg )
} // end namespace } // end namespace
int hstat( const char * const filename, struct stat * const st )
{ return dereference ? stat( filename, st ) : lstat( filename, st ); }
int open_instream( const std::string & name ) int open_instream( const std::string & name )
{ {
const int infd = open( name.c_str(), O_RDONLY | O_BINARY ); const int infd = open( name.c_str(), O_RDONLY | O_BINARY );
@ -288,8 +296,9 @@ int main( const int argc, const char * const argv[] )
{ {
std::string archive_name; std::string archive_name;
int debug_level = 0; int debug_level = 0;
int num_workers = -1; // start this many worker threads
int level = 6; // compression level, < 0 means uncompressed int level = 6; // compression level, < 0 means uncompressed
int num_workers = -1; // start this many worker threads
int out_slots = 64;
Program_mode program_mode = m_none; Program_mode program_mode = m_none;
bool ignore_ids = false; bool ignore_ids = false;
bool keep_damaged = false; bool keep_damaged = false;
@ -301,8 +310,9 @@ int main( const int argc, const char * const argv[] )
{ show_error( "Bad library version. At least lzlib 1.0 is required." ); { show_error( "Bad library version. At least lzlib 1.0 is required." );
return 1; } return 1; }
enum { opt_ano = 256, opt_aso, opt_bso, opt_crc, opt_dbg, opt_dso, opt_grp, enum { opt_ano = 256, opt_aso, opt_bso, opt_crc, opt_dbg, opt_dso, opt_exc,
opt_id, opt_kd, opt_nso, opt_own, opt_per, opt_sol, opt_un }; opt_grp, opt_hlp, opt_id, opt_kd, opt_nso, opt_out, opt_own, opt_per,
opt_sol, opt_un };
const Arg_parser::Option options[] = const Arg_parser::Option options[] =
{ {
{ '0', 0, Arg_parser::no }, { '0', 0, Arg_parser::no },
@ -321,7 +331,7 @@ int main( const int argc, const char * const argv[] )
{ 'C', "directory", Arg_parser::yes }, { 'C', "directory", Arg_parser::yes },
{ 'd', "diff", Arg_parser::no }, { 'd', "diff", Arg_parser::no },
{ 'f', "file", Arg_parser::yes }, { 'f', "file", Arg_parser::yes },
{ 'h', "help", Arg_parser::no }, { 'h', "dereference", Arg_parser::no },
{ 'H', "format", Arg_parser::yes }, { 'H', "format", Arg_parser::yes },
{ 'n', "threads", Arg_parser::yes }, { 'n', "threads", Arg_parser::yes },
{ 'q', "quiet", Arg_parser::no }, { 'q', "quiet", Arg_parser::no },
@ -335,11 +345,14 @@ int main( const int argc, const char * const argv[] )
{ opt_bso, "bsolid", Arg_parser::no }, { opt_bso, "bsolid", Arg_parser::no },
{ opt_dbg, "debug", Arg_parser::yes }, { opt_dbg, "debug", Arg_parser::yes },
{ opt_dso, "dsolid", Arg_parser::no }, { opt_dso, "dsolid", Arg_parser::no },
{ opt_exc, "exclude", Arg_parser::yes },
{ opt_grp, "group", Arg_parser::yes }, { opt_grp, "group", Arg_parser::yes },
{ opt_hlp, "help", Arg_parser::no },
{ opt_id, "ignore-ids", Arg_parser::no }, { opt_id, "ignore-ids", Arg_parser::no },
{ opt_kd, "keep-damaged", Arg_parser::no }, { opt_kd, "keep-damaged", Arg_parser::no },
{ opt_crc, "missing-crc", Arg_parser::no }, { opt_crc, "missing-crc", Arg_parser::no },
{ opt_nso, "no-solid", Arg_parser::no }, { opt_nso, "no-solid", Arg_parser::no },
{ opt_out, "out-slots", Arg_parser::yes },
{ opt_own, "owner", Arg_parser::yes }, { opt_own, "owner", Arg_parser::yes },
{ opt_per, "permissive", Arg_parser::no }, { opt_per, "permissive", Arg_parser::no },
{ opt_sol, "solid", Arg_parser::no }, { opt_sol, "solid", Arg_parser::no },
@ -375,7 +388,7 @@ int main( const int argc, const char * const argv[] )
case 'C': break; // skip chdir case 'C': break; // skip chdir
case 'd': set_mode( program_mode, m_diff ); break; case 'd': set_mode( program_mode, m_diff ); break;
case 'f': if( sarg != "-" ) archive_name = sarg; break; case 'f': if( sarg != "-" ) archive_name = sarg; break;
case 'h': show_help( num_online ); return 0; case 'h': dereference = true; break;
case 'H': break; // ignore format case 'H': break; // ignore format
case 'n': num_workers = getnum( arg, 0, max_workers ); break; case 'n': num_workers = getnum( arg, 0, max_workers ); break;
case 'q': verbosity = -1; break; case 'q': verbosity = -1; break;
@ -390,10 +403,13 @@ int main( const int argc, const char * const argv[] )
case opt_crc: missing_crc = true; break; case opt_crc: missing_crc = true; break;
case opt_dbg: debug_level = getnum( arg, 0, 3 ); break; case opt_dbg: debug_level = getnum( arg, 0, 3 ); break;
case opt_dso: solidity = dsolid; break; case opt_dso: solidity = dsolid; break;
case opt_exc: Exclude::add_pattern( sarg ); break;
case opt_grp: set_group( arg ); break; case opt_grp: set_group( arg ); break;
case opt_hlp: show_help( num_online ); return 0;
case opt_id: ignore_ids = true; break; case opt_id: ignore_ids = true; break;
case opt_kd: keep_damaged = true; break; case opt_kd: keep_damaged = true; break;
case opt_nso: solidity = no_solid; break; case opt_nso: solidity = no_solid; break;
case opt_out: out_slots = getnum( arg, 1, 1024 ); break;
case opt_own: set_owner( arg ); break; case opt_own: set_owner( arg ); break;
case opt_per: permissive = true; break; case opt_per: permissive = true; break;
case opt_sol: solidity = solid; break; case opt_sol: solidity = solid; break;
@ -414,7 +430,8 @@ int main( const int argc, const char * const argv[] )
case m_none: show_error( "Missing operation.", 0, true ); return 2; case m_none: show_error( "Missing operation.", 0, true ); return 2;
case m_append: case m_append:
case m_create: return encode( archive_name, parser, filenames, level, case m_create: return encode( archive_name, parser, filenames, level,
num_workers, debug_level, program_mode == m_append ); num_workers, out_slots, debug_level,
program_mode == m_append, dereference );
case m_concatenate: return concatenate( archive_name, parser, filenames ); case m_concatenate: return concatenate( archive_name, parser, filenames );
case m_diff: case m_diff:
case m_extract: case m_extract:

29
tarlz.h
View file

@ -70,15 +70,17 @@ inline bool dotdot_at_i( const char * const filename, const int i )
} }
enum { initial_line_length = 1000 }; // must be >= 87 for format_member_name
class Resizable_buffer class Resizable_buffer
{ {
char * p; char * p;
unsigned long size_; // size_ < LONG_MAX unsigned long size_; // size_ < LONG_MAX
public: public:
explicit Resizable_buffer( const unsigned long initial_size ) // must be >= 87 for format_member_name
enum { default_initial_size = 2 * header_size };
explicit Resizable_buffer( const unsigned long initial_size =
default_initial_size )
: p( (char *)std::malloc( initial_size ) ), size_( p ? initial_size : 0 ) {} : p( (char *)std::malloc( initial_size ) ), size_( p ? initial_size : 0 ) {}
~Resizable_buffer() { if( p ) std::free( p ); p = 0; size_ = 0; } ~Resizable_buffer() { if( p ) std::free( p ); p = 0; size_ = 0; }
@ -136,6 +138,7 @@ public:
const std::string & linkpath() const { return linkpath_; } const std::string & linkpath() const { return linkpath_; }
const std::string & path() const { return path_; } const std::string & path() const { return path_; }
unsigned long long file_size() const { return file_size_; } unsigned long long file_size() const { return file_size_; }
unsigned long long get_file_size_and_reset( const Tar_header header );
void linkpath( const char * const lp ) { linkpath_ = lp; full_size_ = -1; } void linkpath( const char * const lp ) { linkpath_ = lp; full_size_ = -1; }
void path( const char * const p ) { path_ = p; full_size_ = -1; } void path( const char * const p ) { path_ = p; full_size_ = -1; }
@ -311,6 +314,7 @@ extern int cl_data_size;
extern Solidity solidity; extern Solidity solidity;
bool writeblock_wrapper( const int outfd, const uint8_t * const buffer, bool writeblock_wrapper( const int outfd, const uint8_t * const buffer,
const int size ); const int size );
bool write_eof_records( const int outfd, const bool compressed );
const char * remove_leading_dotslash( const char * const filename, const char * remove_leading_dotslash( const char * const filename,
const bool dotdot = false ); const bool dotdot = false );
bool fill_headers( const char * const filename, Extended & extended, bool fill_headers( const char * const filename, Extended & extended,
@ -323,23 +327,32 @@ void set_error_status( const int retval );
int final_exit_status( int retval, const bool show_msg = true ); int final_exit_status( int retval, const bool show_msg = true );
unsigned ustar_chksum( const uint8_t * const header ); unsigned ustar_chksum( const uint8_t * const header );
bool verify_ustar_chksum( const uint8_t * const header ); bool verify_ustar_chksum( const uint8_t * const header );
bool has_lz_ext( const std::string & name );
class Arg_parser; class Arg_parser;
int concatenate( const std::string & archive_name, const Arg_parser & parser, int concatenate( std::string archive_name, const Arg_parser & parser,
const int filenames ); const int filenames );
int encode( const std::string & archive_name, const Arg_parser & parser, int encode( const std::string & archive_name, const Arg_parser & parser,
const int filenames, const int level, const int num_workers, const int filenames, const int level, const int num_workers,
const int debug_level, const bool append ); const int out_slots, const int debug_level, const bool append,
const bool dereference );
// defined in create_lz.cc // defined in create_lz.cc
int encode_lz( const Arg_parser & parser, const int dictionary_size, int encode_lz( const Arg_parser & parser, const int dictionary_size,
const int match_len_limit, const int num_workers, const int match_len_limit, const int num_workers,
const int outfd, const int debug_level ); const int outfd, const int out_slots, const int debug_level,
const bool dereference );
// defined in exclude.cc
namespace Exclude {
void add_pattern( const std::string & arg );
bool excluded( const char * const filename );
} // end namespace Exclude
// defined in extract.cc // defined in extract.cc
enum Program_mode { m_none, m_append, m_concatenate, m_create, m_diff, enum Program_mode { m_none, m_append, m_concatenate, m_create, m_diff,
m_extract, m_list }; m_extract, m_list };
bool block_is_zero( const uint8_t * const buf, const int size ); bool block_is_zero( const uint8_t * const buf, const int size );
void format_member_name( const Extended & extended, const Tar_header header, bool format_member_name( const Extended & extended, const Tar_header header,
Resizable_buffer & rbuf, const bool long_format ); Resizable_buffer & rbuf, const bool long_format );
bool compare_prefix_dir( const char * const dir, const char * const name ); bool compare_prefix_dir( const char * const dir, const char * const name );
bool compare_tslash( const char * const name1, const char * const name2 ); bool compare_tslash( const char * const name1, const char * const name2 );
@ -377,6 +390,8 @@ int seek_read( const int fd, uint8_t * const buf, const int size,
// defined in main.cc // defined in main.cc
extern int verbosity; extern int verbosity;
struct stat;
int hstat( const char * const filename, struct stat * const st );
int open_instream( const std::string & name ); int open_instream( const std::string & name );
int open_outstream( const std::string & name, const bool create = true ); int open_outstream( const std::string & name, const bool create = true );
void cleanup_and_fail( const int retval = 1 ); // terminate the program void cleanup_and_fail( const int retval = 1 ); // terminate the program

View file

@ -55,6 +55,7 @@ bad3_lz="${testdir}"/test3_bad3.tar.lz
bad4_lz="${testdir}"/test3_bad4.tar.lz bad4_lz="${testdir}"/test3_bad4.tar.lz
bad5_lz="${testdir}"/test3_bad5.tar.lz bad5_lz="${testdir}"/test3_bad5.tar.lz
bad6_lz="${testdir}"/test3_bad6.tar.lz bad6_lz="${testdir}"/test3_bad6.tar.lz
eof="${testdir}"/eof.tar
eof_lz="${testdir}"/eof.tar.lz eof_lz="${testdir}"/eof.tar.lz
fail=0 fail=0
lwarn=0 lwarn=0
@ -112,17 +113,13 @@ printf "testing tarlz-%s..." "$2"
[ ! -e out.tar.lz ] || test_failed $LINENO [ ! -e out.tar.lz ] || test_failed $LINENO
"${TARLZ}" -rf out.tar.lz || test_failed $LINENO "${TARLZ}" -rf out.tar.lz || test_failed $LINENO
[ ! -e out.tar.lz ] || test_failed $LINENO [ ! -e out.tar.lz ] || test_failed $LINENO
"${TARLZ}" -q -rf - "${in}" "${TARLZ}" -r || test_failed $LINENO
[ $? = 1 ] || test_failed $LINENO
[ ! -e - ] || test_failed $LINENO
"${TARLZ}" -q -r "${in}"
[ $? = 1 ] || test_failed $LINENO
"${TARLZ}" --uncompressed -q -rf out.tar "${in}" "${TARLZ}" --uncompressed -q -rf out.tar "${in}"
[ $? = 1 ] || test_failed $LINENO [ $? = 1 ] || test_failed $LINENO
[ ! -e out.tar ] || test_failed $LINENO [ ! -e out.tar ] || test_failed $LINENO
cat "${test3_lz}" > test.tar.lz || framework_failure cat "${test3_lz}" > test.tar.lz || framework_failure
"${TARLZ}" --uncompressed -q -rf test.tar.lz "${in}" "${TARLZ}" --uncompressed -q -rf test.tar.lz "${in}"
[ $? = 1 ] || test_failed $LINENO [ $? = 2 ] || test_failed $LINENO
cmp "${test3_lz}" test.tar.lz || test_failed $LINENO cmp "${test3_lz}" test.tar.lz || test_failed $LINENO
rm -f test.tar.lz || framework_failure rm -f test.tar.lz || framework_failure
cat "${test3}" > test.tar || framework_failure cat "${test3}" > test.tar || framework_failure
@ -175,7 +172,7 @@ rm -f test.txt || framework_failure
cmp "${in}" test.txt || test_failed $LINENO cmp "${in}" test.txt || test_failed $LINENO
rm -f test.txt || framework_failure rm -f test.txt || framework_failure
# reference files for cmp # test3 reference files for cmp
cat "${testdir}"/rfoo > cfoo || framework_failure cat "${testdir}"/rfoo > cfoo || framework_failure
cat "${testdir}"/rbar > cbar || framework_failure cat "${testdir}"/rbar > cbar || framework_failure
cat "${testdir}"/rbaz > cbaz || framework_failure cat "${testdir}"/rbaz > cbaz || framework_failure
@ -223,7 +220,31 @@ cmp cfoo dir/foo || test_failed $LINENO
cmp cbar dir/bar || test_failed $LINENO cmp cbar dir/bar || test_failed $LINENO
cmp cbaz dir/baz || test_failed $LINENO cmp cbaz dir/baz || test_failed $LINENO
rm -rf dir || framework_failure rm -rf dir || framework_failure
#
# --exclude
"${TARLZ}" -xf "${test3}" --exclude='f*o' --exclude=baz || test_failed $LINENO
[ ! -e foo ] || test_failed $LINENO
cmp cbar bar || test_failed $LINENO
[ ! -e baz ] || test_failed $LINENO
rm -f foo bar baz || framework_failure
"${TARLZ}" -xf "${test3_lz}" --exclude=bar || test_failed $LINENO
cmp cfoo foo || test_failed $LINENO
[ ! -e bar ] || test_failed $LINENO
cmp cbaz baz || test_failed $LINENO
rm -f foo bar baz || framework_failure
"${TARLZ}" -q -xf "${test3dir_lz}" --exclude='?ar' || test_failed $LINENO
cmp cfoo dir/foo || test_failed $LINENO
[ ! -e dir/bar ] || test_failed $LINENO
cmp cbaz dir/baz || test_failed $LINENO
rm -rf dir || framework_failure
"${TARLZ}" -q -xf "${test3dir_lz}" --exclude=dir || test_failed $LINENO
[ ! -e dir ] || test_failed $LINENO
rm -rf dir || framework_failure
"${TARLZ}" -q -xf "${test3dir_lz}" --exclude='*o' dir/foo || test_failed $LINENO
[ ! -e dir ] || test_failed $LINENO
rm -rf dir || framework_failure
# eof
"${TARLZ}" -q -tf "${testdir}"/test3_eof1.tar.lz "${TARLZ}" -q -tf "${testdir}"/test3_eof1.tar.lz
[ $? = 2 ] || test_failed $LINENO [ $? = 2 ] || test_failed $LINENO
"${TARLZ}" -q -tf "${testdir}"/test3_eof2.tar.lz || test_failed $LINENO "${TARLZ}" -q -tf "${testdir}"/test3_eof2.tar.lz || test_failed $LINENO
@ -314,6 +335,9 @@ for i in 1 2 3 4 5 6 ; do
done done
# test --concatenate # test --concatenate
cat "${in}" > out.tar.lz || framework_failure # invalid tar.lz
"${TARLZ}" -Aqf out.tar.lz "${test3_lz}"
[ $? = 2 ] || test_failed $LINENO
cat "${in_tar_lz}" > out.tar.lz || framework_failure cat "${in_tar_lz}" > out.tar.lz || framework_failure
"${TARLZ}" -Af out.tar.lz "${test3_lz}" || test_failed $LINENO "${TARLZ}" -Af out.tar.lz "${test3_lz}" || test_failed $LINENO
"${TARLZ}" -xf out.tar.lz || test_failed $LINENO "${TARLZ}" -xf out.tar.lz || test_failed $LINENO
@ -321,14 +345,82 @@ cmp "${in}" test.txt || test_failed $LINENO
cmp cfoo foo || test_failed $LINENO cmp cfoo foo || test_failed $LINENO
cmp cbar bar || test_failed $LINENO cmp cbar bar || test_failed $LINENO
cmp cbaz baz || test_failed $LINENO cmp cbaz baz || test_failed $LINENO
rm -f test.txt foo bar baz || framework_failure
touch aout.tar.lz || framework_failure # concatenate to empty file touch aout.tar.lz || framework_failure # concatenate to empty file
"${TARLZ}" -Aqf aout.tar.lz "${in_tar}"
[ $? = 2 ] || test_failed $LINENO
"${TARLZ}" -Af aout.tar.lz "${in_tar_lz}" "${test3_lz}" || test_failed $LINENO
cmp out.tar.lz aout.tar.lz || test_failed $LINENO
"${TARLZ}" -Af aout.tar.lz || test_failed $LINENO # concatenate nothing
cmp out.tar.lz aout.tar.lz || test_failed $LINENO
"${TARLZ}" -Aqf aout.tar.lz aout.tar.lz || test_failed $LINENO
cmp out.tar.lz aout.tar.lz || test_failed $LINENO
"${TARLZ}" -Aq "${in_tar_lz}" "${test3}" > aout.tar.lz # to stdout
[ $? = 2 ] || test_failed $LINENO
cmp "${in_tar_lz}" aout.tar.lz || test_failed $LINENO
"${TARLZ}" -A "${in_tar_lz}" "${test3_lz}" > aout.tar.lz || test_failed $LINENO
cmp out.tar.lz aout.tar.lz || test_failed $LINENO
cat "${eof_lz}" > aout.tar.lz || framework_failure # concatenate to empty archive
"${TARLZ}" -Aqf aout.tar.lz "${in_tar}"
[ $? = 2 ] || test_failed $LINENO
"${TARLZ}" -Af aout.tar.lz "${in_tar_lz}" "${test3_lz}" || test_failed $LINENO "${TARLZ}" -Af aout.tar.lz "${in_tar_lz}" "${test3_lz}" || test_failed $LINENO
cmp out.tar.lz aout.tar.lz || test_failed $LINENO cmp out.tar.lz aout.tar.lz || test_failed $LINENO
cat "${in_tar_lz}" > aout.tar.lz || framework_failure cat "${in_tar_lz}" > aout.tar.lz || framework_failure
"${TARLZ}" -Aqf aout.tar.lz "${test3_lz}" "${test3}" "${TARLZ}" -Aqf aout.tar.lz "${test3_lz}" "${test3}"
[ $? = 2 ] || test_failed $LINENO [ $? = 2 ] || test_failed $LINENO
cmp out.tar.lz aout.tar.lz || test_failed $LINENO cmp out.tar.lz aout.tar.lz || test_failed $LINENO
rm -f test.txt foo bar baz out.tar.lz aout.tar.lz || framework_failure rm -f aout.tar.lz || framework_failure
touch aout.tar.lz || framework_failure # --exclude
"${TARLZ}" -Af aout.tar.lz "${in_tar_lz}" "${test3_lz}" --exclude 'test3*' ||
test_failed $LINENO
"${TARLZ}" -Af aout.tar.lz "${in_tar_lz}" "${test3_lz}" --exclude '*txt*' ||
test_failed $LINENO
cmp out.tar.lz aout.tar.lz || test_failed $LINENO
rm -f out.tar.lz aout.tar.lz || framework_failure
# --uncompressed
cat "${in}" > out.tar || framework_failure # invalid tar
"${TARLZ}" -Aqf out.tar "${test3}"
[ $? = 2 ] || test_failed $LINENO
cat "${in_tar}" > out.tar || framework_failure
"${TARLZ}" -Af out.tar "${test3}" || test_failed $LINENO
"${TARLZ}" -xf out.tar || test_failed $LINENO
cmp "${in}" test.txt || test_failed $LINENO
cmp cfoo foo || test_failed $LINENO
cmp cbar bar || test_failed $LINENO
cmp cbaz baz || test_failed $LINENO
rm -f test.txt foo bar baz || framework_failure
touch aout.tar || framework_failure # concatenate to empty file
"${TARLZ}" -Aqf aout.tar "${in_tar_lz}"
[ $? = 2 ] || test_failed $LINENO
"${TARLZ}" -Af aout.tar "${in_tar}" "${test3}" || test_failed $LINENO
cmp out.tar aout.tar || test_failed $LINENO
"${TARLZ}" -Af aout.tar || test_failed $LINENO # concatenate nothing
cmp out.tar aout.tar || test_failed $LINENO
"${TARLZ}" -Aqf aout.tar aout.tar || test_failed $LINENO
cmp out.tar aout.tar || test_failed $LINENO
"${TARLZ}" -Aq "${in_tar}" "${test3_lz}" > aout.tar # to stdout
[ $? = 2 ] || test_failed $LINENO
cmp "${in_tar}" aout.tar || test_failed $LINENO
"${TARLZ}" -A "${in_tar}" "${test3}" > aout.tar || test_failed $LINENO
cmp out.tar aout.tar || test_failed $LINENO
cat "${eof}" > aout.tar || framework_failure # concatenate to empty archive
"${TARLZ}" -Aqf aout.tar "${in_tar_lz}"
[ $? = 2 ] || test_failed $LINENO
"${TARLZ}" -Af aout.tar "${in_tar}" "${test3}" || test_failed $LINENO
cmp out.tar aout.tar || test_failed $LINENO
cat "${in_tar}" > aout.tar || framework_failure
"${TARLZ}" -Aqf aout.tar "${test3}" "${test3_lz}"
[ $? = 2 ] || test_failed $LINENO
cmp out.tar aout.tar || test_failed $LINENO
rm -f aout.tar || framework_failure
touch aout.tar || framework_failure # --exclude
"${TARLZ}" -Af aout.tar "${test3}" "${in_tar}" --exclude 'test3*' ||
test_failed $LINENO
"${TARLZ}" -Af aout.tar "${test3}" "${in_tar}" --exclude '*txt*' ||
test_failed $LINENO
cmp out.tar aout.tar || test_failed $LINENO
rm -f out.tar aout.tar || framework_failure
# test --create # test --create
cat "${in}" > test.txt || framework_failure cat "${in}" > test.txt || framework_failure
@ -346,7 +438,7 @@ rm -f test.txt out.tar out.tar.lz || framework_failure
cat cfoo > foo || framework_failure cat cfoo > foo || framework_failure
rm -f bar || framework_failure rm -f bar || framework_failure
cat cbaz > baz || framework_failure cat cbaz > baz || framework_failure
"${TARLZ}" -q -cf out.tar.lz foo bar baz "${TARLZ}" -0 -q -cf out.tar.lz foo bar baz
[ $? = 1 ] || test_failed $LINENO [ $? = 1 ] || test_failed $LINENO
rm -f foo bar baz || framework_failure rm -f foo bar baz || framework_failure
"${TARLZ}" -xf out.tar.lz --missing-crc || test_failed $LINENO "${TARLZ}" -xf out.tar.lz --missing-crc || test_failed $LINENO
@ -364,13 +456,9 @@ rm -f out.tar.lz || framework_failure
cat cfoo > foo || framework_failure cat cfoo > foo || framework_failure
cat cbar > bar || framework_failure cat cbar > bar || framework_failure
cat cbaz > baz || framework_failure cat cbaz > baz || framework_failure
"${TARLZ}" -0 -cf out.tar.lz foo bar baz || test_failed $LINENO "${TARLZ}" -0 -cf out.tar.lz foo bar baz --out-slots=1 || test_failed $LINENO
"${TARLZ}" -0 -q -cf aout.tar.lz foo bar aout.tar.lz baz || test_failed $LINENO "${TARLZ}" -0 -q -cf aout.tar.lz foo bar aout.tar.lz baz || test_failed $LINENO
cmp out.tar.lz aout.tar.lz || test_failed $LINENO cmp out.tar.lz aout.tar.lz || test_failed $LINENO
"${TARLZ}" -q -Af aout.tar.lz aout.tar.lz || test_failed $LINENO
cmp out.tar.lz aout.tar.lz || test_failed $LINENO
"${TARLZ}" -q -rf aout.tar.lz aout.tar.lz || test_failed $LINENO
cmp out.tar.lz aout.tar.lz || test_failed $LINENO
rm -f aout.tar.lz || framework_failure rm -f aout.tar.lz || framework_failure
# #
"${TARLZ}" -0 -cf aout.tar.lz foo bar baz -C / || test_failed $LINENO "${TARLZ}" -0 -cf aout.tar.lz foo bar baz -C / || test_failed $LINENO
@ -425,16 +513,81 @@ cmp cbaz dir1/baz || test_failed $LINENO
rm -rf dir1 || framework_failure rm -rf dir1 || framework_failure
rm -f out.tar.lz aout.tar.lz || framework_failure rm -f out.tar.lz aout.tar.lz || framework_failure
# --exclude
cat cfoo > foo || framework_failure
cat cbar > bar || framework_failure
cat cbaz > baz || framework_failure
"${TARLZ}" -0 -cf out.tar.lz foo bar baz --exclude 'ba?' || test_failed $LINENO
rm -f foo bar baz || framework_failure
"${TARLZ}" -xf out.tar.lz || test_failed $LINENO
cmp cfoo foo || test_failed $LINENO
[ ! -e bar ] || test_failed $LINENO
[ ! -e baz ] || test_failed $LINENO
rm -f out.tar.lz foo bar baz || framework_failure
cat cfoo > foo || framework_failure
cat cbar > bar || framework_failure
cat cbaz > baz || framework_failure
"${TARLZ}" --un -cf out.tar foo bar baz --exclude 'ba*' || test_failed $LINENO
rm -f foo bar baz || framework_failure
"${TARLZ}" -xf out.tar || test_failed $LINENO
cmp cfoo foo || test_failed $LINENO
[ ! -e bar ] || test_failed $LINENO
[ ! -e baz ] || test_failed $LINENO
rm -f out.tar foo bar baz || framework_failure
# test --dereference
touch dummy_file || framework_failure
if ln dummy_file dummy_link 2> /dev/null &&
ln -s dummy_file dummy_slink 2> /dev/null ; then
ln_works=yes
else
printf "\nwarning: skipping link test: 'ln' does not work on your system."
fi
rm -f dummy_slink dummy_link dummy_file || framework_failure
if [ "${ln_works}" = yes ] ; then
mkdir dir || framework_failure
cat cfoo > dir/foo || framework_failure
cat cbar > dir/bar || framework_failure
cat cbaz > dir/baz || framework_failure
ln -s dir dir_link || framework_failure
"${TARLZ}" -0 -cf out1 dir_link || test_failed $LINENO
"${TARLZ}" --un -cf out2 dir_link || test_failed $LINENO
"${TARLZ}" -0 -n0 -cf out3 dir_link || test_failed $LINENO
"${TARLZ}" -0 -h -cf hout1 dir_link || test_failed $LINENO
"${TARLZ}" --un -h -cf hout2 dir_link || test_failed $LINENO
"${TARLZ}" -0 -n0 -h -cf hout3 dir_link || test_failed $LINENO
rm -rf dir dir_link || framework_failure
for i in 1 2 3 ; do
"${TARLZ}" -xf out$i || test_failed $LINENO $i
[ -h dir_link ] || test_failed $LINENO $i
[ ! -e dir_link/foo ] || test_failed $LINENO $i
[ ! -e dir_link/bar ] || test_failed $LINENO $i
[ ! -e dir_link/baz ] || test_failed $LINENO $i
rm -rf dir_link out$i || framework_failure
"${TARLZ}" -xf hout$i || test_failed $LINENO $i
[ -d dir_link ] || test_failed $LINENO $i
cmp cfoo dir_link/foo || test_failed $LINENO $i
cmp cbar dir_link/bar || test_failed $LINENO $i
cmp cbaz dir_link/baz || test_failed $LINENO $i
rm -rf dir_link hout$i || framework_failure
done
fi
# test --append # test --append
cat cfoo > foo || framework_failure cat cfoo > foo || framework_failure
cat cbar > bar || framework_failure cat cbar > bar || framework_failure
cat cbaz > baz || framework_failure cat cbaz > baz || framework_failure
"${TARLZ}" -0 -cf out.tar.lz foo bar baz || test_failed $LINENO "${TARLZ}" -0 -cf out.tar.lz foo bar baz --out-slots=1024 || test_failed $LINENO
"${TARLZ}" -0 -cf nout.tar.lz foo bar baz --no-solid || test_failed $LINENO "${TARLZ}" -0 -cf nout.tar.lz foo bar baz --no-solid || test_failed $LINENO
"${TARLZ}" -0 -cf aout.tar.lz foo || test_failed $LINENO "${TARLZ}" -0 -cf aout.tar.lz foo || test_failed $LINENO
"${TARLZ}" -0 -rf aout.tar.lz bar baz --no-solid || test_failed $LINENO "${TARLZ}" -0 -rf aout.tar.lz bar baz --no-solid || test_failed $LINENO
cmp nout.tar.lz aout.tar.lz || test_failed $LINENO cmp nout.tar.lz aout.tar.lz || test_failed $LINENO
rm -f nout.tar.lz aout.tar.lz || framework_failure rm -f nout.tar.lz aout.tar.lz || framework_failure
touch aout.tar || framework_failure # wrong extension empty file
"${TARLZ}" -0 -rf aout.tar foo bar baz || test_failed $LINENO
cmp out.tar.lz aout.tar || test_failed $LINENO
rm -f aout.tar || framework_failure
touch aout.tar.lz || framework_failure # append to empty file touch aout.tar.lz || framework_failure # append to empty file
"${TARLZ}" -0 -rf aout.tar.lz foo bar baz || test_failed $LINENO "${TARLZ}" -0 -rf aout.tar.lz foo bar baz || test_failed $LINENO
cmp out.tar.lz aout.tar.lz || test_failed $LINENO cmp out.tar.lz aout.tar.lz || test_failed $LINENO
@ -445,10 +598,55 @@ cmp out.tar.lz aout.tar.lz || test_failed $LINENO
"${TARLZ}" -0 -q -rf aout.tar.lz nx_file "${TARLZ}" -0 -q -rf aout.tar.lz nx_file
[ $? = 1 ] || test_failed $LINENO [ $? = 1 ] || test_failed $LINENO
cmp out.tar.lz aout.tar.lz || test_failed $LINENO cmp out.tar.lz aout.tar.lz || test_failed $LINENO
cat "${eof_lz}" > aout.tar.lz || framework_failure # append to empty archive "${TARLZ}" -0 -q -rf aout.tar.lz aout.tar.lz || test_failed $LINENO
cmp out.tar.lz aout.tar.lz || test_failed $LINENO
"${TARLZ}" -0 -r foo bar baz > aout.tar.lz || test_failed $LINENO # to stdout
cmp out.tar.lz aout.tar.lz || test_failed $LINENO
"${TARLZ}" --un -q -rf aout.tar.lz foo bar baz # wrong extension archive
[ $? = 2 ] || test_failed $LINENO
cmp out.tar.lz aout.tar.lz || test_failed $LINENO
cat "${eof_lz}" > aout.tar.lz || framework_failure # append to empty archive
"${TARLZ}" -0 -rf aout.tar.lz foo bar baz || test_failed $LINENO "${TARLZ}" -0 -rf aout.tar.lz foo bar baz || test_failed $LINENO
cmp out.tar.lz aout.tar.lz || test_failed $LINENO cmp out.tar.lz aout.tar.lz || test_failed $LINENO
"${TARLZ}" --un -q -rf aout.tar.lz foo bar baz # wrong extension empty archive
[ $? = 2 ] || test_failed $LINENO
cmp out.tar.lz aout.tar.lz || test_failed $LINENO
rm -f out.tar.lz aout.tar.lz || framework_failure rm -f out.tar.lz aout.tar.lz || framework_failure
#
"${TARLZ}" --un -cf out.tar foo bar baz || test_failed $LINENO
"${TARLZ}" --un -cf aout.tar foo || test_failed $LINENO
"${TARLZ}" --un -rf aout.tar foo bar baz --exclude foo || test_failed $LINENO
cmp out.tar aout.tar || test_failed $LINENO
rm -f aout.tar || framework_failure
touch aout.tar.lz empty || framework_failure # wrong extension empty file
"${TARLZ}" --un -q -rf aout.tar.lz foo bar baz
[ $? = 2 ] || test_failed $LINENO
cmp aout.tar.lz empty || test_failed $LINENO
rm -f aout.tar.lz empty || framework_failure
touch aout.tar || framework_failure # append to empty file
"${TARLZ}" --un -rf aout.tar foo bar baz || test_failed $LINENO
cmp out.tar aout.tar || test_failed $LINENO
"${TARLZ}" --un -rf aout.tar || test_failed $LINENO # append nothing
cmp out.tar aout.tar || test_failed $LINENO
"${TARLZ}" --un -rf aout.tar -C nx_dir || test_failed $LINENO
cmp out.tar aout.tar || test_failed $LINENO
"${TARLZ}" --un -q -rf aout.tar nx_file
[ $? = 1 ] || test_failed $LINENO
cmp out.tar aout.tar || test_failed $LINENO
"${TARLZ}" --un -q -rf aout.tar aout.tar || test_failed $LINENO
cmp out.tar aout.tar || test_failed $LINENO
"${TARLZ}" --un -r foo bar baz > aout.tar || test_failed $LINENO # to stdout
cmp out.tar aout.tar || test_failed $LINENO
"${TARLZ}" -0 -q -rf aout.tar foo bar baz # wrong extension archive
[ $? = 2 ] || test_failed $LINENO
cmp out.tar aout.tar || test_failed $LINENO
cat "${eof}" > aout.tar || framework_failure # append to empty archive
"${TARLZ}" --un -rf aout.tar foo bar baz || test_failed $LINENO
cmp out.tar aout.tar || test_failed $LINENO
"${TARLZ}" -0 -q -rf aout.tar foo bar baz # wrong extension empty archive
[ $? = 2 ] || test_failed $LINENO
cmp out.tar aout.tar || test_failed $LINENO
rm -f out.tar aout.tar || framework_failure
# append to solid archive # append to solid archive
"${TARLZ}" --solid -q -0 -cf out.tar.lz "${in}" foo bar || test_failed $LINENO "${TARLZ}" --solid -q -0 -cf out.tar.lz "${in}" foo bar || test_failed $LINENO
@ -483,9 +681,11 @@ rm -f foo bar baz || framework_failure
if cmp out.tar aout.tar > /dev/null ; then if cmp out.tar aout.tar > /dev/null ; then
printf "\nwarning: --diff test can't be run as root." printf "\nwarning: --diff test can't be run as root."
else else
"${TARLZ}" -q -df "${test3_lz}" "${TARLZ}" -df "${test3_lz}" > /dev/null
[ $? = 1 ] || test_failed $LINENO [ $? = 1 ] || test_failed $LINENO
"${TARLZ}" -df "${test3_lz}" --ignore-ids || test_failed $LINENO "${TARLZ}" -df "${test3_lz}" --ignore-ids || test_failed $LINENO
"${TARLZ}" -df "${test3_lz}" --exclude '*' || test_failed $LINENO
"${TARLZ}" -df "${in_tar_lz}" --exclude '*' || test_failed $LINENO
fi fi
rm -f out.tar aout.tar foo bar baz || framework_failure rm -f out.tar aout.tar foo bar baz || framework_failure
@ -505,10 +705,7 @@ rmdir dir1 || framework_failure
rmdir dir1 rmdir dir1
rm -f out.tar || framework_failure rm -f out.tar || framework_failure
touch dummy_file || framework_failure if [ "${ln_works}" = yes ] ; then
if ln dummy_file dummy_link 2> /dev/null &&
ln -s dummy_file dummy_slink 2> /dev/null ; then
ln_works=yes
name_100=name_100_bytes_long_nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn name_100=name_100_bytes_long_nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
path_100=dir1/dir2/dir3/path_100_bytes_long_nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn path_100=dir1/dir2/dir3/path_100_bytes_long_nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
path_106=dir1/dir2/dir3/path_longer_than_100_bytes_nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn path_106=dir1/dir2/dir3/path_longer_than_100_bytes_nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
@ -543,15 +740,12 @@ if ln dummy_file dummy_link 2> /dev/null &&
cmp "${in}" dir1/dir2/dir3/link || test_failed $LINENO cmp "${in}" dir1/dir2/dir3/link || test_failed $LINENO
"${TARLZ}" -0 -q -c ../tmp/dir1 | "${TARLZ}" -x || test_failed $LINENO "${TARLZ}" -0 -q -c ../tmp/dir1 | "${TARLZ}" -x || test_failed $LINENO
diff -ru tmp/dir1 dir1 || test_failed $LINENO diff -ru tmp/dir1 dir1 || test_failed $LINENO
rm -rf tmp/dir1 dir1 || framework_failure rm -rf tmp dir1 || framework_failure
"${TARLZ}" -xf "${testdir}"/ts_in_link.tar.lz || test_failed $LINENO "${TARLZ}" -xf "${testdir}"/ts_in_link.tar.lz || test_failed $LINENO
"${TARLZ}" -df "${testdir}"/ts_in_link.tar.lz --ignore-ids || "${TARLZ}" -df "${testdir}"/ts_in_link.tar.lz --ignore-ids ||
test_failed $LINENO test_failed $LINENO
rm -f link1 link2 link3 link4 || framework_failure rm -f link1 link2 link3 link4 || framework_failure
else
printf "\nwarning: skipping link test: 'ln' does not work on your system."
fi fi
rm -f dummy_slink dummy_link dummy_file || framework_failure
printf "\ntesting long names..." printf "\ntesting long names..."

BIN
testsuite/eof.tar Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.