diff options
-rw-r--r-- | AUTHORS | 1 | ||||
-rw-r--r-- | COPYING | 338 | ||||
-rw-r--r-- | ChangeLog | 37 | ||||
-rw-r--r-- | INSTALL | 65 | ||||
-rw-r--r-- | Makefile.in | 137 | ||||
-rw-r--r-- | NEWS | 5 | ||||
-rw-r--r-- | README | 62 | ||||
-rw-r--r-- | arg_parser.cc | 196 | ||||
-rw-r--r-- | arg_parser.h | 98 | ||||
-rwxr-xr-x | configure | 196 | ||||
-rw-r--r-- | create.cc | 412 | ||||
-rw-r--r-- | doc/tarlz.1 | 88 | ||||
-rw-r--r-- | doc/tarlz.info | 311 | ||||
-rw-r--r-- | doc/tarlz.texi | 348 | ||||
-rw-r--r-- | extract.cc | 561 | ||||
-rw-r--r-- | lzip.h | 112 | ||||
-rw-r--r-- | main.cc | 369 | ||||
-rw-r--r-- | tarlz.h | 68 | ||||
-rwxr-xr-x | testsuite/check.sh | 438 | ||||
-rw-r--r-- | testsuite/dotdot1.tar.lz | bin | 0 -> 139 bytes | |||
-rw-r--r-- | testsuite/dotdot2.tar.lz | bin | 0 -> 140 bytes | |||
-rw-r--r-- | testsuite/dotdot3.tar.lz | bin | 0 -> 141 bytes | |||
-rw-r--r-- | testsuite/dotdot4.tar.lz | bin | 0 -> 140 bytes | |||
-rw-r--r-- | testsuite/dotdot5.tar.lz | bin | 0 -> 139 bytes | |||
-rw-r--r-- | testsuite/eof.tar.lz | bin | 0 -> 44 bytes | |||
-rw-r--r-- | testsuite/test.txt | 676 | ||||
-rw-r--r-- | testsuite/test.txt.lz | bin | 0 -> 7392 bytes | |||
-rw-r--r-- | testsuite/test.txt.tar | bin | 0 -> 38400 bytes | |||
-rw-r--r-- | testsuite/test.txt.tar.lz | bin | 0 -> 7495 bytes | |||
-rw-r--r-- | testsuite/test3.tar | bin | 0 -> 4096 bytes | |||
-rw-r--r-- | testsuite/test3.tar.lz | bin | 0 -> 356 bytes | |||
-rw-r--r-- | testsuite/test3_bad1.tar | bin | 0 -> 4096 bytes | |||
-rw-r--r-- | testsuite/test3_bad1.tar.lz | bin | 0 -> 356 bytes | |||
-rw-r--r-- | testsuite/test3_bad2.tar | bin | 0 -> 4096 bytes | |||
-rw-r--r-- | testsuite/test3_bad2.tar.lz | bin | 0 -> 356 bytes | |||
-rw-r--r-- | testsuite/test3_bad3.tar | bin | 0 -> 4096 bytes | |||
-rw-r--r-- | testsuite/test3_bad3.tar.lz | bin | 0 -> 356 bytes | |||
-rw-r--r-- | testsuite/test3_bad4.tar | bin | 0 -> 4096 bytes | |||
-rw-r--r-- | testsuite/test3_bad4.tar.lz | bin | 0 -> 356 bytes | |||
-rw-r--r-- | testsuite/test3_bad5.tar | bin | 0 -> 4608 bytes | |||
-rw-r--r-- | testsuite/test3_bad5.tar.lz | bin | 0 -> 356 bytes | |||
-rw-r--r-- | testsuite/test3_bad6.tar.lz | bin | 0 -> 866 bytes | |||
-rw-r--r-- | testsuite/test3a.tar.lz | bin | 0 -> 358 bytes |
43 files changed, 4518 insertions, 0 deletions
@@ -0,0 +1 @@ +Tarlz was written by Antonio Diaz Diaz. @@ -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. @@ -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 @@ -0,0 +1,5 @@ +Changes in version 0.4: + +Some missing #includes have been fixed. + +Open files in binary mode on OS2. @@ -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; + } @@ -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; + } + }; @@ -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 ); + } + } @@ -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 Binary files differnew file mode 100644 index 0000000..9884d9f --- /dev/null +++ b/testsuite/dotdot1.tar.lz diff --git a/testsuite/dotdot2.tar.lz b/testsuite/dotdot2.tar.lz Binary files differnew file mode 100644 index 0000000..a60b898 --- /dev/null +++ b/testsuite/dotdot2.tar.lz diff --git a/testsuite/dotdot3.tar.lz b/testsuite/dotdot3.tar.lz Binary files differnew file mode 100644 index 0000000..163fb5c --- /dev/null +++ b/testsuite/dotdot3.tar.lz diff --git a/testsuite/dotdot4.tar.lz b/testsuite/dotdot4.tar.lz Binary files differnew file mode 100644 index 0000000..8c6a0ee --- /dev/null +++ b/testsuite/dotdot4.tar.lz diff --git a/testsuite/dotdot5.tar.lz b/testsuite/dotdot5.tar.lz Binary files differnew file mode 100644 index 0000000..a62cd18 --- /dev/null +++ b/testsuite/dotdot5.tar.lz diff --git a/testsuite/eof.tar.lz b/testsuite/eof.tar.lz Binary files differnew file mode 100644 index 0000000..328273c --- /dev/null +++ b/testsuite/eof.tar.lz 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 Binary files differnew file mode 100644 index 0000000..46f98a7 --- /dev/null +++ b/testsuite/test.txt.lz diff --git a/testsuite/test.txt.tar b/testsuite/test.txt.tar Binary files differnew file mode 100644 index 0000000..d687b43 --- /dev/null +++ b/testsuite/test.txt.tar diff --git a/testsuite/test.txt.tar.lz b/testsuite/test.txt.tar.lz Binary files differnew file mode 100644 index 0000000..15c0131 --- /dev/null +++ b/testsuite/test.txt.tar.lz diff --git a/testsuite/test3.tar b/testsuite/test3.tar Binary files differnew file mode 100644 index 0000000..d58fb45 --- /dev/null +++ b/testsuite/test3.tar diff --git a/testsuite/test3.tar.lz b/testsuite/test3.tar.lz Binary files differnew file mode 100644 index 0000000..779ace4 --- /dev/null +++ b/testsuite/test3.tar.lz diff --git a/testsuite/test3_bad1.tar b/testsuite/test3_bad1.tar Binary files differnew file mode 100644 index 0000000..005b6a3 --- /dev/null +++ b/testsuite/test3_bad1.tar diff --git a/testsuite/test3_bad1.tar.lz b/testsuite/test3_bad1.tar.lz Binary files differnew file mode 100644 index 0000000..9f5d40f --- /dev/null +++ b/testsuite/test3_bad1.tar.lz diff --git a/testsuite/test3_bad2.tar b/testsuite/test3_bad2.tar Binary files differnew file mode 100644 index 0000000..d2c546b --- /dev/null +++ b/testsuite/test3_bad2.tar diff --git a/testsuite/test3_bad2.tar.lz b/testsuite/test3_bad2.tar.lz Binary files differnew file mode 100644 index 0000000..182c048 --- /dev/null +++ b/testsuite/test3_bad2.tar.lz diff --git a/testsuite/test3_bad3.tar b/testsuite/test3_bad3.tar Binary files differnew file mode 100644 index 0000000..7d78e87 --- /dev/null +++ b/testsuite/test3_bad3.tar diff --git a/testsuite/test3_bad3.tar.lz b/testsuite/test3_bad3.tar.lz Binary files differnew file mode 100644 index 0000000..c711a1a --- /dev/null +++ b/testsuite/test3_bad3.tar.lz diff --git a/testsuite/test3_bad4.tar b/testsuite/test3_bad4.tar Binary files differnew file mode 100644 index 0000000..68312b3 --- /dev/null +++ b/testsuite/test3_bad4.tar diff --git a/testsuite/test3_bad4.tar.lz b/testsuite/test3_bad4.tar.lz Binary files differnew file mode 100644 index 0000000..226db72 --- /dev/null +++ b/testsuite/test3_bad4.tar.lz diff --git a/testsuite/test3_bad5.tar b/testsuite/test3_bad5.tar Binary files differnew file mode 100644 index 0000000..e482969 --- /dev/null +++ b/testsuite/test3_bad5.tar diff --git a/testsuite/test3_bad5.tar.lz b/testsuite/test3_bad5.tar.lz Binary files differnew file mode 100644 index 0000000..5b4feb3 --- /dev/null +++ b/testsuite/test3_bad5.tar.lz diff --git a/testsuite/test3_bad6.tar.lz b/testsuite/test3_bad6.tar.lz Binary files differnew file mode 100644 index 0000000..42b3888 --- /dev/null +++ b/testsuite/test3_bad6.tar.lz diff --git a/testsuite/test3a.tar.lz b/testsuite/test3a.tar.lz Binary files differnew file mode 100644 index 0000000..8eb3f43 --- /dev/null +++ b/testsuite/test3a.tar.lz |