diff --git a/ChangeLog b/ChangeLog index d17580b..77358bd 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2024-01-03 Antonio Diaz Diaz + + * Version 0.25 released. + * New option '--ignore-metadata. + * create.cc, decode.cc, decode_lz.cc: + '#include ' for major, minor, makedev on BSD systems. + * compress.cc: Reformat file diagnostics as 'PROGRAM: FILE: MESSAGE'. + (compress_archive): Create missing intermediate directories. + * configure, Makefile.in: New variable 'MAKEINFO'. + 2023-09-20 Antonio Diaz Diaz * Version 0.24 released. @@ -42,7 +52,7 @@ * main.cc (getnum): Show option name and valid range if error. (check_lib): Check that LZ_API_VERSION and LZ_version_string match. (main): Report an error if -o is used with any operation except -z. - * Set variable LIBS from configure. + * configure: Set variable LIBS. 2021-06-14 Antonio Diaz Diaz @@ -59,7 +69,7 @@ * Version 0.19 released. * extended.cc: Print a diagnostic for each unknown keyword found. - * tarlz.h: Add a missing '#include '. + * tarlz.h: Add a missing '#include ' for 'mode_t'. 2020-11-21 Antonio Diaz Diaz @@ -219,7 +229,7 @@ * Version 0.1 released. -Copyright (C) 2013-2023 Antonio Diaz Diaz. +Copyright (C) 2013-2024 Antonio Diaz Diaz. This file is a collection of facts, and thus it is not copyrightable, but just in case, you have unlimited permission to copy, distribute, and diff --git a/INSTALL b/INSTALL index 4b3c2b2..4de87a7 100644 --- a/INSTALL +++ b/INSTALL @@ -4,12 +4,11 @@ You will need a C++98 compiler with support for 'long long', and the compression library lzlib installed. (gcc 3.3.6 or newer is recommended). I use gcc 6.1.0 and 3.3.6, but the code should compile with any standards compliant compiler. - -Lzlib must be version 1.12 or newer. - Gcc is available at http://gcc.gnu.org. Lzlib is available at http://www.nongnu.org/lzip/lzlib.html. +Lzlib must be version 1.12 or newer. + The operating system must allow signal handlers read access to objects with static storage duration so that the cleanup handler for Control-C can delete the partial output file in '-z, --compress' mode. @@ -75,7 +74,7 @@ After running 'configure', you can run 'make' and 'make install' as explained above. -Copyright (C) 2013-2023 Antonio Diaz Diaz. +Copyright (C) 2013-2024 Antonio Diaz Diaz. This file is free documentation: you have unlimited permission to copy, distribute, and modify it. diff --git a/Makefile.in b/Makefile.in index a07cb9e..76c1fc8 100644 --- a/Makefile.in +++ b/Makefile.in @@ -29,6 +29,10 @@ main.o : main.cc %.o : %.cc $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $< +# prevent 'make' from trying to remake source files +$(VPATH)/configure $(VPATH)/Makefile.in $(VPATH)/doc/$(pkgname).texi : ; +%.h %.cc : ; + $(objs) : Makefile arg_parser.o : arg_parser.h archive_reader.o : tarlz.h lzip_index.h archive_reader.h @@ -48,7 +52,6 @@ extended.o : tarlz.h lzip_index.o : tarlz.h lzip_index.h main.o : tarlz.h arg_parser.h - doc : info man info : $(VPATH)/doc/$(pkgname).info diff --git a/NEWS b/NEWS index d2e6732..108d6ea 100644 --- a/NEWS +++ b/NEWS @@ -1,17 +1,14 @@ -Changes in version 0.24: +Changes in version 0.25: -The option '-C, --directory' is now position-dependent also for diff and -extract. (Reported by Devon Sean McCullough). +The new option '--ignore-metadata', which makes '-d, --diff' ignore +differences in file permissions, owner and group IDs, and modification time, +has been added. -Option '--uncompressed' can now be omitted if it can be deduced from the -archive name. (An uncompressed archive name lacks a '.lz' or '.tlz' extension). +'#include ' for major, minor, makedev on BSD systems. -The diagnostic shown when a file being archived or an archive being -compressed fails to read, now shows the cause of the failure; end-of-file or -read error. +File diagnostics of '-z' have been reformatted as 'PROGRAM: FILE: MESSAGE'. -'-z, --compress' now exits with error status 2 if any input archive is an -empty file. +The option '-o, --output' now creates missing intermediate directories when +compressing to a file. -A failure in the '--diff' test of the testsuite on OS/2 has been fixed. -(Reported by Elbert Pol). +The variable MAKEINFO has been added to configure and Makefile.in. diff --git a/README b/README index 50d63ed..5f1dedb 100644 --- a/README +++ b/README @@ -6,7 +6,7 @@ lzlib. Tarlz creates tar archives using a simplified and safer variant of the POSIX pax format compressed in lzip format, keeping the alignment between tar -members and lzip members. The resulting multimember tar.lz archive is fully +members and lzip members. The resulting multimember tar.lz archive is backward compatible with standard 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. @@ -61,7 +61,7 @@ large, making undetected corruption and archiver misbehavior more probable. Headers and metadata must be protected separately from data because the integrity checking of lzip may not be able to detect the corruption before -the metadata has been used, for example, to create a new file in the wrong +the metadata have been used, for example, to create a new file in the wrong place. Because of the above, tarlz protects the extended records with a Cyclic @@ -87,11 +87,10 @@ tar.lz +===============+=================================================+========+ -Copyright (C) 2013-2023 Antonio Diaz Diaz. +Copyright (C) 2013-2024 Antonio Diaz Diaz. This file is free documentation: you have unlimited permission to copy, distribute, and modify it. -The file Makefile.in is a data file used by configure to produce the -Makefile. It has the same copyright owner and permissions that configure -itself. +The file Makefile.in is a data file used by configure to produce the Makefile. +It has the same copyright owner and permissions that configure itself. diff --git a/archive_reader.cc b/archive_reader.cc index 5adcd08..c4438ae 100644 --- a/archive_reader.cc +++ b/archive_reader.cc @@ -1,5 +1,5 @@ /* Tarlz - Archiver with multimember lzip compression - Copyright (C) 2013-2023 Antonio Diaz Diaz. + Copyright (C) 2013-2024 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 @@ -76,7 +76,7 @@ void xLZ_decompress_write( LZ_Decoder * const decoder, Archive_descriptor::Archive_descriptor( const std::string & archive_name ) : name( archive_name ), namep( name.empty() ? "(stdin)" : name.c_str() ), infd( non_tty_infd( archive_name, namep ) ), - lzip_index( infd, true, false ), + lzip_index( infd ), seekable( lseek( infd, 0, SEEK_SET ) == 0 ), indexed( seekable && lzip_index.retval() == 0 ) {} diff --git a/archive_reader.h b/archive_reader.h index 9eb7b3d..e8963e0 100644 --- a/archive_reader.h +++ b/archive_reader.h @@ -1,5 +1,5 @@ /* Tarlz - Archiver with multimember lzip compression - Copyright (C) 2013-2023 Antonio Diaz Diaz. + Copyright (C) 2013-2024 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 diff --git a/arg_parser.cc b/arg_parser.cc index 5d46a9d..0c04d8e 100644 --- a/arg_parser.cc +++ b/arg_parser.cc @@ -1,5 +1,5 @@ -/* Arg_parser - POSIX/GNU command line argument parser. (C++ version) - Copyright (C) 2006-2023 Antonio Diaz Diaz. +/* Arg_parser - POSIX/GNU command-line argument parser. (C++ version) + Copyright (C) 2006-2024 Antonio Diaz Diaz. This library is free software. Redistribution and use in source and binary forms, with or without modification, are permitted provided diff --git a/arg_parser.h b/arg_parser.h index 272e919..1eeec9a 100644 --- a/arg_parser.h +++ b/arg_parser.h @@ -1,5 +1,5 @@ -/* Arg_parser - POSIX/GNU command line argument parser. (C++ version) - Copyright (C) 2006-2023 Antonio Diaz Diaz. +/* Arg_parser - POSIX/GNU command-line argument parser. (C++ version) + Copyright (C) 2006-2024 Antonio Diaz Diaz. This library is free software. Redistribution and use in source and binary forms, with or without modification, are permitted provided diff --git a/common.cc b/common.cc index d43a43c..b653e01 100644 --- a/common.cc +++ b/common.cc @@ -1,5 +1,5 @@ /* Tarlz - Archiver with multimember lzip compression - Copyright (C) 2013-2023 Antonio Diaz Diaz. + Copyright (C) 2013-2024 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 diff --git a/common_decode.cc b/common_decode.cc index 99956ff..a0ff89d 100644 --- a/common_decode.cc +++ b/common_decode.cc @@ -1,5 +1,5 @@ /* Tarlz - Archiver with multimember lzip compression - Copyright (C) 2013-2023 Antonio Diaz Diaz. + Copyright (C) 2013-2024 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 @@ -227,24 +227,23 @@ bool check_skip_filename( const Cl_options & cl_opts, } -bool make_path( const std::string & name ) +bool make_dirs( const std::string & name ) { - const mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; - unsigned end = name.size(); // first slash before last component + int i = name.size(); + while( i > 0 && name[i-1] == '/' ) --i; // remove trailing slashes + while( i > 0 && name[i-1] != '/' ) --i; // remove last component + while( i > 0 && name[i-1] == '/' ) --i; // remove more slashes + const int dirsize = i; // first slash before last component - while( end > 0 && name[end-1] == '/' ) --end; // remove trailing slashes - while( end > 0 && name[end-1] != '/' ) --end; // remove last component - while( end > 0 && name[end-1] == '/' ) --end; // remove more slashes - - unsigned index = 0; - while( index < end ) + for( i = 0; i < dirsize; ) // if dirsize == 0, dirname is '/' or empty { - while( index < end && name[index] == '/' ) ++index; - unsigned first = index; - while( index < end && name[index] != '/' ) ++index; - if( first < index ) + while( i < dirsize && name[i] == '/' ) ++i; + const int first = i; + while( i < dirsize && name[i] != '/' ) ++i; + if( first < i ) { - const std::string partial( name, 0, index ); + const std::string partial( name, 0, i ); + const mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; struct stat st; if( lstat( partial.c_str(), &st ) == 0 ) { if( !S_ISDIR( st.st_mode ) ) { errno = ENOTDIR; return false; } } diff --git a/common_mutex.cc b/common_mutex.cc index 5149b93..fb253ed 100644 --- a/common_mutex.cc +++ b/common_mutex.cc @@ -1,5 +1,5 @@ /* Tarlz - Archiver with multimember lzip compression - Copyright (C) 2013-2023 Antonio Diaz Diaz. + Copyright (C) 2013-2024 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 diff --git a/common_mutex.h b/common_mutex.h index f7f1b5f..ed3999c 100644 --- a/common_mutex.h +++ b/common_mutex.h @@ -1,5 +1,5 @@ /* Tarlz - Archiver with multimember lzip compression - Copyright (C) 2013-2023 Antonio Diaz Diaz. + Copyright (C) 2013-2024 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 diff --git a/compress.cc b/compress.cc index 43fc537..3091889 100644 --- a/compress.cc +++ b/compress.cc @@ -1,5 +1,5 @@ /* Tarlz - Archiver with multimember lzip compression - Copyright (C) 2013-2023 Antonio Diaz Diaz. + Copyright (C) 2013-2024 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 @@ -53,12 +53,11 @@ void cleanup_and_fail( const int retval ) if( delete_output_on_interrupt ) { delete_output_on_interrupt = false; - if( verbosity >= 0 ) - std::fprintf( stderr, "%s: Deleting output file '%s', if it exists.\n", - program_name, output_filename.c_str() ); + show_file_error( output_filename.c_str(), + "Deleting output file, if it exists." ); if( outfd >= 0 ) { close( outfd ); outfd = -1; } if( std::remove( output_filename.c_str() ) != 0 && errno != ENOENT ) - show_error( "WARNING: deletion of output file (apparently) failed." ); + show_error( "warning: deletion of output file failed", errno ); } std::exit( retval ); } @@ -103,7 +102,7 @@ void close_and_set_permissions( const struct stat * const in_statsp ) if( in_statsp ) { const mode_t mode = in_statsp->st_mode; - // fchown will in many cases return with EPERM, which can be safely ignored. + // fchown in many cases returns with EPERM, which can be safely ignored. if( fchown( outfd, in_statsp->st_uid, in_statsp->st_gid ) == 0 ) { if( fchmod( outfd, mode ) != 0 ) warning = true; } else @@ -112,10 +111,8 @@ void close_and_set_permissions( const struct stat * const in_statsp ) warning = true; } if( close( outfd ) != 0 ) - { - show_error( "Error closing output file", errno ); - cleanup_and_fail( 1 ); - } + { show_file_error( output_filename.c_str(), "Error closing output file", + errno ); cleanup_and_fail( 1 ); } outfd = -1; delete_output_on_interrupt = false; if( in_statsp ) @@ -126,7 +123,8 @@ void close_and_set_permissions( const struct stat * const in_statsp ) if( utime( output_filename.c_str(), &t ) != 0 ) warning = true; } if( warning && verbosity >= 1 ) - show_error( "Can't change output file attributes." ); + show_file_error( output_filename.c_str(), + "warning: can't change output file attributes", errno ); } @@ -233,6 +231,9 @@ int compress_archive( const Cl_options & cl_opts, if( to_file && outfd < 0 && ( is_header || is_zero ) ) { // open outfd after checking infd + if( !make_dirs( output_filename ) ) + { show_file_error( output_filename.c_str(), intdir_msg, errno ); + return 1; } outfd = open_outstream( output_filename, true, 0, false ); // check tty only once and don't try to delete a tty if( outfd < 0 || !check_tty_out() ) { close( infd ); return 1; } diff --git a/configure b/configure index e1e3aac..37dbeac 100755 --- a/configure +++ b/configure @@ -1,12 +1,12 @@ #! /bin/sh # configure script for Tarlz - Archiver with multimember lzip compression -# Copyright (C) 2013-2023 Antonio Diaz Diaz. +# Copyright (C) 2013-2024 Antonio Diaz Diaz. # # This configure script is free software: you have unlimited permission # to copy, distribute, and modify it. pkgname=tarlz -pkgversion=0.24 +pkgversion=0.25 progname=tarlz srctrigger=doc/${pkgname}.texi @@ -36,7 +36,7 @@ no_create= while [ $# != 0 ] ; do # Get the first arg, and shuffle - option="$1" ; arg2=no + option=$1 ; arg2=no shift # Add the argument quoted to args @@ -44,12 +44,12 @@ while [ $# != 0 ] ; do else args="${args} \"${option}\"" ; fi # Split out the argument for options that take them - case "${option}" in - *=*) optarg="`echo "${option}" | sed -e 's,^[^=]*=,,;s,/$,,'`" ;; + case ${option} in + *=*) optarg=`echo "${option}" | sed -e 's,^[^=]*=,,;s,/$,,'` ;; esac # Process the options - case "${option}" in + case ${option} in --help | -h) echo "Usage: $0 [OPTION]... [VAR=VALUE]..." echo @@ -67,10 +67,10 @@ while [ $# != 0 ] ; do echo " --infodir=DIR info files directory [${infodir}]" echo " --mandir=DIR man pages directory [${mandir}]" echo " CXX=COMPILER C++ compiler to use [${CXX}]" - echo " CPPFLAGS=OPTIONS command line options for the preprocessor [${CPPFLAGS}]" - echo " CXXFLAGS=OPTIONS command line options for the C++ compiler [${CXXFLAGS}]" + echo " CPPFLAGS=OPTIONS command-line options for the preprocessor [${CPPFLAGS}]" + echo " CXXFLAGS=OPTIONS command-line options for the C++ compiler [${CXXFLAGS}]" echo " CXXFLAGS+=OPTIONS append options to the current value of CXXFLAGS" - echo " LDFLAGS=OPTIONS command line options for the linker [${LDFLAGS}]" + echo " LDFLAGS=OPTIONS command-line options for the linker [${LDFLAGS}]" echo " LIBS=OPTIONS libraries to pass to the linker [${LIBS}]" echo " MAKEINFO=NAME makeinfo program to use [${MAKEINFO}]" echo @@ -78,30 +78,30 @@ while [ $# != 0 ] ; do --version | -V) echo "Configure script for ${pkgname} version ${pkgversion}" exit 0 ;; - --srcdir) srcdir="$1" ; arg2=yes ;; - --prefix) prefix="$1" ; arg2=yes ;; - --exec-prefix) exec_prefix="$1" ; arg2=yes ;; - --bindir) bindir="$1" ; arg2=yes ;; - --datarootdir) datarootdir="$1" ; arg2=yes ;; - --infodir) infodir="$1" ; arg2=yes ;; - --mandir) mandir="$1" ; arg2=yes ;; + --srcdir) srcdir=$1 ; arg2=yes ;; + --prefix) prefix=$1 ; arg2=yes ;; + --exec-prefix) exec_prefix=$1 ; arg2=yes ;; + --bindir) bindir=$1 ; arg2=yes ;; + --datarootdir) datarootdir=$1 ; arg2=yes ;; + --infodir) infodir=$1 ; arg2=yes ;; + --mandir) mandir=$1 ; arg2=yes ;; - --srcdir=*) srcdir="${optarg}" ;; - --prefix=*) prefix="${optarg}" ;; - --exec-prefix=*) exec_prefix="${optarg}" ;; - --bindir=*) bindir="${optarg}" ;; - --datarootdir=*) datarootdir="${optarg}" ;; - --infodir=*) infodir="${optarg}" ;; - --mandir=*) mandir="${optarg}" ;; - --no-create) no_create=yes ;; + --srcdir=*) srcdir=${optarg} ;; + --prefix=*) prefix=${optarg} ;; + --exec-prefix=*) exec_prefix=${optarg} ;; + --bindir=*) bindir=${optarg} ;; + --datarootdir=*) datarootdir=${optarg} ;; + --infodir=*) infodir=${optarg} ;; + --mandir=*) mandir=${optarg} ;; + --no-create) no_create=yes ;; - CXX=*) CXX="${optarg}" ;; - CPPFLAGS=*) CPPFLAGS="${optarg}" ;; - CXXFLAGS=*) CXXFLAGS="${optarg}" ;; + CXX=*) CXX=${optarg} ;; + CPPFLAGS=*) CPPFLAGS=${optarg} ;; + CXXFLAGS=*) CXXFLAGS=${optarg} ;; CXXFLAGS+=*) CXXFLAGS="${CXXFLAGS} ${optarg}" ;; - LDFLAGS=*) LDFLAGS="${optarg}" ;; + LDFLAGS=*) LDFLAGS=${optarg} ;; LIBS=*) LIBS="${optarg} ${LIBS}" ;; - MAKEINFO=*) MAKEINFO="${optarg}" ;; + MAKEINFO=*) MAKEINFO=${optarg} ;; --*) echo "configure: WARNING: unrecognized option: '${option}'" 1>&2 ;; @@ -128,7 +128,7 @@ if [ -z "${srcdir}" ] ; then if [ ! -r "${srcdir}/${srctrigger}" ] ; then srcdir=.. ; fi if [ ! -r "${srcdir}/${srctrigger}" ] ; then ## the sed command below emulates the dirname command - srcdir="`echo "$0" | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'`" + srcdir=`echo "$0" | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` fi fi @@ -175,7 +175,7 @@ echo "MAKEINFO = ${MAKEINFO}" rm -f Makefile cat > Makefile << EOF # Makefile for Tarlz - Archiver with multimember lzip compression -# Copyright (C) 2013-2023 Antonio Diaz Diaz. +# Copyright (C) 2013-2024 Antonio Diaz Diaz. # This file was generated automatically by configure. Don't edit. # # This Makefile is free software: you have unlimited permission @@ -201,5 +201,5 @@ EOF cat "${srcdir}/Makefile.in" >> Makefile echo "OK. Now you can run make." -echo "If make fails, check that the compression library lzlib is correctly" -echo "installed (see INSTALL)." +echo "If make fails, check that the compression library lzlib is correctly installed" +echo "(see INSTALL)." diff --git a/create.cc b/create.cc index 11c4041..5878dd3 100644 --- a/create.cc +++ b/create.cc @@ -1,5 +1,5 @@ /* Tarlz - Archiver with multimember lzip compression - Copyright (C) 2013-2023 Antonio Diaz Diaz. + Copyright (C) 2013-2024 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 @@ -26,6 +26,8 @@ #if !defined __FreeBSD__ && !defined __OpenBSD__ && !defined __NetBSD__ && \ !defined __DragonFly__ && !defined __APPLE__ && !defined __OS2__ #include // for major, minor +#else +#include // for major, minor #endif #include #include diff --git a/create.h b/create.h index 0b0c83b..d5ef7bc 100644 --- a/create.h +++ b/create.h @@ -1,5 +1,5 @@ /* Tarlz - Archiver with multimember lzip compression - Copyright (C) 2013-2023 Antonio Diaz Diaz. + Copyright (C) 2013-2024 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 diff --git a/create_lz.cc b/create_lz.cc index b848d06..5436bf5 100644 --- a/create_lz.cc +++ b/create_lz.cc @@ -1,5 +1,5 @@ /* Tarlz - Archiver with multimember lzip compression - Copyright (C) 2013-2023 Antonio Diaz Diaz. + Copyright (C) 2013-2024 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 diff --git a/decode.cc b/decode.cc index 1742df2..bcac4c8 100644 --- a/decode.cc +++ b/decode.cc @@ -1,5 +1,5 @@ /* Tarlz - Archiver with multimember lzip compression - Copyright (C) 2013-2023 Antonio Diaz Diaz. + Copyright (C) 2013-2024 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 @@ -29,6 +29,8 @@ #if !defined __FreeBSD__ && !defined __OpenBSD__ && !defined __NetBSD__ && \ !defined __DragonFly__ && !defined __APPLE__ && !defined __OS2__ #include // for major, minor, makedev +#else +#include // for major, minor, makedev #endif #include @@ -38,6 +40,9 @@ #include "archive_reader.h" #include "decode.h" +#ifndef O_DIRECTORY +#define O_DIRECTORY 0 +#endif namespace { @@ -124,7 +129,7 @@ int extract_member( const Cl_options & cl_opts, Archive_reader & ar, if( !show_member_name( extended, header, 1, grbuf ) ) return 1; // remove file (or empty dir) before extraction to prevent following links std::remove( filename ); - if( !make_path( filename ) ) + if( !make_dirs( filename ) ) { show_file_error( filename, intdir_msg, errno ); set_error_status( 1 ); @@ -192,7 +197,7 @@ int extract_member( const Cl_options & cl_opts, Archive_reader & ar, chown( filename, extended.get_uid(), extended.get_gid() ) != 0 ) ) { if( outfd >= 0 ) mode &= ~( S_ISUID | S_ISGID | S_ISVTX ); - // chown will in many cases return with EPERM, which can be safely ignored. + // chown in many cases returns with EPERM, which can be safely ignored. if( errno != EPERM && errno != EINVAL ) { show_file_error( filename, chown_msg, errno ); set_error_status( 1 ); } } @@ -286,7 +291,7 @@ bool compare_file_type( std::string & estr, std::string & ostr, struct stat st; bool diff = false, size_differs = false, type_differs = true; if( hstat( filename, &st, cl_opts.dereference ) != 0 ) - format_file_error( estr, filename, "warning: Can't stat", errno ); + format_file_error( estr, filename, "warning: can't stat", errno ); else if( ( typeflag == tf_regular || typeflag == tf_hiperf ) && !S_ISREG( st.st_mode ) ) format_file_diff( ostr, filename, "Is not a regular file" ); @@ -303,14 +308,14 @@ bool compare_file_type( std::string & estr, std::string & ostr, else { type_differs = false; - if( typeflag != tf_symlink ) + if( typeflag != tf_symlink && !cl_opts.ignore_metadata ) { const mode_t mode = parse_octal( header + mode_o, mode_l ); // 12 bits if( mode != ( st.st_mode & ( S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO ) ) ) { format_file_diff( ostr, filename, "Mode differs" ); diff = true; } } - if( !cl_opts.ignore_ids ) + if( !cl_opts.ignore_ids && !cl_opts.ignore_metadata ) { if( extended.get_uid() != (long long)st.st_uid ) { format_file_diff( ostr, filename, "Uid differs" ); diff = true; } @@ -319,7 +324,7 @@ bool compare_file_type( std::string & estr, std::string & ostr, } if( typeflag != tf_symlink ) { - if( typeflag != tf_directory && + if( typeflag != tf_directory && !cl_opts.ignore_metadata && extended.mtime().sec() != (long long)st.st_mtime ) { if( (time_t)extended.mtime().sec() == st.st_mtime ) diff --git a/decode.h b/decode.h index 1649b81..05d3072 100644 --- a/decode.h +++ b/decode.h @@ -1,5 +1,5 @@ /* Tarlz - Archiver with multimember lzip compression - Copyright (C) 2013-2023 Antonio Diaz Diaz. + Copyright (C) 2013-2024 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 @@ -23,7 +23,6 @@ inline bool uid_gid_in_range( const long long uid, const long long gid ) gid == (long long)( (gid_t)gid ); } const char * const dotdot_msg = "Contains a '..' component, skipping."; -const char * const intdir_msg = "Failed to create intermediate directory"; const char * const cantln_msg = "Can't %slink '%s' to '%s'"; const char * const mkdir_msg = "Can't create directory"; const char * const mknod_msg = "Can't create device node"; diff --git a/decode_lz.cc b/decode_lz.cc index 15ac2a7..867ffa5 100644 --- a/decode_lz.cc +++ b/decode_lz.cc @@ -1,5 +1,5 @@ /* Tarlz - Archiver with multimember lzip compression - Copyright (C) 2013-2023 Antonio Diaz Diaz. + Copyright (C) 2013-2024 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 @@ -29,6 +29,8 @@ #if !defined __FreeBSD__ && !defined __OpenBSD__ && !defined __NetBSD__ && \ !defined __DragonFly__ && !defined __APPLE__ && !defined __OS2__ #include // for major, minor, makedev +#else +#include // for major, minor, makedev #endif #include @@ -258,10 +260,10 @@ Trival skip_member_lz( Archive_reader_i & ar, Packet_courier & courier, Trival compare_member_lz( const Cl_options & cl_opts, - Archive_reader_i & ar, Packet_courier & courier, - const Extended & extended, const Tar_header header, - Resizable_buffer & rbuf, const long member_id, - const int worker_id ) + Archive_reader_i & ar, Packet_courier & courier, + const Extended & extended, const Tar_header header, + Resizable_buffer & rbuf, const long member_id, + const int worker_id ) { if( verbosity < 1 ) rbuf()[0] = 0; else if( !format_member_name( extended, header, rbuf, verbosity > 1 ) ) @@ -357,7 +359,7 @@ Trival extract_member_lz( const Cl_options & cl_opts, /* Remove file before extraction to prevent following links. Don't remove an empty dir because other thread may need it. */ if( typeflag != tf_directory ) std::remove( filename ); - if( !make_path( filename ) ) + if( !make_dirs( filename ) ) { if( format_file_error( rbuf, filename, intdir_msg, errno ) && !courier.collect_packet( member_id, worker_id, rbuf(), Packet::diag ) ) @@ -451,7 +453,7 @@ Trival extract_member_lz( const Cl_options & cl_opts, chown( filename, extended.get_uid(), extended.get_gid() ) != 0 ) ) { if( outfd >= 0 ) mode &= ~( S_ISUID | S_ISGID | S_ISVTX ); - // chown will in many cases return with EPERM, which can be safely ignored. + // chown in many cases returns with EPERM, which can be safely ignored. if( errno != EPERM && errno != EINVAL ) { if( format_file_error( rbuf, filename, chown_msg, errno ) && diff --git a/delete.cc b/delete.cc index 3aeac42..6e54cf3 100644 --- a/delete.cc +++ b/delete.cc @@ -1,5 +1,5 @@ /* Tarlz - Archiver with multimember lzip compression - Copyright (C) 2013-2023 Antonio Diaz Diaz. + Copyright (C) 2013-2024 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 diff --git a/delete_lz.cc b/delete_lz.cc index 7824a66..b67efa0 100644 --- a/delete_lz.cc +++ b/delete_lz.cc @@ -1,5 +1,5 @@ /* Tarlz - Archiver with multimember lzip compression - Copyright (C) 2013-2023 Antonio Diaz Diaz. + Copyright (C) 2013-2024 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 diff --git a/doc/tarlz.1 b/doc/tarlz.1 index 9b6ddad..9d63da5 100644 --- a/doc/tarlz.1 +++ b/doc/tarlz.1 @@ -1,5 +1,5 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.2. -.TH TARLZ "1" "September 2023" "tarlz 0.24" "User Commands" +.TH TARLZ "1" "January 2024" "tarlz 0.25" "User Commands" .SH NAME tarlz \- creates tar archives with multimember lzip compression .SH SYNOPSIS @@ -10,12 +10,12 @@ Tarlz is a massively parallel (multi\-threaded) combined implementation of the tar archiver and the lzip compressor. Tarlz uses the compression library lzlib. .PP -Tarlz creates, lists, and extracts archives in a simplified and safer -variant of the POSIX pax format compressed in lzip format, keeping the -alignment between tar members and lzip members. The resulting multimember -tar.lz archive is fully backward compatible with standard 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 creates tar archives using a simplified and safer variant of the POSIX +pax format compressed in lzip format, keeping the alignment between tar +members and lzip members. The resulting multimember tar.lz archive is +backward compatible with standard 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. .PP Keeping the alignment between tar members and lzip members has two advantages. It adds an indexed lzip layer on top of the tar archive, making @@ -127,6 +127,9 @@ exclude files matching a shell pattern \fB\-\-ignore\-ids\fR ignore differences in owner and group IDs .TP +\fB\-\-ignore\-metadata\fR +compare only file size and file content +.TP \fB\-\-ignore\-overflow\fR ignore mtime overflow differences on 32\-bit .TP @@ -149,7 +152,7 @@ If no archive is specified, tarlz tries to read it from standard input or write it to standard output. .PP Exit status: 0 for a normal exit, 1 for environmental problems -(file not found, files differ, invalid command line options, I/O errors, +(file not found, files differ, invalid command\-line options, I/O errors, etc), 2 to indicate a corrupt or invalid input file, 3 for an internal consistency error (e.g., bug) which caused tarlz to panic. .SH "REPORTING BUGS" @@ -157,8 +160,8 @@ Report bugs to lzip\-bug@nongnu.org .br Tarlz home page: http://www.nongnu.org/lzip/tarlz.html .SH COPYRIGHT -Copyright \(co 2023 Antonio Diaz Diaz. -Using lzlib 1.13 +Copyright \(co 2024 Antonio Diaz Diaz. +Using lzlib 1.14\-rc1 License GPLv2+: GNU GPL version 2 or later .br This is free software: you are free to change and redistribute it. diff --git a/doc/tarlz.info b/doc/tarlz.info index fa6682d..25ba882 100644 --- a/doc/tarlz.info +++ b/doc/tarlz.info @@ -11,12 +11,12 @@ File: tarlz.info, Node: Top, Next: Introduction, Up: (dir) Tarlz Manual ************ -This manual is for Tarlz (version 0.24, 20 September 2023). +This manual is for Tarlz (version 0.25, 3 January 2024). * Menu: * Introduction:: Purpose and features of tarlz -* Invoking tarlz:: Command line interface +* Invoking tarlz:: Command-line interface * Portable character set:: POSIX portable filename character set * File format:: Detailed format of the compressed archive * Amendments to pax format:: The reasons for the differences with pax @@ -28,7 +28,7 @@ This manual is for Tarlz (version 0.24, 20 September 2023). * Concept index:: Index of concepts - Copyright (C) 2013-2023 Antonio Diaz Diaz. + Copyright (C) 2013-2024 Antonio Diaz Diaz. This manual is free documentation: you have unlimited permission to copy, distribute, and modify it. @@ -46,8 +46,8 @@ library lzlib. Tarlz creates tar archives using a simplified and safer variant of the POSIX pax format compressed in lzip format, keeping the alignment between tar members and lzip members. The resulting multimember tar.lz archive is -fully backward compatible with standard tar tools like GNU tar, which treat -it like any other tar.lz archive. Tarlz can append files to the end of such +backward compatible with standard 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. Keeping the alignment between tar members and lzip members has two @@ -433,6 +433,12 @@ to '-1 --solid'. Make '--diff' ignore differences in owner and group IDs. This option is useful when comparing an '--anonymous' archive. +'--ignore-metadata' + Make '--diff' ignore any differences in metadata (file permissions, + owner and group IDs, modification time). Compare only file type, file + size, and file content. This option is useful when file permissions + have not been fully restored because uid/gid changed on extraction. + '--ignore-overflow' Make '--diff' ignore differences in mtime caused by overflow on 32-bit systems with a 32-bit time_t. @@ -485,7 +491,7 @@ to '-1 --solid'. Exit status: 0 for a normal exit, 1 for environmental problems (file not -found, files differ, invalid command line options, I/O errors, etc), 2 to +found, files differ, invalid command-line options, I/O errors, etc), 2 to indicate a corrupt or invalid input file, 3 for an internal consistency error (e.g., bug) which caused tarlz to panic. @@ -529,7 +535,7 @@ In the diagram below, a box like this: bytes (for example 512). - A tar.lz file consists of a series of lzip members (compressed data + A tar.lz file consists of one or more lzip members (compressed data sets). The members simply appear one after another in the file, with no additional information before, between, or after them. @@ -564,7 +570,7 @@ binary zeros, interpreted as an end-of-archive indicator. These EOA blocks are either compressed in a separate lzip member or compressed along with the tar members contained in the last lzip member. For a compressed archive to be recognized by tarlz as appendable, the last lzip member must contain -between 512 and 32256 zeros alone. +between 512 and 32256 zeros alone (without any non-zero bytes). The diagram below shows the correspondence between each tar member (formed by one or two headers plus optional data) in the tar archive and @@ -618,7 +624,7 @@ space, equal-sign, and newline. 'gid' The unsigned decimal representation of the group ID of the group that owns the following file. The gid record is created only for files with - a group ID greater than 2_097_151 (octal 7777777). *Note + a group ID greater than 2_097_151 (octal 7_777_777). *Note ustar-uid-gid::. 'linkpath' @@ -653,13 +659,14 @@ space, equal-sign, and newline. digits from the ISO/IEC 646:1991 (ASCII) standard. This record overrides the field 'size' in the following ustar header block. The size record is created only for files with a size value greater than - 8_589_934_591 (octal 77777777777); that is, 8 GiB (2^33 bytes) or + 8_589_934_591 (octal 77_777_777_777); that is, 8 GiB (2^33 bytes) or larger. 'uid' The unsigned decimal representation of the user ID of the file owner of the following file. The uid record is created only for files with a - user ID greater than 2_097_151 (octal 7777777). *Note ustar-uid-gid::. + user ID greater than 2_097_151 (octal 7_777_777). *Note + ustar-uid-gid::. 'GNU.crc32' CRC32-C (Castagnoli) of the extended header data excluding the 8 bytes @@ -737,7 +744,7 @@ S_IROTH 00004 S_IWOTH 00002 S_IXOTH 00001 The fields 'uid' and 'gid' are the user and group IDs of the owner and group of the file, respectively. If the file uid or gid are greater than -2_097_151 (octal 7777777), an extended record is used to store the uid or +2_097_151 (octal 7_777_777), an extended record is used to store the uid or gid. The field 'size' contains the octal representation of the size of the @@ -747,13 +754,13 @@ records following the header is (size / 512) rounded to the next integer. For all other values of typeflag, tarlz either sets the size field to 0 or ignores it, and does not store or expect any logical records following the header. If the file size is larger than 8_589_934_591 bytes -(octal 77777777777), an extended record is used to store the file size. +(octal 77_777_777_777), an extended record is used to store the file size. The field 'mtime' contains the octal representation of the modification time of the file at the time it was archived, obtained from the function 'stat'. If the modification time is negative or larger than 8_589_934_591 -(octal 77777777777) seconds since the epoch, an extended record is used to -store the modification time. The ustar range of mtime goes from +(octal 77_777_777_777) seconds since the epoch, an extended record is used +to store the modification time. The ustar range of mtime goes from '1970-01-01 00:00:00 UTC' to '2242-03-16 12:56:31 UTC'. The field 'chksum' contains the octal representation of the value of the @@ -835,7 +842,7 @@ more probable. Headers and metadata must be protected separately from data because the integrity checking of lzip may not be able to detect the corruption before -the metadata has been used, for example, to create a new file in the wrong +the metadata have been used, for example, to create a new file in the wrong place. Because of the above, tarlz protects the extended records with a Cyclic @@ -923,7 +930,7 @@ There is no portable way to tell what charset a text string is coded into. Therefore, tarlz stores all fields representing text strings unmodified, without conversion to UTF-8 nor any other transformation. This prevents accidental double UTF-8 conversions. If the need arises this behavior will -be adjusted with a command line option in the future. +be adjusted with a command-line option in the future.  File: tarlz.info, Node: Program design, Next: Multi-threaded decoding, Prev: Amendments to pax format, Up: Top @@ -1252,25 +1259,25 @@ Concept index  Tag Table: Node: Top216 -Node: Introduction1210 -Node: Invoking tarlz4041 -Ref: --data-size13085 -Ref: --bsolid17521 -Node: Portable character set23119 -Node: File format23762 -Ref: key_crc3230703 -Ref: ustar-uid-gid33968 -Ref: ustar-mtime34770 -Node: Amendments to pax format36770 -Ref: crc3237479 -Ref: flawed-compat38790 -Node: Program design42872 -Node: Multi-threaded decoding46797 -Ref: mt-extraction50078 -Node: Minimum archive sizes51384 -Node: Examples53511 -Node: Problems55878 -Node: Concept index56433 +Node: Introduction1207 +Node: Invoking tarlz4032 +Ref: --data-size13076 +Ref: --bsolid17512 +Node: Portable character set23425 +Node: File format24068 +Ref: key_crc3231050 +Ref: ustar-uid-gid34315 +Ref: ustar-mtime35122 +Node: Amendments to pax format37125 +Ref: crc3237834 +Ref: flawed-compat39146 +Node: Program design43228 +Node: Multi-threaded decoding47153 +Ref: mt-extraction50434 +Node: Minimum archive sizes51740 +Node: Examples53867 +Node: Problems56234 +Node: Concept index56789  End Tag Table diff --git a/doc/tarlz.texi b/doc/tarlz.texi index 2c757e8..f37164f 100644 --- a/doc/tarlz.texi +++ b/doc/tarlz.texi @@ -6,8 +6,8 @@ @finalout @c %**end of header -@set UPDATED 20 September 2023 -@set VERSION 0.24 +@set UPDATED 3 January 2024 +@set VERSION 0.25 @dircategory Archiving @direntry @@ -37,7 +37,7 @@ This manual is for Tarlz (version @value{VERSION}, @value{UPDATED}). @menu * Introduction:: Purpose and features of tarlz -* Invoking tarlz:: Command line interface +* Invoking tarlz:: Command-line interface * Portable character set:: POSIX portable filename character set * File format:: Detailed format of the compressed archive * Amendments to pax format:: The reasons for the differences with pax @@ -50,7 +50,7 @@ This manual is for Tarlz (version @value{VERSION}, @value{UPDATED}). @end menu @sp 1 -Copyright @copyright{} 2013-2023 Antonio Diaz Diaz. +Copyright @copyright{} 2013-2024 Antonio Diaz Diaz. This manual is free documentation: you have unlimited permission to copy, distribute, and modify it. @@ -68,7 +68,7 @@ compression library @uref{http://www.nongnu.org/lzip/lzlib.html,,lzlib}. Tarlz creates tar archives using a simplified and safer variant of the POSIX pax format compressed in lzip format, keeping the alignment between tar -members and lzip members. The resulting multimember tar.lz archive is fully +members and lzip members. The resulting multimember tar.lz archive is backward compatible with standard 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. @@ -477,6 +477,12 @@ Multiple @option{--exclude} options can be specified. Make @option{--diff} ignore differences in owner and group IDs. This option is useful when comparing an @option{--anonymous} archive. +@item --ignore-metadata +Make @option{--diff} ignore any differences in metadata (file permissions, +owner and group IDs, modification time). Compare only file type, file size, +and file content. This option is useful when file permissions have not been +fully restored because uid/gid changed on extraction. + @item --ignore-overflow Make @option{--diff} ignore differences in mtime caused by overflow on 32-bit systems with a 32-bit time_t. @@ -534,7 +540,7 @@ keyword appearing in the same block of extended records. @end table Exit status: 0 for a normal exit, 1 for environmental problems -(file not found, files differ, invalid command line options, I/O errors, +(file not found, files differ, invalid command-line options, I/O errors, etc), 2 to indicate a corrupt or invalid input file, 3 for an internal consistency error (e.g., bug) which caused tarlz to panic. @@ -582,7 +588,7 @@ represents a variable number of bytes or a fixed but large number of bytes (for example 512). @sp 1 -A tar.lz file consists of a series of lzip members (compressed data sets). +A tar.lz file consists of one or more lzip members (compressed data sets). The members simply appear one after another in the file, with no additional information before, between, or after them. @@ -622,7 +628,7 @@ binary zeros, interpreted as an end-of-archive indicator. These EOA blocks are either compressed in a separate lzip member or compressed along with the tar members contained in the last lzip member. For a compressed archive to be recognized by tarlz as appendable, the last lzip member must contain -between 512 and 32256 zeros alone. +between 512 and 32256 zeros alone (without any non-zero bytes). The diagram below shows the correspondence between each tar member (formed by one or two headers plus optional data) in the tar archive and each @@ -694,7 +700,7 @@ time outside of the ustar range. @xref{ustar-mtime}. @item gid The unsigned decimal representation of the group ID of the group that owns the following file. The gid record is created only for files with a group ID -greater than 2_097_151 (octal 7777777). @xref{ustar-uid-gid}. +greater than 2_097_151 @w{(octal 7_777_777)}. @xref{ustar-uid-gid}. @item linkpath The file name of a link being created to another file, of any type, @@ -726,12 +732,12 @@ The size of the file in bytes, expressed as a decimal number using digits from the ISO/IEC 646:1991 (ASCII) standard. This record overrides the field @samp{size} in the following ustar header block. The size record is created only for files with a size value greater than 8_589_934_591 -@w{(octal 77777777777)}; that is, @w{8 GiB} (2^33 bytes) or larger. +@w{(octal 77_777_777_777)}; that is, @w{8 GiB} (2^33 bytes) or larger. @item uid The unsigned decimal representation of the user ID of the file owner of the following file. The uid record is created only for files with a user ID -greater than 2_097_151 (octal 7777777). @xref{ustar-uid-gid}. +greater than 2_097_151 @w{(octal 7_777_777)}. @xref{ustar-uid-gid}. @anchor{key_crc32} @item GNU.crc32 @@ -815,7 +821,8 @@ table shows the symbolic name of each bit and its octal value: @anchor{ustar-uid-gid} The fields @samp{uid} and @samp{gid} are the user and group IDs of the owner and group of the file, respectively. If the file uid or gid are greater than -2_097_151 (octal 7777777), an extended record is used to store the uid or gid. +2_097_151 @w{(octal 7_777_777)}, an extended record is used to store the uid +or gid. The field @samp{size} contains the octal representation of the size of the file in bytes. If the field @samp{typeflag} specifies a file of type '0' @@ -824,13 +831,13 @@ records following the header is @w{(size / 512)} rounded to the next integer. For all other values of typeflag, tarlz either sets the size field to 0 or ignores it, and does not store or expect any logical records following the header. If the file size is larger than 8_589_934_591 bytes -@w{(octal 77777777777)}, an extended record is used to store the file size. +@w{(octal 77_777_777_777)}, an extended record is used to store the file size. @anchor{ustar-mtime} The field @samp{mtime} contains the octal representation of the modification time of the file at the time it was archived, obtained from the function @samp{stat}. If the modification time is negative or larger than -8_589_934_591 @w{(octal 77777777777)} seconds since the epoch, an extended +8_589_934_591 @w{(octal 77_777_777_777)} seconds since the epoch, an extended record is used to store the modification time. The ustar range of mtime goes from @w{@samp{1970-01-01 00:00:00 UTC}} to @w{@samp{2242-03-16 12:56:31 UTC}}. @@ -914,7 +921,7 @@ large, making undetected corruption and archiver misbehavior more probable. Headers and metadata must be protected separately from data because the integrity checking of lzip may not be able to detect the corruption before -the metadata has been used, for example, to create a new file in the wrong +the metadata have been used, for example, to create a new file in the wrong place. Because of the above, tarlz protects the extended records with a Cyclic @@ -999,7 +1006,7 @@ There is no portable way to tell what charset a text string is coded into. Therefore, tarlz stores all fields representing text strings unmodified, without conversion to UTF-8 nor any other transformation. This prevents accidental double UTF-8 conversions. If the need arises this behavior will -be adjusted with a command line option in the future. +be adjusted with a command-line option in the future. @node Program design diff --git a/exclude.cc b/exclude.cc index 31a4e37..44a53a5 100644 --- a/exclude.cc +++ b/exclude.cc @@ -1,5 +1,5 @@ /* Tarlz - Archiver with multimember lzip compression - Copyright (C) 2013-2023 Antonio Diaz Diaz. + Copyright (C) 2013-2024 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 diff --git a/extended.cc b/extended.cc index 80a4980..0dfba9b 100644 --- a/extended.cc +++ b/extended.cc @@ -1,5 +1,5 @@ /* Tarlz - Archiver with multimember lzip compression - Copyright (C) 2013-2023 Antonio Diaz Diaz. + Copyright (C) 2013-2024 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 @@ -354,7 +354,7 @@ bool Extended::parse( const char * const buf, const int edsize, if( stored_crc != computed_crc ) { if( verbosity >= 2 ) - std::fprintf( stderr, "CRC32C = %08X\n", (unsigned)computed_crc ); + std::fprintf( stderr, "CRC32-C = %08X\n", (unsigned)computed_crc ); return false; } } diff --git a/lzip_index.cc b/lzip_index.cc index 63952de..bcdc54f 100644 --- a/lzip_index.cc +++ b/lzip_index.cc @@ -1,5 +1,5 @@ /* Tarlz - Archiver with multimember lzip compression - Copyright (C) 2013-2023 Antonio Diaz Diaz. + Copyright (C) 2013-2024 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 @@ -47,17 +47,16 @@ const char * bad_version( const unsigned version ) } // end namespace -bool Lzip_index::check_header_error( const Lzip_header & header, - const bool first ) +bool Lzip_index::check_header( const Lzip_header & header, const bool first ) { if( !header.check_magic() ) { error_ = bad_magic_msg; retval_ = 2; if( first ) bad_magic_ = true; - return true; } + return false; } if( !header.check_version() ) - { error_ = bad_version( header.version() ); retval_ = 2; return true; } + { error_ = bad_version( header.version() ); retval_ = 2; return false; } if( !isvalid_ds( header.dictionary_size() ) ) - { error_ = bad_dict_msg; retval_ = 2; return true; } - return false; + { error_ = bad_dict_msg; retval_ = 2; return false; } + return true; } void Lzip_index::set_errno_error( const char * const msg ) @@ -78,16 +77,14 @@ void Lzip_index::set_num_error( const char * const msg, unsigned long long num ) bool Lzip_index::read_header( const int fd, Lzip_header & header, const long long pos ) { - if( seek_read( fd, header.data, Lzip_header::size, pos ) != Lzip_header::size ) + if( seek_read( fd, header.data, header.size, pos ) != header.size ) { set_errno_error( "Error reading member header: " ); return false; } return true; } // If successful, push last member and set pos to member header. -bool Lzip_index::skip_trailing_data( const int fd, unsigned long long & pos, - const bool ignore_trailing, - const bool loose_trailing ) +bool Lzip_index::skip_trailing_data( const int fd, unsigned long long & pos ) { if( pos < min_member_size ) return false; enum { block_size = 16384, @@ -108,34 +105,31 @@ bool Lzip_index::skip_trailing_data( const int fd, unsigned long long & pos, if( buffer[i-1] <= max_msb ) // most significant byte of member_size { const Lzip_trailer & trailer = - *(const Lzip_trailer *)( buffer + i - Lzip_trailer::size ); + *(const Lzip_trailer *)( buffer + i - trailer.size ); const unsigned long long member_size = trailer.member_size(); if( member_size == 0 ) // skip trailing zeros - { while( i > Lzip_trailer::size && buffer[i-9] == 0 ) --i; continue; } - if( member_size > ipos + i || !trailer.check_consistency() ) - continue; + { while( i > trailer.size && buffer[i-9] == 0 ) --i; continue; } + if( member_size > ipos + i || !trailer.check_consistency() ) continue; Lzip_header header; if( !read_header( fd, header, ipos + i - member_size ) ) return false; if( !header.check() ) continue; const Lzip_header & header2 = *(const Lzip_header *)( buffer + i ); - const bool full_h2 = bsize - i >= Lzip_header::size; + const bool full_h2 = bsize - i >= header.size; if( header2.check_prefix( bsize - i ) ) // last member { if( !full_h2 ) error_ = "Last member in input file is truncated."; - else if( !check_header_error( header2, false ) ) + else if( check_header( header2, false ) ) error_ = "Last member in input file is truncated or corrupt."; retval_ = 2; return false; } - if( !loose_trailing && full_h2 && header2.check_corrupt() ) + if( full_h2 && header2.check_corrupt() ) { error_ = corrupt_mm_msg; retval_ = 2; return false; } - if( !ignore_trailing ) - { error_ = trailing_msg; retval_ = 2; return false; } - pos = ipos + i - member_size; + pos = ipos + i - member_size; // good member const unsigned dictionary_size = header.dictionary_size(); - member_vector.push_back( Member( 0, trailer.data_size(), pos, - member_size, dictionary_size ) ); if( dictionary_size_ < dictionary_size ) dictionary_size_ = dictionary_size; + member_vector.push_back( Member( 0, trailer.data_size(), pos, + member_size, dictionary_size ) ); return true; } if( ipos == 0 ) @@ -150,8 +144,7 @@ bool Lzip_index::skip_trailing_data( const int fd, unsigned long long & pos, } -Lzip_index::Lzip_index( const int infd, const bool ignore_trailing, - const bool loose_trailing ) +Lzip_index::Lzip_index( const int infd ) : insize( lseek( infd, 0, SEEK_END ) ), retval_( 0 ), dictionary_size_( 0 ), bad_magic_( false ) { @@ -164,42 +157,38 @@ Lzip_index::Lzip_index( const int infd, const bool ignore_trailing, retval_ = 2; return; } Lzip_header header; - if( !read_header( infd, header, 0 ) ) return; - if( check_header_error( header, true ) ) return; + if( !read_header( infd, header, 0 ) || + !check_header( header, true ) ) return; unsigned long long pos = insize; // always points to a header or to EOF while( pos >= min_member_size ) { Lzip_trailer trailer; - if( seek_read( infd, trailer.data, Lzip_trailer::size, - pos - Lzip_trailer::size ) != Lzip_trailer::size ) + if( seek_read( infd, trailer.data, trailer.size, pos - trailer.size ) != + trailer.size ) { set_errno_error( "Error reading member trailer: " ); break; } const unsigned long long member_size = trailer.member_size(); if( member_size > pos || !trailer.check_consistency() ) // bad trailer { if( member_vector.empty() ) - { if( skip_trailing_data( infd, pos, ignore_trailing, loose_trailing ) ) - continue; else return; } - set_num_error( "Bad trailer at pos ", pos - Lzip_trailer::size ); - break; + { if( skip_trailing_data( infd, pos ) ) continue; return; } + set_num_error( "Bad trailer at pos ", pos - trailer.size ); break; } if( !read_header( infd, header, pos - member_size ) ) break; if( !header.check() ) // bad header { if( member_vector.empty() ) - { if( skip_trailing_data( infd, pos, ignore_trailing, loose_trailing ) ) - continue; else return; } - set_num_error( "Bad header at pos ", pos - member_size ); - break; + { if( skip_trailing_data( infd, pos ) ) continue; return; } + set_num_error( "Bad header at pos ", pos - member_size ); break; } - pos -= member_size; + pos -= member_size; // good member const unsigned dictionary_size = header.dictionary_size(); - member_vector.push_back( Member( 0, trailer.data_size(), pos, - member_size, dictionary_size ) ); if( dictionary_size_ < dictionary_size ) dictionary_size_ = dictionary_size; + member_vector.push_back( Member( 0, trailer.data_size(), pos, + member_size, dictionary_size ) ); } - if( pos != 0 || member_vector.empty() ) + if( pos != 0 || member_vector.empty() || retval_ != 0 ) { member_vector.clear(); if( retval_ == 0 ) { error_ = "Can't create file index."; retval_ = 2; } diff --git a/lzip_index.h b/lzip_index.h index a1aee9a..822f537 100644 --- a/lzip_index.h +++ b/lzip_index.h @@ -1,5 +1,5 @@ /* Tarlz - Archiver with multimember lzip compression - Copyright (C) 2013-2023 Antonio Diaz Diaz. + Copyright (C) 2013-2024 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 @@ -22,7 +22,7 @@ class Block { - long long pos_, size_; // pos + size <= INT64_MAX + long long pos_, size_; // pos >= 0, size >= 0, pos + size <= INT64_MAX public: Block( const long long p, const long long s ) : pos_( p ), size_( s ) {} @@ -43,9 +43,11 @@ class Lzip_index Block dblock, mblock; // data block, member block unsigned dictionary_size; - Member( const long long dp, const long long ds, - const long long mp, const long long ms, const unsigned dict_size ) - : dblock( dp, ds ), mblock( mp, ms ), dictionary_size( dict_size ) {} + Member( const long long dpos, const long long dsize, + const long long mpos, const long long msize, + const unsigned dict_size ) + : dblock( dpos, dsize ), mblock( mpos, msize ), + dictionary_size( dict_size ) {} }; std::vector< Member > member_vector; @@ -55,16 +57,14 @@ class Lzip_index unsigned dictionary_size_; // largest dictionary size in the file bool bad_magic_; // bad magic in first header - bool check_header_error( const Lzip_header & header, const bool first ); + bool check_header( const Lzip_header & header, const bool first ); void set_errno_error( const char * const msg ); void set_num_error( const char * const msg, unsigned long long num ); bool read_header( const int fd, Lzip_header & header, const long long pos ); - bool skip_trailing_data( const int fd, unsigned long long & pos, - const bool ignore_trailing, const bool loose_trailing ); + bool skip_trailing_data( const int fd, unsigned long long & pos ); public: - Lzip_index( const int infd, const bool ignore_trailing, - const bool loose_trailing ); + Lzip_index( const int infd ); long members() const { return member_vector.size(); } const std::string & error() const { return error_; } diff --git a/main.cc b/main.cc index cee9261..db37f76 100644 --- a/main.cc +++ b/main.cc @@ -1,5 +1,5 @@ /* Tarlz - Archiver with multimember lzip compression - Copyright (C) 2013-2023 Antonio Diaz Diaz. + Copyright (C) 2013-2024 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 @@ -16,7 +16,7 @@ */ /* Exit status: 0 for a normal exit, 1 for environmental problems - (file not found, files differ, invalid command line options, I/O errors, + (file not found, files differ, invalid command-line options, I/O errors, etc), 2 to indicate a corrupt or invalid input file, 3 for an internal consistency error (e.g., bug) which caused tarlz to panic. */ @@ -56,7 +56,7 @@ const char * const program_name = "tarlz"; namespace { -const char * const program_year = "2023"; +const char * const program_year = "2024"; const char * invocation_name = program_name; // default value @@ -65,12 +65,12 @@ void show_help( const long num_online ) std::printf( "Tarlz is a massively parallel (multi-threaded) combined implementation of\n" "the tar archiver and the lzip compressor. Tarlz uses the compression library\n" "lzlib.\n" - "\nTarlz creates, lists, and extracts archives in a simplified and safer\n" - "variant of the POSIX pax format compressed in lzip format, keeping the\n" - "alignment between tar members and lzip members. The resulting multimember\n" - "tar.lz archive is fully backward compatible with standard tar tools like GNU\n" - "tar, which treat it like any other tar.lz archive. Tarlz can append files to\n" - "the end of such compressed archives.\n" + "\nTarlz creates tar archives using a simplified and safer variant of the POSIX\n" + "pax format compressed in lzip format, keeping the alignment between tar\n" + "members and lzip members. The resulting multimember tar.lz archive is\n" + "backward compatible with standard tar tools like GNU tar, which treat it\n" + "like any other tar.lz archive. Tarlz can append files to the end of such\n" + "compressed archives.\n" "\nKeeping the alignment between tar members and lzip members has two\n" "advantages. It adds an indexed lzip layer on top of the tar archive, making\n" "it possible to decode the archive safely in parallel. It also minimizes the\n" @@ -116,6 +116,7 @@ void show_help( const long num_online ) " --group= use name/ID for files added to archive\n" " --exclude= exclude files matching a shell pattern\n" " --ignore-ids ignore differences in owner and group IDs\n" + " --ignore-metadata compare only file size and file content\n" " --ignore-overflow ignore mtime overflow differences on 32-bit\n" " --keep-damaged don't delete partially extracted files\n" " --missing-crc exit with error status if missing extended CRC\n" @@ -131,7 +132,7 @@ void show_help( const long num_online ) std::printf( "\nIf no archive is specified, tarlz tries to read it from standard input or\n" "write it to standard output.\n" "\nExit status: 0 for a normal exit, 1 for environmental problems\n" - "(file not found, files differ, invalid command line options, I/O errors,\n" + "(file not found, files differ, invalid command-line options, I/O errors,\n" "etc), 2 to indicate a corrupt or invalid input file, 3 for an internal\n" "consistency error (e.g., bug) which caused tarlz to panic.\n" "\nReport bugs to lzip-bug@nongnu.org\n" @@ -211,9 +212,9 @@ int check_lib() // separate numbers of 5 or more digits in groups of 3 digits using '_' const char * format_num3( long long num ) { + enum { buffers = 8, bufsize = 4 * sizeof num, n = 10 }; const char * const si_prefix = "kMGTPEZYRQ"; const char * const binary_prefix = "KMGTPEZYRQ"; - enum { buffers = 8, bufsize = 4 * sizeof num }; static char buffer[buffers][bufsize]; // circle of static buffers for printf static int current = 0; @@ -221,14 +222,17 @@ const char * format_num3( long long num ) char * p = buf + bufsize - 1; // fill the buffer backwards *p = 0; // terminator const bool negative = num < 0; - char prefix = 0; // try binary first, then si - for( int i = 0; i < 8 && num != 0 && ( num / 1024 ) * 1024 == num; ++i ) - { num /= 1024; prefix = binary_prefix[i]; } - if( prefix ) *(--p) = 'i'; - else - for( int i = 0; i < 8 && num != 0 && ( num / 1000 ) * 1000 == num; ++i ) - { num /= 1000; prefix = si_prefix[i]; } - if( prefix ) *(--p) = prefix; + if( num > 1024 || num < -1024 ) + { + char prefix = 0; // try binary first, then si + for( int i = 0; i < n && num != 0 && num % 1024 == 0; ++i ) + { num /= 1024; prefix = binary_prefix[i]; } + if( prefix ) *(--p) = 'i'; + else + for( int i = 0; i < n && num != 0 && num % 1000 == 0; ++i ) + { num /= 1000; prefix = si_prefix[i]; } + if( prefix ) *(--p) = prefix; + } const bool split = num >= 10000 || num <= -10000; for( int i = 0; ; ) @@ -251,8 +255,7 @@ void show_option_error( const char * const arg, const char * const msg, } -// Recognized formats: [QRYZEPTGM][i], k, Ki -// +// Recognized formats: k, Ki, [MGTPEZYRQ][i] long long getnum( const char * const arg, const char * const option_name, const long long llimit = LLONG_MIN, const long long ulimit = LLONG_MAX ) @@ -524,8 +527,8 @@ int main( const int argc, const char * const argv[] ) if( argc > 0 ) invocation_name = argv[0]; enum { opt_ano = 256, opt_aso, opt_bso, opt_chk, opt_crc, opt_dbg, opt_del, - opt_dso, opt_exc, opt_grp, opt_hlp, opt_id, opt_kd, opt_mti, opt_nso, - opt_ofl, opt_out, opt_own, opt_per, opt_sol, opt_un, opt_wn }; + opt_dso, opt_exc, opt_grp, opt_hlp, opt_iid, opt_imd, opt_kd, opt_mti, + opt_nso, opt_ofl, opt_out, opt_own, opt_per, opt_sol, opt_un, opt_wn }; const Arg_parser::Option options[] = { { '0', 0, Arg_parser::no }, @@ -566,7 +569,8 @@ int main( const int argc, const char * const argv[] ) { opt_exc, "exclude", Arg_parser::yes }, { opt_grp, "group", Arg_parser::yes }, { opt_hlp, "help", Arg_parser::no }, - { opt_id, "ignore-ids", Arg_parser::no }, + { opt_iid, "ignore-ids", Arg_parser::no }, + { opt_imd, "ignore-metadata", Arg_parser::no }, { opt_kd, "keep-damaged", Arg_parser::no }, { opt_crc, "missing-crc", Arg_parser::no }, { opt_mti, "mtime", Arg_parser::yes }, @@ -642,7 +646,8 @@ int main( const int argc, const char * const argv[] ) case opt_exc: Exclude::add_pattern( sarg ); break; case opt_grp: cl_opts.gid = parse_group( arg, pn ); break; case opt_hlp: show_help( num_online ); return 0; - case opt_id: cl_opts.ignore_ids = true; break; + case opt_iid: cl_opts.ignore_ids = true; break; + case opt_imd: cl_opts.ignore_metadata = true; break; case opt_kd: cl_opts.keep_damaged = true; break; case opt_mti: cl_opts.mtime = parse_mtime( arg, pn ); cl_opts.mtime_set = true; break; @@ -654,7 +659,7 @@ int main( const int argc, const char * const argv[] ) case opt_sol: cl_opts.solidity = solid; break; case opt_un: cl_opts.set_level( -1 ); break; case opt_wn: cl_opts.warn_newer = true; break; - default : internal_error( "uncaught option" ); + default: internal_error( "uncaught option." ); } } // end process options diff --git a/tarlz.h b/tarlz.h index e069d5e..16ae6e0 100644 --- a/tarlz.h +++ b/tarlz.h @@ -1,5 +1,5 @@ /* Tarlz - Archiver with multimember lzip compression - Copyright (C) 2013-2023 Antonio Diaz Diaz. + Copyright (C) 2013-2024 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 @@ -419,7 +419,7 @@ enum Program_mode { m_none, m_append, m_compress, m_concatenate, m_create, enum Solidity { no_solid, bsolid, dsolid, asolid, solid }; class Arg_parser; -struct Cl_options // command line options +struct Cl_options // command-line options { const Arg_parser & parser; std::string archive_name; @@ -438,6 +438,7 @@ struct Cl_options // command line options bool dereference; bool filenames_given; bool ignore_ids; + bool ignore_metadata; bool ignore_overflow; bool keep_damaged; bool level_set; // compression level set in command line @@ -451,10 +452,10 @@ struct Cl_options // command line options : parser( ap ), mtime( 0 ), uid( -1 ), gid( -1 ), program_mode( m_none ), solidity( bsolid ), data_size( 0 ), debug_level( 0 ), level( 6 ), num_files( 0 ), num_workers( -1 ), out_slots( 64 ), dereference( false ), - filenames_given( false ), ignore_ids( false ), ignore_overflow( false ), - keep_damaged( false ), level_set( false ), missing_crc( false ), - mtime_set( false ), permissive( false ), preserve_permissions( false ), - warn_newer( false ) {} + filenames_given( false ), ignore_ids( false ), ignore_metadata( false ), + ignore_overflow( false ), keep_damaged( false ), level_set( false ), + missing_crc( false ), mtime_set( false ), permissive( false ), + preserve_permissions( false ), warn_newer( false ) {} void set_level( const int l ) { level = l; level_set = true; } @@ -469,7 +470,6 @@ inline void set_retval( int & retval, const int new_val ) const char * const bad_magic_msg = "Bad magic number (file not in lzip format)."; const char * const bad_dict_msg = "Invalid dictionary size in member header."; const char * const corrupt_mm_msg = "Corrupt header in multimember file."; -const char * const trailing_msg = "Trailing data not allowed."; const char * const bad_hdr_msg = "Corrupt or invalid tar header."; const char * const gblrec_msg = "Error in global extended records."; const char * const extrec_msg = "Error in extended records."; @@ -490,6 +490,7 @@ const char * const nfound_msg = "Not found in archive."; const char * const seek_msg = "Seek error"; const char * const werr_msg = "Write error"; const char * const chdir_msg = "Error changing working directory"; +const char * const intdir_msg = "Failed to create intermediate directory"; // defined in common.cc unsigned long long parse_octal( const uint8_t * const ptr, const int size ); @@ -505,7 +506,7 @@ bool show_member_name( const Extended & extended, const Tar_header header, bool check_skip_filename( const Cl_options & cl_opts, std::vector< char > & name_pending, const char * const filename, const int chdir_fd = -1 ); -bool make_path( const std::string & name ); +bool make_dirs( const std::string & name ); // defined in common_mutex.cc void exit_fail_mt( const int retval = 1 ); // terminate the program diff --git a/testsuite/check.sh b/testsuite/check.sh index 66c525b..9027bd5 100755 --- a/testsuite/check.sh +++ b/testsuite/check.sh @@ -1,14 +1,14 @@ #! /bin/sh # check script for Tarlz - Archiver with multimember lzip compression -# Copyright (C) 2013-2023 Antonio Diaz Diaz. +# Copyright (C) 2013-2024 Antonio Diaz Diaz. # # This script is free software: you have unlimited permission # to copy, distribute, and modify it. LC_ALL=C export LC_ALL -objdir="`pwd`" -testdir="`cd "$1" ; pwd`" +objdir=`pwd` +testdir=`cd "$1" ; pwd` TARLZ="${objdir}"/tarlz framework_failure() { echo "failure in testing framework" ; exit 1 ; } @@ -154,13 +154,13 @@ printf "testing tarlz-%s..." "$2" [ $? = 1 ] || test_failed $LINENO "${TARLZ}" -q -x -C nx_dir "${test3_lz}" [ $? = 1 ] || test_failed $LINENO -touch empty.tar.lz empty.tlz # list an empty lz file +touch empty.tar.lz empty.tlz || framework_failure # list an empty lz file "${TARLZ}" -q -tf empty.tar.lz [ $? = 2 ] || test_failed $LINENO "${TARLZ}" -q -tf empty.tlz [ $? = 2 ] || test_failed $LINENO rm -f empty.tar.lz empty.tlz || framework_failure -touch empty.tar # compress an empty archive +touch empty.tar || framework_failure # compress an empty archive "${TARLZ}" -q -z empty.tar [ $? = 2 ] || test_failed $LINENO [ ! -e empty.tar.lz ] || test_failed $LINENO @@ -245,6 +245,7 @@ rm -f foo bar baz || framework_failure cmp cfoo foo || test_failed $LINENO cmp cbar bar || test_failed $LINENO cmp cbaz baz || test_failed $LINENO +# time and mode comparison always fails on OS/2 if "${TARLZ}" -df "${test3}" --ignore-ids ; then d_works=yes else printf "warning: some '--diff' tests will be skipped.\n" fi @@ -290,10 +291,12 @@ for i in "${test3}" "${test3_lz}" ; do cmp cfoo dir1/foo || test_failed $LINENO "$i" cmp cbar dir2/bar || test_failed $LINENO "$i" cmp cbaz dir3/baz || test_failed $LINENO "$i" - "${TARLZ}" -q -df "$i" -C dir1 foo -C ../dir2 --ignore-ids bar \ - -C ../dir3 baz || test_failed $LINENO "$i" - "${TARLZ}" -q -df "$i" -C dir3 baz -C ../dir2 bar -C ../dir1 foo \ - --ignore-ids || test_failed $LINENO "$i" + if [ "${d_works}" = yes ] ; then + "${TARLZ}" -df "$i" -C dir1 foo -C ../dir2 --ignore-ids bar \ + -C ../dir3 baz || test_failed $LINENO "$i" + "${TARLZ}" -df "$i" -C dir3 baz -C ../dir2 bar -C ../dir1 foo \ + --ignore-ids || test_failed $LINENO "$i" + fi rm -rf dir1 dir2 dir3 || framework_failure done for i in "${test3dir}" "${test3dir_lz}" ; do @@ -303,10 +306,12 @@ for i in "${test3dir}" "${test3dir_lz}" ; do cmp cfoo dir1/dir/foo || test_failed $LINENO "$i" cmp cbar dir2/dir/bar || test_failed $LINENO "$i" cmp cbaz dir3/dir/baz || test_failed $LINENO "$i" - "${TARLZ}" -q -df "$i" --ignore-ids -C dir1 dir/foo -C ../dir2 dir/bar \ - -C ../dir3 dir/baz || test_failed $LINENO "$i" - "${TARLZ}" -q -df "${test3}" -C dir1/dir foo -C ../../dir2/dir bar \ - --ignore-ids -C ../../dir3/dir baz || test_failed $LINENO "$i" + if [ "${d_works}" = yes ] ; then + "${TARLZ}" -q -df "$i" --ignore-ids -C dir1 dir/foo -C ../dir2 dir/bar \ + -C ../dir3 dir/baz || test_failed $LINENO "$i" + "${TARLZ}" -q -df "${test3}" -C dir1/dir foo -C ../../dir2/dir bar \ + --ignore-ids -C ../../dir3/dir baz || test_failed $LINENO "$i" + fi rm -rf dir1 dir2 dir3 || framework_failure done @@ -768,7 +773,6 @@ rm -rf out.tar dir || framework_failure printf "\ntesting --diff..." -# test --diff "${TARLZ}" -xf "${test3_lz}" || test_failed $LINENO "${TARLZ}" -cf out.tar foo || test_failed $LINENO "${TARLZ}" -cf aout.tar foo --anonymous || test_failed $LINENO @@ -787,6 +791,8 @@ else rm -f bar || framework_failure "${TARLZ}" -n$i -df "${test3_lz}" --ignore-ids foo baz || test_failed $LINENO $i + "${TARLZ}" -n$i -df "${test3_lz}" --ignore-metadata foo baz || + test_failed $LINENO $i "${TARLZ}" -n$i -df "${test3_lz}" --exclude bar --ignore-ids || test_failed $LINENO $i rm -f foo baz || framework_failure @@ -1199,6 +1205,9 @@ cmp out.tar.lz out || test_failed $LINENO "${TARLZ}" -0 -B8KiB -z --bsolid outz.tar || test_failed $LINENO cmp out.tar.lz outz.tar.lz || test_failed $LINENO rm -f out outz.tar.lz || framework_failure +"${TARLZ}" -0 -B8KiB -z -o a/b/c/out --bsolid out.tar || test_failed $LINENO +cmp out.tar.lz a/b/c/out || test_failed $LINENO +rm -rf a || framework_failure # "${TARLZ}" -0 -n0 --asolid -cf out.tar.lz test.txt foo bar baz test.txt || test_failed $LINENO "${TARLZ}" -0 -n0 --asolid -cf out3.tar.lz foo bar baz || test_failed $LINENO