summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AUTHORS1
-rw-r--r--COPYING338
-rw-r--r--ChangeLog37
-rw-r--r--INSTALL65
-rw-r--r--Makefile.in137
-rw-r--r--NEWS5
-rw-r--r--README62
-rw-r--r--arg_parser.cc196
-rw-r--r--arg_parser.h98
-rwxr-xr-xconfigure196
-rw-r--r--create.cc412
-rw-r--r--doc/tarlz.188
-rw-r--r--doc/tarlz.info311
-rw-r--r--doc/tarlz.texi348
-rw-r--r--extract.cc561
-rw-r--r--lzip.h112
-rw-r--r--main.cc369
-rw-r--r--tarlz.h68
-rwxr-xr-xtestsuite/check.sh438
-rw-r--r--testsuite/dotdot1.tar.lzbin0 -> 139 bytes
-rw-r--r--testsuite/dotdot2.tar.lzbin0 -> 140 bytes
-rw-r--r--testsuite/dotdot3.tar.lzbin0 -> 141 bytes
-rw-r--r--testsuite/dotdot4.tar.lzbin0 -> 140 bytes
-rw-r--r--testsuite/dotdot5.tar.lzbin0 -> 139 bytes
-rw-r--r--testsuite/eof.tar.lzbin0 -> 44 bytes
-rw-r--r--testsuite/test.txt676
-rw-r--r--testsuite/test.txt.lzbin0 -> 7392 bytes
-rw-r--r--testsuite/test.txt.tarbin0 -> 38400 bytes
-rw-r--r--testsuite/test.txt.tar.lzbin0 -> 7495 bytes
-rw-r--r--testsuite/test3.tarbin0 -> 4096 bytes
-rw-r--r--testsuite/test3.tar.lzbin0 -> 356 bytes
-rw-r--r--testsuite/test3_bad1.tarbin0 -> 4096 bytes
-rw-r--r--testsuite/test3_bad1.tar.lzbin0 -> 356 bytes
-rw-r--r--testsuite/test3_bad2.tarbin0 -> 4096 bytes
-rw-r--r--testsuite/test3_bad2.tar.lzbin0 -> 356 bytes
-rw-r--r--testsuite/test3_bad3.tarbin0 -> 4096 bytes
-rw-r--r--testsuite/test3_bad3.tar.lzbin0 -> 356 bytes
-rw-r--r--testsuite/test3_bad4.tarbin0 -> 4096 bytes
-rw-r--r--testsuite/test3_bad4.tar.lzbin0 -> 356 bytes
-rw-r--r--testsuite/test3_bad5.tarbin0 -> 4608 bytes
-rw-r--r--testsuite/test3_bad5.tar.lzbin0 -> 356 bytes
-rw-r--r--testsuite/test3_bad6.tar.lzbin0 -> 866 bytes
-rw-r--r--testsuite/test3a.tar.lzbin0 -> 358 bytes
43 files changed, 4518 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..a8fcb34
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1 @@
+Tarlz was written by Antonio Diaz Diaz.
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..4ad17ae
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,338 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) <year> <name of author>
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..a54be98
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,37 @@
+2018-04-23 Antonio Diaz Diaz <antonio@gnu.org>
+
+ * Version 0.4 released.
+ * Added some missing #includes.
+ * main.cc: Open files in binary mode on OS2.
+
+2018-03-19 Antonio Diaz Diaz <antonio@gnu.org>
+
+ * Version 0.3 released.
+ * Project renamed to 'tarlz' from 'pmtar' (Poor Man's Tar).
+ * Added new option '-C, --directory'.
+ * Implemented lzip compression of members at archive creation.
+ * Added new option '-r, --append'.
+ * Added new options '--owner', '--group'.
+ * Added new options '--asolid', '--dsolid', '--solid'.
+ * Implemented file appending to compressed archive.
+ * Implemented transparent decompression of the archive.
+ * Implemented skipping over damaged (un)compressed members.
+ * Implemented recursive extraction/listing of directories.
+ * Implemented verbose extract/list output.
+ * tarlz.texi: New file.
+
+2014-01-22 Antonio Diaz Diaz <antonio@gnu.org>
+
+ * Version 0.2 released.
+ * configure: Options now accept a separate argument.
+
+2013-02-16 Antonio Diaz Diaz <ant_diaz@teleline.es>
+
+ * Version 0.1 released.
+
+
+Copyright (C) 2013-2018 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
+modify it.
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..c05e8a8
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,65 @@
+Requirements
+------------
+You will need a C++ compiler and the lzlib compression library installed.
+I use gcc 5.3.0 and 4.1.2, but the code should compile with any
+standards compliant compiler.
+Gcc is available at http://gcc.gnu.org.
+Lzlib is available at http://www.nongnu.org/lzip/lzlib.html.
+
+
+Procedure
+---------
+1. Unpack the archive if you have not done so already:
+
+ tar -xf tarlz[version].tar.lz
+or
+ lzip -cd tarlz[version].tar.lz | tar -xf -
+
+This creates the directory ./tarlz[version] containing the source from
+the main archive.
+
+2. Change to tarlz directory and run configure.
+ (Try 'configure --help' for usage instructions).
+
+ cd tarlz[version]
+ ./configure
+
+3. Run make.
+
+ make
+
+4. Optionally, type 'make check' to run the tests that come with tarlz.
+
+5. Type 'make install' to install the program and any data files and
+ documentation.
+
+ Or type 'make install-compress', which additionally compresses the
+ info manual and the man page after installation. (Installing
+ compressed docs may become the default in the future).
+
+ You can install only the program, the info manual or the man page by
+ typing 'make install-bin', 'make install-info' or 'make install-man'
+ respectively.
+
+
+Another way
+-----------
+You can also compile tarlz into a separate directory.
+To do this, you must use a version of 'make' that supports the 'VPATH'
+variable, such as GNU 'make'. 'cd' to the directory where you want the
+object files and executables to go and run the 'configure' script.
+'configure' automatically checks for the source code in '.', in '..' and
+in the directory that 'configure' is in.
+
+'configure' recognizes the option '--srcdir=DIR' to control where to
+look for the sources. Usually 'configure' can determine that directory
+automatically.
+
+After running 'configure', you can run 'make' and 'make install' as
+explained above.
+
+
+Copyright (C) 2013-2018 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
new file mode 100644
index 0000000..1b94c12
--- /dev/null
+++ b/Makefile.in
@@ -0,0 +1,137 @@
+
+DISTNAME = $(pkgname)-$(pkgversion)
+INSTALL = install
+INSTALL_PROGRAM = $(INSTALL) -m 755
+INSTALL_DATA = $(INSTALL) -m 644
+INSTALL_DIR = $(INSTALL) -d -m 755
+LIBS = -llz
+SHELL = /bin/sh
+CAN_RUN_INSTALLINFO = $(SHELL) -c "install-info --version" > /dev/null 2>&1
+
+objs = arg_parser.o create.o extract.o main.o
+
+
+.PHONY : all install install-bin install-info install-man \
+ install-strip install-compress install-strip-compress \
+ install-bin-strip install-info-compress install-man-compress \
+ uninstall uninstall-bin uninstall-info uninstall-man \
+ doc info man check dist clean distclean
+
+all : $(progname)
+
+$(progname) : $(objs)
+ $(CXX) $(LDFLAGS) $(CXXFLAGS) -o $@ $(objs) $(LIBS)
+
+main.o : main.cc
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -DPROGVERSION=\"$(pkgversion)\" -c -o $@ $<
+
+%.o : %.cc
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<
+
+$(objs) : Makefile
+arg_parser.o : arg_parser.h
+create.o : arg_parser.h lzip.h tarlz.h
+extract.o : arg_parser.h lzip.h tarlz.h
+main.o : arg_parser.h tarlz.h
+
+
+doc : info man
+
+info : $(VPATH)/doc/$(pkgname).info
+
+$(VPATH)/doc/$(pkgname).info : $(VPATH)/doc/$(pkgname).texi
+ cd $(VPATH)/doc && makeinfo $(pkgname).texi
+
+man : $(VPATH)/doc/$(progname).1
+
+$(VPATH)/doc/$(progname).1 : $(progname)
+ help2man -n 'creates tar archives with multimember lzip compression' \
+ -o $@ ./$(progname)
+
+Makefile : $(VPATH)/configure $(VPATH)/Makefile.in
+ ./config.status
+
+check : all
+ @$(VPATH)/testsuite/check.sh $(VPATH)/testsuite $(pkgversion)
+
+install : install-bin install-info install-man
+install-strip : install-bin-strip install-info install-man
+install-compress : install-bin install-info-compress install-man-compress
+install-strip-compress : install-bin-strip install-info-compress install-man-compress
+
+install-bin : all
+ if [ ! -d "$(DESTDIR)$(bindir)" ] ; then $(INSTALL_DIR) "$(DESTDIR)$(bindir)" ; fi
+ $(INSTALL_PROGRAM) ./$(progname) "$(DESTDIR)$(bindir)/$(progname)"
+
+install-bin-strip : all
+ $(MAKE) INSTALL_PROGRAM='$(INSTALL_PROGRAM) -s' install-bin
+
+install-info :
+ if [ ! -d "$(DESTDIR)$(infodir)" ] ; then $(INSTALL_DIR) "$(DESTDIR)$(infodir)" ; fi
+ -rm -f "$(DESTDIR)$(infodir)/$(pkgname).info"*
+ $(INSTALL_DATA) $(VPATH)/doc/$(pkgname).info "$(DESTDIR)$(infodir)/$(pkgname).info"
+ -if $(CAN_RUN_INSTALLINFO) ; then \
+ install-info --info-dir="$(DESTDIR)$(infodir)" "$(DESTDIR)$(infodir)/$(pkgname).info" ; \
+ fi
+
+install-info-compress : install-info
+ lzip -v -9 "$(DESTDIR)$(infodir)/$(pkgname).info"
+
+install-man :
+ if [ ! -d "$(DESTDIR)$(mandir)/man1" ] ; then $(INSTALL_DIR) "$(DESTDIR)$(mandir)/man1" ; fi
+ -rm -f "$(DESTDIR)$(mandir)/man1/$(progname).1"*
+ $(INSTALL_DATA) $(VPATH)/doc/$(progname).1 "$(DESTDIR)$(mandir)/man1/$(progname).1"
+
+install-man-compress : install-man
+ lzip -v -9 "$(DESTDIR)$(mandir)/man1/$(progname).1"
+
+uninstall : uninstall-man uninstall-info uninstall-bin
+
+uninstall-bin :
+ -rm -f "$(DESTDIR)$(bindir)/$(progname)"
+
+uninstall-info :
+ -if $(CAN_RUN_INSTALLINFO) ; then \
+ install-info --info-dir="$(DESTDIR)$(infodir)" --remove "$(DESTDIR)$(infodir)/$(pkgname).info" ; \
+ fi
+ -rm -f "$(DESTDIR)$(infodir)/$(pkgname).info"*
+
+uninstall-man :
+ -rm -f "$(DESTDIR)$(mandir)/man1/$(progname).1"*
+
+dist : doc
+ ln -sf $(VPATH) $(DISTNAME)
+ tar -Hustar --owner=root --group=root -cvf $(DISTNAME).tar \
+ $(DISTNAME)/AUTHORS \
+ $(DISTNAME)/COPYING \
+ $(DISTNAME)/ChangeLog \
+ $(DISTNAME)/INSTALL \
+ $(DISTNAME)/Makefile.in \
+ $(DISTNAME)/NEWS \
+ $(DISTNAME)/README \
+ $(DISTNAME)/configure \
+ $(DISTNAME)/doc/$(progname).1 \
+ $(DISTNAME)/doc/$(pkgname).info \
+ $(DISTNAME)/doc/$(pkgname).texi \
+ $(DISTNAME)/*.h \
+ $(DISTNAME)/*.cc \
+ $(DISTNAME)/testsuite/check.sh \
+ $(DISTNAME)/testsuite/test.txt \
+ $(DISTNAME)/testsuite/test.txt.tar \
+ $(DISTNAME)/testsuite/test3.tar \
+ $(DISTNAME)/testsuite/test3_bad[1-5].tar \
+ $(DISTNAME)/testsuite/test.txt.lz \
+ $(DISTNAME)/testsuite/test.txt.tar.lz \
+ $(DISTNAME)/testsuite/test3.tar.lz \
+ $(DISTNAME)/testsuite/test3a.tar.lz \
+ $(DISTNAME)/testsuite/test3_bad[1-6].tar.lz \
+ $(DISTNAME)/testsuite/dotdot[1-5].tar.lz \
+ $(DISTNAME)/testsuite/eof.tar.lz
+ rm -f $(DISTNAME)
+ lzip -v -9 $(DISTNAME).tar
+
+clean :
+ -rm -f $(progname) $(objs)
+
+distclean : clean
+ -rm -f Makefile config.status *.tar *.tar.lz
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..815ed45
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,5 @@
+Changes in version 0.4:
+
+Some missing #includes have been fixed.
+
+Open files in binary mode on OS2.
diff --git a/README b/README
new file mode 100644
index 0000000..bafec18
--- /dev/null
+++ b/README
@@ -0,0 +1,62 @@
+Description
+
+Tarlz is a small and simple implementation of the tar archiver. By
+default tarlz creates, lists and extracts archives in the 'ustar' format
+compressed with lzip on a per file basis. Tarlz can append files to the
+end of such compressed archives.
+
+Each tar member is compressed in its own lzip member, as well as the
+end-of-file blocks. This same method works for any tar format (gnu,
+ustar, posix) and is fully backward compatible with standard tar tools
+like GNU tar, which treat the resulting multimember tar.lz archive like
+any other tar.lz archive.
+
+Tarlz can create tar archives with four levels of compression
+granularity; per file, per directory, appendable solid, and solid.
+
+Tarlz is intended as a showcase project for the maintainers of real tar
+programs to evaluate the format and perhaps implement it in their tools.
+
+The diagram below shows the correspondence between tar members (formed
+by a header plus optional data) in the tar archive and lzip members in
+the resulting multimember tar.lz archive:
+
+tar
++========+======+========+======+========+======+========+
+| header | data | header | data | header | data | eof |
++========+======+========+======+========+======+========+
+
+tar.lz
++===============+===============+===============+========+
+| member | member | member | member |
++===============+===============+===============+========+
+
+Of course, compressing each file (or each directory) individually is
+less efficient than compressing the whole tar archive, but it has the
+following advantages:
+
+ * The resulting multimember tar.lz archive can be decompressed in
+ parallel with plzip, multiplying the decompression speed.
+
+ * New members can be appended to the archive (by removing the eof
+ member) just like to an uncompressed tar archive.
+
+ * It is a safe posix-style backup format. In case of corruption,
+ tarlz can extract all the undamaged members from the tar.lz
+ archive, skipping over the damaged members, just like the standard
+ (uncompressed) tar. Moreover, lziprecover can be used to recover at
+ least part of the contents of the damaged members.
+
+ * A multimember tar.lz archive is usually smaller than the
+ corresponding solidly compressed tar.gz archive, except when
+ individually compressing files smaller than about 32 KiB.
+
+
+Copyright (C) 2013-2018 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.
diff --git a/arg_parser.cc b/arg_parser.cc
new file mode 100644
index 0000000..008ebc8
--- /dev/null
+++ b/arg_parser.cc
@@ -0,0 +1,196 @@
+/* Arg_parser - POSIX/GNU command line argument parser. (C++ version)
+ Copyright (C) 2006-2018 Antonio Diaz Diaz.
+
+ This library is free software. Redistribution and use in source and
+ binary forms, with or without modification, are permitted provided
+ that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ This library 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.
+*/
+
+#include <cstring>
+#include <string>
+#include <vector>
+
+#include "arg_parser.h"
+
+
+bool Arg_parser::parse_long_option( const char * const opt, const char * const arg,
+ const Option options[], int & argind )
+ {
+ unsigned len;
+ int index = -1;
+ bool exact = false, ambig = false;
+
+ for( len = 0; opt[len+2] && opt[len+2] != '='; ++len ) ;
+
+ // Test all long options for either exact match or abbreviated matches.
+ for( int i = 0; options[i].code != 0; ++i )
+ if( options[i].name && std::strncmp( options[i].name, &opt[2], len ) == 0 )
+ {
+ if( std::strlen( options[i].name ) == len ) // Exact match found
+ { index = i; exact = true; break; }
+ else if( index < 0 ) index = i; // First nonexact match found
+ else if( options[index].code != options[i].code ||
+ options[index].has_arg != options[i].has_arg )
+ ambig = true; // Second or later nonexact match found
+ }
+
+ if( ambig && !exact )
+ {
+ error_ = "option '"; error_ += opt; error_ += "' is ambiguous";
+ return false;
+ }
+
+ if( index < 0 ) // nothing found
+ {
+ error_ = "unrecognized option '"; error_ += opt; error_ += '\'';
+ return false;
+ }
+
+ ++argind;
+ data.push_back( Record( options[index].code ) );
+
+ if( opt[len+2] ) // '--<long_option>=<argument>' syntax
+ {
+ if( options[index].has_arg == no )
+ {
+ error_ = "option '--"; error_ += options[index].name;
+ error_ += "' doesn't allow an argument";
+ return false;
+ }
+ if( options[index].has_arg == yes && !opt[len+3] )
+ {
+ error_ = "option '--"; error_ += options[index].name;
+ error_ += "' requires an argument";
+ return false;
+ }
+ data.back().argument = &opt[len+3];
+ return true;
+ }
+
+ if( options[index].has_arg == yes )
+ {
+ if( !arg || !arg[0] )
+ {
+ error_ = "option '--"; error_ += options[index].name;
+ error_ += "' requires an argument";
+ return false;
+ }
+ ++argind; data.back().argument = arg;
+ return true;
+ }
+
+ return true;
+ }
+
+
+bool Arg_parser::parse_short_option( const char * const opt, const char * const arg,
+ const Option options[], int & argind )
+ {
+ int cind = 1; // character index in opt
+
+ while( cind > 0 )
+ {
+ int index = -1;
+ const unsigned char c = opt[cind];
+
+ if( c != 0 )
+ for( int i = 0; options[i].code; ++i )
+ if( c == options[i].code )
+ { index = i; break; }
+
+ if( index < 0 )
+ {
+ error_ = "invalid option -- '"; error_ += c; error_ += '\'';
+ return false;
+ }
+
+ data.push_back( Record( c ) );
+ if( opt[++cind] == 0 ) { ++argind; cind = 0; } // opt finished
+
+ if( options[index].has_arg != no && cind > 0 && opt[cind] )
+ {
+ data.back().argument = &opt[cind]; ++argind; cind = 0;
+ }
+ else if( options[index].has_arg == yes )
+ {
+ if( !arg || !arg[0] )
+ {
+ error_ = "option requires an argument -- '"; error_ += c;
+ error_ += '\'';
+ return false;
+ }
+ data.back().argument = arg; ++argind; cind = 0;
+ }
+ }
+ return true;
+ }
+
+
+Arg_parser::Arg_parser( const int argc, const char * const argv[],
+ const Option options[], const bool in_order )
+ {
+ if( argc < 2 || !argv || !options ) return;
+
+ std::vector< const char * > non_options; // skipped non-options
+ int argind = 1; // index in argv
+
+ while( argind < argc )
+ {
+ const unsigned char ch1 = argv[argind][0];
+ const unsigned char ch2 = ch1 ? argv[argind][1] : 0;
+
+ if( ch1 == '-' && ch2 ) // we found an option
+ {
+ const char * const opt = argv[argind];
+ const char * const arg = ( argind + 1 < argc ) ? argv[argind+1] : 0;
+ if( ch2 == '-' )
+ {
+ if( !argv[argind][2] ) { ++argind; break; } // we found "--"
+ else if( !parse_long_option( opt, arg, options, argind ) ) break;
+ }
+ else if( !parse_short_option( opt, arg, options, argind ) ) break;
+ }
+ else
+ {
+ if( in_order ) data.push_back( Record( argv[argind++] ) );
+ else non_options.push_back( argv[argind++] );
+ }
+ }
+ if( error_.size() ) data.clear();
+ else
+ {
+ for( unsigned i = 0; i < non_options.size(); ++i )
+ data.push_back( Record( non_options[i] ) );
+ while( argind < argc )
+ data.push_back( Record( argv[argind++] ) );
+ }
+ }
+
+
+Arg_parser::Arg_parser( const char * const opt, const char * const arg,
+ const Option options[] )
+ {
+ if( !opt || !opt[0] || !options ) return;
+
+ if( opt[0] == '-' && opt[1] ) // we found an option
+ {
+ int argind = 1; // dummy
+ if( opt[1] == '-' )
+ { if( opt[2] ) parse_long_option( opt, arg, options, argind ); }
+ else
+ parse_short_option( opt, arg, options, argind );
+ if( error_.size() ) data.clear();
+ }
+ else data.push_back( Record( opt ) );
+ }
diff --git a/arg_parser.h b/arg_parser.h
new file mode 100644
index 0000000..f015881
--- /dev/null
+++ b/arg_parser.h
@@ -0,0 +1,98 @@
+/* Arg_parser - POSIX/GNU command line argument parser. (C++ version)
+ Copyright (C) 2006-2018 Antonio Diaz Diaz.
+
+ This library is free software. Redistribution and use in source and
+ binary forms, with or without modification, are permitted provided
+ that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ This library 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.
+*/
+
+/* Arg_parser reads the arguments in 'argv' and creates a number of
+ option codes, option arguments and non-option arguments.
+
+ In case of error, 'error' returns a non-empty error message.
+
+ 'options' is an array of 'struct Option' terminated by an element
+ containing a code which is zero. A null name means a short-only
+ option. A code value outside the unsigned char range means a
+ long-only option.
+
+ Arg_parser normally makes it appear as if all the option arguments
+ were specified before all the non-option arguments for the purposes
+ of parsing, even if the user of your program intermixed option and
+ non-option arguments. If you want the arguments in the exact order
+ the user typed them, call 'Arg_parser' with 'in_order' = true.
+
+ The argument '--' terminates all options; any following arguments are
+ treated as non-option arguments, even if they begin with a hyphen.
+
+ The syntax for optional option arguments is '-<short_option><argument>'
+ (without whitespace), or '--<long_option>=<argument>'.
+*/
+
+class Arg_parser
+ {
+public:
+ enum Has_arg { no, yes, maybe };
+
+ struct Option
+ {
+ int code; // Short option letter or code ( code != 0 )
+ const char * name; // Long option name (maybe null)
+ Has_arg has_arg;
+ };
+
+private:
+ struct Record
+ {
+ int code;
+ std::string argument;
+ explicit Record( const int c ) : code( c ) {}
+ explicit Record( const char * const arg ) : code( 0 ), argument( arg ) {}
+ };
+
+ std::string error_;
+ std::vector< Record > data;
+
+ bool parse_long_option( const char * const opt, const char * const arg,
+ const Option options[], int & argind );
+ bool parse_short_option( const char * const opt, const char * const arg,
+ const Option options[], int & argind );
+
+public:
+ Arg_parser( const int argc, const char * const argv[],
+ const Option options[], const bool in_order = false );
+
+ // Restricted constructor. Parses a single token and argument (if any)
+ Arg_parser( const char * const opt, const char * const arg,
+ const Option options[] );
+
+ const std::string & error() const { return error_; }
+
+ // The number of arguments parsed (may be different from argc)
+ int arguments() const { return data.size(); }
+
+ // If code( i ) is 0, argument( i ) is a non-option.
+ // Else argument( i ) is the option's argument (or empty).
+ int code( const int i ) const
+ {
+ if( i >= 0 && i < arguments() ) return data[i].code;
+ else return 0;
+ }
+
+ const std::string & argument( const int i ) const
+ {
+ if( i >= 0 && i < arguments() ) return data[i].argument;
+ else return error_;
+ }
+ };
diff --git a/configure b/configure
new file mode 100755
index 0000000..a8a5d34
--- /dev/null
+++ b/configure
@@ -0,0 +1,196 @@
+#! /bin/sh
+# configure script for Tarlz - Archiver with multimember lzip compression
+# Copyright (C) 2013-2018 Antonio Diaz Diaz.
+#
+# This configure script is free software: you have unlimited permission
+# to copy, distribute and modify it.
+
+pkgname=tarlz
+pkgversion=0.4
+progname=tarlz
+srctrigger=doc/${pkgname}.texi
+
+# clear some things potentially inherited from environment.
+LC_ALL=C
+export LC_ALL
+srcdir=
+prefix=/usr/local
+exec_prefix='$(prefix)'
+bindir='$(exec_prefix)/bin'
+datarootdir='$(prefix)/share'
+infodir='$(datarootdir)/info'
+mandir='$(datarootdir)/man'
+CXX=g++
+CPPFLAGS=
+CXXFLAGS='-Wall -W -O2'
+LDFLAGS=
+
+# checking whether we are using GNU C++.
+/bin/sh -c "${CXX} --version" > /dev/null 2>&1 ||
+ {
+ CXX=c++
+ CXXFLAGS=-O2
+ }
+
+# Loop over all args
+args=
+no_create=
+while [ $# != 0 ] ; do
+
+ # Get the first arg, and shuffle
+ option=$1 ; arg2=no
+ shift
+
+ # Add the argument quoted to args
+ args="${args} \"${option}\""
+
+ # Split out the argument for options that take them
+ case ${option} in
+ *=*) optarg=`echo ${option} | sed -e 's,^[^=]*=,,;s,/$,,'` ;;
+ esac
+
+ # Process the options
+ case ${option} in
+ --help | -h)
+ echo "Usage: $0 [OPTION]... [VAR=VALUE]..."
+ echo
+ echo "To assign makefile variables (e.g., CXX, CXXFLAGS...), specify them as"
+ echo "arguments to configure in the form VAR=VALUE."
+ echo
+ echo "Options and variables: [defaults in brackets]"
+ echo " -h, --help display this help and exit"
+ echo " -V, --version output version information and exit"
+ echo " --srcdir=DIR find the sources in DIR [. or ..]"
+ echo " --prefix=DIR install into DIR [${prefix}]"
+ echo " --exec-prefix=DIR base directory for arch-dependent files [${exec_prefix}]"
+ echo " --bindir=DIR user executables directory [${bindir}]"
+ echo " --datarootdir=DIR base directory for doc and data [${datarootdir}]"
+ 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 " LDFLAGS=OPTIONS command line options for the linker [${LDFLAGS}]"
+ echo
+ exit 0 ;;
+ --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=${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} ;;
+ LDFLAGS=*) LDFLAGS=${optarg} ;;
+
+ --*)
+ echo "configure: WARNING: unrecognized option: '${option}'" 1>&2 ;;
+ *=* | *-*-*) ;;
+ *)
+ echo "configure: unrecognized option: '${option}'" 1>&2
+ echo "Try 'configure --help' for more information." 1>&2
+ exit 1 ;;
+ esac
+
+ # Check if the option took a separate argument
+ if [ "${arg2}" = yes ] ; then
+ if [ $# != 0 ] ; then args="${args} \"$1\"" ; shift
+ else echo "configure: Missing argument to '${option}'" 1>&2
+ exit 1
+ fi
+ fi
+done
+
+# Find the source files, if location was not specified.
+srcdirtext=
+if [ -z "${srcdir}" ] ; then
+ srcdirtext="or . or .." ; srcdir=.
+ 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,^$,.,'`
+ fi
+fi
+
+if [ ! -r "${srcdir}/${srctrigger}" ] ; then
+ echo "configure: Can't find sources in ${srcdir} ${srcdirtext}" 1>&2
+ echo "configure: (At least ${srctrigger} is missing)." 1>&2
+ exit 1
+fi
+
+# Set srcdir to . if that's what it is.
+if [ "`pwd`" = "`cd "${srcdir}" ; pwd`" ] ; then srcdir=. ; fi
+
+echo
+if [ -z "${no_create}" ] ; then
+ echo "creating config.status"
+ rm -f config.status
+ cat > config.status << EOF
+#! /bin/sh
+# This file was generated automatically by configure. Don't edit.
+# Run this file to recreate the current configuration.
+#
+# This script is free software: you have unlimited permission
+# to copy, distribute and modify it.
+
+exec /bin/sh $0 ${args} --no-create
+EOF
+ chmod +x config.status
+fi
+
+echo "creating Makefile"
+echo "VPATH = ${srcdir}"
+echo "prefix = ${prefix}"
+echo "exec_prefix = ${exec_prefix}"
+echo "bindir = ${bindir}"
+echo "datarootdir = ${datarootdir}"
+echo "infodir = ${infodir}"
+echo "mandir = ${mandir}"
+echo "CXX = ${CXX}"
+echo "CPPFLAGS = ${CPPFLAGS}"
+echo "CXXFLAGS = ${CXXFLAGS}"
+echo "LDFLAGS = ${LDFLAGS}"
+rm -f Makefile
+cat > Makefile << EOF
+# Makefile for Tarlz - Archiver with multimember lzip compression
+# Copyright (C) 2013-2018 Antonio Diaz Diaz.
+# This file was generated automatically by configure. Don't edit.
+#
+# This Makefile is free software: you have unlimited permission
+# to copy, distribute and modify it.
+
+pkgname = ${pkgname}
+pkgversion = ${pkgversion}
+progname = ${progname}
+VPATH = ${srcdir}
+prefix = ${prefix}
+exec_prefix = ${exec_prefix}
+bindir = ${bindir}
+datarootdir = ${datarootdir}
+infodir = ${infodir}
+mandir = ${mandir}
+CXX = ${CXX}
+CPPFLAGS = ${CPPFLAGS}
+CXXFLAGS = ${CXXFLAGS}
+LDFLAGS = ${LDFLAGS}
+EOF
+cat "${srcdir}/Makefile.in" >> Makefile
+
+echo "OK. Now you can run make."
+echo "If make fails, verify that the lzlib compression library is correctly"
+echo "installed (see INSTALL)."
diff --git a/create.cc b/create.cc
new file mode 100644
index 0000000..c5bed25
--- /dev/null
+++ b/create.cc
@@ -0,0 +1,412 @@
+/* Tarlz - Archiver with multimember lzip compression
+ Copyright (C) 2013-2018 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 <algorithm>
+#include <cerrno>
+#include <climits>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <string>
+#include <vector>
+#include <stdint.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <ftw.h>
+#include <grp.h>
+#include <pwd.h>
+#include <lzlib.h>
+
+#include "arg_parser.h"
+#include "lzip.h"
+#include "tarlz.h"
+
+int cl_owner = -1; // global vars needed by add_member
+int cl_group = -1;
+int cl_solid = 0; // 1 = dsolid, 2 = asolid, 3 = solid
+
+namespace {
+
+LZ_Encoder * encoder = 0; // local vars needed by add_member
+int outfd = -1;
+int gretval = 0;
+
+int seek_read( const int fd, uint8_t * const buf, const int size,
+ const long long pos )
+ {
+ if( lseek( fd, pos, SEEK_SET ) == pos )
+ return readblock( fd, buf, size );
+ return 0;
+ }
+
+// Check archive type, remove EOF blocks, and leave outfd file pos at EOF
+bool check_appendable()
+ {
+ struct stat st;
+ if( fstat( outfd, &st ) != 0 || !S_ISREG( st.st_mode ) ) return false;
+ uint8_t buf[header_size];
+ int rd = readblock( outfd, buf, header_size );
+ if( rd == 0 && errno == 0 ) return true; // append to empty archive
+ if( rd < min_member_size || ( rd != header_size && errno ) ) return false;
+ const Lzip_header * const p = (Lzip_header *)buf; // shut up gcc
+ if( !p->verify_magic() ) return false;
+ LZ_Decoder * decoder = LZ_decompress_open(); // decompress first header
+ if( !decoder || LZ_decompress_errno( decoder ) != LZ_ok ||
+ LZ_decompress_write( decoder, buf, rd ) != rd ||
+ ( rd = LZ_decompress_read( decoder, buf, header_size ) ) <
+ magic_o + magic_l )
+ { LZ_decompress_close( decoder ); return false; }
+ LZ_decompress_close( decoder );
+ const bool maybe_eof = ( buf[0] == 0 );
+ if( !verify_ustar_chksum( buf ) && !maybe_eof ) return false;
+ const long long end = lseek( outfd, 0, SEEK_END );
+ if( end < min_member_size ) return false;
+
+ Lzip_trailer trailer;
+ if( seek_read( outfd, trailer.data, Lzip_trailer::size,
+ end - Lzip_trailer::size ) != Lzip_trailer::size )
+ return false;
+ const long long member_size = trailer.member_size();
+ if( member_size < min_member_size || member_size > end ||
+ ( maybe_eof && member_size != end ) ) return false;
+
+ Lzip_header header;
+ if( seek_read( outfd, header.data, Lzip_header::size,
+ end - member_size ) != Lzip_header::size )
+ return false;
+ if( !header.verify_magic() || !isvalid_ds( header.dictionary_size() ) )
+ return false;
+
+ const unsigned long long data_size = trailer.data_size();
+ if( data_size < header_size || data_size > 32256 ) return false;
+ const unsigned data_crc = trailer.data_crc();
+ const CRC32 crc32;
+ uint32_t crc = 0xFFFFFFFFU;
+ for( unsigned i = 0; i < data_size; ++i ) crc32.update_byte( crc, 0 );
+ crc ^= 0xFFFFFFFFU;
+ if( crc != data_crc ) return false;
+
+ if( lseek( outfd, end - member_size, SEEK_SET ) != end - member_size ||
+ ftruncate( outfd, end - member_size ) != 0 ) return false;
+ return true;
+ }
+
+
+bool archive_write( const uint8_t * const buf, const int size )
+ {
+ if( !encoder ) // uncompressed
+ return ( writeblock( outfd, buf, size ) == size );
+ enum { obuf_size = 65536 };
+ uint8_t obuf[obuf_size];
+ int sz = 0;
+ if( size <= 0 ) LZ_compress_finish( encoder ); // flush encoder
+ while( sz < size || size <= 0 )
+ {
+ const int wr = LZ_compress_write( encoder, buf + sz, size - sz );
+ if( wr < 0 ) internal_error( "library error (LZ_compress_write)." );
+ sz += wr;
+ const int rd = LZ_compress_read( encoder, obuf, obuf_size );
+ if( rd < 0 ) internal_error( "library error (LZ_compress_read)." );
+ if( rd == 0 && sz == size ) break;
+ if( writeblock( outfd, obuf, rd ) != rd ) return false;
+ }
+ if( LZ_compress_finished( encoder ) == 1 &&
+ LZ_compress_restart_member( encoder, LLONG_MAX ) < 0 )
+ internal_error( "library error (LZ_compress_restart_member)." );
+ return true;
+ }
+
+
+void print_octal( char * const buf, int size, unsigned long long num )
+ {
+ while( --size >= 0 ) { buf[size] = '0' + ( num % 8 ); num /= 8; }
+ }
+
+
+const char * remove_leading_dotdot( const char * const filename )
+ {
+ static std::string prefix;
+ const char * p = filename;
+
+ for( int i = 0; filename[i]; ++i )
+ if( filename[i] == '.' && filename[i+1] == '.' &&
+ ( i == 0 || filename[i-1] == '/' ) &&
+ ( filename[i+2] == 0 || filename[i+2] == '/' ) ) p = filename + i + 2;
+ while( *p == '/' || ( *p == '.' && p[1] == '/' ) ) ++p;
+ if( p != filename )
+ {
+ std::string msg( filename, p - filename );
+ if( prefix != msg )
+ {
+ prefix = msg;
+ msg = "Removing leading '"; msg += prefix; msg += "' from member names.";
+ show_error( msg.c_str() );
+ }
+ }
+ if( *p == 0 ) p = ".";
+ return p;
+ }
+
+
+bool split_name( const char * const filename, Tar_header header )
+ {
+ const char * const stored_name = remove_leading_dotdot( filename );
+ const int len = std::strlen( stored_name );
+ enum { max_len = prefix_l + 1 + name_l }; // prefix + '/' + name
+ if( len <= name_l ) // stored_name fits in name
+ { std::memcpy( header + name_o, stored_name, len ); return true; }
+ if( len <= max_len ) // find shortest prefix
+ for( int i = len - name_l - 1; i < len && i <= prefix_l; ++i )
+ if( stored_name[i] == '/' )
+ {
+ std::memcpy( header + name_o, stored_name + i + 1, len - i - 1 );
+ std::memcpy( header + prefix_o, stored_name, i );
+ return true;
+ }
+ return false;
+ }
+
+int add_member( const char * const filename, const struct stat *,
+ const int flag, struct FTW * )
+ {
+ struct stat st;
+ if( lstat( filename, &st ) != 0 )
+ { show_file_error( filename, "Can't stat input file", errno );
+ gretval = 1; return 0; }
+ Tar_header header;
+ std::memset( header, 0, header_size );
+ if( !split_name( filename, header ) )
+ { show_file_error( filename, "File name is too long." );
+ gretval = 2; return 0; }
+
+ const mode_t mode = st.st_mode;
+ print_octal( header + mode_o, mode_l - 1,
+ mode & ( S_ISUID | S_ISGID | S_ISVTX |
+ S_IRWXU | S_IRWXG | S_IRWXO ) );
+ const uid_t uid = ( cl_owner >= 0 ) ? (uid_t)cl_owner : st.st_uid;
+ const gid_t gid = ( cl_group >= 0 ) ? (gid_t)cl_group : st.st_gid;
+ print_octal( header + uid_o, uid_l - 1, uid );
+ print_octal( header + gid_o, gid_l - 1, gid );
+ unsigned long long file_size = 0;
+ print_octal( header + mtime_o, mtime_l - 1, st.st_mtime );
+ Typeflag typeflag;
+ if( S_ISREG( mode ) ) { typeflag = tf_regular; file_size = st.st_size; }
+ else if( S_ISDIR( mode ) )
+ {
+ typeflag = tf_directory;
+ if( flag == FTW_DNR )
+ { show_file_error( filename, "Can't open directory", errno );
+ gretval = 1; return 0; }
+ }
+ else if( S_ISLNK( mode ) )
+ {
+ typeflag = tf_symlink;
+ if( st.st_size > linkname_l ||
+ readlink( filename, header + linkname_o, linkname_l ) != st.st_size )
+ {
+ show_file_error( filename, "Link destination name is too long." );
+ gretval = 2; return 0;
+ }
+ }
+ else if( S_ISCHR( mode ) || S_ISBLK( mode ) )
+ {
+ typeflag = S_ISCHR( mode ) ? tf_chardev : tf_blockdev;
+ print_octal( header + devmajor_o, devmajor_l - 1, major( st.st_dev ) );
+ print_octal( header + devminor_o, devminor_l - 1, minor( st.st_dev ) );
+ }
+ else if( S_ISFIFO( mode ) ) typeflag = tf_fifo;
+ else { show_file_error( filename, "Unknown file type." );
+ gretval = 2; return 0; }
+ header[typeflag_o] = typeflag;
+ std::memcpy( header + magic_o, ustar_magic, magic_l - 1 );
+ header[version_o] = header[version_o+1] = '0';
+ const struct passwd * const pw = getpwuid( uid );
+ if( pw && pw->pw_name )
+ std::strncpy( header + uname_o, pw->pw_name, uname_l - 1 );
+ const struct group * const gr = getgrgid( gid );
+ if( gr && gr->gr_name )
+ std::strncpy( header + gname_o, gr->gr_name, gname_l - 1 );
+ print_octal( header + size_o, size_l - 1, file_size );
+ print_octal( header + chksum_o, chksum_l - 1,
+ ustar_chksum( (const uint8_t *)header ) );
+
+ const int infd = file_size ? open_instream( filename ) : -1;
+ if( file_size && infd < 0 ) { gretval = 1; return 0; }
+ if( !archive_write( (const uint8_t *)header, header_size ) )
+ { show_error( "Error writing archive header", errno ); return 1; }
+ if( file_size )
+ {
+ enum { bufsize = 32 * header_size };
+ uint8_t buf[bufsize];
+ unsigned long long rest = file_size;
+ while( rest > 0 )
+ {
+ int size = std::min( rest, (unsigned long long)bufsize );
+ const int rd = readblock( infd, buf, size );
+ rest -= rd;
+ if( rd != size )
+ {
+ if( verbosity >= 0 )
+ std::fprintf( stderr, "File '%s' ends unexpectedly at pos %llu\n",
+ filename, file_size - rest );
+ close( infd ); return 1;
+ }
+ if( rest == 0 ) // last read
+ {
+ const int rem = file_size % header_size;
+ if( rem > 0 )
+ { const int padding = header_size - rem;
+ std::memset( buf + size, 0, padding ); size += padding; }
+ }
+ if( !archive_write( buf, size ) )
+ { show_error( "Error writing archive", errno ); close( infd );
+ return 1; }
+ }
+ if( close( infd ) != 0 )
+ { show_file_error( filename, "Error closing file", errno ); return 1; }
+ }
+ if( encoder && cl_solid == 0 && !archive_write( 0, 0 ) ) // flush encoder
+ { show_error( "Error flushing encoder", errno ); return 1; }
+ if( verbosity >= 1 ) std::fprintf( stderr, "%s\n", filename );
+ return 0;
+ }
+
+} // end namespace
+
+
+unsigned ustar_chksum( const uint8_t * const buf )
+ {
+ unsigned chksum = chksum_l * 0x20; // treat chksum field as spaces
+ for( int i = 0; i < chksum_o; ++i ) chksum += buf[i];
+ for( int i = chksum_o + chksum_l; i < header_size; ++i ) chksum += buf[i];
+ return chksum;
+ }
+
+
+bool verify_ustar_chksum( const uint8_t * const buf )
+ { return ( verify_ustar_magic( buf ) &&
+ ustar_chksum( buf ) == strtoul( (const char *)buf + chksum_o, 0, 8 ) ); }
+
+
+int encode( const std::string & archive_name, const Arg_parser & parser,
+ const int filenames, const int level, const bool append )
+ {
+ struct Lzma_options
+ {
+ int dictionary_size; // 4 KiB .. 512 MiB
+ int match_len_limit; // 5 .. 273
+ };
+ const Lzma_options option_mapping[] =
+ {
+ { 65535, 16 }, // -0
+ { 1 << 20, 5 }, // -1
+ { 3 << 19, 6 }, // -2
+ { 1 << 21, 8 }, // -3
+ { 3 << 20, 12 }, // -4
+ { 1 << 22, 20 }, // -5
+ { 1 << 23, 36 }, // -6
+ { 1 << 24, 68 }, // -7
+ { 3 << 23, 132 }, // -8
+ { 1 << 25, 273 } }; // -9
+ const bool compressed = ( level >= 0 && level <= 9 );
+
+ if( !append )
+ {
+ if( !filenames )
+ { show_error( "Cowardly refusing to create an empty archive.", 0, true );
+ return 1; }
+ if( archive_name.empty() ) outfd = STDOUT_FILENO;
+ else if( ( outfd = open_outstream( archive_name ) ) < 0 ) return 1;
+ }
+ else
+ {
+ if( !filenames )
+ { 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( !compressed )
+ { show_error( "'--append' is incompatible with '--uncompressed'.", 0, true );
+ return 1; }
+ if( ( outfd = open_outstream( archive_name, false ) ) < 0 ) return 1;
+ if( !check_appendable() )
+ { show_error( "This does not look like an appendable tar.lz archive." );
+ return 2; }
+ }
+
+ if( compressed )
+ {
+ encoder = LZ_compress_open( option_mapping[level].dictionary_size,
+ option_mapping[level].match_len_limit, LLONG_MAX );
+ if( !encoder || LZ_compress_errno( encoder ) != LZ_ok )
+ {
+ if( !encoder || LZ_compress_errno( encoder ) == LZ_mem_error )
+ show_error( "Not enough memory. Try a lower compression level." );
+ else
+ internal_error( "invalid argument to encoder." );
+ return 1;
+ }
+ }
+
+ int retval = 0;
+ std::string deslashed; // arg without trailing slashes
+ for( int i = 0; i < parser.arguments(); ++i ) // write members
+ {
+ const int code = parser.code( i );
+ const std::string & arg = parser.argument( i );
+ const char * filename = arg.c_str();
+ if( code == 'C' && chdir( filename ) != 0 )
+ { show_file_error( filename, "Error changing working directory", errno );
+ retval = 1; break; }
+ if( code ) continue; // skip options
+ unsigned len = arg.size();
+ while( len > 1 && arg[len-1] == '/' ) --len;
+ if( len < arg.size() )
+ { deslashed.assign( arg, 0, len ); filename = deslashed.c_str(); }
+ struct stat st;
+ if( lstat( filename, &st ) != 0 )
+ { show_file_error( filename, "Can't stat input file", errno );
+ if( gretval < 1 ) gretval = 1; }
+ else if( ( retval = nftw( filename, add_member, 16, FTW_PHYS ) ) != 0 )
+ break; // write error
+ else if( encoder && cl_solid == 1 && !archive_write( 0, 0 ) ) // flush encoder
+ { show_error( "Error flushing encoder", errno ); retval = 1; }
+ }
+
+ if( !retval ) // write End-Of-Archive records
+ {
+ uint8_t buf[header_size];
+ std::memset( buf, 0, header_size );
+ if( encoder && cl_solid == 2 && !archive_write( 0, 0 ) ) // flush encoder
+ { show_error( "Error flushing encoder", errno ); retval = 1; }
+ else if( !archive_write( buf, header_size ) ||
+ !archive_write( buf, header_size ) ||
+ ( encoder && !archive_write( 0, 0 ) ) ) // flush encoder
+ { show_error( "Error writing end-of-archive blocks", errno );
+ retval = 1; }
+ }
+ if( close( outfd ) != 0 && !retval )
+ { show_error( "Error closing archive", errno ); retval = 1; }
+ if( retval && archive_name.size() && !append )
+ std::remove( archive_name.c_str() );
+ if( !retval && gretval )
+ { show_error( "Exiting with failure status due to previous errors." );
+ retval = gretval; }
+ return retval;
+ }
diff --git a/doc/tarlz.1 b/doc/tarlz.1
new file mode 100644
index 0000000..6b685c5
--- /dev/null
+++ b/doc/tarlz.1
@@ -0,0 +1,88 @@
+.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.46.1.
+.TH TARLZ "1" "April 2018" "tarlz 0.4" "User Commands"
+.SH NAME
+tarlz \- creates tar archives with multimember lzip compression
+.SH SYNOPSIS
+.B tarlz
+[\fI\,options\/\fR] [\fI\,files\/\fR]
+.SH DESCRIPTION
+Tarlz \- Archiver with multimember lzip compression.
+.SH OPTIONS
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+display this help and exit
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+output version information and exit
+.TP
+\fB\-c\fR, \fB\-\-create\fR
+create a new archive
+.TP
+\fB\-C\fR, \fB\-\-directory=\fR<dir>
+change to directory <dir>
+.TP
+\fB\-f\fR, \fB\-\-file=\fR<archive>
+use archive file <archive>
+.TP
+\fB\-q\fR, \fB\-\-quiet\fR
+suppress all messages
+.TP
+\fB\-r\fR, \fB\-\-append\fR
+append files to the end of an archive
+.TP
+\fB\-t\fR, \fB\-\-list\fR
+list the contents of an archive
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+verbosely list files processed
+.TP
+\fB\-x\fR, \fB\-\-extract\fR
+extract files from an archive
+.TP
+\fB\-0\fR .. \fB\-9\fR
+set compression level [default 6]
+.TP
+\fB\-\-asolid\fR
+create solidly compressed appendable archive
+.TP
+\fB\-\-dsolid\fR
+create per\-directory compressed archive
+.TP
+\fB\-\-solid\fR
+create solidly compressed archive
+.TP
+\fB\-\-group=\fR<group>
+use <group> name/id for added files
+.TP
+\fB\-\-owner=\fR<owner>
+use <owner> name/id for added files
+.TP
+\fB\-\-uncompressed\fR
+don't compress the created archive
+.PP
+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
+invalid input file, 3 for an internal consistency error (eg, bug) which
+caused tarlz to panic.
+.SH "REPORTING BUGS"
+Report bugs to lzip\-bug@nongnu.org
+.br
+Tarlz home page: http://www.nongnu.org/lzip/tarlz.html
+.SH COPYRIGHT
+Copyright \(co 2018 Antonio Diaz Diaz.
+License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html>
+.br
+This is free software: you are free to change and redistribute it.
+There is NO WARRANTY, to the extent permitted by law.
+.SH "SEE ALSO"
+The full documentation for
+.B tarlz
+is maintained as a Texinfo manual. If the
+.B info
+and
+.B tarlz
+programs are properly installed at your site, the command
+.IP
+.B info tarlz
+.PP
+should give you access to the complete manual.
diff --git a/doc/tarlz.info b/doc/tarlz.info
new file mode 100644
index 0000000..140336b
--- /dev/null
+++ b/doc/tarlz.info
@@ -0,0 +1,311 @@
+This is tarlz.info, produced by makeinfo version 4.13+ from tarlz.texi.
+
+INFO-DIR-SECTION Data Compression
+START-INFO-DIR-ENTRY
+* Tarlz: (tarlz). Archiver with multimember lzip compression
+END-INFO-DIR-ENTRY
+
+
+File: tarlz.info, Node: Top, Next: Introduction, Up: (dir)
+
+Tarlz Manual
+************
+
+This manual is for Tarlz (version 0.4, 23 April 2018).
+
+* Menu:
+
+* Introduction:: Purpose and features of tarlz
+* Invoking tarlz:: Command line interface
+* Examples:: A small tutorial with examples
+* Problems:: Reporting bugs
+* Concept index:: Index of concepts
+
+
+ Copyright (C) 2013-2018 Antonio Diaz Diaz.
+
+ This manual is free documentation: you have unlimited permission to
+copy, distribute and modify it.
+
+
+File: tarlz.info, Node: Introduction, Next: Invoking tarlz, Prev: Top, Up: Top
+
+1 Introduction
+**************
+
+Tarlz is a small and simple implementation of the tar archiver. By
+default tarlz creates, lists and extracts archives in the 'ustar' format
+compressed with lzip on a per file basis. Tarlz can append files to the
+end of such compressed archives.
+
+ Each tar member is compressed in its own lzip member, as well as the
+end-of-file blocks. This same method works for any tar format (gnu,
+ustar, posix) and is fully backward compatible with standard tar tools
+like GNU tar, which treat the resulting multimember tar.lz archive like
+any other tar.lz archive.
+
+ Tarlz can create tar archives with four levels of compression
+granularity; per file, per directory, appendable solid, and solid.
+
+ Tarlz is intended as a showcase project for the maintainers of real
+tar programs to evaluate the format and perhaps implement it in their
+tools.
+
+ The diagram below shows the correspondence between tar members
+(formed by a header plus optional data) in the tar archive and lzip
+members in the resulting multimember tar.lz archive: *Note File format:
+(lzip)File format.
+
+tar
++========+======+========+======+========+======+========+
+| header | data | header | data | header | data | eof |
++========+======+========+======+========+======+========+
+
+tar.lz
++===============+===============+===============+========+
+| member | member | member | member |
++===============+===============+===============+========+
+
+Of course, compressing each file (or each directory) individually is
+less efficient than compressing the whole tar archive, but it has the
+following advantages:
+
+ * The resulting multimember tar.lz archive can be decompressed in
+ parallel with plzip, multiplying the decompression speed.
+
+ * New members can be appended to the archive (by removing the eof
+ member) just like to an uncompressed tar archive.
+
+ * It is a safe posix-style backup format. In case of corruption,
+ tarlz can extract all the undamaged members from the tar.lz
+ archive, skipping over the damaged members, just like the standard
+ (uncompressed) tar. Moreover, lziprecover can be used to recover at
+ least part of the contents of the damaged members.
+
+ * A multimember tar.lz archive is usually smaller than the
+ corresponding solidly compressed tar.gz archive, except when
+ individually compressing files smaller than about 32 KiB.
+
+
+File: tarlz.info, Node: Invoking tarlz, Next: Examples, Prev: Introduction, Up: Top
+
+2 Invoking tarlz
+****************
+
+The format for running tarlz is:
+
+ tarlz [OPTIONS] [FILES]
+
+On archive creation or appending, tarlz removes leading and trailing
+slashes from file names, as well as file name prefixes containing a
+'..' component. On extraction, archive members containing a '..'
+component are skipped.
+
+ tarlz supports the following options:
+
+'-h'
+'--help'
+ Print an informative help message describing the options and exit.
+
+'-V'
+'--version'
+ Print the version number of tarlz on the standard output and exit.
+
+'-c'
+'--create'
+ Create a new archive.
+
+'-C DIR'
+'--directory=DIR'
+ Change to directory DIR. When creating or appending, the position
+ of each '-C' option in the command line is significant; it will
+ change the current working directory for the following FILES until
+ a new '-C' option appears in the command line. When extracting, all
+ the '-C' options are executed in sequence before starting the
+ extraction. Listing ignores any '-C' options specified. DIR is
+ relative to the then current working directory, perhaps changed by
+ a previous '-C' option.
+
+'-f ARCHIVE'
+'--file=ARCHIVE'
+ Use archive file ARCHIVE. '-' used as an ARCHIVE argument reads
+ from standard input or writes to standard output.
+
+'-q'
+'--quiet'
+ Quiet operation. Suppress all messages.
+
+'-r'
+'--append'
+ Append files to the end of an archive. The archive must be a
+ regular (seekable) file compressed as a multimember lzip file, and
+ the two end-of-file blocks plus any zero padding must be contained
+ in the last lzip member of the archive. First this last member is
+ removed, then the new members are appended, and then a new
+ end-of-file member is appended to the archive. Exit with status 0
+ without modifying the archive if no FILES have been specified.
+ tarlz can't append files to an uncompressed tar archive.
+
+'-t'
+'--list'
+ List the contents of an archive.
+
+'-v'
+'--verbose'
+ Verbosely list files processed.
+
+'-x'
+'--extract'
+ Extract files from an archive.
+
+'-0 .. -9'
+ Set the compression level. The default compression level is '-6'.
+
+'--asolid'
+ When creating or appending to a compressed archive, use appendable
+ solid compression. All the files being added to the archive are
+ compressed into a single lzip member, but the end-of-file blocks
+ are compressed into a separate lzip member. This creates a solidly
+ compressed appendable archive.
+
+'--dsolid'
+ When creating or appending to a compressed archive, use solid
+ compression for each directory especified in the command line. The
+ end-of-file blocks are compressed into a separate lzip member. This
+ creates a compressed appendable archive with a separate lzip
+ member for each top-level directory.
+
+'--solid'
+ When creating or appending to a compressed archive, use solid
+ compression. The files being added to the archive, along with the
+ end-of-file blocks, are compressed into a single lzip member. The
+ resulting archive is not appendable. No more files can be later
+ appended to the archive without decompressing it first.
+
+'--group=GROUP'
+ When creating or appending, use GROUP for files added to the
+ archive. If GROUP is not a valid group name, it is decoded as a
+ decimal numeric group ID.
+
+'--owner=OWNER'
+ When creating or appending, use OWNER for files added to the
+ archive. If OWNER is not a valid user name, it is decoded as a
+ decimal numeric user ID.
+
+'--uncompressed'
+ With '--create', don't compress the created tar archive. Create an
+ uncompressed tar archive instead.
+
+
+ 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
+invalid input file, 3 for an internal consistency error (eg, bug) which
+caused tarlz to panic.
+
+
+File: tarlz.info, Node: Examples, Next: Problems, Prev: Invoking tarlz, Up: Top
+
+3 A small tutorial with examples
+********************************
+
+Example 1: Create a multimember compressed archive 'archive.tar.lz'
+containing files 'a', 'b' and 'c'.
+
+ tarlz -cf archive.tar.lz a b c
+
+
+Example 2: Append files 'd' and 'e' to the multimember compressed
+archive 'archive.tar.lz'.
+
+ tarlz -rf archive.tar.lz d e
+
+
+Example 3: Create a solidly compressed appendable archive
+'archive.tar.lz' containing files 'a', 'b' and 'c'. Then append files
+'d' and 'e' to the archive.
+
+ tarlz --asolid -cf archive.tar.lz a b c
+ tarlz --asolid -rf archive.tar.lz d e
+
+
+Example 4: Create a compressed appendable archive containing directories
+'dir1', 'dir2' and 'dir3' with a separate lzip member per directory.
+Then append files 'a', 'b', 'c', 'd' and 'e' to the archive, all of
+them contained in a single lzip member. The resulting archive
+'archive.tar.lz' contains 5 lzip members (including the eof member).
+
+ tarlz --dsolid -cf archive.tar.lz dir1 dir2 dir3
+ tarlz --asolid -rf archive.tar.lz a b c d e
+
+
+Example 5: Create a solidly compressed archive 'archive.tar.lz'
+containing files 'a', 'b' and 'c'. Note that no more files can be later
+appended to the archive without decompressing it first.
+
+ tarlz --solid -cf archive.tar.lz a b c
+
+
+Example 6: Extract all files from archive 'archive.tar.lz'.
+
+ tarlz -xf archive.tar.lz
+
+
+Example 7: Extract files 'a' and 'c' from archive 'archive.tar.lz'.
+
+ tarlz -xf archive.tar.lz a c
+
+
+Example 8: Copy the contents of directory 'sourcedir' to the directory
+'targetdir'.
+
+ tarlz -C sourcedir -c . | tarlz -C targetdir -x
+
+
+File: tarlz.info, Node: Problems, Next: Concept index, Prev: Examples, Up: Top
+
+4 Reporting bugs
+****************
+
+There are probably bugs in tarlz. There are certainly errors and
+omissions in this manual. If you report them, they will get fixed. If
+you don't, no one will ever know about them and they will remain unfixed
+for all eternity, if not longer.
+
+ If you find a bug in tarlz, please send electronic mail to
+<lzip-bug@nongnu.org>. Include the version number, which you can find
+by running 'tarlz --version'.
+
+
+File: tarlz.info, Node: Concept index, Prev: Problems, Up: Top
+
+Concept index
+*************
+
+
+* Menu:
+
+* bugs: Problems. (line 6)
+* examples: Examples. (line 6)
+* getting help: Problems. (line 6)
+* introduction: Introduction. (line 6)
+* invoking: Invoking tarlz. (line 6)
+* options: Invoking tarlz. (line 6)
+* usage: Invoking tarlz. (line 6)
+* version: Invoking tarlz. (line 6)
+
+
+
+Tag Table:
+Node: Top223
+Node: Introduction785
+Node: Invoking tarlz3280
+Node: Examples7278
+Node: Problems8975
+Node: Concept index9501
+
+End Tag Table
+
+
+Local Variables:
+coding: iso-8859-15
+End:
diff --git a/doc/tarlz.texi b/doc/tarlz.texi
new file mode 100644
index 0000000..db51dd6
--- /dev/null
+++ b/doc/tarlz.texi
@@ -0,0 +1,348 @@
+\input texinfo @c -*-texinfo-*-
+@c %**start of header
+@setfilename tarlz.info
+@documentencoding ISO-8859-15
+@settitle Tarlz Manual
+@finalout
+@c %**end of header
+
+@set UPDATED 23 April 2018
+@set VERSION 0.4
+
+@dircategory Data Compression
+@direntry
+* Tarlz: (tarlz). Archiver with multimember lzip compression
+@end direntry
+
+
+@ifnothtml
+@titlepage
+@title Tarlz
+@subtitle Archiver with multimember lzip compression
+@subtitle for Tarlz version @value{VERSION}, @value{UPDATED}
+@author by Antonio Diaz Diaz
+
+@page
+@vskip 0pt plus 1filll
+@end titlepage
+
+@contents
+@end ifnothtml
+
+@node Top
+@top
+
+This manual is for Tarlz (version @value{VERSION}, @value{UPDATED}).
+
+@menu
+* Introduction:: Purpose and features of tarlz
+* Invoking tarlz:: Command line interface
+* Examples:: A small tutorial with examples
+* Problems:: Reporting bugs
+* Concept index:: Index of concepts
+@end menu
+
+@sp 1
+Copyright @copyright{} 2013-2018 Antonio Diaz Diaz.
+
+This manual is free documentation: you have unlimited permission
+to copy, distribute and modify it.
+
+
+@node Introduction
+@chapter Introduction
+@cindex introduction
+
+Tarlz is a small and simple implementation of the tar archiver. By
+default tarlz creates, lists and extracts archives in the 'ustar' format
+compressed with lzip on a per file basis. Tarlz can append files to the
+end of such compressed archives.
+
+Each tar member is compressed in its own lzip member, as well as the
+end-of-file blocks. This same method works for any tar format (gnu,
+ustar, posix) and is fully backward compatible with standard tar tools
+like GNU tar, which treat the resulting multimember tar.lz archive like
+any other tar.lz archive.
+
+Tarlz can create tar archives with four levels of compression
+granularity; per file, per directory, appendable solid, and solid.
+
+Tarlz is intended as a showcase project for the maintainers of real tar
+programs to evaluate the format and perhaps implement it in their tools.
+
+The diagram below shows the correspondence between tar members (formed
+by a header plus optional data) in the tar archive and
+@uref{http://www.nongnu.org/lzip/manual/lzip_manual.html#File-format,,lzip members}
+in the resulting multimember tar.lz archive:
+@ifnothtml
+@xref{File format,,,lzip}.
+@end ifnothtml
+
+@verbatim
+tar
++========+======+========+======+========+======+========+
+| header | data | header | data | header | data | eof |
++========+======+========+======+========+======+========+
+
+tar.lz
++===============+===============+===============+========+
+| member | member | member | member |
++===============+===============+===============+========+
+@end verbatim
+
+@noindent
+Of course, compressing each file (or each directory) individually is
+less efficient than compressing the whole tar archive, but it has the
+following advantages:
+
+@itemize @bullet
+@item
+The resulting multimember tar.lz archive can be decompressed in
+parallel with plzip, multiplying the decompression speed.
+
+@item
+New members can be appended to the archive (by removing the eof
+member) just like to an uncompressed tar archive.
+
+@item
+It is a safe posix-style backup format. In case of corruption,
+tarlz can extract all the undamaged members from the tar.lz
+archive, skipping over the damaged members, just like the standard
+(uncompressed) tar. Moreover, lziprecover can be used to recover at
+least part of the contents of the damaged members.
+
+@item
+A multimember tar.lz archive is usually smaller than the
+corresponding solidly compressed tar.gz archive, except when
+individually compressing files smaller than about 32 KiB.
+@end itemize
+
+
+@node Invoking tarlz
+@chapter Invoking tarlz
+@cindex invoking
+@cindex options
+@cindex usage
+@cindex version
+
+The format for running tarlz is:
+
+@example
+tarlz [@var{options}] [@var{files}]
+@end example
+
+@noindent
+On archive creation or appending, tarlz removes leading and trailing
+slashes from file names, as well as file name prefixes containing a
+@samp{..} component. On extraction, archive members containing a
+@samp{..} component are skipped.
+
+tarlz supports the following options:
+
+@table @code
+@item -h
+@itemx --help
+Print an informative help message describing the options and exit.
+
+@item -V
+@itemx --version
+Print the version number of tarlz on the standard output and exit.
+
+@item -c
+@itemx --create
+Create a new archive.
+
+@item -C @var{dir}
+@itemx --directory=@var{dir}
+Change to directory @var{dir}. When creating or appending, the position
+of each @code{-C} option in the command line is significant; it will
+change the current working directory for the following @var{files} until
+a new @code{-C} option appears in the command line. When extracting, all
+the @code{-C} options are executed in sequence before starting the
+extraction. Listing ignores any @code{-C} options specified. @var{dir}
+is relative to the then current working directory, perhaps changed by a
+previous @code{-C} option.
+
+@item -f @var{archive}
+@itemx --file=@var{archive}
+Use archive file @var{archive}. @samp{-} used as an @var{archive}
+argument reads from standard input or writes to standard output.
+
+@item -q
+@itemx --quiet
+Quiet operation. Suppress all messages.
+
+@item -r
+@itemx --append
+Append files to the end of an archive. The archive must be a regular
+(seekable) file compressed as a multimember lzip file, and the two
+end-of-file blocks plus any zero padding must be contained in the last
+lzip member of the archive. First this last member is removed, then the
+new members are appended, and then a new end-of-file member is appended
+to the archive. Exit with status 0 without modifying the archive if no
+@var{files} have been specified. tarlz can't append files to an
+uncompressed tar archive.
+
+@item -t
+@itemx --list
+List the contents of an archive.
+
+@item -v
+@itemx --verbose
+Verbosely list files processed.
+
+@item -x
+@itemx --extract
+Extract files from an archive.
+
+@item -0 .. -9
+Set the compression level. The default compression level is @samp{-6}.
+
+@item --asolid
+When creating or appending to a compressed archive, use appendable solid
+compression. All the files being added to the archive are compressed
+into a single lzip member, but the end-of-file blocks are compressed
+into a separate lzip member. This creates a solidly compressed
+appendable archive.
+
+@item --dsolid
+When creating or appending to a compressed archive, use solid
+compression for each directory especified in the command line. The
+end-of-file blocks are compressed into a separate lzip member. This
+creates a compressed appendable archive with a separate lzip member for
+each top-level directory.
+
+@item --solid
+When creating or appending to a compressed archive, use solid
+compression. The files being added to the archive, along with the
+end-of-file blocks, are compressed into a single lzip member. The
+resulting archive is not appendable. No more files can be later appended
+to the archive without decompressing it first.
+
+@item --group=@var{group}
+When creating or appending, use @var{group} for files added to the
+archive. If @var{group} is not a valid group name, it is decoded as a
+decimal numeric group ID.
+
+@item --owner=@var{owner}
+When creating or appending, use @var{owner} for files added to the
+archive. If @var{owner} is not a valid user name, it is decoded as a
+decimal numeric user ID.
+
+@item --uncompressed
+With @code{--create}, don't compress the created tar archive. Create an
+uncompressed tar archive instead.
+
+@end table
+
+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
+invalid input file, 3 for an internal consistency error (eg, bug) which
+caused tarlz to panic.
+
+
+@node Examples
+@chapter A small tutorial with examples
+@cindex examples
+
+@noindent
+Example 1: Create a multimember compressed archive @samp{archive.tar.lz}
+containing files @samp{a}, @samp{b} and @samp{c}.
+
+@example
+tarlz -cf archive.tar.lz a b c
+@end example
+
+@sp 1
+@noindent
+Example 2: Append files @samp{d} and @samp{e} to the multimember
+compressed archive @samp{archive.tar.lz}.
+
+@example
+tarlz -rf archive.tar.lz d e
+@end example
+
+@sp 1
+@noindent
+Example 3: Create a solidly compressed appendable archive
+@samp{archive.tar.lz} containing files @samp{a}, @samp{b} and @samp{c}.
+Then append files @samp{d} and @samp{e} to the archive.
+
+@example
+tarlz --asolid -cf archive.tar.lz a b c
+tarlz --asolid -rf archive.tar.lz d e
+@end example
+
+@sp 1
+@noindent
+Example 4: Create a compressed appendable archive containing directories
+@samp{dir1}, @samp{dir2} and @samp{dir3} with a separate lzip member per
+directory. Then append files @samp{a}, @samp{b}, @samp{c}, @samp{d} and
+@samp{e} to the archive, all of them contained in a single lzip member.
+The resulting archive @samp{archive.tar.lz} contains 5 lzip members
+(including the eof member).
+
+@example
+tarlz --dsolid -cf archive.tar.lz dir1 dir2 dir3
+tarlz --asolid -rf archive.tar.lz a b c d e
+@end example
+
+@sp 1
+@noindent
+Example 5: Create a solidly compressed archive @samp{archive.tar.lz}
+containing files @samp{a}, @samp{b} and @samp{c}. Note that no more
+files can be later appended to the archive without decompressing it
+first.
+
+@example
+tarlz --solid -cf archive.tar.lz a b c
+@end example
+
+@sp 1
+@noindent
+Example 6: Extract all files from archive @samp{archive.tar.lz}.
+
+@example
+tarlz -xf archive.tar.lz
+@end example
+
+@sp 1
+@noindent
+Example 7: Extract files @samp{a} and @samp{c} from archive
+@samp{archive.tar.lz}.
+
+@example
+tarlz -xf archive.tar.lz a c
+@end example
+
+@sp 1
+@noindent
+Example 8: Copy the contents of directory @samp{sourcedir} to the
+directory @samp{targetdir}.
+
+@example
+tarlz -C sourcedir -c . | tarlz -C targetdir -x
+@end example
+
+
+@node Problems
+@chapter Reporting bugs
+@cindex bugs
+@cindex getting help
+
+There are probably bugs in tarlz. There are certainly errors and
+omissions in this manual. If you report them, they will get fixed. If
+you don't, no one will ever know about them and they will remain unfixed
+for all eternity, if not longer.
+
+If you find a bug in tarlz, please send electronic mail to
+@email{lzip-bug@@nongnu.org}. Include the version number, which you can
+find by running @w{@code{tarlz --version}}.
+
+
+@node Concept index
+@unnumbered Concept index
+
+@printindex cp
+
+@bye
diff --git a/extract.cc b/extract.cc
new file mode 100644
index 0000000..67f4a20
--- /dev/null
+++ b/extract.cc
@@ -0,0 +1,561 @@
+/* Tarlz - Archiver with multimember lzip compression
+ Copyright (C) 2013-2018 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 <algorithm>
+#include <cerrno>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <string>
+#include <vector>
+#include <stdint.h>
+#include <unistd.h>
+#include <utime.h>
+#include <sys/stat.h>
+#include <lzlib.h>
+
+#include "arg_parser.h"
+#include "lzip.h"
+#include "tarlz.h"
+
+
+namespace {
+
+int gretval = 0;
+
+bool make_path( const std::string & name )
+ {
+ const mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
+ unsigned end = name.size(); // first slash before last component
+
+ while( end > 0 && name[end-1] == '/' ) --end; // remove trailing slashes
+ while( end > 0 && name[end-1] != '/' ) --end; // remove last component
+ while( end > 0 && name[end-1] == '/' ) --end; // remove more slashes
+
+ unsigned index = 0;
+ while( index < end )
+ {
+ while( index < end && name[index] == '/' ) ++index;
+ unsigned first = index;
+ while( index < end && name[index] != '/' ) ++index;
+ if( first < index )
+ {
+ const std::string partial( name, 0, index );
+ struct stat st;
+ if( stat( partial.c_str(), &st ) == 0 )
+ { if( !S_ISDIR( st.st_mode ) ) return false; }
+ else if( mkdir( partial.c_str(), mode ) != 0 )
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+// Returns in buf the first rd bytes of the second lzip member or
+// the first 512 bytes of the second tar member, and sets islz if lzip member
+bool skip_first_member( const int infd, uint8_t * const buf,
+ int & rd, bool & islz )
+ {
+ while( true )
+ {
+ for( int i = 0; i < rd; ++i )
+ if( buf[i] == 'L' && (*(Lzip_header *)( buf + i )).verify_prefix( rd - i ) )
+ {
+ const int ts = rd - i; // tail size
+ std::memmove( buf, buf + i, ts );
+ if( ts >= (int)sizeof lzip_magic )
+ { rd = ts; islz = true; return true; }
+ int rd2 = readblock( infd, buf + ts, header_size - ts );
+ if( rd2 != header_size - ts && errno )
+ { show_error( "Error reading archive", errno ); return false; }
+ if( ts + rd2 >= min_member_size &&
+ (*(Lzip_header *)buf).verify_magic() )
+ { rd = ts + rd2; islz = true; return true; }
+ std::memmove( buf, buf + ts, rd2 );
+ int rd3 = readblock( infd, buf + rd2, header_size - rd2 );
+ if( rd3 != header_size - rd2 && errno )
+ { show_error( "Error reading archive", errno ); return false; }
+ rd = rd2 + rd3; i = -1;
+ }
+ if( rd < header_size ) return false; // eof
+ if( rd == header_size && verify_ustar_chksum( buf ) )
+ { islz = false; return true; }
+ rd = readblock( infd, buf, header_size );
+ if( rd != header_size && errno )
+ { show_error( "Error reading archive", errno ); return false; }
+ }
+ }
+
+
+inline bool block_is_zero( const uint8_t * const buf, const int size )
+ {
+ for( int i = 0; i < size; ++i ) if( buf[i] != 0 ) return false;
+ return true;
+ }
+
+
+bool archive_read( const int infd, uint8_t * const buf, const int size )
+ {
+ static LZ_Decoder * decoder = 0;
+ static bool first_call = true;
+ static bool at_eof = false;
+
+ if( first_call ) // check format
+ {
+ first_call = false;
+ if( size != header_size )
+ internal_error( "size != header_size on first call." );
+ int rd = readblock( infd, buf, size );
+ if( rd != size && errno )
+ { show_error( "Error reading archive", errno ); return false; }
+ bool islz =
+ ( rd >= min_member_size && (*(Lzip_header *)buf).verify_magic() );
+ const bool istar = ( rd == size && verify_ustar_chksum( buf ) );
+ const bool iseof =
+ ( !islz && !istar && rd == size && block_is_zero( buf, size ) );
+ if( !islz && !istar && !iseof )
+ {
+ show_error( "This does not look like a tar archive." );
+ show_error( "Skipping to next header." );
+// std::fprintf( stderr, "%07o\n", ustar_chksum( buf ) );
+ gretval = 2;
+ if( !skip_first_member( infd, buf, rd, islz ) ) return false;
+ }
+ if( !islz ) return true; // uncompressed
+ decoder = LZ_decompress_open(); // compressed
+ if( !decoder || LZ_decompress_errno( decoder ) != LZ_ok )
+ { show_error( "Not enough memory." );
+ LZ_decompress_close( decoder ); return false; }
+ if( LZ_decompress_write( decoder, buf, rd ) != rd )
+ internal_error( "library error (LZ_decompress_write)." );
+ if( !archive_read( infd, buf, size ) ) return false;
+ if( verify_ustar_chksum( buf ) || block_is_zero( buf, size ) ) return true;
+ show_error( "This does not look like a tar archive." );
+ show_error( "Skipping to next header." );
+ gretval = 2;
+ if( LZ_decompress_sync_to_member( decoder ) < 0 )
+ internal_error( "library error (LZ_decompress_sync_to_member)." );
+ }
+
+ if( !decoder ) // uncompressed
+ { if( readblock( infd, buf, size ) == size ) return true;
+ show_error( "Archive ends unexpectedly." ); return false; }
+ const int ibuf_size = 16384;
+ uint8_t ibuf[ibuf_size];
+ int sz = 0;
+ while( sz < size )
+ {
+ if( !at_eof && LZ_decompress_write_size( decoder ) > 0 )
+ {
+ const int rsize = std::min( ibuf_size, LZ_decompress_write_size( decoder ) );
+ const int rd = readblock( infd, ibuf, rsize );
+ if( LZ_decompress_write( decoder, ibuf, rd ) != rd )
+ internal_error( "library error (LZ_decompress_write)." );
+ if( rd < rsize )
+ {
+ at_eof = true; LZ_decompress_finish( decoder );
+ if( errno )
+ { show_error( "Error reading archive", errno ); return false; }
+ }
+ }
+ const int rd = LZ_decompress_read( decoder, buf + sz, size - sz );
+ if( rd < 0 )
+ {
+ show_error( "Skipping to next header." );
+ gretval = 2;
+ if( LZ_decompress_sync_to_member( decoder ) < 0 )
+ internal_error( "library error (LZ_decompress_sync_to_member)." );
+ continue;
+ }
+ if( rd == 0 && LZ_decompress_finished( decoder ) == 1 )
+ { LZ_decompress_close( decoder );
+ show_error( "Archive ends unexpectedly." ); return false; }
+ sz += rd;
+ if( sz == size && LZ_decompress_finished( decoder ) == 1 &&
+ LZ_decompress_close( decoder ) < 0 )
+ { show_error( "LZ_decompress_close failed." ); return false; }
+ }
+ return true;
+ }
+
+
+const char * mode_string( const Tar_header header )
+ {
+ static char buf[11];
+ const Typeflag typeflag = (Typeflag)header[typeflag_o];
+
+ std::memcpy( buf, "----------", sizeof buf - 1 );
+ switch( typeflag )
+ {
+ case tf_regular: break;
+ case tf_link: buf[0] = 'h'; break;
+ case tf_symlink: buf[0] = 'l'; break;
+ case tf_chardev: buf[0] = 'c'; break;
+ case tf_blockdev: buf[0] = 'b'; break;
+ case tf_directory: buf[0] = 'd'; break;
+ case tf_fifo: buf[0] = 'p'; break;
+ case tf_hiperf: buf[0] = 'C'; break;
+ default: buf[0] = '?';
+ }
+ const mode_t mode = strtoul( header + mode_o, 0, 8 ); // 12 bits
+ const bool setuid = mode & S_ISUID;
+ const bool setgid = mode & S_ISGID;
+ const bool sticky = mode & S_ISVTX;
+ if( mode & S_IRUSR ) buf[1] = 'r';
+ if( mode & S_IWUSR ) buf[2] = 'w';
+ if( mode & S_IXUSR ) buf[3] = setuid ? 's' : 'x';
+ else if( setuid ) buf[3] = 'S';
+ if( mode & S_IRGRP ) buf[4] = 'r';
+ if( mode & S_IWGRP ) buf[5] = 'w';
+ if( mode & S_IXGRP ) buf[6] = setgid ? 's' : 'x';
+ else if( setgid ) buf[6] = 'S';
+ if( mode & S_IROTH ) buf[7] = 'r';
+ if( mode & S_IWOTH ) buf[8] = 'w';
+ if( mode & S_IXOTH ) buf[9] = sticky ? 't' : 'x';
+ else if( sticky ) buf[9] = 'T';
+ return buf;
+ }
+
+
+const char * user_group_string( const Tar_header header )
+ {
+ enum { bufsize = uname_l + 1 + gname_l + 1 };
+ static char buf[bufsize];
+
+ if( header[uname_o] && header[gname_o] )
+ snprintf( buf, bufsize, "%.32s/%.32s", header + uname_o, header + gname_o );
+ else
+ {
+ const int uid = strtoul( header + uid_o, 0, 8 );
+ const int gid = strtoul( header + gid_o, 0, 8 );
+ snprintf( buf, bufsize, "%u/%u", uid, gid );
+ }
+ return buf;
+ }
+
+
+const char * link_string( const Tar_header header )
+ {
+ enum { bufsize = 9 + linkname_l + 1 };
+ static char buf[bufsize];
+ const Typeflag typeflag = (Typeflag)header[typeflag_o];
+
+ if( typeflag == tf_link )
+ snprintf( buf, bufsize, " link to %.100s", header + linkname_o );
+ else if( typeflag == tf_symlink )
+ snprintf( buf, bufsize, " -> %.100s", header + linkname_o );
+ else buf[0] = 0;
+ return buf;
+ }
+
+
+void show_member_name( const char * const filename, const Tar_header header,
+ const int vlevel )
+ {
+ if( verbosity < vlevel ) return;
+ if( verbosity > vlevel )
+ {
+ const time_t mtime = strtoull( header + mtime_o, 0, 8 ); // 33 bits
+ struct tm * tm = localtime( &mtime );
+ std::printf( "%s %s %9llu %4d-%02u-%02u %02u:%02u %s%s\n",
+ mode_string( header ), user_group_string( header ),
+ strtoull( header + size_o, 0, 8 ), 1900 + tm->tm_year,
+ 1 + tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min,
+ filename, link_string( header ) );
+ }
+ else std::printf( "%s\n", filename );
+ std::fflush( stdout );
+ }
+
+
+int list_member( const int infd, const char * const filename,
+ const unsigned long long file_size, const Tar_header header,
+ const bool skip )
+ {
+ if( !skip ) show_member_name( filename, header, 0 );
+
+ const unsigned bufsize = 32 * header_size;
+ uint8_t buf[bufsize];
+ unsigned long long rest = file_size;
+ const int rem = file_size % header_size;
+ const int padding = rem ? header_size - rem : 0;
+ while( rest > 0 )
+ {
+ const int rsize = ( rest >= bufsize ) ? bufsize : rest + padding;
+ if( !archive_read( infd, buf, rsize ) ) return 2;
+ if( rest < bufsize ) break;
+ rest -= rsize;
+ }
+ return 0;
+ }
+
+
+bool contains_dotdot( const char * const filename )
+ {
+ for( int i = 0; filename[i]; ++i )
+ if( filename[i] == '.' && filename[i+1] == '.' &&
+ ( i == 0 || filename[i-1] == '/' ) &&
+ ( filename[i+2] == 0 || filename[i+2] == '/' ) ) return true;
+ return false;
+ }
+
+
+int extract_member( const int infd, const char * const filename,
+ const unsigned long long file_size, const Tar_header header )
+ {
+ if( contains_dotdot( filename ) )
+ {
+ show_file_error( filename, "Contains a '..' component, skipping." );
+ return list_member( infd, filename, file_size, header, true );
+ }
+ const mode_t mode = strtoul( header + mode_o, 0, 8 ); // 12 bits
+ const time_t mtime = strtoull( header + mtime_o, 0, 8 ); // 33 bits
+ const Typeflag typeflag = (Typeflag)header[typeflag_o];
+ const bool islink = ( typeflag == tf_link || typeflag == tf_symlink );
+ int outfd = -1;
+
+ show_member_name( filename, header, 1 );
+ std::remove( filename );
+ make_path( filename );
+ switch( typeflag )
+ {
+ case tf_regular:
+ case tf_hiperf:
+ outfd = open_outstream( filename );
+ if( outfd < 0 ) return 2;
+ chmod( filename, mode ); // ignore errors
+ break;
+ case tf_link:
+ case tf_symlink:
+ {
+ char linkname[linkname_l+1];
+ std::memcpy( linkname, header + linkname_o, linkname_l );
+ linkname[linkname_l] = 0;
+/* if( contains_dotdot( linkname ) )
+ {
+ show_file_error( filename,
+ "Link destination contains a '..' component, skipping." );
+ return list_member( infd, filename, file_size, header, false );
+ }*/
+ const bool hard = typeflag == tf_link;
+ if( ( hard && link( linkname, filename ) != 0 ) ||
+ ( !hard && symlink( linkname, filename ) != 0 ) )
+ {
+ if( verbosity >= 0 )
+ std::fprintf( stderr, "Can't %slink file '%s' to '%s': %s.\n",
+ hard ? "" : "sym", linkname, filename,
+ std::strerror( errno ) );
+ return 2;
+ }
+ } break;
+ case tf_directory:
+ if( mkdir( filename, mode ) != 0 && errno != EEXIST )
+ {
+ show_file_error( filename, "Can't create directory", errno );
+ return 2;
+ }
+ break;
+ case tf_chardev:
+ case tf_blockdev:
+ {
+ const unsigned dev = makedev( strtoul( header + devmajor_o, 0, 8 ),
+ strtoul( header + devminor_o, 0, 8 ) );
+ const int dmode = ( typeflag == tf_chardev ? S_IFCHR : S_IFBLK ) | mode;
+ if( mknod( filename, dmode, dev ) != 0 )
+ {
+ show_file_error( filename, "Can't create device node", errno );
+ return 2;
+ }
+ break;
+ }
+ case tf_fifo:
+ if( mkfifo( filename, mode ) != 0 && errno != EEXIST )
+ {
+ show_file_error( filename, "Can't create FIFO file", errno );
+ return 2;
+ }
+ break;
+ default:
+ if( verbosity >= 0 )
+ std::fprintf( stderr, "File type '%c' not supported for file '%s'.\n",
+ typeflag, filename );
+ return 2;
+ }
+
+ const uid_t uid = (uid_t)strtoul( header + uid_o, 0, 8 );
+ const gid_t gid = (gid_t)strtoul( header + gid_o, 0, 8 );
+ if( !islink && chown( filename, uid, gid ) != 0 &&
+ errno != EPERM && errno != EINVAL )
+ {
+ show_file_error( filename, "Can't change file owner", errno );
+ return 2;
+ }
+
+ const unsigned bufsize = 32 * header_size;
+ uint8_t buf[bufsize];
+ unsigned long long rest = file_size;
+ const int rem = file_size % header_size;
+ const int padding = rem ? header_size - rem : 0;
+ while( rest > 0 )
+ {
+ const int rsize = ( rest >= bufsize ) ? bufsize : rest + padding;
+ if( !archive_read( infd, buf, rsize ) )
+ { if( outfd >= 0 ) { close( outfd ); std::remove( filename ); }
+ return 2; }
+ const int wsize = ( rest >= bufsize ) ? bufsize : rest;
+ if( outfd >= 0 && writeblock( outfd, buf, wsize ) != wsize )
+ { show_file_error( filename, "Error writing file", errno ); return 2; }
+ rest -= wsize;
+ }
+ if( outfd >= 0 && close( outfd ) != 0 )
+ { show_file_error( filename, "Error closing file", errno ); return 2; }
+ if( !islink )
+ {
+ struct utimbuf t;
+ t.actime = mtime;
+ t.modtime = mtime;
+ utime( filename, &t ); // ignore errors
+ }
+ return 0;
+ }
+
+
+const char * remove_leading_slash( const char * const filename )
+ {
+ static bool first_post = true;
+ const char * p = filename;
+
+ while( *p == '/' || ( *p == '.' && p[1] == '/' ) ) ++p;
+ if( p != filename && first_post )
+ {
+ first_post = false;
+ std::string msg( "Removing leading '" );
+ msg.append( filename, p - filename );
+ msg += "' from member names.";
+ show_error( msg.c_str() );
+ }
+ if( *p == 0 ) p = ".";
+ return p;
+ }
+
+
+// return true if dir is a parent directory of name
+bool compare_prefix_dir( const char * const dir, const char * const name )
+ {
+ int len = 0;
+ while( dir[len] && dir[len] == name[len] ) ++len;
+ return ( !dir[len] && len > 0 && ( dir[len-1] == '/' || name[len] == '/' ) );
+ }
+
+
+// compare two file names ignoring trailing slashes
+bool compare_tslash( const char * const name1, const char * const name2 )
+ {
+ const char * p = name1;
+ const char * q = name2;
+ while( *p && *p == *q ) { ++p; ++q; }
+ while( *p == '/' ) ++p;
+ while( *q == '/' ) ++q;
+ return ( !*p && !*q );
+ }
+
+} // end namespace
+
+
+int decode( const std::string & archive_name, const Arg_parser & parser,
+ const int filenames, const bool listing )
+ {
+ const int infd = archive_name.size() ?
+ open_instream( archive_name ) : STDIN_FILENO;
+ if( infd < 0 ) return 1;
+
+ std::vector< bool > name_pending( parser.arguments(), false );
+ for( int i = 0; i < parser.arguments(); ++i )
+ {
+ const int code = parser.code( i );
+ if( code == 'C' && !listing )
+ {
+ const char * const filename = parser.argument( i ).c_str();
+ if( chdir( filename ) != 0 )
+ { show_file_error( filename, "Error changing working directory", errno );
+ return 1; }
+ }
+ if( !code ) name_pending[i] = true;
+ }
+
+ int retval = 0;
+ bool skipping = false;
+ while( true ) // process one member per iteration
+ {
+ uint8_t buf[header_size];
+ if( !archive_read( infd, buf, header_size ) ) return 2;
+ if( !verify_ustar_chksum( buf ) )
+ {
+ if( block_is_zero( buf, header_size ) ) break;
+ gretval = 2;
+ if( !skipping )
+ { skipping = true; show_error( "Skipping to next header." ); }
+ continue;
+ }
+ skipping = false;
+
+ const char * const header = (const char *)buf;
+ enum { max_filename_size = prefix_l + 1 + name_l + 1 };
+ char stored_name[max_filename_size];
+ int len = 0;
+ while( len < prefix_l && header[prefix_o+len] )
+ { stored_name[len] = header[prefix_o+len]; ++len; }
+ if( len && header[name_o] ) stored_name[len++] = '/';
+ for( int i = 0; i < name_l && header[name_o+i]; ++i )
+ { stored_name[len] = header[name_o+i]; ++len; }
+ while( len > 0 && stored_name[len-1] == '/' ) --len; // trailing '/'
+ stored_name[len] = 0;
+ const char * const filename = remove_leading_slash( stored_name );
+
+ bool skip = filenames > 0;
+ if( skip )
+ for( int i = 0; i < parser.arguments(); ++i )
+ if( parser.code( i ) == 0 &&
+ ( compare_prefix_dir( parser.argument( i ).c_str(), filename ) ||
+ compare_tslash( filename, parser.argument( i ).c_str() ) ) )
+ { skip = false; name_pending[i] = false; break; }
+
+ const Typeflag typeflag = (Typeflag)header[typeflag_o];
+ const unsigned long long file_size =
+ ( typeflag == tf_regular || typeflag == tf_hiperf ) ?
+ strtoull( header + size_o, 0, 8 ) : 0;
+ if( listing || skip )
+ retval = list_member( infd, filename, file_size, header, skip );
+ else
+ retval = extract_member( infd, filename, file_size, header );
+ if( retval ) return retval;
+ }
+
+ for( int i = 0; i < parser.arguments(); ++i )
+ if( parser.code( i ) == 0 && name_pending[i] )
+ {
+ show_file_error( parser.argument( i ).c_str(), "Not found in archive." );
+ if( gretval < 1 ) gretval = 1;
+ }
+ if( !retval && gretval )
+ { show_error( "Exiting with failure status due to previous errors." );
+ retval = gretval; }
+ return retval;
+ }
diff --git a/lzip.h b/lzip.h
new file mode 100644
index 0000000..03ce788
--- /dev/null
+++ b/lzip.h
@@ -0,0 +1,112 @@
+/* Tarlz - Archiver with multimember lzip compression
+ Copyright (C) 2013-2018 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/>.
+*/
+
+#ifndef LZ_API_VERSION
+#define LZ_API_VERSION 1
+#endif
+
+enum {
+ min_dictionary_bits = 12,
+ min_dictionary_size = 1 << min_dictionary_bits,
+ max_dictionary_bits = 29,
+ max_dictionary_size = 1 << max_dictionary_bits,
+ min_member_size = 36 };
+
+
+class CRC32
+ {
+ uint32_t data[256]; // Table of CRCs of all 8-bit messages.
+
+public:
+ CRC32()
+ {
+ for( unsigned n = 0; n < 256; ++n )
+ {
+ unsigned c = n;
+ for( int k = 0; k < 8; ++k )
+ { if( c & 1 ) c = 0xEDB88320U ^ ( c >> 1 ); else c >>= 1; }
+ data[n] = c;
+ }
+ }
+
+ void update_byte( uint32_t & crc, const uint8_t byte ) const
+ { crc = data[(crc^byte)&0xFF] ^ ( crc >> 8 ); }
+ };
+
+
+inline bool isvalid_ds( const unsigned dictionary_size )
+ { return ( dictionary_size >= min_dictionary_size &&
+ dictionary_size <= max_dictionary_size ); }
+
+
+const uint8_t lzip_magic[5] = { 0x4C, 0x5A, 0x49, 0x50, 1 }; // "LZIP\1"
+
+struct Lzip_header
+ {
+ uint8_t data[6]; // 0-3 magic bytes
+ // 4 version
+ // 5 coded_dict_size
+ enum { size = 6 };
+
+ bool verify_magic() const
+ { return ( std::memcmp( data, lzip_magic, 5 ) == 0 ); }
+
+ bool verify_prefix( const int sz ) const // detect (truncated) header
+ {
+ for( int i = 0; i < sz && i < 5; ++i )
+ if( data[i] != lzip_magic[i] ) return false;
+ return ( sz > 0 );
+ }
+
+ unsigned dictionary_size() const
+ {
+ unsigned sz = ( 1 << ( data[5] & 0x1F ) );
+ if( sz > min_dictionary_size )
+ sz -= ( sz / 16 ) * ( ( data[5] >> 5 ) & 7 );
+ return sz;
+ }
+ };
+
+
+struct Lzip_trailer
+ {
+ uint8_t data[20]; // 0-3 CRC32 of the uncompressed data
+ // 4-11 size of the uncompressed data
+ // 12-19 member size including header and trailer
+ enum { size = 20 };
+
+ unsigned data_crc() const
+ {
+ unsigned tmp = 0;
+ for( int i = 3; i >= 0; --i ) { tmp <<= 8; tmp += data[i]; }
+ return tmp;
+ }
+
+ unsigned long long data_size() const
+ {
+ unsigned long long tmp = 0;
+ for( int i = 11; i >= 4; --i ) { tmp <<= 8; tmp += data[i]; }
+ return tmp;
+ }
+
+ unsigned long long member_size() const
+ {
+ unsigned long long tmp = 0;
+ for( int i = 19; i >= 12; --i ) { tmp <<= 8; tmp += data[i]; }
+ return tmp;
+ }
+ };
diff --git a/main.cc b/main.cc
new file mode 100644
index 0000000..19a6d31
--- /dev/null
+++ b/main.cc
@@ -0,0 +1,369 @@
+/* Tarlz - Archiver with multimember lzip compression
+ Copyright (C) 2013-2018 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/>.
+*/
+/*
+ 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 invalid input file, 3 for an internal consistency error
+ (eg, bug) which caused tarlz to panic.
+*/
+
+#define _FILE_OFFSET_BITS 64
+
+#include <cctype>
+#include <cerrno>
+#include <climits>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <string>
+#include <vector>
+#include <fcntl.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <grp.h>
+#include <pwd.h>
+#if defined(__OS2__)
+#include <io.h>
+#endif
+
+#include "arg_parser.h"
+#include "tarlz.h"
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+#if CHAR_BIT != 8
+#error "Environments where CHAR_BIT != 8 are not supported."
+#endif
+
+int verbosity = 0;
+
+namespace {
+
+const char * const Program_name = "Tarlz";
+const char * const program_name = "tarlz";
+const char * const program_year = "2018";
+const char * invocation_name = 0;
+
+enum Mode { m_none, m_append, m_create, m_extract, m_list };
+
+
+void show_help()
+ {
+ std::printf( "%s - Archiver with multimember lzip compression.\n", Program_name );
+ std::printf( "\nUsage: %s [options] [files]\n", invocation_name );
+ std::printf( "\nOptions:\n"
+ " -h, --help display this help and exit\n"
+ " -V, --version output version information and exit\n"
+ " -c, --create create a new archive\n"
+ " -C, --directory=<dir> change to directory <dir>\n"
+ " -f, --file=<archive> use archive file <archive>\n"
+ " -q, --quiet suppress all messages\n"
+ " -r, --append append files to the end of an archive\n"
+ " -t, --list list the contents of an archive\n"
+ " -v, --verbose verbosely list files processed\n"
+ " -x, --extract extract files from an archive\n"
+ " -0 .. -9 set compression level [default 6]\n"
+ " --asolid create solidly compressed appendable archive\n"
+ " --dsolid create per-directory compressed archive\n"
+ " --solid create solidly compressed archive\n"
+ " --group=<group> use <group> name/id for added files\n"
+ " --owner=<owner> use <owner> name/id for added files\n"
+ " --uncompressed don't compress the created archive\n"
+ "\nExit status: 0 for a normal exit, 1 for environmental problems (file\n"
+ "not found, invalid flags, I/O errors, etc), 2 to indicate a corrupt or\n"
+ "invalid input file, 3 for an internal consistency error (eg, bug) which\n"
+ "caused tarlz to panic.\n"
+ "\nReport bugs to lzip-bug@nongnu.org\n"
+ "Tarlz home page: http://www.nongnu.org/lzip/tarlz.html\n" );
+ }
+
+
+void show_version()
+ {
+ std::printf( "%s %s\n", program_name, PROGVERSION );
+ std::printf( "Copyright (C) %s Antonio Diaz Diaz.\n", program_year );
+ std::printf( "License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html>\n"
+ "This is free software: you are free to change and redistribute it.\n"
+ "There is NO WARRANTY, to the extent permitted by law.\n" );
+ }
+
+
+unsigned long long getnum( const char * const ptr,
+ const unsigned long long llimit,
+ const unsigned long long ulimit )
+ {
+ char * tail;
+ errno = 0;
+ unsigned long long result = strtoull( ptr, &tail, 0 );
+ if( tail == ptr )
+ {
+ show_error( "Bad or missing numerical argument.", 0, true );
+ std::exit( 1 );
+ }
+
+ if( !errno && tail[0] )
+ {
+ const unsigned factor = ( tail[1] == 'i' ) ? 1024 : 1000;
+ int exponent = 0; // 0 = bad multiplier
+ switch( tail[0] )
+ {
+ case 'Y': exponent = 8; break;
+ case 'Z': exponent = 7; break;
+ case 'E': exponent = 6; break;
+ case 'P': exponent = 5; break;
+ case 'T': exponent = 4; break;
+ case 'G': exponent = 3; break;
+ case 'M': exponent = 2; break;
+ case 'K': if( factor == 1024 ) exponent = 1; break;
+ case 'k': if( factor == 1000 ) exponent = 1; break;
+ }
+ if( exponent <= 0 )
+ {
+ show_error( "Bad multiplier in numerical argument.", 0, true );
+ std::exit( 1 );
+ }
+ for( int i = 0; i < exponent; ++i )
+ {
+ if( ulimit / factor >= result ) result *= factor;
+ else { errno = ERANGE; break; }
+ }
+ }
+ if( !errno && ( result < llimit || result > ulimit ) ) errno = ERANGE;
+ if( errno )
+ {
+ show_error( "Numerical argument out of limits." );
+ std::exit( 1 );
+ }
+ return result;
+ }
+
+
+void set_mode( Mode & program_mode, const Mode new_mode )
+ {
+ if( program_mode != m_none && program_mode != new_mode )
+ {
+ show_error( "Only one operation can be specified.", 0, true );
+ std::exit( 1 );
+ }
+ program_mode = new_mode;
+ }
+
+
+void set_owner( const char * const arg )
+ {
+ const struct passwd * const pw = getpwnam( arg );
+ if( pw ) cl_owner = pw->pw_uid;
+ else if( std::isdigit( arg[0] ) ) cl_owner = getnum( arg, 0, INT_MAX );
+ else { show_file_error( arg, "Invalid owner" ); std::exit( 1 ); }
+ }
+
+void set_group( const char * const arg )
+ {
+ const struct group * const gr = getgrnam( arg );
+ if( gr ) cl_group = gr->gr_gid;
+ else if( std::isdigit( arg[0] ) ) cl_group = getnum( arg, 0, INT_MAX );
+ else { show_file_error( arg, "Invalid group" ); std::exit( 1 ); }
+ }
+
+} // end namespace
+
+
+int open_instream( const std::string & name )
+ {
+ const int infd = open( name.c_str(), O_RDONLY | O_BINARY );
+ if( infd < 0 )
+ show_file_error( name.c_str(), "Can't open for reading", errno );
+ return infd;
+ }
+
+
+int open_outstream( const std::string & name, const bool create )
+ {
+ const int flags = (create ? O_CREAT | O_WRONLY | O_TRUNC : O_RDWR) | O_BINARY;
+ const mode_t outfd_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
+
+ const int outfd = open( name.c_str(), flags, outfd_mode );
+ if( outfd < 0 )
+ show_file_error( name.c_str(), create ?
+ "Can't create file" : "Error opening file", errno );
+ return outfd;
+ }
+
+
+/* Returns the number of bytes really read.
+ If (returned value < size) and (errno == 0), means EOF was reached.
+*/
+int readblock( const int fd, uint8_t * const buf, const int size )
+ {
+ int sz = 0;
+ errno = 0;
+ while( sz < size )
+ {
+ const int n = read( fd, buf + sz, size - sz );
+ if( n > 0 ) sz += n;
+ else if( n == 0 ) break; // EOF
+ else if( errno != EINTR ) break;
+ errno = 0;
+ }
+ return sz;
+ }
+
+
+/* Returns the number of bytes really written.
+ If (returned value < size), it is always an error.
+*/
+int writeblock( const int fd, const uint8_t * const buf, const int size )
+ {
+ int sz = 0;
+ errno = 0;
+ while( sz < size )
+ {
+ const int n = write( fd, buf + sz, size - sz );
+ if( n > 0 ) sz += n;
+ else if( n < 0 && errno != EINTR ) break;
+ errno = 0;
+ }
+ return sz;
+ }
+
+
+void show_error( const char * const msg, const int errcode, const bool help )
+ {
+ if( verbosity < 0 ) return;
+ if( msg && msg[0] )
+ std::fprintf( stderr, "%s: %s%s%s\n", program_name, msg,
+ ( errcode > 0 ) ? ": " : "",
+ ( errcode > 0 ) ? std::strerror( errcode ) : "" );
+ if( help )
+ std::fprintf( stderr, "Try '%s --help' for more information.\n",
+ invocation_name );
+ }
+
+
+void show_file_error( const char * const filename, const char * const msg,
+ const int errcode )
+ {
+ if( verbosity >= 0 )
+ std::fprintf( stderr, "%s: %s: %s%s%s\n", program_name, filename, msg,
+ ( errcode > 0 ) ? ": " : "",
+ ( errcode > 0 ) ? std::strerror( errcode ) : "" );
+ }
+
+
+void internal_error( const char * const msg )
+ {
+ if( verbosity >= 0 )
+ std::fprintf( stderr, "%s: internal error: %s\n", program_name, msg );
+ std::exit( 3 );
+ }
+
+
+int main( const int argc, const char * const argv[] )
+ {
+ std::string archive_name;
+ Mode program_mode = m_none;
+ int level = 6; // compression level, < 0 = uncompressed
+ invocation_name = argv[0];
+
+ enum { opt_aso = 256, opt_dso, opt_grp, opt_own, opt_sol, opt_un };
+ const Arg_parser::Option options[] =
+ {
+ { '0', 0, Arg_parser::no },
+ { '1', 0, Arg_parser::no },
+ { '2', 0, Arg_parser::no },
+ { '3', 0, Arg_parser::no },
+ { '4', 0, Arg_parser::no },
+ { '5', 0, Arg_parser::no },
+ { '6', 0, Arg_parser::no },
+ { '7', 0, Arg_parser::no },
+ { '8', 0, Arg_parser::no },
+ { '9', 0, Arg_parser::no },
+ { 'c', "create", Arg_parser::no },
+ { 'C', "directory", Arg_parser::yes },
+ { 'f', "file", Arg_parser::yes },
+ { 'h', "help", Arg_parser::no },
+ { 'H', "format", Arg_parser::yes },
+ { 'q', "quiet", Arg_parser::no },
+ { 'r', "append", Arg_parser::no },
+ { 't', "list", Arg_parser::no },
+ { 'v', "verbose", Arg_parser::no },
+ { 'V', "version", Arg_parser::no },
+ { 'x', "extract", Arg_parser::no },
+ { opt_aso, "asolid", Arg_parser::no },
+ { opt_dso, "dsolid", Arg_parser::no },
+ { opt_grp, "group", Arg_parser::yes },
+ { opt_own, "owner", Arg_parser::yes },
+ { opt_sol, "solid", Arg_parser::no },
+ { opt_un, "uncompressed", Arg_parser::no },
+ { 0 , 0, Arg_parser::no } };
+
+ const Arg_parser parser( argc, argv, options, true );
+ if( parser.error().size() ) // bad option
+ { show_error( parser.error().c_str(), 0, true ); return 1; }
+
+ int filenames = 0;
+ for( int argind = 0; argind < parser.arguments(); ++argind )
+ {
+ const int code = parser.code( argind );
+ if( !code ) { ++filenames; continue; } // skip non-options
+ const std::string & sarg = parser.argument( argind );
+ const char * const arg = sarg.c_str();
+ switch( code )
+ {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ level = code - '0'; break;
+ case 'c': set_mode( program_mode, m_create ); break;
+ case 'C': break; // skip chdir
+ case 'f': if( sarg != "-" ) archive_name = sarg; break;
+ case 'h': show_help(); return 0;
+ case 'H': break; // ignore format
+ case 'q': verbosity = -1; break;
+ case 'r': set_mode( program_mode, m_append ); break;
+ case 't': set_mode( program_mode, m_list ); break;
+ case 'v': if( verbosity < 4 ) ++verbosity; break;
+ case 'V': show_version(); return 0;
+ case 'x': set_mode( program_mode, m_extract ); break;
+ case opt_aso: cl_solid = 2; break;
+ case opt_dso: cl_solid = 1; break;
+ case opt_grp: set_group( arg ); break;
+ case opt_own: set_owner( arg ); break;
+ case opt_sol: cl_solid = 3; break;
+ case opt_un: level = -1; break;
+ default : internal_error( "uncaught option" );
+ }
+ } // end process options
+
+#if defined(__MSVCRT__) || defined(__OS2__)
+ setmode( STDIN_FILENO, O_BINARY );
+ setmode( STDOUT_FILENO, O_BINARY );
+#endif
+
+ switch( program_mode )
+ {
+ case m_none: show_error( "Missing operation.", 0, true ); return 2;
+ case m_append:
+ case m_create: return encode( archive_name, parser, filenames, level,
+ program_mode == m_append );
+ case m_extract:
+ case m_list:
+ return decode( archive_name, parser, filenames, program_mode == m_list );
+ }
+ }
diff --git a/tarlz.h b/tarlz.h
new file mode 100644
index 0000000..3bb10c9
--- /dev/null
+++ b/tarlz.h
@@ -0,0 +1,68 @@
+/* Tarlz - Archiver with multimember lzip compression
+ Copyright (C) 2013-2018 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/>.
+*/
+
+enum { header_size = 512 };
+typedef char Tar_header[header_size];
+
+enum Offsets {
+ name_o = 0, mode_o = 100, uid_o = 108, gid_o = 116, size_o = 124,
+ mtime_o = 136, chksum_o = 148, typeflag_o = 156, linkname_o = 157,
+ magic_o = 257, version_o = 263, uname_o = 265, gname_o = 297,
+ devmajor_o = 329, devminor_o = 337, prefix_o = 345 };
+
+enum Lengths {
+ name_l = 100, mode_l = 8, uid_l = 8, gid_l = 8, size_l = 12,
+ mtime_l = 12, chksum_l = 8, typeflag_l = 1, linkname_l = 100,
+ magic_l = 6, version_l = 2, uname_l = 32, gname_l = 32,
+ devmajor_l = 8, devminor_l = 8, prefix_l = 155 };
+
+enum Typeflag {
+ tf_regular = '0', tf_link = '1', tf_symlink = '2', tf_chardev = '3',
+ tf_blockdev = '4', tf_directory = '5', tf_fifo = '6', tf_hiperf = '7' };
+
+const uint8_t ustar_magic[magic_l] =
+ { 0x75, 0x73, 0x74, 0x61, 0x72, 0 }; // "ustar\0"
+
+inline bool verify_ustar_magic( const uint8_t * const buf )
+ { return std::memcmp( buf + magic_o, ustar_magic, magic_l ) == 0; }
+
+
+// defined in create.cc
+extern int cl_owner;
+extern int cl_group;
+extern int cl_solid;
+unsigned ustar_chksum( const uint8_t * const buf );
+bool verify_ustar_chksum( const uint8_t * const buf );
+class Arg_parser;
+int encode( const std::string & archive_name, const Arg_parser & parser,
+ const int filenames, const int level, const bool append );
+
+// defined in extract.cc
+int decode( const std::string & archive_name, const Arg_parser & parser,
+ const int filenames, const bool listing );
+
+// defined in main.cc
+extern int verbosity;
+int open_instream( const std::string & name );
+int open_outstream( const std::string & name, const bool create = true );
+int readblock( const int fd, uint8_t * const buf, const int size );
+int writeblock( const int fd, const uint8_t * const buf, const int size );
+void show_error( const char * const msg, const int errcode = 0,
+ const bool help = false );
+void show_file_error( const char * const filename, const char * const msg,
+ const int errcode = 0 );
+void internal_error( const char * const msg );
diff --git a/testsuite/check.sh b/testsuite/check.sh
new file mode 100755
index 0000000..07b0d34
--- /dev/null
+++ b/testsuite/check.sh
@@ -0,0 +1,438 @@
+#! /bin/sh
+# check script for Tarlz - Archiver with multimember lzip compression
+# Copyright (C) 2013-2018 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`
+TARLZ="${objdir}"/tarlz
+framework_failure() { echo "failure in testing framework" ; exit 1 ; }
+
+if [ ! -f "${TARLZ}" ] || [ ! -x "${TARLZ}" ] ; then
+ echo "${TARLZ}: cannot execute"
+ exit 1
+fi
+
+[ -e "${TARLZ}" ] 2> /dev/null ||
+ {
+ echo "$0: a POSIX shell is required to run the tests"
+ echo "Try bash -c \"$0 $1 $2\""
+ exit 1
+ }
+
+if [ -d tmp ] ; then rm -rf tmp ; fi
+mkdir tmp
+cd "${objdir}"/tmp || framework_failure
+
+in="${testdir}"/test.txt
+in_lz="${testdir}"/test.txt.lz
+in_tar="${testdir}"/test.txt.tar
+in_tar_lz="${testdir}"/test.txt.tar.lz
+test3="${testdir}"/test3.tar
+test3_lz="${testdir}"/test3.tar.lz
+test3a_lz="${testdir}"/test3a.tar.lz
+bad1="${testdir}"/test3_bad1.tar
+bad2="${testdir}"/test3_bad2.tar
+bad3="${testdir}"/test3_bad3.tar
+bad4="${testdir}"/test3_bad4.tar
+bad5="${testdir}"/test3_bad5.tar
+bad1_lz="${testdir}"/test3_bad1.tar.lz
+bad2_lz="${testdir}"/test3_bad2.tar.lz
+bad3_lz="${testdir}"/test3_bad3.tar.lz
+bad4_lz="${testdir}"/test3_bad4.tar.lz
+bad5_lz="${testdir}"/test3_bad5.tar.lz
+bad6_lz="${testdir}"/test3_bad6.tar.lz
+eof_lz="${testdir}"/eof.tar.lz
+fail=0
+test_failed() { fail=1 ; printf " $1" ; [ -z "$2" ] || printf "($2)" ; }
+
+# Description of test files for lziprecover:
+# test3.tar: 3 members (foo bar baz) + 2 zeroed 512-byte blocks
+# test3_bad1.tar: byte at offset 259 changed from 't' to '0' (magic)
+# test3_bad2.tar: byte at offset 1283 changed from 't' to '0' (magic)
+# test3_bad3.tar: byte at offset 2559 changed from 0x00 to 0x20 (padding)
+# test3_bad4.tar: byte at offset 1283 changed from 't' to '0' (magic)
+# byte at offset 2307 changed from 't' to '0' (magic)
+# test3_bad5.tar: 510 zeros + "LZ" prepended to test3.tar (bogus lz header)
+# test3_bad1.tar.lz: byte at offset 2 changed from 'I' to 'i' (magic)
+# test3_bad2.tar.lz: byte at offset 49 changed from 0x49 to 0x69 (mid stream)
+# test3_bad3.tar.lz: byte at offset 149 changed from 0x2D to 0x3D (mid stream)
+# test3_bad4.tar.lz: combined damage of test3_bad2.tar.lz and test3_bad3.tar.lz
+# test3_bad5.tar.lz: [71-134] --> zeroed (first trailer + seconf header)
+# test3_bad6.tar.lz: 510 zeros prepended to test3.tar.lz (header in two blocks)
+
+printf "testing tarlz-%s..." "$2"
+
+"${TARLZ}" -qtf ${in}
+[ $? = 2 ] || test_failed $LINENO
+"${TARLZ}" -qtf ${in_lz}
+[ $? = 2 ] || test_failed $LINENO
+"${TARLZ}" -qtf nx_file
+[ $? = 1 ] || test_failed $LINENO
+"${TARLZ}" -tf 2> /dev/null
+[ $? = 1 ] || test_failed $LINENO
+"${TARLZ}" -qcf out.tar.lz
+[ $? = 1 ] || test_failed $LINENO
+[ ! -e out.tar.lz ] || test_failed $LINENO
+"${TARLZ}" -rf out.tar.lz || test_failed $LINENO
+[ ! -e out.tar.lz ] || test_failed $LINENO
+"${TARLZ}" -qrf - ${in}
+[ $? = 1 ] || test_failed $LINENO
+[ ! -e - ] || test_failed $LINENO
+"${TARLZ}" -qr ${in}
+[ $? = 1 ] || test_failed $LINENO
+"${TARLZ}" --uncompressed -qrf out.tar ${in}
+[ $? = 1 ] || test_failed $LINENO
+[ ! -e out.tar ] || test_failed $LINENO
+cat ${test3_lz} > test.tar.lz || framework_failure
+"${TARLZ}" --uncompressed -qrf test.tar.lz ${in}
+[ $? = 1 ] || test_failed $LINENO
+cmp ${test3_lz} test.tar.lz || test_failed $LINENO
+rm -f test.tar.lz || framework_failure
+cat ${test3} > test.tar || framework_failure
+"${TARLZ}" -qrf test.tar ${in}
+[ $? = 2 ] || test_failed $LINENO
+cmp ${test3} test.tar || test_failed $LINENO
+rm -f test.tar || framework_failure
+"${TARLZ}" -qc ${in} nx_file > /dev/null
+[ $? = 1 ] || test_failed $LINENO
+"${TARLZ}" -qc -C nx_dir ${in}
+[ $? = 1 ] || test_failed $LINENO
+"${TARLZ}" -qx -C nx_dir ${test3_lz}
+[ $? = 1 ] || test_failed $LINENO
+"${TARLZ}" -qcr
+[ $? = 1 ] || test_failed $LINENO
+"${TARLZ}" -qct
+[ $? = 1 ] || test_failed $LINENO
+"${TARLZ}" -qcx
+[ $? = 1 ] || test_failed $LINENO
+"${TARLZ}" -qtx
+[ $? = 1 ] || test_failed $LINENO
+"${TARLZ}" -qctx
+[ $? = 1 ] || test_failed $LINENO
+"${TARLZ}" --help > /dev/null || test_failed $LINENO
+"${TARLZ}" -V > /dev/null || test_failed $LINENO
+"${TARLZ}" --bad_option -tf ${test3_lz} 2> /dev/null
+[ $? = 1 ] || test_failed $LINENO
+"${TARLZ}" -tf 2> /dev/null
+[ $? = 1 ] || test_failed $LINENO
+"${TARLZ}" --owner=invalid_oner_name -tf ${test3_lz} 2> /dev/null
+[ $? = 1 ] || test_failed $LINENO
+"${TARLZ}" --group=invalid_goup_name -tf ${test3_lz} 2> /dev/null
+[ $? = 1 ] || test_failed $LINENO
+
+"${TARLZ}" -tf ${eof_lz} || test_failed $LINENO
+"${TARLZ}" -xf ${eof_lz} || test_failed $LINENO
+"${TARLZ}" -tf ${in_tar_lz} > /dev/null || test_failed $LINENO
+"${TARLZ}" -xf ${in_tar_lz} || test_failed $LINENO
+cmp ${in} test.txt || test_failed $LINENO
+rm -f test.txt || framework_failure
+"${TARLZ}" -C nx_dir -tf ${in_tar} > /dev/null || test_failed $LINENO
+"${TARLZ}" -xf ${in_tar} || test_failed $LINENO
+cmp ${in} test.txt || test_failed $LINENO
+rm -f test.txt || framework_failure
+
+rm -f foo bar baz || framework_failure
+"${TARLZ}" -xf ${test3_lz} || test_failed $LINENO
+[ -e foo ] || test_failed $LINENO
+[ -e bar ] || test_failed $LINENO
+[ -e baz ] || test_failed $LINENO
+rm -f foo bar baz || framework_failure
+"${TARLZ}" -xf ${test3} || test_failed $LINENO
+[ -e foo ] || test_failed $LINENO
+[ -e bar ] || test_failed $LINENO
+[ -e baz ] || test_failed $LINENO
+rm -f foo bar baz || framework_failure
+"${TARLZ}" -qxf ${test3a_lz} || test_failed $LINENO
+[ -e dir/foo ] || test_failed $LINENO
+[ -e dir/bar ] || test_failed $LINENO
+[ -e dir/baz ] || test_failed $LINENO
+rm -rf dir || framework_failure
+
+cat ${in} > test.txt || framework_failure
+"${TARLZ}" -0 -cf out.tar.lz test.txt || test_failed $LINENO
+rm -f test.txt || framework_failure
+"${TARLZ}" -xf out.tar.lz || test_failed $LINENO
+cmp ${in} test.txt || test_failed $LINENO
+cat ${in} > test.txt || framework_failure
+"${TARLZ}" --uncompressed -cf out.tar test.txt || test_failed $LINENO
+rm -f test.txt || framework_failure
+"${TARLZ}" -xf out.tar || test_failed $LINENO
+cmp ${in} test.txt || test_failed $LINENO
+rm -f test.txt out.tar out.tar.lz || framework_failure
+
+printf "foo" > foo || framework_failure
+rm -f bar || framework_failure
+printf "baz" > baz || framework_failure
+"${TARLZ}" -qcf out.tar.lz foo bar baz
+[ $? = 1 ] || test_failed $LINENO
+rm -f foo bar baz || framework_failure
+"${TARLZ}" -xf out.tar.lz || test_failed $LINENO
+[ -e foo ] || test_failed $LINENO
+[ ! -e bar ] || test_failed $LINENO
+[ -e baz ] || test_failed $LINENO
+rm -f foo bar baz || framework_failure
+"${TARLZ}" -qxf out.tar.lz bar
+[ $? = 1 ] || test_failed $LINENO
+[ ! -e foo ] || test_failed $LINENO
+[ ! -e bar ] || test_failed $LINENO
+[ ! -e baz ] || test_failed $LINENO
+rm -f out.tar.lz || framework_failure
+
+printf "foo" > foo || framework_failure
+printf "bar" > bar || framework_failure
+printf "baz" > baz || framework_failure
+"${TARLZ}" -0 -cf out.tar.lz foo bar baz || test_failed $LINENO
+"${TARLZ}" --dsolid -0 -cf aout.tar.lz foo bar baz || test_failed $LINENO
+cmp out.tar.lz aout.tar.lz || test_failed $LINENO
+rm -f aout.tar.lz || framework_failure
+"${TARLZ}" -0 -qcf aout.tar.lz foo/ ./bar ./baz/ || test_failed $LINENO
+cmp out.tar.lz aout.tar.lz || test_failed $LINENO
+rm -f aout.tar.lz || framework_failure
+"${TARLZ}" -0 -cf aout.tar.lz foo || test_failed $LINENO
+"${TARLZ}" -0 -rf aout.tar.lz bar baz || test_failed $LINENO
+cmp out.tar.lz aout.tar.lz || test_failed $LINENO
+rm -f aout.tar.lz || framework_failure
+touch aout.tar.lz || framework_failure # append to empty file
+"${TARLZ}" -0 -rf aout.tar.lz foo bar baz || test_failed $LINENO
+cmp out.tar.lz aout.tar.lz || test_failed $LINENO
+"${TARLZ}" -0 -rf aout.tar.lz || test_failed $LINENO # append nothing
+cmp out.tar.lz aout.tar.lz || test_failed $LINENO
+"${TARLZ}" -0 -rf aout.tar.lz -C nx_dir || test_failed $LINENO
+cmp out.tar.lz aout.tar.lz || test_failed $LINENO
+"${TARLZ}" -0 -qrf aout.tar.lz nx_file
+[ $? = 1 ] || 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
+cmp out.tar.lz aout.tar.lz || test_failed $LINENO
+mv -f foo foo.orig || framework_failure
+mv -f bar bar.orig || framework_failure
+mv -f baz baz.orig || framework_failure
+"${TARLZ}" -xf out.tar.lz foo/ bar// baz/// || test_failed $LINENO
+cmp foo foo.orig || test_failed $LINENO
+cmp bar bar.orig || test_failed $LINENO
+cmp baz baz.orig || test_failed $LINENO
+rm -f foo bar baz || framework_failure
+"${TARLZ}" -xf out.tar.lz || test_failed $LINENO
+cmp foo foo.orig || test_failed $LINENO
+cmp bar bar.orig || test_failed $LINENO
+cmp baz baz.orig || test_failed $LINENO
+mkdir dir1 || framework_failure
+"${TARLZ}" -C dir1 -xf out.tar.lz || test_failed $LINENO
+cmp dir1/foo foo.orig || test_failed $LINENO
+cmp dir1/bar bar.orig || test_failed $LINENO
+cmp dir1/baz baz.orig || test_failed $LINENO
+rm -f aout.tar.lz foo bar baz || framework_failure
+"${TARLZ}" -C dir1 -0 -cf aout.tar.lz foo bar baz || test_failed $LINENO
+"${TARLZ}" -xf aout.tar.lz || test_failed $LINENO
+cmp foo foo.orig || test_failed $LINENO
+cmp bar bar.orig || test_failed $LINENO
+cmp baz baz.orig || test_failed $LINENO
+rm -f aout.tar.lz foo bar baz || framework_failure
+"${TARLZ}" -C dir1 -0 -c foo bar baz | "${TARLZ}" -x || test_failed $LINENO
+cmp foo foo.orig || test_failed $LINENO
+cmp bar bar.orig || test_failed $LINENO
+cmp baz baz.orig || test_failed $LINENO
+rm -f dir1/foo dir1/bar dir1/baz || framework_failure
+"${TARLZ}" -0 -c foo bar baz | "${TARLZ}" -C dir1 -x || test_failed $LINENO
+cmp dir1/foo foo.orig || test_failed $LINENO
+cmp dir1/bar bar.orig || test_failed $LINENO
+cmp dir1/baz baz.orig || test_failed $LINENO
+rm -f dir1/foo dir1/bar dir1/baz || framework_failure
+"${TARLZ}" -0 -c foo bar baz | "${TARLZ}" -x foo bar baz -C dir1 ||
+ test_failed $LINENO
+cmp dir1/foo foo.orig || test_failed $LINENO
+cmp dir1/bar bar.orig || test_failed $LINENO
+cmp dir1/baz baz.orig || test_failed $LINENO
+rm -f foo dir1/bar baz || framework_failure
+"${TARLZ}" -0 -cf aout.tar.lz -C dir1 foo -C .. bar -C dir1 baz ||
+ test_failed $LINENO
+cmp out.tar.lz aout.tar.lz || test_failed $LINENO
+"${TARLZ}" -0 -cf aout.tar.lz dir1/foo dir1/baz || test_failed $LINENO
+rm -rf dir1 || framework_failure
+"${TARLZ}" -xf aout.tar.lz dir1 || test_failed $LINENO
+cmp dir1/foo foo.orig || test_failed $LINENO
+cmp dir1/baz baz.orig || test_failed $LINENO
+rm -rf dir1 || framework_failure
+rm -f out.tar.lz aout.tar.lz || framework_failure
+
+# append to solid archive
+printf "foo" > foo || framework_failure
+printf "bar" > bar || framework_failure
+printf "baz" > baz || framework_failure
+"${TARLZ}" --solid -0 -cf out.tar.lz foo || test_failed $LINENO
+for i in --asolid --dsolid --solid -0 ; do
+ "${TARLZ}" $i -qrf out.tar.lz bar baz
+ [ $? = 2 ] || test_failed $LINENO $i
+ [ -e out.tar.lz ] || test_failed $LINENO $i
+done
+rm -f out.tar.lz || framework_failure
+for i in --asolid --dsolid -0 ; do
+ for j in --asolid --dsolid --solid -0 ; do
+ "${TARLZ}" $i -0 -cf out.tar.lz foo ||
+ test_failed $LINENO "$i $j"
+ "${TARLZ}" $j -0 -rf out.tar.lz bar baz ||
+ test_failed $LINENO "$i $j"
+ rm -f foo bar baz || framework_failure
+ "${TARLZ}" -xf out.tar.lz || test_failed $LINENO "$i $j"
+ cmp foo foo.orig || test_failed $LINENO "$i $j"
+ cmp bar bar.orig || test_failed $LINENO "$i $j"
+ cmp baz baz.orig || test_failed $LINENO "$i $j"
+ rm -f out.tar.lz || framework_failure
+ done
+done
+rm -f foo foo.orig bar bar.orig baz baz.orig || framework_failure
+
+mkdir dir1 || framework_failure
+"${TARLZ}" -0 -cf out.tar dir1 || test_failed $LINENO
+rmdir dir1 || framework_failure
+"${TARLZ}" -xf out.tar || test_failed $LINENO
+[ -d dir1 ] || test_failed $LINENO
+rmdir dir1
+mkdir dir1 || framework_failure
+"${TARLZ}" --uncompressed -cf out.tar dir1 || test_failed $LINENO
+rmdir dir1 || framework_failure
+"${TARLZ}" -xf out.tar || test_failed $LINENO
+[ -d dir1 ] || test_failed $LINENO
+rmdir dir1
+rm -f out.tar || framework_failure
+
+if ln ${in} dummy_link 2> /dev/null &&
+ ln -s ${in} dummy_slink 2> /dev/null ; then
+ name_100=name_100_bytes_long_nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
+ path_100=dir1/dir2/dir3/path_100_bytes_long_nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
+ path_106=dir1/dir2/dir3/path_longer_than_100_bytes_nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
+ mkdir dir1 || framework_failure
+ mkdir dir1/dir2 || framework_failure
+ mkdir dir1/dir2/dir3 || framework_failure
+ cat ${in} > dir1/dir2/dir3/in || framework_failure
+ ln dir1/dir2/dir3/in dir1/dir2/dir3/${name_100} || framework_failure
+ ln dir1/dir2/dir3/in ${path_100} || framework_failure
+ ln dir1/dir2/dir3/in ${path_106} || framework_failure
+ ln -s in dir1/dir2/dir3/link || framework_failure
+ ln -s ${name_100} dir1/dir2/dir3/link_100 || framework_failure
+ "${TARLZ}" -0 -cf out.tar dir1 || test_failed $LINENO
+ rm -rf dir1 || framework_failure
+ "${TARLZ}" -xf out.tar || test_failed $LINENO
+ cmp ${in} dir1/dir2/dir3/in || test_failed $LINENO
+ cmp ${in} dir1/dir2/dir3/${name_100} || test_failed $LINENO
+ cmp ${in} ${path_100} || test_failed $LINENO
+ cmp ${in} ${path_106} || test_failed $LINENO
+ cmp ${in} dir1/dir2/dir3/link || test_failed $LINENO
+ cmp ${in} dir1/dir2/dir3/link_100 || test_failed $LINENO
+ rm -f dir1/dir2/dir3/in || framework_failure
+ cmp ${in} dir1/dir2/dir3/link 2> /dev/null && test_failed $LINENO
+ cmp ${in} dir1/dir2/dir3/link_100 || test_failed $LINENO
+ "${TARLZ}" -xf out.tar || test_failed $LINENO
+ rm -f out.tar || framework_failure
+ cmp ${in} dir1/dir2/dir3/in || test_failed $LINENO
+ cmp ${in} dir1/dir2/dir3/link || test_failed $LINENO
+ "${TARLZ}" -0 -qc ../tmp/dir1 > /dev/null || test_failed $LINENO
+ rm -rf dir1 || framework_failure
+else
+ printf "\nwarning: skipping link test: 'ln' does not work on your system."
+fi
+rm -f dummy_link dummy_slink || framework_failure
+
+"${TARLZ}" -qxf "${testdir}"/dotdot1.tar.lz || test_failed $LINENO
+[ ! -e ../dir ] || test_failed $LINENO
+"${TARLZ}" -qxf "${testdir}"/dotdot2.tar.lz || test_failed $LINENO
+[ ! -e ../dir ] || test_failed $LINENO
+"${TARLZ}" -qxf "${testdir}"/dotdot3.tar.lz || test_failed $LINENO
+[ ! -e dir ] || test_failed $LINENO
+"${TARLZ}" -qxf "${testdir}"/dotdot4.tar.lz || test_failed $LINENO
+[ ! -e dir ] || test_failed $LINENO
+"${TARLZ}" -qxf "${testdir}"/dotdot5.tar.lz || test_failed $LINENO
+[ ! -e dir ] || test_failed $LINENO
+
+printf "\ntesting bad input..."
+
+dd if=${in_tar} of=truncated.tar bs=1000 count=1 2> /dev/null
+"${TARLZ}" -qtf truncated.tar > /dev/null
+[ $? = 2 ] || test_failed $LINENO
+"${TARLZ}" -qxf truncated.tar
+[ $? = 2 ] || test_failed $LINENO
+[ ! -e test.txt ] || test_failed $LINENO
+
+rm -f foo bar baz || framework_failure
+"${TARLZ}" -qxf ${bad1_lz}
+[ $? = 2 ] || test_failed $LINENO
+[ ! -e foo ] || test_failed $LINENO
+[ -e bar ] || test_failed $LINENO
+[ -e baz ] || test_failed $LINENO
+rm -f foo bar baz || framework_failure
+"${TARLZ}" -qxf ${bad2_lz}
+[ $? = 2 ] || test_failed $LINENO
+[ ! -e foo ] || test_failed $LINENO
+[ -e bar ] || test_failed $LINENO
+[ -e baz ] || test_failed $LINENO
+rm -f foo bar baz || framework_failure
+"${TARLZ}" -qxf ${bad3_lz}
+[ $? = 2 ] || test_failed $LINENO
+[ -e foo ] || test_failed $LINENO
+[ ! -e bar ] || test_failed $LINENO
+[ -e baz ] || test_failed $LINENO
+rm -f foo bar baz || framework_failure
+"${TARLZ}" -qxf ${bad4_lz}
+[ $? = 2 ] || test_failed $LINENO
+[ ! -e foo ] || test_failed $LINENO
+[ ! -e bar ] || test_failed $LINENO
+[ -e baz ] || test_failed $LINENO
+rm -f foo bar baz || framework_failure
+"${TARLZ}" -qxf ${bad5_lz}
+[ $? = 2 ] || test_failed $LINENO
+[ ! -e foo ] || test_failed $LINENO
+[ ! -e bar ] || test_failed $LINENO
+[ -e baz ] || test_failed $LINENO
+rm -f foo bar baz || framework_failure
+"${TARLZ}" -qxf ${bad6_lz}
+[ $? = 2 ] || test_failed $LINENO
+[ -e foo ] || test_failed $LINENO
+[ -e bar ] || test_failed $LINENO
+[ -e baz ] || test_failed $LINENO
+
+rm -f foo bar baz || framework_failure
+"${TARLZ}" -qxf ${bad1}
+[ $? = 2 ] || test_failed $LINENO
+[ ! -e foo ] || test_failed $LINENO
+[ -e bar ] || test_failed $LINENO
+[ -e baz ] || test_failed $LINENO
+rm -f foo bar baz || framework_failure
+"${TARLZ}" -qxf ${bad2}
+[ $? = 2 ] || test_failed $LINENO
+[ -e foo ] || test_failed $LINENO
+[ ! -e bar ] || test_failed $LINENO
+[ -e baz ] || test_failed $LINENO
+rm -f foo bar baz || framework_failure
+"${TARLZ}" -qxf ${bad3}
+[ $? = 2 ] || test_failed $LINENO
+[ -e foo ] || test_failed $LINENO
+[ -e bar ] || test_failed $LINENO
+[ ! -e baz ] || test_failed $LINENO
+rm -f foo bar baz || framework_failure
+"${TARLZ}" -qxf ${bad4}
+[ $? = 2 ] || test_failed $LINENO
+[ -e foo ] || test_failed $LINENO
+[ ! -e bar ] || test_failed $LINENO
+[ ! -e baz ] || test_failed $LINENO
+rm -f foo bar baz || framework_failure
+"${TARLZ}" -qxf ${bad5}
+[ $? = 2 ] || test_failed $LINENO
+[ -e foo ] || test_failed $LINENO
+[ -e bar ] || test_failed $LINENO
+[ -e baz ] || test_failed $LINENO
+rm -f foo bar baz || framework_failure
+
+echo
+if [ ${fail} = 0 ] ; then
+ echo "tests completed successfully."
+ cd "${objdir}" && rm -r tmp
+else
+ echo "tests failed."
+fi
+exit ${fail}
diff --git a/testsuite/dotdot1.tar.lz b/testsuite/dotdot1.tar.lz
new file mode 100644
index 0000000..9884d9f
--- /dev/null
+++ b/testsuite/dotdot1.tar.lz
Binary files differ
diff --git a/testsuite/dotdot2.tar.lz b/testsuite/dotdot2.tar.lz
new file mode 100644
index 0000000..a60b898
--- /dev/null
+++ b/testsuite/dotdot2.tar.lz
Binary files differ
diff --git a/testsuite/dotdot3.tar.lz b/testsuite/dotdot3.tar.lz
new file mode 100644
index 0000000..163fb5c
--- /dev/null
+++ b/testsuite/dotdot3.tar.lz
Binary files differ
diff --git a/testsuite/dotdot4.tar.lz b/testsuite/dotdot4.tar.lz
new file mode 100644
index 0000000..8c6a0ee
--- /dev/null
+++ b/testsuite/dotdot4.tar.lz
Binary files differ
diff --git a/testsuite/dotdot5.tar.lz b/testsuite/dotdot5.tar.lz
new file mode 100644
index 0000000..a62cd18
--- /dev/null
+++ b/testsuite/dotdot5.tar.lz
Binary files differ
diff --git a/testsuite/eof.tar.lz b/testsuite/eof.tar.lz
new file mode 100644
index 0000000..328273c
--- /dev/null
+++ b/testsuite/eof.tar.lz
Binary files differ
diff --git a/testsuite/test.txt b/testsuite/test.txt
new file mode 100644
index 0000000..9196a3a
--- /dev/null
+++ b/testsuite/test.txt
@@ -0,0 +1,676 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) <year> <name of author>
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) <year> <name of author>
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/testsuite/test.txt.lz b/testsuite/test.txt.lz
new file mode 100644
index 0000000..46f98a7
--- /dev/null
+++ b/testsuite/test.txt.lz
Binary files differ
diff --git a/testsuite/test.txt.tar b/testsuite/test.txt.tar
new file mode 100644
index 0000000..d687b43
--- /dev/null
+++ b/testsuite/test.txt.tar
Binary files differ
diff --git a/testsuite/test.txt.tar.lz b/testsuite/test.txt.tar.lz
new file mode 100644
index 0000000..15c0131
--- /dev/null
+++ b/testsuite/test.txt.tar.lz
Binary files differ
diff --git a/testsuite/test3.tar b/testsuite/test3.tar
new file mode 100644
index 0000000..d58fb45
--- /dev/null
+++ b/testsuite/test3.tar
Binary files differ
diff --git a/testsuite/test3.tar.lz b/testsuite/test3.tar.lz
new file mode 100644
index 0000000..779ace4
--- /dev/null
+++ b/testsuite/test3.tar.lz
Binary files differ
diff --git a/testsuite/test3_bad1.tar b/testsuite/test3_bad1.tar
new file mode 100644
index 0000000..005b6a3
--- /dev/null
+++ b/testsuite/test3_bad1.tar
Binary files differ
diff --git a/testsuite/test3_bad1.tar.lz b/testsuite/test3_bad1.tar.lz
new file mode 100644
index 0000000..9f5d40f
--- /dev/null
+++ b/testsuite/test3_bad1.tar.lz
Binary files differ
diff --git a/testsuite/test3_bad2.tar b/testsuite/test3_bad2.tar
new file mode 100644
index 0000000..d2c546b
--- /dev/null
+++ b/testsuite/test3_bad2.tar
Binary files differ
diff --git a/testsuite/test3_bad2.tar.lz b/testsuite/test3_bad2.tar.lz
new file mode 100644
index 0000000..182c048
--- /dev/null
+++ b/testsuite/test3_bad2.tar.lz
Binary files differ
diff --git a/testsuite/test3_bad3.tar b/testsuite/test3_bad3.tar
new file mode 100644
index 0000000..7d78e87
--- /dev/null
+++ b/testsuite/test3_bad3.tar
Binary files differ
diff --git a/testsuite/test3_bad3.tar.lz b/testsuite/test3_bad3.tar.lz
new file mode 100644
index 0000000..c711a1a
--- /dev/null
+++ b/testsuite/test3_bad3.tar.lz
Binary files differ
diff --git a/testsuite/test3_bad4.tar b/testsuite/test3_bad4.tar
new file mode 100644
index 0000000..68312b3
--- /dev/null
+++ b/testsuite/test3_bad4.tar
Binary files differ
diff --git a/testsuite/test3_bad4.tar.lz b/testsuite/test3_bad4.tar.lz
new file mode 100644
index 0000000..226db72
--- /dev/null
+++ b/testsuite/test3_bad4.tar.lz
Binary files differ
diff --git a/testsuite/test3_bad5.tar b/testsuite/test3_bad5.tar
new file mode 100644
index 0000000..e482969
--- /dev/null
+++ b/testsuite/test3_bad5.tar
Binary files differ
diff --git a/testsuite/test3_bad5.tar.lz b/testsuite/test3_bad5.tar.lz
new file mode 100644
index 0000000..5b4feb3
--- /dev/null
+++ b/testsuite/test3_bad5.tar.lz
Binary files differ
diff --git a/testsuite/test3_bad6.tar.lz b/testsuite/test3_bad6.tar.lz
new file mode 100644
index 0000000..42b3888
--- /dev/null
+++ b/testsuite/test3_bad6.tar.lz
Binary files differ
diff --git a/testsuite/test3a.tar.lz b/testsuite/test3a.tar.lz
new file mode 100644
index 0000000..8eb3f43
--- /dev/null
+++ b/testsuite/test3a.tar.lz
Binary files differ