summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--ChangeLog19
-rw-r--r--INSTALL9
-rw-r--r--Makefile.in6
-rw-r--r--NEWS40
-rw-r--r--README6
-rw-r--r--arg_parser.cc2
-rw-r--r--arg_parser.h2
-rwxr-xr-xconfigure20
-rw-r--r--doc/zcat.122
-rw-r--r--doc/zcmp.16
-rw-r--r--doc/zdiff.16
-rw-r--r--doc/zgrep.122
-rw-r--r--doc/ztest.119
-rw-r--r--doc/zupdate.123
-rw-r--r--doc/zutils.info125
-rw-r--r--doc/zutils.texi101
-rw-r--r--rc.cc32
-rw-r--r--rc.h3
-rw-r--r--recursive.cc63
-rwxr-xr-xtestsuite/check.sh150
-rw-r--r--testsuite/test_bad_crc.lzbin0 -> 7376 bytes
-rw-r--r--testsuite/zcat_vs.dat68
-rw-r--r--testsuite/zero_bad_crc.gzbin0 -> 20 bytes
-rw-r--r--testsuite/zero_bad_crc.lzbin0 -> 36 bytes
-rw-r--r--zcat.cc147
-rw-r--r--zcatgrep.cc6
-rw-r--r--zcmp.cc15
-rw-r--r--zcmpdiff.cc4
-rw-r--r--zdiff.cc8
-rw-r--r--zgrep.cc184
-rw-r--r--ztest.cc84
-rw-r--r--zupdate.cc108
-rw-r--r--zutils.cc11
-rw-r--r--zutils.h2
34 files changed, 858 insertions, 455 deletions
diff --git a/ChangeLog b/ChangeLog
index ef275c0..2aa81e5 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,20 @@
+2019-01-01 Antonio Diaz Diaz <antonio@gnu.org>
+
+ * Version 1.8 released.
+ * zcat.cc: Fixed a buffer overflow on outbuf when '-v' is used.
+ * zcat.cc (cat): A canary byte has been added to outbuf.
+ * Added new option '-R, --dereference-recursive'.
+ * Option '-r, --recursive' now skips symlinks.
+ * If no files and recursive, examine current working directory.
+ * recursive.cc (test_full_name): Detect directory loops.
+ * recursive.cc: Ignore directories if not --recursive.
+ * recursive.cc: Remove extra trailing slashes from directory args.
+ * zcatgrep.cc (open_instream): Show correct errno.
+ * zutils.cc (good_status): Wait for killed child.
+ * Test and document continuation or exit of zcat, zgrep, ztest
+ and zupdate in case of error.
+ * configure: Accept appending to CXXFLAGS, 'CXXFLAGS+=OPTIONS'.
+
2018-02-13 Antonio Diaz Diaz <antonio@gnu.org>
* Version 1.7 released.
@@ -140,7 +157,7 @@
* Version 0.1 released.
-Copyright (C) 2009-2018 Antonio Diaz Diaz.
+Copyright (C) 2009-2019 Antonio Diaz Diaz.
This file is a collection of facts, and thus it is not copyrightable,
but just in case, you have unlimited permission to copy, distribute and
diff --git a/INSTALL b/INSTALL
index 695931a..42cb5df 100644
--- a/INSTALL
+++ b/INSTALL
@@ -1,12 +1,11 @@
Requirements
------------
You will need a C++ compiler.
-I use gcc 5.3.0 and 4.1.2, but the code should compile with any
-standards compliant compiler.
+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.
-Compressors for bzip2, gzip and lzip formats are required to run the
-tests.
+Compressors for bzip2, gzip and lzip formats are required to run the tests.
If you are installing zutils along with GNU gzip and want to keep the
gzip scripts, the recommended method is to configure gzip as follows:
@@ -70,7 +69,7 @@ After running 'configure', you can run 'make' and 'make install' as
explained above.
-Copyright (C) 2009-2018 Antonio Diaz Diaz.
+Copyright (C) 2009-2019 Antonio Diaz Diaz.
This file is free documentation: you have unlimited permission to copy,
distribute and modify it.
diff --git a/Makefile.in b/Makefile.in
index 67b6998..7ca371a 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -223,7 +223,11 @@ dist : doc
$(DISTNAME)/z*.in \
$(DISTNAME)/testsuite/check.sh \
$(DISTNAME)/testsuite/test.txt \
- $(DISTNAME)/testsuite/test.txt.tar
+ $(DISTNAME)/testsuite/test.txt.tar \
+ $(DISTNAME)/testsuite/zcat_vs.dat \
+ $(DISTNAME)/testsuite/test_bad_crc.lz \
+ $(DISTNAME)/testsuite/zero_bad_crc.lz \
+ $(DISTNAME)/testsuite/zero_bad_crc.gz
rm -f $(DISTNAME)
lzip -v -9 $(DISTNAME).tar
diff --git a/NEWS b/NEWS
index 329f7af..af8629f 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,38 @@
-Changes in version 1.7:
+Changes in version 1.8:
-zgrep now passes the '--color' option to grep. (But it only works if the
-grep program used supports it).
+A buffer overflow has been fixed in zcat which happened sometimes when
+the '-v, --show-nonprinting' option was used (or indirectly enabled).
+A canary byte has been added to the output buffer to prevent the buffer
+overflow from happening again.
+
+The option '-R, --dereference-recursive', which recursively follows
+symbolic links, has been added to zcat, zgrep, ztest and zupdate.
+
+The option '-r, --recursive' now skips symlinks that are encountered
+recursively.
+
+If no files are given to zcat, zgrep, ztest and zupdate, a recursive
+search will now examine the current working directory.
+
+Recursive directory loops are now detected.
+
+zcat and zgrep now ignore directories given in the command line if
+'--recursive' is not specified, instead of reporting an error.
+
+Extra trailing slashes are now removed from directories given in the
+command line before recursing into them.
+
+zcat and zgrep now show the right error when they can't open an input
+file instead of showing "No such file or directory".
+
+Killed decompressors are now waited for, preventing failure caused by
+too many open pipes.
+
+Test and document that if a file fails to decompress, zcat, zgrep and
+ztest continue processing the rest of the files.
+
+Test and document that if an error happens while recompressing a file,
+zupdate exits immediately without recompressing the rest of the files.
+
+The configure script now accepts appending options to CXXFLAGS using the
+syntax 'CXXFLAGS+=OPTIONS'.
diff --git a/README b/README
index 36ddc31..7ca6729 100644
--- a/README
+++ b/README
@@ -10,8 +10,8 @@ These utilities are not wrapper scripts but safer and more efficient C++
programs. In particular the '--recursive' option is very efficient in
those utilities supporting it.
-The provided utilities are zcat, zcmp, zdiff, zgrep, ztest and zupdate.
-The supported formats are bzip2, gzip, lzip and xz.
+The utilities provided are zcat, zcmp, zdiff, zgrep, ztest and zupdate.
+The formats supported are bzip2, gzip, lzip and xz.
Zutils uses external compressors. The compressor to be used for each
format is configurable at runtime.
@@ -37,7 +37,7 @@ have been compressed. Decompressed is used to refer to data which have
undergone the process of decompression.
-Copyright (C) 2009-2018 Antonio Diaz Diaz.
+Copyright (C) 2009-2019 Antonio Diaz Diaz.
This file is free documentation: you have unlimited permission to copy,
distribute and modify it.
diff --git a/arg_parser.cc b/arg_parser.cc
index 008ebc8..ea32fde 100644
--- a/arg_parser.cc
+++ b/arg_parser.cc
@@ -1,5 +1,5 @@
/* Arg_parser - POSIX/GNU command line argument parser. (C++ version)
- Copyright (C) 2006-2018 Antonio Diaz Diaz.
+ Copyright (C) 2006-2019 Antonio Diaz Diaz.
This library is free software. Redistribution and use in source and
binary forms, with or without modification, are permitted provided
diff --git a/arg_parser.h b/arg_parser.h
index f015881..ceb9933 100644
--- a/arg_parser.h
+++ b/arg_parser.h
@@ -1,5 +1,5 @@
/* Arg_parser - POSIX/GNU command line argument parser. (C++ version)
- Copyright (C) 2006-2018 Antonio Diaz Diaz.
+ Copyright (C) 2006-2019 Antonio Diaz Diaz.
This library is free software. Redistribution and use in source and
binary forms, with or without modification, are permitted provided
diff --git a/configure b/configure
index 579b578..d8fd626 100755
--- a/configure
+++ b/configure
@@ -1,12 +1,12 @@
#! /bin/sh
# configure script for Zutils - Utilities dealing with compressed files
-# Copyright (C) 2009-2018 Antonio Diaz Diaz.
+# Copyright (C) 2009-2019 Antonio Diaz Diaz.
#
# This configure script is free software: you have unlimited permission
# to copy, distribute and modify it.
pkgname=zutils
-pkgversion=1.7
+pkgversion=1.8
srctrigger=doc/${pkgname}.texi
# clear some things potentially inherited from environment.
@@ -73,6 +73,7 @@ while [ $# != 0 ] ; do
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 " CXXFLAGS+=OPTIONS append options to the current value of CXXFLAGS"
echo " LDFLAGS=OPTIONS command line options for the linker [${LDFLAGS}]"
echo " DIFF=NAME diff program to use with zdiff [${DIFF}]"
echo " GREP=NAME grep program to use with zgrep [${GREP}]"
@@ -100,12 +101,13 @@ while [ $# != 0 ] ; do
--sysconfdir=*) sysconfdir=${optarg} ;;
--no-create) no_create=yes ;;
- CXX=*) CXX=${optarg} ;;
- CPPFLAGS=*) CPPFLAGS=${optarg} ;;
- CXXFLAGS=*) CXXFLAGS=${optarg} ;;
- LDFLAGS=*) LDFLAGS=${optarg} ;;
- DIFF=*) DIFF=${optarg} ;;
- GREP=*) GREP=${optarg} ;;
+ CXX=*) CXX=${optarg} ;;
+ CPPFLAGS=*) CPPFLAGS=${optarg} ;;
+ CXXFLAGS=*) CXXFLAGS=${optarg} ;;
+ CXXFLAGS+=*) CXXFLAGS="${CXXFLAGS} ${optarg}" ;;
+ LDFLAGS=*) LDFLAGS=${optarg} ;;
+ DIFF=*) DIFF=${optarg} ;;
+ GREP=*) GREP=${optarg} ;;
--*)
echo "configure: WARNING: unrecognized option: '${option}'" 1>&2 ;;
@@ -180,7 +182,7 @@ echo "GREP = ${GREP}"
rm -f Makefile
cat > Makefile << EOF
# Makefile for Zutils - Utilities dealing with compressed files
-# Copyright (C) 2009-2018 Antonio Diaz Diaz.
+# Copyright (C) 2009-2019 Antonio Diaz Diaz.
# This file was generated automatically by configure. Don't edit.
#
# This Makefile is free software: you have unlimited permission
diff --git a/doc/zcat.1 b/doc/zcat.1
index 4cfbdf3..443f813 100644
--- a/doc/zcat.1
+++ b/doc/zcat.1
@@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.46.1.
-.TH ZCAT "1" "February 2018" "zcat (zutils) 1.7" "User Commands"
+.TH ZCAT "1" "January 2019" "zcat (zutils) 1.8" "User Commands"
.SH NAME
zcat \- decompress and concatenate files to standard output
.SH SYNOPSIS
@@ -9,14 +9,17 @@ zcat \- decompress and concatenate files to standard output
Zcat copies each given file to standard output. If any given file is
compressed, its decompressed content is used. If a given file does not
exist, and its name does not end with one of the known extensions, zcat
-tries the compressed file names corresponding to the supported formats.
+tries the compressed file names corresponding to the formats supported.
.PP
-If no files are specified, or if a file is specified as '\-', data are
-read from standard input, decompressed if needed, and sent to standard
-output. Data read from standard input must be of the same type; all
-uncompressed or all in the same compression format.
+If a file is specified as '\-', data are read from standard input,
+decompressed if needed, and sent to standard output. Data read from
+standard input must be of the same type; all uncompressed or all in the
+same compression format.
.PP
-The supported formats are bzip2, gzip, lzip and xz.
+If no files are specified, recursive searches examine the current
+working directory, and nonrecursive searches read standard input.
+.PP
+The formats supported are bzip2, gzip, lzip and xz.
.PP
Exit status is 0 if no errors occurred, non\-zero otherwise.
.SH OPTIONS
@@ -57,6 +60,9 @@ suppress all messages
\fB\-r\fR, \fB\-\-recursive\fR
operate recursively on directories
.TP
+\fB\-R\fR, \fB\-\-dereference\-recursive\fR
+recursively follow symbolic links
+.TP
\fB\-s\fR, \fB\-\-squeeze\-blank\fR
never more than one single blank line
.TP
@@ -88,7 +94,7 @@ Report bugs to zutils\-bug@nongnu.org
.br
Zutils home page: http://www.nongnu.org/zutils/zutils.html
.SH COPYRIGHT
-Copyright \(co 2018 Antonio Diaz Diaz.
+Copyright \(co 2019 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.
diff --git a/doc/zcmp.1 b/doc/zcmp.1
index 03a3f19..e56b46f 100644
--- a/doc/zcmp.1
+++ b/doc/zcmp.1
@@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.46.1.
-.TH ZCMP "1" "February 2018" "zcmp (zutils) 1.7" "User Commands"
+.TH ZCMP "1" "January 2019" "zcmp (zutils) 1.8" "User Commands"
.SH NAME
zcmp \- decompress and compare two files byte by byte
.SH SYNOPSIS
@@ -12,7 +12,7 @@ are numbered starting with 1. If any given file is compressed, its
decompressed content is used. Compressed files are decompressed on the
fly; no temporary files are created.
.PP
-The supported formats are bzip2, gzip, lzip and xz.
+The formats supported are bzip2, gzip, lzip and xz.
.PP
Zcmp compares file1 to file2. If file2 is omitted zcmp tries the
following:
@@ -85,7 +85,7 @@ Report bugs to zutils\-bug@nongnu.org
.br
Zutils home page: http://www.nongnu.org/zutils/zutils.html
.SH COPYRIGHT
-Copyright \(co 2018 Antonio Diaz Diaz.
+Copyright \(co 2019 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.
diff --git a/doc/zdiff.1 b/doc/zdiff.1
index aee3b3a..4edcf5e 100644
--- a/doc/zdiff.1
+++ b/doc/zdiff.1
@@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.46.1.
-.TH ZDIFF "1" "February 2018" "zdiff (zutils) 1.7" "User Commands"
+.TH ZDIFF "1" "January 2019" "zdiff (zutils) 1.8" "User Commands"
.SH NAME
zdiff \- decompress and compare two files line by line
.SH SYNOPSIS
@@ -12,7 +12,7 @@ compressed, its decompressed content is used. Zdiff is a front end to
the diff program and has the limitation that messages from diff refer to
temporary filenames instead of those specified.
.PP
-The supported formats are bzip2, gzip, lzip and xz.
+The formats supported are bzip2, gzip, lzip and xz.
.PP
Zdiff compares file1 to file2. If file2 is omitted zdiff tries the
following:
@@ -109,7 +109,7 @@ Report bugs to zutils\-bug@nongnu.org
.br
Zutils home page: http://www.nongnu.org/zutils/zutils.html
.SH COPYRIGHT
-Copyright \(co 2018 Antonio Diaz Diaz.
+Copyright \(co 2019 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.
diff --git a/doc/zgrep.1 b/doc/zgrep.1
index 5533b43..2418ef2 100644
--- a/doc/zgrep.1
+++ b/doc/zgrep.1
@@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.46.1.
-.TH ZGREP "1" "February 2018" "zgrep (zutils) 1.7" "User Commands"
+.TH ZGREP "1" "January 2019" "zgrep (zutils) 1.8" "User Commands"
.SH NAME
zgrep \- search compressed files for a regular expression
.SH SYNOPSIS
@@ -11,14 +11,17 @@ on any combination of compressed and uncompressed files. If any given
file is compressed, its decompressed content is used. If a given file
does not exist, and its name does not end with one of the known
extensions, zgrep tries the compressed file names corresponding to the
-supported formats.
+formats supported.
.PP
-If no files are specified, or if a file is specified as '\-', data are
-read from standard input, decompressed if needed, and fed to grep. Data
-read from standard input must be of the same type; all uncompressed or
-all in the same compression format.
+If a file is specified as '\-', data are read from standard input,
+decompressed if needed, and fed to grep. Data read from standard input
+must be of the same type; all uncompressed or all in the same
+compression format.
.PP
-The supported formats are bzip2, gzip, lzip and xz.
+If no files are specified, recursive searches examine the current
+working directory, and nonrecursive searches read standard input.
+.PP
+The formats supported are bzip2, gzip, lzip and xz.
.PP
Exit status is 0 if match, 1 if no match, 2 if trouble.
.SH OPTIONS
@@ -104,6 +107,9 @@ suppress all messages
\fB\-r\fR, \fB\-\-recursive\fR
operate recursively on directories
.TP
+\fB\-R\fR, \fB\-\-dereference\-recursive\fR
+recursively follow symbolic links
+.TP
\fB\-s\fR, \fB\-\-no\-messages\fR
suppress error messages
.TP
@@ -138,7 +144,7 @@ Report bugs to zutils\-bug@nongnu.org
.br
Zutils home page: http://www.nongnu.org/zutils/zutils.html
.SH COPYRIGHT
-Copyright \(co 2018 Antonio Diaz Diaz.
+Copyright \(co 2019 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.
diff --git a/doc/ztest.1 b/doc/ztest.1
index 7cad696..bffa73f 100644
--- a/doc/ztest.1
+++ b/doc/ztest.1
@@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.46.1.
-.TH ZTEST "1" "February 2018" "ztest (zutils) 1.7" "User Commands"
+.TH ZTEST "1" "January 2019" "ztest (zutils) 1.8" "User Commands"
.SH NAME
ztest \- verify the integrity of compressed files
.SH SYNOPSIS
@@ -7,12 +7,14 @@ ztest \- verify the integrity of compressed files
[\fI\,options\/\fR] [\fI\,files\/\fR]
.SH DESCRIPTION
Ztest verifies the integrity of the specified compressed files.
-Uncompressed files are ignored. If no files are specified, or if a file
-is specified as '\-', the integrity of compressed data read from standard
-input is verified. Data read from standard input must be all in the same
-compression format.
+Uncompressed files are ignored. If a file is specified as '\-', the
+integrity of compressed data read from standard input is verified. Data
+read from standard input must be all in the same compression format.
.PP
-The supported formats are bzip2, gzip, lzip and xz.
+If no files are specified, recursive searches examine the current
+working directory, and nonrecursive searches read standard input.
+.PP
+The formats supported are bzip2, gzip, lzip and xz.
.PP
Note that error detection in the xz format is broken. First, some xz
files lack integrity information. Second, not all xz decompressors can
@@ -47,6 +49,9 @@ suppress all messages
\fB\-r\fR, \fB\-\-recursive\fR
operate recursively on directories
.TP
+\fB\-R\fR, \fB\-\-dereference\-recursive\fR
+recursively follow symbolic links
+.TP
\fB\-v\fR, \fB\-\-verbose\fR
be verbose (a 2nd \fB\-v\fR gives more)
.TP
@@ -66,7 +71,7 @@ Report bugs to zutils\-bug@nongnu.org
.br
Zutils home page: http://www.nongnu.org/zutils/zutils.html
.SH COPYRIGHT
-Copyright \(co 2018 Antonio Diaz Diaz.
+Copyright \(co 2019 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.
diff --git a/doc/zupdate.1 b/doc/zupdate.1
index f0025d7..955b8c5 100644
--- a/doc/zupdate.1
+++ b/doc/zupdate.1
@@ -1,19 +1,21 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.46.1.
-.TH ZUPDATE "1" "February 2018" "zupdate (zutils) 1.7" "User Commands"
+.TH ZUPDATE "1" "January 2019" "zupdate (zutils) 1.8" "User Commands"
.SH NAME
zupdate \- recompress bzip2, gzip, xz files to lzip format
.SH SYNOPSIS
.B zupdate
[\fI\,options\/\fR] [\fI\,files\/\fR]
.SH DESCRIPTION
-Zupdate recompresses files from bzip2, gzip, and xz formats to lzip format.
-The originals are compared with the new files and then deleted.
+Zupdate recompresses files from bzip2, gzip, and xz formats to lzip
+format. Each original is compared with the new file and then deleted.
Only regular files with standard file name extensions are recompressed,
-other files are ignored.
-Compressed files are decompressed and then recompressed on the fly; no
-temporary files are created.
-The lzip format is chosen as destination because it is by far the most
-appropriate for long\-term data archiving.
+other files are ignored. Compressed files are decompressed and then
+recompressed on the fly; no temporary files are created. The lzip format
+is chosen as destination because it is the most appropriate for
+long\-term data archiving.
+.PP
+If no files are specified, recursive searches examine the current
+working directory, and nonrecursive searches do nothing.
.PP
If the lzip compressed version of a file already exists, the file is
skipped unless the '\-\-force' option is given. In this case, if the
@@ -54,6 +56,9 @@ suppress all messages
\fB\-r\fR, \fB\-\-recursive\fR
operate recursively on directories
.TP
+\fB\-R\fR, \fB\-\-dereference\-recursive\fR
+recursively follow symbolic links
+.TP
\fB\-v\fR, \fB\-\-verbose\fR
be verbose (a 2nd \fB\-v\fR gives more)
.TP
@@ -76,7 +81,7 @@ Report bugs to zutils\-bug@nongnu.org
.br
Zutils home page: http://www.nongnu.org/zutils/zutils.html
.SH COPYRIGHT
-Copyright \(co 2018 Antonio Diaz Diaz.
+Copyright \(co 2019 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.
diff --git a/doc/zutils.info b/doc/zutils.info
index 11ea41a..bf99e2d 100644
--- a/doc/zutils.info
+++ b/doc/zutils.info
@@ -12,7 +12,7 @@ File: zutils.info, Node: Top, Next: Introduction, Up: (dir)
Zutils Manual
*************
-This manual is for Zutils (version 1.7, 13 February 2018).
+This manual is for Zutils (version 1.8, 1 January 2019).
* Menu:
@@ -29,7 +29,7 @@ This manual is for Zutils (version 1.7, 13 February 2018).
* Concept index:: Index of concepts
- Copyright (C) 2009-2018 Antonio Diaz Diaz.
+ Copyright (C) 2009-2019 Antonio Diaz Diaz.
This manual is free documentation: you have unlimited permission to
copy, distribute and modify it.
@@ -50,8 +50,8 @@ are created.
C++ programs. In particular the '--recursive' option is very efficient
in those utilities supporting it.
-The provided utilities are zcat, zcmp, zdiff, zgrep, ztest and zupdate.
-The supported formats are bzip2, gzip, lzip and xz.
+The utilities provided are zcat, zcmp, zdiff, zgrep, ztest and zupdate.
+The formats supported are bzip2, gzip, lzip and xz.
Zutils uses external compressors. The compressor to be used for each
format is configurable at runtime.
@@ -110,7 +110,8 @@ described here.
'-V'
'--version'
- Print the version number on the standard output and exit.
+ Print the version number on the standard output and exit. This
+ version number should be included in all bug reports.
'-M FORMAT_LIST'
'--format=FORMAT_LIST'
@@ -190,12 +191,17 @@ File: zutils.info, Node: Zcat, Next: Zcmp, Prev: The zutilsrc file, Up: Top
zcat copies each given file to standard output. If any given file is
compressed, its decompressed content is used. If a given file does not
exist, and its name does not end with one of the known extensions, zcat
-tries the compressed file names corresponding to the supported formats.
+tries the compressed file names corresponding to the formats supported.
+If a file fails to decompress, zcat continues copying the rest of the
+files.
- If no files are specified, or if a file is specified as '-', data
-are read from standard input, decompressed if needed, and sent to
-standard output. Data read from standard input must be of the same type;
-all uncompressed or all in the same compression format.
+ If a file is specified as '-', data are read from standard input,
+decompressed if needed, and sent to standard output. Data read from
+standard input must be of the same type; all uncompressed or all in the
+same compression format.
+
+ If no files are specified, recursive searches examine the current
+working directory, and nonrecursive searches read standard input.
The format for running zcat is:
@@ -240,7 +246,14 @@ Exit status is 0 if no errors occurred, non-zero otherwise.
'-r'
'--recursive'
- Operate recursively on directories.
+ For each directory operand, read and process all files in that
+ directory, recursively. Follow symbolic links in the command line,
+ but skip symlinks that are encountered recursively.
+
+'-R'
+'--dereference-recursive'
+ For each directory operand, read and process all files in that
+ directory, recursively, following all symbolic links.
'-s'
'--squeeze-blank'
@@ -459,12 +472,16 @@ on any combination of compressed and uncompressed files. If any given
file is compressed, its decompressed content is used. If a given file
does not exist, and its name does not end with one of the known
extensions, zgrep tries the compressed file names corresponding to the
-supported formats.
+formats supported. If a file fails to decompress, zgrep continues
+searching the rest of the files.
- If no files are specified, or if a file is specified as '-', data
-are read from standard input, decompressed if needed, and fed to grep.
-Data read from standard input must be of the same type; all uncompressed
-or all in the same compression format.
+ If a file is specified as '-', data are read from standard input,
+decompressed if needed, and fed to grep. Data read from standard input
+must be of the same type; all uncompressed or all in the same
+compression format.
+
+ If no files are specified, recursive searches examine the current
+working directory, and nonrecursive searches read standard input.
The format for running zgrep is:
@@ -573,7 +590,14 @@ grep program used supports them):
'-r'
'--recursive'
- Operate recursively on directories.
+ For each directory operand, read and process all files in that
+ directory, recursively. Follow symbolic links in the command line,
+ but skip symlinks that are encountered recursively.
+
+'-R'
+'--dereference-recursive'
+ For each directory operand, read and process all files in that
+ directory, recursively, following all symbolic links.
'-s'
'--no-messages'
@@ -602,10 +626,14 @@ File: zutils.info, Node: Ztest, Next: Zupdate, Prev: Zgrep, Up: Top
*******
ztest verifies the integrity of the specified compressed files.
-Uncompressed files are ignored. If no files are specified, or if a file
-is specified as '-', the integrity of compressed data read from
-standard input is verified. Data read from standard input must be all in
-the same compression format.
+Uncompressed files are ignored. If a file is specified as '-', the
+integrity of compressed data read from standard input is verified. Data
+read from standard input must be all in the same compression format. If
+a file fails to decompress, ztest continues verifying the rest of the
+files.
+
+ If no files are specified, recursive searches examine the current
+working directory, and nonrecursive searches read standard input.
Note that error detection in the xz format is broken. First, some xz
files lack integrity information. Second, not all xz decompressors can
@@ -640,7 +668,14 @@ environmental problems (file not found, invalid flags, I/O errors, etc),
'-r'
'--recursive'
- Operate recursively on directories.
+ For each directory operand, read and process all files in that
+ directory, recursively. Follow symbolic links in the command line,
+ but skip symlinks that are encountered recursively.
+
+'-R'
+'--dereference-recursive'
+ For each directory operand, read and process all files in that
+ directory, recursively, following all symbolic links.
'-v'
'--verbose'
@@ -658,9 +693,14 @@ zupdate recompresses files from bzip2, gzip, and xz formats to lzip
format. Each original is compared with the new file and then deleted.
Only regular files with standard file name extensions are recompressed,
other files are ignored. Compressed files are decompressed and then
-recompressed on the fly; no temporary files are created. The lzip format
-is chosen as destination because it is by far the most appropriate for
-long-term data archiving.
+recompressed on the fly; no temporary files are created. If an error
+happens while recompressing a file, zupdate exits immediately without
+recompressing the rest of the files. The lzip format is chosen as
+destination because it is the most appropriate for long-term data
+archiving.
+
+ If no files are specified, recursive searches examine the current
+working directory, and nonrecursive searches do nothing.
If the lzip compressed version of a file already exists, the file is
skipped unless the '--force' option is given. In this case, if the
@@ -679,7 +719,7 @@ files produced have the extensions '.lz' or '.tar.lz'.
Recompressing a file is much like copying or moving it; therefore
zupdate preserves the access and modification dates, permissions, and,
-when possible, ownership of the file just as "cp -p" does. (If the user
+when possible, ownership of the file just as 'cp -p' does. (If the user
ID or the group ID can't be duplicated, the file permission bits S_ISUID
and S_ISGID are cleared).
@@ -718,7 +758,14 @@ otherwise.
'-r'
'--recursive'
- Operate recursively on directories.
+ For each directory operand, read and process all files in that
+ directory, recursively. Follow symbolic links in the command line,
+ but skip symlinks that are encountered recursively.
+
+'-R'
+'--dereference-recursive'
+ For each directory operand, read and process all files in that
+ directory, recursively, following all symbolic links.
'-v'
'--verbose'
@@ -770,18 +817,18 @@ Concept index

Tag Table:
Node: Top222
-Node: Introduction1151
-Node: Common options3775
-Ref: compressor-requirements5533
-Node: The zutilsrc file5905
-Node: Zcat6830
-Node: Zcmp8884
-Node: Zdiff11343
-Node: Zgrep14047
-Node: Ztest17541
-Node: Zupdate19375
-Node: Problems22247
-Node: Concept index22781
+Node: Introduction1149
+Node: Common options3773
+Ref: compressor-requirements5596
+Node: The zutilsrc file5968
+Node: Zcat6893
+Node: Zcmp9445
+Node: Zdiff11904
+Node: Zgrep14608
+Node: Ztest18603
+Node: Zupdate20938
+Node: Problems24364
+Node: Concept index24898

End Tag Table
diff --git a/doc/zutils.texi b/doc/zutils.texi
index 343c297..789643a 100644
--- a/doc/zutils.texi
+++ b/doc/zutils.texi
@@ -6,8 +6,8 @@
@finalout
@c %**end of header
-@set UPDATED 13 February 2018
-@set VERSION 1.7
+@set UPDATED 1 January 2019
+@set VERSION 1.8
@dircategory Data Compression
@direntry
@@ -49,7 +49,7 @@ This manual is for Zutils (version @value{VERSION}, @value{UPDATED}).
@end menu
@sp 1
-Copyright @copyright{} 2009-2018 Antonio Diaz Diaz.
+Copyright @copyright{} 2009-2019 Antonio Diaz Diaz.
This manual is free documentation: you have unlimited permission
to copy, distribute and modify it.
@@ -70,8 +70,8 @@ programs. In particular the @samp{--recursive} option is very efficient
in those utilities supporting it.
@noindent
-The provided utilities are zcat, zcmp, zdiff, zgrep, ztest and zupdate.@*
-The supported formats are bzip2, gzip, lzip and xz.@*
+The utilities provided are zcat, zcmp, zdiff, zgrep, ztest and zupdate.@*
+The formats supported are bzip2, gzip, lzip and xz.@*
Zutils uses external compressors. The compressor to be used for each
format is configurable at runtime.
@@ -133,6 +133,7 @@ only supports the @samp{--help} form of this option.
@item -V
@itemx --version
Print the version number on the standard output and exit.
+This version number should be included in all bug reports.
@item -M @var{format_list}
@itemx --format=@var{format_list}
@@ -221,12 +222,17 @@ where <format> is one of @samp{bz2}, @samp{gz}, @samp{lz} or @samp{xz}.
zcat copies each given file to standard output. If any given file is
compressed, its decompressed content is used. If a given file does not
exist, and its name does not end with one of the known extensions, zcat
-tries the compressed file names corresponding to the supported formats.
+tries the compressed file names corresponding to the formats supported.
+If a file fails to decompress, zcat continues copying the rest of the
+files.
-If no files are specified, or if a file is specified as @samp{-}, data
-are read from standard input, decompressed if needed, and sent to
-standard output. Data read from standard input must be of the same type;
-all uncompressed or all in the same compression format.
+If a file is specified as @samp{-}, data are read from standard input,
+decompressed if needed, and sent to standard output. Data read from
+standard input must be of the same type; all uncompressed or all in the
+same compression format.
+
+If no files are specified, recursive searches examine the current
+working directory, and nonrecursive searches read standard input.
The format for running zcat is:
@@ -274,7 +280,14 @@ Quiet operation. Suppress all messages.
@item -r
@itemx --recursive
-Operate recursively on directories.
+For each directory operand, read and process all files in that
+directory, recursively. Follow symbolic links in the command line, but
+skip symlinks that are encountered recursively.
+
+@item -R
+@itemx --dereference-recursive
+For each directory operand, read and process all files in that
+directory, recursively, following all symbolic links.
@item -s
@itemx --squeeze-blank
@@ -509,12 +522,16 @@ on any combination of compressed and uncompressed files. If any given
file is compressed, its decompressed content is used. If a given file
does not exist, and its name does not end with one of the known
extensions, zgrep tries the compressed file names corresponding to the
-supported formats.
+formats supported. If a file fails to decompress, zgrep continues
+searching the rest of the files.
-If no files are specified, or if a file is specified as @samp{-}, data
-are read from standard input, decompressed if needed, and fed to grep.
-Data read from standard input must be of the same type; all uncompressed
-or all in the same compression format.
+If a file is specified as @samp{-}, data are read from standard input,
+decompressed if needed, and fed to grep. Data read from standard input
+must be of the same type; all uncompressed or all in the same
+compression format.
+
+If no files are specified, recursive searches examine the current
+working directory, and nonrecursive searches read standard input.
The format for running zgrep is:
@@ -629,7 +646,14 @@ found, even if an error was detected.
@item -r
@itemx --recursive
-Operate recursively on directories.
+For each directory operand, read and process all files in that
+directory, recursively. Follow symbolic links in the command line, but
+skip symlinks that are encountered recursively.
+
+@item -R
+@itemx --dereference-recursive
+For each directory operand, read and process all files in that
+directory, recursively, following all symbolic links.
@item -s
@itemx --no-messages
@@ -658,10 +682,14 @@ Match only whole lines.
@cindex ztest
ztest verifies the integrity of the specified compressed files.
-Uncompressed files are ignored. If no files are specified, or if a file
-is specified as @samp{-}, the integrity of compressed data read from
-standard input is verified. Data read from standard input must be all in
-the same compression format.
+Uncompressed files are ignored. If a file is specified as @samp{-}, the
+integrity of compressed data read from standard input is verified. Data
+read from standard input must be all in the same compression format. If
+a file fails to decompress, ztest continues verifying the rest of the
+files.
+
+If no files are specified, recursive searches examine the current
+working directory, and nonrecursive searches read standard input.
Note that error detection in the xz format is broken. First, some xz
files lack integrity information. Second, not all xz decompressors can
@@ -703,7 +731,14 @@ Quiet operation. Suppress all messages.
@item -r
@itemx --recursive
-Operate recursively on directories.
+For each directory operand, read and process all files in that
+directory, recursively. Follow symbolic links in the command line, but
+skip symlinks that are encountered recursively.
+
+@item -R
+@itemx --dereference-recursive
+For each directory operand, read and process all files in that
+directory, recursively, following all symbolic links.
@item -v
@itemx --verbose
@@ -721,9 +756,14 @@ zupdate recompresses files from bzip2, gzip, and xz formats to lzip
format. Each original is compared with the new file and then deleted.
Only regular files with standard file name extensions are recompressed,
other files are ignored. Compressed files are decompressed and then
-recompressed on the fly; no temporary files are created. The lzip format
-is chosen as destination because it is by far the most appropriate for
-long-term data archiving.
+recompressed on the fly; no temporary files are created. If an error
+happens while recompressing a file, zupdate exits immediately without
+recompressing the rest of the files. The lzip format is chosen as
+destination because it is the most appropriate for long-term data
+archiving.
+
+If no files are specified, recursive searches examine the current
+working directory, and nonrecursive searches do nothing.
If the lzip compressed version of a file already exists, the file is
skipped unless the @samp{--force} option is given. In this case, if the
@@ -743,7 +783,7 @@ extensions @samp{.lz} or @samp{.tar.lz}.
Recompressing a file is much like copying or moving it; therefore
zupdate preserves the access and modification dates, permissions, and,
-when possible, ownership of the file just as "cp -p" does. (If the user
+when possible, ownership of the file just as @samp{cp -p} does. (If the user
ID or the group ID can't be duplicated, the file permission bits S_ISUID
and S_ISGID are cleared).
@@ -785,7 +825,14 @@ Quiet operation. Suppress all messages.
@item -r
@itemx --recursive
-Operate recursively on directories.
+For each directory operand, read and process all files in that
+directory, recursively. Follow symbolic links in the command line, but
+skip symlinks that are encountered recursively.
+
+@item -R
+@itemx --dereference-recursive
+For each directory operand, read and process all files in that
+directory, recursively, following all symbolic links.
@item -v
@itemx --verbose
diff --git a/rc.cc b/rc.cc
index 5014806..308cf0c 100644
--- a/rc.cc
+++ b/rc.cc
@@ -1,5 +1,5 @@
/* Zutils - Utilities dealing with compressed files
- Copyright (C) 2009-2018 Antonio Diaz Diaz.
+ Copyright (C) 2009-2019 Antonio Diaz Diaz.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -37,7 +37,7 @@ int verbosity = 0;
namespace {
const char * const config_file_name = "zutilsrc";
-const char * const program_year = "2018";
+const char * const program_year = "2019";
std::string compressor_names[num_formats] =
{ "bzip2", "gzip", "lzip", "xz" }; // default compressor names
@@ -211,8 +211,8 @@ int process_rcfile( const std::string & name )
bool enabled_format( const int format_index )
{
- if( enabled_formats.size() <= num_formats ) return true;
- if( format_index < 0 ) return enabled_formats[num_formats];
+ if( enabled_formats.size() <= num_formats ) return true; // all enabled
+ if( format_index < 0 ) return enabled_formats[num_formats]; // uncompressed
return enabled_formats[format_index];
}
@@ -336,11 +336,9 @@ 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", program_name, msg );
- if( errcode > 0 ) std::fprintf( stderr, ": %s", std::strerror( errcode ) );
- std::fputc( '\n', stderr );
- }
+ 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 );
@@ -350,18 +348,10 @@ void show_error( const char * const msg, const int errcode, const bool help )
void show_file_error( const char * const filename, const char * const msg,
const int errcode )
{
- if( verbosity < 0 ) return;
- std::fprintf( stderr, "%s: %s: %s", program_name, filename, msg );
- if( errcode > 0 ) std::fprintf( stderr, ": %s", std::strerror( errcode ) );
- std::fputc( '\n', stderr );
- }
-
-
-void show_error2( const char * const msg, const char * const name )
- {
if( verbosity >= 0 )
- std::fprintf( stderr, "%s: %s '%s': %s\n",
- program_name, msg, name, std::strerror( errno ) );
+ std::fprintf( stderr, "%s: %s: %s%s%s\n", program_name, filename, msg,
+ ( errcode > 0 ) ? ": " : "",
+ ( errcode > 0 ) ? std::strerror( errcode ) : "" );
}
@@ -376,7 +366,7 @@ void internal_error( const char * const msg )
void show_close_error( const char * const prog_name )
{
if( verbosity >= 0 )
- std::fprintf( stderr, "%s: Can't close output of %s: %s\n",
+ std::fprintf( stderr, "%s: Error closing output of %s: %s\n",
program_name, prog_name, std::strerror( errno ) );
}
diff --git a/rc.h b/rc.h
index 0992d0c..2a059bc 100644
--- a/rc.h
+++ b/rc.h
@@ -1,5 +1,5 @@
/* Zutils - Utilities dealing with compressed files
- Copyright (C) 2009-2018 Antonio Diaz Diaz.
+ Copyright (C) 2009-2019 Antonio Diaz Diaz.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -51,7 +51,6 @@ 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 show_error2( const char * const msg, const char * const name );
void internal_error( const char * const msg );
void show_close_error( const char * const prog_name = "data feeder" );
void show_exec_error( const char * const prog_name );
diff --git a/recursive.cc b/recursive.cc
index 93943f2..2f347f9 100644
--- a/recursive.cc
+++ b/recursive.cc
@@ -1,5 +1,5 @@
/* Zutils - Utilities dealing with compressed files
- Copyright (C) 2009-2018 Antonio Diaz Diaz.
+ Copyright (C) 2009-2019 Antonio Diaz Diaz.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -15,9 +15,41 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+// Returns true if full_name is a regular file with an enabled extension
+// or (a link to) a directory.
+bool test_full_name( const std::string & full_name, const struct stat * stp,
+ const bool follow )
+ {
+ struct stat st, st2;
+ if( follow && stat( full_name.c_str(), &st ) != 0 ) return false;
+ if( !follow && lstat( full_name.c_str(), &st ) != 0 ) return false;
+ if( S_ISREG( st.st_mode ) ) // regular file
+ return enabled_format( extension_format( extension_index( full_name ) ) );
+ if( !S_ISDIR( st.st_mode ) ) return false;
+
+ std::string prev_dir( full_name );
+ bool loop = ( stp && st.st_ino == stp->st_ino && st.st_dev == stp->st_dev );
+ if( !loop )
+ for( unsigned i = prev_dir.size(); i > 1; )
+ {
+ while( i > 0 && prev_dir[i-1] != '/' ) --i;
+ if( i == 0 ) break;
+ if( i > 1 ) --i; // remove trailing slash except at root dir
+ prev_dir.resize( i );
+ if( stat( prev_dir.c_str(), &st2 ) != 0 || !S_ISDIR( st2.st_mode ) ||
+ ( st.st_ino == st2.st_ino && st.st_dev == st2.st_dev ) )
+ { loop = true; break; }
+ }
+ if( loop ) // full_name already visited or above tree
+ show_file_error( full_name.c_str(), "warning: Recursive directory loop" );
+ return !loop; // (link to) directory
+ }
+
+
+// Returns in input_filename the next filename.
bool next_filename( std::list< std::string > & filenames,
std::string & input_filename, bool & error,
- const bool recursive, const bool ignore_stdin = false,
+ const int recursive, const bool ignore_stdin = false,
const bool no_messages = false )
{
while( !filenames.empty() )
@@ -29,18 +61,30 @@ bool next_filename( std::list< std::string > & filenames,
if( ignore_stdin ) continue;
input_filename.clear(); return true;
}
- if( recursive )
+ struct stat st;
+ if( stat( input_filename.c_str(), &st ) == 0 && S_ISDIR( st.st_mode ) )
{
- struct stat st;
- if( stat( input_filename.c_str(), &st ) == 0 && S_ISDIR( st.st_mode ) )
+ if( recursive )
{
DIR * const dirp = opendir( input_filename.c_str() );
if( !dirp )
{
if( !no_messages )
- show_error2( "Can't open directory", input_filename.c_str() );
+ show_file_error( input_filename.c_str(), "Can't open directory", errno );
error = true; continue;
}
+ for( unsigned i = input_filename.size();
+ i > 1 && input_filename[i-1] == '/'; --i )
+ input_filename.resize( i - 1 ); // remove trailing slashes
+ struct stat stdot, *stdotp = 0;
+ if( input_filename[0] != '/' ) // relative path
+ {
+ if( input_filename == "." ) input_filename.clear();
+ if( stat( ".", &stdot ) == 0 && S_ISDIR( stdot.st_mode ) )
+ stdotp = &stdot;
+ }
+ if( input_filename.size() && input_filename != "/" )
+ input_filename += '/';
std::list< std::string > tmp_list;
while( true )
{
@@ -48,14 +92,13 @@ bool next_filename( std::list< std::string > & filenames,
if( !entryp ) { closedir( dirp ); break; }
const std::string tmp_name( entryp->d_name );
if( tmp_name == "." || tmp_name == ".." ) continue;
- const std::string full_name( input_filename + "/" + tmp_name );
- if( enabled_format( extension_format( extension_index( tmp_name ) ) ) ||
- ( stat( full_name.c_str(), &st ) == 0 && S_ISDIR( st.st_mode ) ) )
+ const std::string full_name( input_filename + tmp_name );
+ if( test_full_name( full_name, stdotp, recursive == 2 ) )
tmp_list.push_back( full_name );
}
filenames.splice( filenames.begin(), tmp_list );
- continue;
}
+ continue;
}
return true;
}
diff --git a/testsuite/check.sh b/testsuite/check.sh
index a1ea5da..8c0e64e 100755
--- a/testsuite/check.sh
+++ b/testsuite/check.sh
@@ -1,6 +1,6 @@
#! /bin/sh
# check script for Zutils - Utilities dealing with compressed files
-# Copyright (C) 2009-2018 Antonio Diaz Diaz.
+# Copyright (C) 2009-2019 Antonio Diaz Diaz.
#
# This script is free software: you have unlimited permission
# to copy, distribute and modify it.
@@ -53,6 +53,9 @@ cat in > -in- || framework_failure
cat in.lz > -in-.lz || framework_failure
cat in.lz > lz_only.lz || framework_failure
cat in in in in in in > in6 || framework_failure
+bad0_lz="${testdir}"/zero_bad_crc.lz
+bad0_gz="${testdir}"/zero_bad_crc.gz
+bad1_lz="${testdir}"/test_bad_crc.lz
fail=0
test_failed() { fail=1 ; printf " $1" ; [ -z "$2" ] || printf "($2)" ; }
@@ -71,6 +74,7 @@ for i in ${extensions}; do
test_failed $LINENO $i
done
+"${ZCAT}" -N -v -s "${testdir}"/zcat_vs.dat > /dev/null || test_failed $LINENO
"${ZCAT}" -N < in > copy || test_failed $LINENO
cmp in copy || test_failed $LINENO
"${ZCAT}" -N < in.gz > copy || test_failed $LINENO
@@ -89,6 +93,18 @@ cmp in copy || test_failed $LINENO
cmp in copy || test_failed $LINENO
"${ZCAT}" -N in in.gz in.bz2 in.lz -- -in- -in-.lz > copy || test_failed $LINENO
cmp in6 copy || test_failed $LINENO
+"${ZCAT}" -Nq in in.gz in.bz2 in.lz "${bad0_lz}" -- -in- -in-.lz > copy
+[ $? = 1 ] || test_failed $LINENO
+cmp in6 copy || test_failed $LINENO
+"${ZCAT}" -Nq "${bad1_lz}" -- -in-.lz in in.gz in.bz2 in.lz > copy
+[ $? = 1 ] || test_failed $LINENO
+cmp in6 copy || test_failed $LINENO
+"${ZCAT}" -N . || test_failed $LINENO
+"${ZCAT}" -N -r . > /dev/null || test_failed $LINENO
+"${ZCAT}" -N -r > /dev/null || test_failed $LINENO
+"${ZCAT}" -N -R . > /dev/null || test_failed $LINENO
+"${ZCAT}" -N -R > /dev/null || test_failed $LINENO
+
"${ZCAT}" -Nq --format=, in.lz
[ $? = 1 ] || test_failed $LINENO
"${ZCAT}" -Nq --format=,lz in.lz
@@ -258,6 +274,9 @@ done
"${ZGREP}" -N pin.tar -e "GNU" > /dev/null || test_failed $LINENO
"${ZGREP}" -N "GNU" < pin.tar > /dev/null || test_failed $LINENO
"${ZGREP}" -N -r "GNU" . > /dev/null || test_failed $LINENO
+"${ZGREP}" -N -r "GNU" > /dev/null || test_failed $LINENO
+"${ZGREP}" -N -R "GNU" . > /dev/null || test_failed $LINENO
+"${ZGREP}" -N -R "GNU" > /dev/null || test_failed $LINENO
"${ZGREP}" -N "nx_pattern" -r . in > /dev/null && test_failed $LINENO
"${ZGREP}" -N -e "GNU" in > /dev/null || test_failed $LINENO
"${ZGREP}" -N "GNU" < in > /dev/null || test_failed $LINENO
@@ -275,6 +294,12 @@ done
test_failed $LINENO
"${ZGREP}" -N -L "nx_pattern" in in.gz in.bz2 in.lz -- -in- > /dev/null &&
test_failed $LINENO
+"${ZGREP}" -Nq -l "01234567890" in "${bad1_lz}" in.lz && test_failed $LINENO
+"${ZGREP}" -Nq -l "01234567890" in "${bad1_lz}" in.lz pin.tar > /dev/null ||
+ test_failed $LINENO
+
+"${ZGREP}" -N "GNU" .
+[ $? = 1 ] || test_failed $LINENO
"${ZGREP}" -N --bad-option 2> /dev/null
[ $? = 2 ] || test_failed $LINENO
"${ZGREP}" -N "GNU" -s nx_file
@@ -300,9 +325,20 @@ done
"${ZTEST}" -N < in.gz || test_failed $LINENO
"${ZTEST}" -N < in.bz2 || test_failed $LINENO
"${ZTEST}" -N < in.lz || test_failed $LINENO
+"${ZTEST}" -N - < in.lz || test_failed $LINENO
"${ZTEST}" -N - in.gz - < in.lz || test_failed $LINENO
"${ZTEST}" -N --lz='lzip -q' < in.lz || test_failed $LINENO
"${ZTEST}" -N -r . || test_failed $LINENO
+"${ZTEST}" -N -r || test_failed $LINENO
+"${ZTEST}" -N -R . || test_failed $LINENO
+"${ZTEST}" -N -R || test_failed $LINENO
+
+"${ZTEST}" -Nq in.gz "${bad0_lz}" in.bz2 "${bad1_lz}" in.lz
+[ $? = 2 ] || test_failed $LINENO
+lines=`"${ZTEST}" -N in.gz "${bad0_lz}" in.bz2 "${bad1_lz}" in.lz 2>&1 | wc -l`
+[ "${lines}" -eq 2 ] || test_failed $LINENO
+lines=`"${ZTEST}" -Nv in.gz "${bad0_lz}" in.bz2 "${bad1_lz}" in.lz 2>&1 | wc -l`
+[ "${lines}" -eq 5 ] || test_failed $LINENO
"${ZTEST}" -Nq < in
[ $? = 2 ] || test_failed $LINENO
dd if=in.lz bs=1000 count=1 2> /dev/null | "${ZTEST}" -N -q
@@ -336,58 +372,122 @@ cat in.gz > a.gz || framework_failure
cat in.lz in.lz > a.lz || framework_failure
"${ZUPDATE}" -Nq -f a.bz2 a.gz
-{ [ $? = 1 ] && [ -e a.bz2 ] && [ -e a.gz ] && [ -e a.lz ] ; } ||
- test_failed $LINENO
+[ $? = 1 ] || test_failed $LINENO
+[ -e a.bz2 ] || test_failed $LINENO
+[ -e a.gz ] || test_failed $LINENO
+[ -e a.lz ] || test_failed $LINENO
rm -f a.lz || framework_failure
-"${ZUPDATE}" -N a.bz2
-{ [ $? = 0 ] && [ ! -e a.bz2 ] && [ -e a.gz ] && [ -e a.lz ] ; } ||
- test_failed $LINENO
+"${ZUPDATE}" -N a.bz2 || test_failed $LINENO
+[ ! -e a.bz2 ] || test_failed $LINENO
+[ -e a.gz ] || test_failed $LINENO
+[ -e a.lz ] || test_failed $LINENO
rm -f a.lz || framework_failure
-"${ZUPDATE}" -N a.gz
-{ [ $? = 0 ] && [ ! -e a.bz2 ] && [ ! -e a.gz ] && [ -e a.lz ] ; } ||
- test_failed $LINENO
+"${ZUPDATE}" -N a.gz || test_failed $LINENO
+[ ! -e a.bz2 ] || test_failed $LINENO
+[ ! -e a.gz ] || test_failed $LINENO
+[ -e a.lz ] || test_failed $LINENO
rm -f a.lz || framework_failure
cat in.bz2 > a.bz2 || framework_failure
cat in.gz > a.gz || framework_failure
"${ZUPDATE}" -Nq a.bz2 a.gz
-{ [ $? = 1 ] && [ ! -e a.bz2 ] && [ -e a.gz ] && [ -e a.lz ] ; } ||
- test_failed $LINENO
+[ $? = 1 ] || test_failed $LINENO
+[ ! -e a.bz2 ] || test_failed $LINENO
+[ -e a.gz ] || test_failed $LINENO
+[ -e a.lz ] || test_failed $LINENO
rm -f a.lz || framework_failure
cat in.bz2 > a.bz2 || framework_failure
cat in.gz > a.gz || framework_failure
-"${ZUPDATE}" -N -f -k a.bz2 a.gz
-{ [ $? = 0 ] && [ -e a.bz2 ] && [ -e a.gz ] && [ -e a.lz ] ; } ||
- test_failed $LINENO
+"${ZUPDATE}" -N -f -k a.bz2 a.gz || test_failed $LINENO
+[ -e a.bz2 ] || test_failed $LINENO
+[ -e a.gz ] || test_failed $LINENO
+[ -e a.lz ] || test_failed $LINENO
rm -f a.lz || framework_failure
cat in.bz2 > a.bz2 || framework_failure
cat in.gz > a.gz || framework_failure
-"${ZUPDATE}" -N -f a.bz2 a.gz
-{ [ $? = 0 ] && [ ! -e a.bz2 ] && [ ! -e a.gz ] && [ ! -e a ] &&
- [ -e a.lz ] ; } || test_failed $LINENO
+"${ZUPDATE}" -N -f a.bz2 a.gz || test_failed $LINENO
+[ ! -e a.bz2 ] || test_failed $LINENO
+[ ! -e a.gz ] || test_failed $LINENO
+[ ! -e a ] || test_failed $LINENO
+[ -e a.lz ] || test_failed $LINENO
rm -f a.lz || framework_failure
cat in.bz2 > a.bz2 || framework_failure
-"${ZUPDATE}" -N -1 -q a.bz2
-{ [ $? = 0 ] && [ ! -e a.bz2 ] && [ -e a.lz ] ; } || test_failed $LINENO
+cat "${bad0_gz}" > b.gz || framework_failure
+cat in.gz > c.gz || framework_failure
+"${ZUPDATE}" -N -f a.bz2 b.gz c.gz 2> /dev/null && test_failed $LINENO
+[ ! -e a.bz2 ] || test_failed $LINENO
+[ -e b.gz ] || test_failed $LINENO
+[ -e c.gz ] || test_failed $LINENO
+[ ! -e a ] || test_failed $LINENO
+[ ! -e b ] || test_failed $LINENO
+[ ! -e c ] || test_failed $LINENO
+[ -e a.lz ] || test_failed $LINENO
+rm -f a.lz b.gz c.gz || framework_failure
+
+cat in.bz2 > a.bz2 || framework_failure
+"${ZUPDATE}" -N -1 -q a.bz2 || test_failed $LINENO
+[ ! -e a.bz2 ] || test_failed $LINENO
+[ -e a.lz ] || test_failed $LINENO
rm -f a.lz || framework_failure
mkdir tmp2
mkdir tmp2/tmp3
cat in.bz2 > tmp2/tmp3/a.bz2 || framework_failure
cat in.gz > tmp2/tmp3/a.gz || framework_failure
-"${ZUPDATE}" -N -r --format=gz tmp2
-{ [ $? = 0 ] && [ -e tmp2/tmp3/a.bz2 ] && [ ! -e tmp2/tmp3/a.gz ] &&
- [ -e tmp2/tmp3/a.lz ] ; } || test_failed $LINENO
+"${ZUPDATE}" -N -r --format=gz tmp2 || test_failed $LINENO
+[ -e tmp2/tmp3/a.bz2 ] || test_failed $LINENO
+[ ! -e tmp2/tmp3/a.gz ] || test_failed $LINENO
+[ -e tmp2/tmp3/a.lz ] || test_failed $LINENO
rm -f tmp2/tmp3/a.lz || framework_failure
-"${ZUPDATE}" -N -r --format=bz2 tmp2
-{ [ $? = 0 ] && [ ! -e tmp2/tmp3/a.bz2 ] && [ ! -e tmp2/tmp3/a.gz ] &&
- [ -e tmp2/tmp3/a.lz ] ; } || test_failed $LINENO
+"${ZUPDATE}" -N -r --format=bz2 tmp2 || test_failed $LINENO
+[ ! -e tmp2/tmp3/a.bz2 ] || test_failed $LINENO
+[ ! -e tmp2/tmp3/a.gz ] || test_failed $LINENO
+[ -e tmp2/tmp3/a.lz ] || test_failed $LINENO
+rm -f tmp2/tmp3/a.lz || framework_failure
+cat in.bz2 > tmp2/tmp3/a.bz2 || framework_failure
+cat in.gz > tmp2/tmp3/a.gz || framework_failure
+cd tmp2 || framework_failure
+"${ZUPDATE}" -N -r -k -f . || test_failed $LINENO
+[ -e tmp3/a.bz2 ] || test_failed $LINENO
+[ -e tmp3/a.gz ] || test_failed $LINENO
+[ -e tmp3/a.lz ] || test_failed $LINENO
+rm -f tmp3/a.lz || framework_failure
+"${ZUPDATE}" -N -r -k -f || test_failed $LINENO
+[ -e tmp3/a.bz2 ] || test_failed $LINENO
+[ -e tmp3/a.gz ] || test_failed $LINENO
+[ -e tmp3/a.lz ] || test_failed $LINENO
+rm -f tmp3/a.lz || framework_failure
+"${ZUPDATE}" -N -R -k -f . || test_failed $LINENO
+[ -e tmp3/a.bz2 ] || test_failed $LINENO
+[ -e tmp3/a.gz ] || test_failed $LINENO
+[ -e tmp3/a.lz ] || test_failed $LINENO
+rm -f tmp3/a.lz || framework_failure
+"${ZUPDATE}" -N -R -k -f || test_failed $LINENO
+[ -e tmp3/a.bz2 ] || test_failed $LINENO
+[ -e tmp3/a.gz ] || test_failed $LINENO
+[ -e tmp3/a.lz ] || test_failed $LINENO
+rm -f tmp3/a.lz || framework_failure
+"${ZUPDATE}" -N -r -f . || test_failed $LINENO
+[ ! -e tmp3/a.bz2 ] || test_failed $LINENO
+[ ! -e tmp3/a.gz ] || test_failed $LINENO
+[ -e tmp3/a.lz ] || test_failed $LINENO
+cd .. || framework_failure
rm -r tmp2 || framework_failure
+if ln -s '.' slink 2> /dev/null ; then
+ "${ZCAT}" -N -r slink > /dev/null || test_failed $LINENO
+ "${ZGREP}" -N -r "GNU" slink > /dev/null || test_failed $LINENO
+ "${ZTEST}" -N -r slink || test_failed $LINENO
+ "${ZUPDATE}" -N -r -f slink || test_failed $LINENO
+else
+ printf "\nwarning: skipping link test: 'ln' does not work on your system."
+fi
+rm -f slink || framework_failure
+
echo
if [ ${fail} = 0 ] ; then
echo "tests completed successfully."
diff --git a/testsuite/test_bad_crc.lz b/testsuite/test_bad_crc.lz
new file mode 100644
index 0000000..c7d5bc9
--- /dev/null
+++ b/testsuite/test_bad_crc.lz
Binary files differ
diff --git a/testsuite/zcat_vs.dat b/testsuite/zcat_vs.dat
new file mode 100644
index 0000000..29978fd
--- /dev/null
+++ b/testsuite/zcat_vs.dat
@@ -0,0 +1,68 @@
+Worst case test file for zcat -vs.
+First 4096 input bytes produce 4095 output bytes because of -s.
+Next 4096 input bytes produce 16384 output bytes, accumulating a total
+of 20479 bytes in the output buffer.
+----------------------------------------------
+
+
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+...............................................................
+€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ \ No newline at end of file
diff --git a/testsuite/zero_bad_crc.gz b/testsuite/zero_bad_crc.gz
new file mode 100644
index 0000000..a2a9991
--- /dev/null
+++ b/testsuite/zero_bad_crc.gz
Binary files differ
diff --git a/testsuite/zero_bad_crc.lz b/testsuite/zero_bad_crc.lz
new file mode 100644
index 0000000..0d3cc93
--- /dev/null
+++ b/testsuite/zero_bad_crc.lz
Binary files differ
diff --git a/zcat.cc b/zcat.cc
index 8ef1929..5d400ad 100644
--- a/zcat.cc
+++ b/zcat.cc
@@ -1,5 +1,5 @@
/* Zcat - decompress and concatenate files to standard output
- Copyright (C) 2010-2018 Antonio Diaz Diaz.
+ Copyright (C) 2010-2019 Antonio Diaz Diaz.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -93,36 +93,39 @@ void show_help()
std::printf( "Zcat copies each given file to standard output. If any given file is\n"
"compressed, its decompressed content is used. If a given file does not\n"
"exist, and its name does not end with one of the known extensions, zcat\n"
- "tries the compressed file names corresponding to the supported formats.\n"
- "\nIf no files are specified, or if a file is specified as '-', data are\n"
- "read from standard input, decompressed if needed, and sent to standard\n"
- "output. Data read from standard input must be of the same type; all\n"
- "uncompressed or all in the same compression format.\n"
- "\nThe supported formats are bzip2, gzip, lzip and xz.\n"
+ "tries the compressed file names corresponding to the formats supported.\n"
+ "\nIf a file is specified as '-', data are read from standard input,\n"
+ "decompressed if needed, and sent to standard output. Data read from\n"
+ "standard input must be of the same type; all uncompressed or all in the\n"
+ "same compression format.\n"
+ "\nIf no files are specified, recursive searches examine the current\n"
+ "working directory, and nonrecursive searches read standard input.\n"
+ "\nThe formats supported are bzip2, gzip, lzip and xz.\n"
"\nUsage: zcat [options] [files]\n"
"\nExit status is 0 if no errors occurred, non-zero otherwise.\n"
"\nOptions:\n"
- " -h, --help display this help and exit\n"
- " -V, --version output version information and exit\n"
- " -A, --show-all equivalent to '-vET'\n"
- " -b, --number-nonblank number nonblank output lines\n"
- " -e equivalent to '-vE'\n"
- " -E, --show-ends display '$' at end of each line\n"
- " -M, --format=<list> process only the formats in <list>\n"
- " -n, --number number all output lines\n"
- " -N, --no-rcfile don't read runtime configuration file\n"
- " -O, --force-format=<fmt> force given format (bz2, gz, lz, xz)\n"
- " -q, --quiet suppress all messages\n"
- " -r, --recursive operate recursively on directories\n"
- " -s, --squeeze-blank never more than one single blank line\n"
- " -t equivalent to '-vT'\n"
- " -T, --show-tabs display TAB characters as '^I'\n"
- " -v, --show-nonprinting use '^' and 'M-' notation, except for LF and TAB\n"
- " --verbose verbose mode (show error messages)\n"
- " --bz2=<command> set compressor and options for bzip2 format\n"
- " --gz=<command> set compressor and options for gzip format\n"
- " --lz=<command> set compressor and options for lzip format\n"
- " --xz=<command> set compressor and options for xz format\n" );
+ " -h, --help display this help and exit\n"
+ " -V, --version output version information and exit\n"
+ " -A, --show-all equivalent to '-vET'\n"
+ " -b, --number-nonblank number nonblank output lines\n"
+ " -e equivalent to '-vE'\n"
+ " -E, --show-ends display '$' at end of each line\n"
+ " -M, --format=<list> process only the formats in <list>\n"
+ " -n, --number number all output lines\n"
+ " -N, --no-rcfile don't read runtime configuration file\n"
+ " -O, --force-format=<fmt> force given format (bz2, gz, lz, xz)\n"
+ " -q, --quiet suppress all messages\n"
+ " -r, --recursive operate recursively on directories\n"
+ " -R, --dereference-recursive recursively follow symbolic links\n"
+ " -s, --squeeze-blank never more than one single blank line\n"
+ " -t equivalent to '-vT'\n"
+ " -T, --show-tabs display TAB characters as '^I'\n"
+ " -v, --show-nonprinting use '^' and 'M-' notation, except for LF and TAB\n"
+ " --verbose verbose mode (show error messages)\n"
+ " --bz2=<command> set compressor and options for bzip2 format\n"
+ " --gz=<command> set compressor and options for gzip format\n"
+ " --lz=<command> set compressor and options for lzip format\n"
+ " --xz=<command> set compressor and options for xz format\n" );
show_help_addr();
}
@@ -153,7 +156,7 @@ int do_cat( const int infd, const int buffer_size,
rd = readblock( infd, inbuf, buffer_size );
if( rd != buffer_size && errno )
{
- show_error2( "Error reading file", input_filename.c_str() );
+ show_file_error( input_filename.c_str(), "Read error", errno );
return 1;
}
if( rd == 0 )
@@ -226,11 +229,13 @@ int do_cat( const int infd, const int buffer_size,
int cat( int infd, const int format_index, const std::string & input_filename,
const Cat_options & cat_options )
{
- enum { buffer_size = 4096 };
- // buffer with space for sentinel newline at the end
+ enum { buffer_size = 4096, outbuf_size = (5 * buffer_size) + 256 + 1 };
+ // buffer with space for sentinel newline at the end
uint8_t * const inbuf = new uint8_t[buffer_size+1];
- // buffer with space for character quoting and 255-digit line number
- uint8_t * const outbuf = new uint8_t[(4*buffer_size)+256];
+ // buffer with space for character quoting, 255-digit line number,
+ // worst case flushing respect to inbuf, and a canary byte.
+ uint8_t * const outbuf = new uint8_t[outbuf_size];
+ outbuf[outbuf_size-1] = 0;
int retval = 0;
Children children;
if( !set_data_feeder( input_filename, &infd, children, format_index ) )
@@ -243,6 +248,8 @@ int cat( int infd, const int format_index, const std::string & input_filename,
if( retval == 0 && close( infd ) != 0 )
{ show_close_error(); retval = 1; }
+ if( outbuf[outbuf_size-1] != 0 )
+ internal_error( "buffer overflow." );
delete[] outbuf; delete[] inbuf;
return retval;
}
@@ -253,10 +260,8 @@ int cat( int infd, const int format_index, const std::string & input_filename,
int main( const int argc, const char * const argv[] )
{
enum { verbose_opt = 256, bz2_opt, gz_opt, lz_opt, xz_opt };
- int infd = -1;
int format_index = -1;
- bool recursive = false;
- std::string input_filename;
+ int recursive = 0; // 1 = '-r', 2 = '-R'
std::list< std::string > filenames;
Cat_options cat_options;
invocation_name = argv[0];
@@ -264,33 +269,34 @@ int main( const int argc, const char * const argv[] )
const Arg_parser::Option options[] =
{
- { 'A', "show-all", Arg_parser::no }, // cat
- { 'b', "number-nonblank", Arg_parser::no }, // cat
- { 'c', "stdout", Arg_parser::no }, // gzip
- { 'd', "decompress", Arg_parser::no }, // gzip
- { 'e', 0, Arg_parser::no }, // cat
- { 'E', "show-ends", Arg_parser::no }, // cat
- { 'f', "force", Arg_parser::no }, // gzip
- { 'h', "help", Arg_parser::no },
- { 'l', "list", Arg_parser::no }, // gzip
- { 'L', "license", Arg_parser::no }, // gzip
- { 'M', "format", Arg_parser::yes },
- { 'n', "number", Arg_parser::no }, // cat
- { 'N', "no-rcfile", Arg_parser::no },
- { 'O', "force-format", Arg_parser::yes },
- { 'q', "quiet", Arg_parser::no },
- { 'r', "recursive", Arg_parser::no },
- { 's', "squeeze-blank", Arg_parser::no }, // cat
- { 't', 0, Arg_parser::no }, // cat
- { 'T', "show-tabs", Arg_parser::no }, // cat
- { 'v', "show-nonprinting", Arg_parser::no }, // cat
- { 'V', "version", Arg_parser::no },
- { verbose_opt, "verbose", Arg_parser::no },
- { bz2_opt, "bz2", Arg_parser::yes },
- { gz_opt, "gz", Arg_parser::yes },
- { lz_opt, "lz", Arg_parser::yes },
- { xz_opt, "xz", Arg_parser::yes },
- { 0 , 0, Arg_parser::no } };
+ { 'A', "show-all", Arg_parser::no }, // cat
+ { 'b', "number-nonblank", Arg_parser::no }, // cat
+ { 'c', "stdout", Arg_parser::no }, // gzip
+ { 'd', "decompress", Arg_parser::no }, // gzip
+ { 'e', 0, Arg_parser::no }, // cat
+ { 'E', "show-ends", Arg_parser::no }, // cat
+ { 'f', "force", Arg_parser::no }, // gzip
+ { 'h', "help", Arg_parser::no },
+ { 'l', "list", Arg_parser::no }, // gzip
+ { 'L', "license", Arg_parser::no }, // gzip
+ { 'M', "format", Arg_parser::yes },
+ { 'n', "number", Arg_parser::no }, // cat
+ { 'N', "no-rcfile", Arg_parser::no },
+ { 'O', "force-format", Arg_parser::yes },
+ { 'q', "quiet", Arg_parser::no },
+ { 'r', "recursive", Arg_parser::no },
+ { 'R', "dereference-recursive", Arg_parser::no },
+ { 's', "squeeze-blank", Arg_parser::no }, // cat
+ { 't', 0, Arg_parser::no }, // cat
+ { 'T', "show-tabs", Arg_parser::no }, // cat
+ { 'v', "show-nonprinting", Arg_parser::no }, // cat
+ { 'V', "version", Arg_parser::no },
+ { verbose_opt, "verbose", Arg_parser::no },
+ { bz2_opt, "bz2", Arg_parser::yes },
+ { gz_opt, "gz", Arg_parser::yes },
+ { lz_opt, "lz", Arg_parser::yes },
+ { xz_opt, "xz", Arg_parser::yes },
+ { 0 , 0, Arg_parser::no } };
const Arg_parser parser( argc, argv, options );
if( parser.error().size() ) // bad option
@@ -312,7 +318,7 @@ int main( const int argc, const char * const argv[] )
case 'b': cat_options.number_lines = 1; break;
case 'c': break;
case 'd': break;
- case 'e': cat_options.show_nonprinting = true; // fall through
+ case 'e': cat_options.show_nonprinting = true; // fall through
case 'E': cat_options.show_ends = true; break;
case 'f': break;
case 'h': show_help(); return 0;
@@ -324,9 +330,10 @@ int main( const int argc, const char * const argv[] )
case 'N': break;
case 'O': format_index = parse_format_type( arg ); break;
case 'q': verbosity = -1; break;
- case 'r': recursive = true; break;
+ case 'r': recursive = 1; break;
+ case 'R': recursive = 2; break;
case 's': cat_options.squeeze_blank = true; break;
- case 't': cat_options.show_nonprinting = true; // fall through
+ case 't': cat_options.show_nonprinting = true; // fall through
case 'T': cat_options.show_tabs = true; break;
case 'v': cat_options.show_nonprinting = true; break;
case 'V': show_version(); return 0;
@@ -347,13 +354,15 @@ int main( const int argc, const char * const argv[] )
for( ; argind < parser.arguments(); ++argind )
filenames.push_back( parser.argument( argind ) );
- if( filenames.empty() ) filenames.push_back( "-" );
+ if( filenames.empty() ) filenames.push_back( recursive ? "." : "-" );
+ std::string input_filename;
int retval = 0;
bool error = false;
bool stdin_used = false;
while( next_filename( filenames, input_filename, error, recursive ) )
{
+ int infd;
if( input_filename.empty() )
{
if( stdin_used ) continue; else stdin_used = true;
@@ -368,12 +377,12 @@ int main( const int argc, const char * const argv[] )
const int tmp = cat( infd, format_index, input_filename, cat_options );
if( tmp > retval ) retval = tmp;
- if( input_filename.size() ) { close( infd ); infd = -1; }
+ if( input_filename.size() ) close( infd );
}
if( std::fclose( stdout ) != 0 )
{
- show_error( "Can't close stdout", errno );
+ show_error( "Error closing stdout", errno );
error = true;
}
if( error && retval == 0 ) retval = 1;
diff --git a/zcatgrep.cc b/zcatgrep.cc
index ad75f14..10bc1ea 100644
--- a/zcatgrep.cc
+++ b/zcatgrep.cc
@@ -1,5 +1,5 @@
/* Common code for zcat and zgrep
- Copyright (C) 2010-2018 Antonio Diaz Diaz.
+ Copyright (C) 2010-2019 Antonio Diaz Diaz.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -39,6 +39,7 @@ int open_instream( std::string & input_filename, const bool search,
int infd = open( input_filename.c_str(), O_RDONLY | O_BINARY );
if( infd < 0 )
{
+ const int saved_errno = errno;
if( search && simple_extension_index( input_filename ) < 0 )
{
for( int i = 0; i < num_formats; ++i )
@@ -51,7 +52,8 @@ int open_instream( std::string & input_filename, const bool search,
}
}
if( infd < 0 && !no_messages )
- show_error2( "Can't open input file", input_filename.c_str() );
+ show_file_error( input_filename.c_str(), "Can't open input file",
+ saved_errno );
}
return infd;
}
diff --git a/zcmp.cc b/zcmp.cc
index 534e0c6..2408ca0 100644
--- a/zcmp.cc
+++ b/zcmp.cc
@@ -1,5 +1,5 @@
/* Zcmp - decompress and compare two files byte by byte
- Copyright (C) 2010-2018 Antonio Diaz Diaz.
+ Copyright (C) 2010-2019 Antonio Diaz Diaz.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -55,7 +55,7 @@ void show_help()
"are numbered starting with 1. If any given file is compressed, its\n"
"decompressed content is used. Compressed files are decompressed on the\n"
"fly; no temporary files are created.\n"
- "\nThe supported formats are bzip2, gzip, lzip and xz.\n"
+ "\nThe formats supported are bzip2, gzip, lzip and xz.\n"
"\nUsage: zcmp [options] file1 [file2]\n"
"\nZcmp compares file1 to file2. If file2 is omitted zcmp tries the\n"
"following:\n"
@@ -243,7 +243,7 @@ int cmp( const long long max_size, const int infd[2],
rd[i] = readblock( infd[i], buffer[i], size );
if( rd[i] != size && errno )
{
- show_error2( "Error reading file", filenames[i].c_str() );
+ show_file_error( filenames[i].c_str(), "Read error", errno );
return 2;
}
}
@@ -274,6 +274,7 @@ int cmp( const long long max_size, const int infd[2],
filenames[0].c_str(), filenames[1].c_str(),
byte_number, line_number, c0, buf0, c1, buf1 );
}
+ std::fflush( stdout );
return 1;
}
else // verbosity > 0 ; show all differences
@@ -296,6 +297,7 @@ int cmp( const long long max_size, const int infd[2],
}
}
}
+ std::fflush( stdout );
}
}
@@ -435,7 +437,8 @@ int main( const int argc, const char * const argv[] )
for( int i = 0; i < 2; ++i )
if( !skip_ignore_initial( ignore_initial[i], infd[i] ) )
{
- show_error2( "Can't skip initial bytes from file", filenames[i].c_str() );
+ show_file_error( filenames[i].c_str(),
+ "Read error skipping initial bytes", errno );
return 2;
}
@@ -450,13 +453,13 @@ int main( const int argc, const char * const argv[] )
{ show_close_error(); retval = 2; }
if( filenames[i] != "-" && close( old_infd[i] ) != 0 )
{
- show_error2( "Can't close input file", filenames[i].c_str() );
+ show_file_error( filenames[i].c_str(), "Error closing input file", errno );
retval = 2;
}
}
if( std::fclose( stdout ) != 0 )
{
- show_error( "Can't close stdout", errno );
+ show_error( "Error closing stdout", errno );
retval = 2;
}
diff --git a/zcmpdiff.cc b/zcmpdiff.cc
index 07d35fe..c779251 100644
--- a/zcmpdiff.cc
+++ b/zcmpdiff.cc
@@ -1,5 +1,5 @@
/* Common code for zcmp and zdiff
- Copyright (C) 2010-2018 Antonio Diaz Diaz.
+ Copyright (C) 2010-2019 Antonio Diaz Diaz.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -24,7 +24,7 @@ int open_instream( const std::string & input_filename )
{
const int infd = open( input_filename.c_str(), O_RDONLY | O_BINARY );
if( infd < 0 )
- show_error2( "Can't open input file", input_filename.c_str() );
+ show_file_error( input_filename.c_str(), "Can't open input file", errno );
return infd;
}
diff --git a/zdiff.cc b/zdiff.cc
index a6a48f4..b9106c7 100644
--- a/zdiff.cc
+++ b/zdiff.cc
@@ -1,5 +1,5 @@
/* Zdiff - decompress and compare two files line by line
- Copyright (C) 2010-2018 Antonio Diaz Diaz.
+ Copyright (C) 2010-2019 Antonio Diaz Diaz.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -53,7 +53,7 @@ void show_help()
"compressed, its decompressed content is used. Zdiff is a front end to\n"
"the diff program and has the limitation that messages from diff refer to\n"
"temporary filenames instead of those specified.\n"
- "\nThe supported formats are bzip2, gzip, lzip and xz.\n"
+ "\nThe formats supported are bzip2, gzip, lzip and xz.\n"
"\nUsage: zdiff [options] file1 [file2]\n"
"\nZdiff compares file1 to file2. If file2 is omitted zdiff tries the\n"
"following:\n"
@@ -141,7 +141,7 @@ bool set_fifonames( const std::string filenames[2] )
if( mkfifo( fifonames[i].c_str(), S_IRUSR | S_IWUSR ) == 0 )
continue;
}
- show_error2( "Can't create FIFO", fifonames[i].c_str() );
+ show_file_error( fifonames[i].c_str(), "Can't create FIFO", errno );
return false;
}
return true;
@@ -419,7 +419,7 @@ int main( const int argc, const char * const argv[] )
for( int i = 0; i < 2; ++i )
if( filenames[i] != "-" && close( infd[i] ) != 0 )
{
- show_error2( "Can't close input file", filenames[i].c_str() );
+ show_file_error( filenames[i].c_str(), "Error closing input file", errno );
retval = 2;
}
diff --git a/zgrep.cc b/zgrep.cc
index 204d5d5..1013b5e 100644
--- a/zgrep.cc
+++ b/zgrep.cc
@@ -1,5 +1,5 @@
/* Zgrep - search compressed files for a regular expression
- Copyright (C) 2010-2018 Antonio Diaz Diaz.
+ Copyright (C) 2010-2019 Antonio Diaz Diaz.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -52,51 +52,54 @@ void show_help()
"file is compressed, its decompressed content is used. If a given file\n"
"does not exist, and its name does not end with one of the known\n"
"extensions, zgrep tries the compressed file names corresponding to the\n"
- "supported formats.\n"
- "\nIf no files are specified, or if a file is specified as '-', data are\n"
- "read from standard input, decompressed if needed, and fed to grep. Data\n"
- "read from standard input must be of the same type; all uncompressed or\n"
- "all in the same compression format.\n"
- "\nThe supported formats are bzip2, gzip, lzip and xz.\n"
+ "formats supported.\n"
+ "\nIf a file is specified as '-', data are read from standard input,\n"
+ "decompressed if needed, and fed to grep. Data read from standard input\n"
+ "must be of the same type; all uncompressed or all in the same\n"
+ "compression format.\n"
+ "\nIf no files are specified, recursive searches examine the current\n"
+ "working directory, and nonrecursive searches read standard input.\n"
+ "\nThe formats supported are bzip2, gzip, lzip and xz.\n"
"\nUsage: zgrep [options] <pattern> [files]\n"
"\nExit status is 0 if match, 1 if no match, 2 if trouble.\n"
"\nOptions:\n"
- " --help display this help and exit\n"
- " -V, --version output version information and exit\n"
- " -a, --text treat all files as text\n"
- " -A, --after-context=<n> print <n> lines of trailing context\n"
- " -b, --byte-offset print the byte offset of each line\n"
- " -B, --before-context=<n> print <n> lines of leading context\n"
- " -c, --count only print a count of matching lines per file\n"
- " -C, --context=<n> print <n> lines of output context\n"
- " --color[=<when>] show matched strings in color\n"
- " -e, --regexp=<pattern> use <pattern> as the pattern to match\n"
- " -E, --extended-regexp <pattern> is an extended regular expression\n"
- " -f, --file=<file> obtain patterns from <file>\n"
- " -F, --fixed-strings <pattern> is a set of newline-separated strings\n"
- " -h, --no-filename suppress the prefixing filename on output\n"
- " -H, --with-filename print the filename for each match\n"
- " -i, --ignore-case ignore case distinctions\n"
- " -I ignore binary files\n"
- " -l, --files-with-matches only print names of files containing matches\n"
- " -L, --files-without-match only print names of files containing no matches\n"
- " -m, --max-count=<n> stop after <n> matches\n"
- " -M, --format=<list> process only the formats in <list>\n"
- " -n, --line-number print the line number of each line\n"
- " -N, --no-rcfile don't read runtime configuration file\n"
- " -o, --only-matching show only the part of a line matching <pattern>\n"
- " -O, --force-format=<fmt> force given format (bz2, gz, lz, xz)\n"
- " -q, --quiet suppress all messages\n"
- " -r, --recursive operate recursively on directories\n"
- " -s, --no-messages suppress error messages\n"
- " -v, --invert-match select non-matching lines\n"
- " --verbose verbose mode (show error messages)\n"
- " -w, --word-regexp match only whole words\n"
- " -x, --line-regexp match only whole lines\n"
- " --bz2=<command> set compressor and options for bzip2 format\n"
- " --gz=<command> set compressor and options for gzip format\n"
- " --lz=<command> set compressor and options for lzip format\n"
- " --xz=<command> set compressor and options for xz format\n"
+ " --help display this help and exit\n"
+ " -V, --version output version information and exit\n"
+ " -a, --text treat all files as text\n"
+ " -A, --after-context=<n> print <n> lines of trailing context\n"
+ " -b, --byte-offset print the byte offset of each line\n"
+ " -B, --before-context=<n> print <n> lines of leading context\n"
+ " -c, --count only print a count of matching lines per file\n"
+ " -C, --context=<n> print <n> lines of output context\n"
+ " --color[=<when>] show matched strings in color\n"
+ " -e, --regexp=<pattern> use <pattern> as the pattern to match\n"
+ " -E, --extended-regexp <pattern> is an extended regular expression\n"
+ " -f, --file=<file> obtain patterns from <file>\n"
+ " -F, --fixed-strings <pattern> is a set of newline-separated strings\n"
+ " -h, --no-filename suppress the prefixing filename on output\n"
+ " -H, --with-filename print the filename for each match\n"
+ " -i, --ignore-case ignore case distinctions\n"
+ " -I ignore binary files\n"
+ " -l, --files-with-matches only print names of files containing matches\n"
+ " -L, --files-without-match only print names of files containing no matches\n"
+ " -m, --max-count=<n> stop after <n> matches\n"
+ " -M, --format=<list> process only the formats in <list>\n"
+ " -n, --line-number print the line number of each line\n"
+ " -N, --no-rcfile don't read runtime configuration file\n"
+ " -o, --only-matching show only the part of a line matching <pattern>\n"
+ " -O, --force-format=<fmt> force given format (bz2, gz, lz, xz)\n"
+ " -q, --quiet suppress all messages\n"
+ " -r, --recursive operate recursively on directories\n"
+ " -R, --dereference-recursive recursively follow symbolic links\n"
+ " -s, --no-messages suppress error messages\n"
+ " -v, --invert-match select non-matching lines\n"
+ " --verbose verbose mode (show error messages)\n"
+ " -w, --word-regexp match only whole words\n"
+ " -x, --line-regexp match only whole lines\n"
+ " --bz2=<command> set compressor and options for bzip2 format\n"
+ " --gz=<command> set compressor and options for gzip format\n"
+ " --lz=<command> set compressor and options for lzip format\n"
+ " --xz=<command> set compressor and options for xz format\n"
"Numbers may be followed by a multiplier: k = kB = 10^3 = 1000,\n"
"Ki = KiB = 2^10 = 1024, M = 10^6, Mi = 2^20, G = 10^9, Gi = 2^30, etc...\n" );
show_help_addr();
@@ -187,7 +190,8 @@ int zgrep_file( int infd, const int format_index,
putchar( buffer[i] );
}
else if( std::fwrite( buffer, 1, size, stdout ) != (unsigned)size )
- { show_error( "Write error", errno ); return 2; }
+ { std::fflush( stdout ); show_error( "Write error", errno ); return 2; }
+ std::fflush( stdout );
}
if( size < buffer_size ) break;
}
@@ -197,7 +201,7 @@ int zgrep_file( int infd, const int format_index,
if( !good_status( children, retval == 1 ) ) retval = 2;
if( list_mode && (retval == 0) == (list_mode == 1) )
- std::printf( "%s\n", input_filename.c_str() );
+ { std::printf( "%s\n", input_filename.c_str() ); std::fflush( stdout ); }
if( close( infd ) != 0 )
{ show_close_error(); return 2; }
if( close( fda[0] ) != 0 )
@@ -213,12 +217,10 @@ int main( const int argc, const char * const argv[] )
enum { help_opt = 256, verbose_opt, color_opt,
bz2_opt, gz_opt, lz_opt, xz_opt };
int format_index = -1;
- int infd = -1;
int list_mode = 0; // 1 = list matches, -1 = list non matches
+ int recursive = 0; // 1 = '-r', 2 = '-R'
int show_name = -1; // tri-state bool
bool no_messages = false;
- bool recursive = false;
- std::string input_filename;
std::list< std::string > filenames;
std::vector< const char * > grep_args; // args to grep, maybe empty
std::string color_option; // needed because of optional arg
@@ -227,43 +229,44 @@ int main( const int argc, const char * const argv[] )
const Arg_parser::Option options[] =
{
- { 'a', "text", Arg_parser::no }, // grep GNU
- { 'A', "after-context", Arg_parser::yes }, // grep GNU
- { 'b', "byte-offset", Arg_parser::no }, // grep GNU
- { 'B', "before-context", Arg_parser::yes }, // grep GNU
- { 'c', "count", Arg_parser::no }, // grep
- { 'C', "context", Arg_parser::yes }, // grep GNU
- { 'e', "regexp", Arg_parser::yes }, // grep
- { 'E', "extended-regexp", Arg_parser::no }, // grep
- { 'f', "file ", Arg_parser::yes }, // grep
- { 'F', "fixed-strings", Arg_parser::no }, // grep
- { 'h', "no-filename", Arg_parser::no }, // grep GNU
- { 'H', "with-filename", Arg_parser::no }, // grep GNU
- { 'i', "ignore-case", Arg_parser::no }, // grep
- { 'I', 0, Arg_parser::no }, // grep GNU
- { 'l', "files-with-matches", Arg_parser::no }, // grep
- { 'L', "files-without-match", Arg_parser::no }, // grep GNU
- { 'm', "max-count", Arg_parser::yes }, // grep GNU
- { 'M', "format", Arg_parser::yes },
- { 'n', "line-number", Arg_parser::no }, // grep
- { 'N', "no-rcfile", Arg_parser::no },
- { 'o', "only-matching", Arg_parser::no }, // grep
- { 'O', "force-format", Arg_parser::yes },
- { 'q', "quiet", Arg_parser::no },
- { 'r', "recursive", Arg_parser::no },
- { 's', "no-messages", Arg_parser::no }, // grep
- { 'v', "invert-match", Arg_parser::no }, // grep
- { 'V', "version", Arg_parser::no },
- { 'w', "word-regexp", Arg_parser::no }, // grep GNU
- { 'x', "line-regexp", Arg_parser::no }, // grep
- { help_opt, "help", Arg_parser::no },
- { verbose_opt, "verbose", Arg_parser::no },
- { color_opt, "color", Arg_parser::maybe },
- { bz2_opt, "bz2", Arg_parser::yes },
- { gz_opt, "gz", Arg_parser::yes },
- { lz_opt, "lz", Arg_parser::yes },
- { xz_opt, "xz", Arg_parser::yes },
- { 0 , 0, Arg_parser::no } };
+ { 'a', "text", Arg_parser::no }, // grep GNU
+ { 'A', "after-context", Arg_parser::yes }, // grep GNU
+ { 'b', "byte-offset", Arg_parser::no }, // grep GNU
+ { 'B', "before-context", Arg_parser::yes }, // grep GNU
+ { 'c', "count", Arg_parser::no }, // grep
+ { 'C', "context", Arg_parser::yes }, // grep GNU
+ { 'e', "regexp", Arg_parser::yes }, // grep
+ { 'E', "extended-regexp", Arg_parser::no }, // grep
+ { 'f', "file ", Arg_parser::yes }, // grep
+ { 'F', "fixed-strings", Arg_parser::no }, // grep
+ { 'h', "no-filename", Arg_parser::no }, // grep GNU
+ { 'H', "with-filename", Arg_parser::no }, // grep GNU
+ { 'i', "ignore-case", Arg_parser::no }, // grep
+ { 'I', 0, Arg_parser::no }, // grep GNU
+ { 'l', "files-with-matches", Arg_parser::no }, // grep
+ { 'L', "files-without-match", Arg_parser::no }, // grep GNU
+ { 'm', "max-count", Arg_parser::yes }, // grep GNU
+ { 'M', "format", Arg_parser::yes },
+ { 'n', "line-number", Arg_parser::no }, // grep
+ { 'N', "no-rcfile", Arg_parser::no },
+ { 'o', "only-matching", Arg_parser::no }, // grep
+ { 'O', "force-format", Arg_parser::yes },
+ { 'q', "quiet", Arg_parser::no },
+ { 'r', "recursive", Arg_parser::no },
+ { 'R', "dereference-recursive", Arg_parser::no },
+ { 's', "no-messages", Arg_parser::no }, // grep
+ { 'v', "invert-match", Arg_parser::no }, // grep
+ { 'V', "version", Arg_parser::no },
+ { 'w', "word-regexp", Arg_parser::no }, // grep GNU
+ { 'x', "line-regexp", Arg_parser::no }, // grep
+ { help_opt, "help", Arg_parser::no },
+ { verbose_opt, "verbose", Arg_parser::no },
+ { color_opt, "color", Arg_parser::maybe },
+ { bz2_opt, "bz2", Arg_parser::yes },
+ { gz_opt, "gz", Arg_parser::yes },
+ { lz_opt, "lz", Arg_parser::yes },
+ { xz_opt, "xz", Arg_parser::yes },
+ { 0 , 0, Arg_parser::no } };
const Arg_parser parser( argc, argv, options );
if( parser.error().size() ) // bad option
@@ -309,7 +312,8 @@ int main( const int argc, const char * const argv[] )
case 'o': grep_args.push_back( "-o" ); break;
case 'O': format_index = parse_format_type( arg ); break;
case 'q': grep_args.push_back( "-q" ); verbosity = -1; break;
- case 'r': recursive = true; break;
+ case 'r': recursive = 1; break;
+ case 'R': recursive = 2; break;
case 's': grep_args.push_back( "-s" ); no_messages = true; break;
case 'v': grep_args.push_back( "-v" ); break;
case 'V': show_version(); return 0;
@@ -349,16 +353,18 @@ int main( const int argc, const char * const argv[] )
for( ; argind < parser.arguments(); ++argind )
filenames.push_back( parser.argument( argind ) );
- if( filenames.empty() ) filenames.push_back( "-" );
+ if( filenames.empty() ) filenames.push_back( recursive ? "." : "-" );
if( show_name < 0 ) show_name = ( filenames.size() != 1 || recursive );
+ std::string input_filename;
int retval = 1;
bool error = false;
bool stdin_used = false;
while( next_filename( filenames, input_filename, error, recursive,
false, no_messages ) )
{
+ int infd;
if( input_filename.empty() )
{
if( stdin_used ) continue; else stdin_used = true;
@@ -377,13 +383,13 @@ int main( const int argc, const char * const argv[] )
list_mode, show_name );
if( tmp == 0 || ( tmp == 2 && retval == 1 ) ) retval = tmp;
- if( input_filename.size() ) { close( infd ); infd = -1; }
+ if( input_filename.size() ) close( infd );
if( retval == 0 && verbosity < 0 ) break;
}
if( std::fclose( stdout ) != 0 )
{
- show_error( "Can't close stdout", errno );
+ show_error( "Error closing stdout", errno );
error = true;
}
if( error && ( retval != 0 || verbosity >= 0 ) ) retval = 2;
diff --git a/ztest.cc b/ztest.cc
index 1145c90..5037fbf 100644
--- a/ztest.cc
+++ b/ztest.cc
@@ -1,5 +1,5 @@
/* Ztest - verify the integrity of compressed files
- Copyright (C) 2010-2018 Antonio Diaz Diaz.
+ Copyright (C) 2010-2019 Antonio Diaz Diaz.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -51,11 +51,12 @@ namespace {
void show_help()
{
std::printf( "Ztest verifies the integrity of the specified compressed files.\n"
- "Uncompressed files are ignored. If no files are specified, or if a file\n"
- "is specified as '-', the integrity of compressed data read from standard\n"
- "input is verified. Data read from standard input must be all in the same\n"
- "compression format.\n"
- "\nThe supported formats are bzip2, gzip, lzip and xz.\n"
+ "Uncompressed files are ignored. If a file is specified as '-', the\n"
+ "integrity of compressed data read from standard input is verified. Data\n"
+ "read from standard input must be all in the same compression format.\n"
+ "\nIf no files are specified, recursive searches examine the current\n"
+ "working directory, and nonrecursive searches read standard input.\n"
+ "\nThe formats supported are bzip2, gzip, lzip and xz.\n"
"\nNote that error detection in the xz format is broken. First, some xz\n"
"files lack integrity information. Second, not all xz decompressors can\n"
"verify the integrity of all xz files. Third, section 2.1.1.2 'Stream\n"
@@ -67,18 +68,19 @@ void show_help()
"problems (file not found, invalid flags, I/O errors, etc), 2 if any\n"
"compressed file is corrupt or invalid.\n"
"\nOptions:\n"
- " -h, --help display this help and exit\n"
- " -V, --version output version information and exit\n"
- " -M, --format=<list> process only the formats in <list>\n"
- " -N, --no-rcfile don't read runtime configuration file\n"
- " -O, --force-format=<fmt> force given format (bz2, gz, lz, xz)\n"
- " -q, --quiet suppress all messages\n"
- " -r, --recursive operate recursively on directories\n"
- " -v, --verbose be verbose (a 2nd -v gives more)\n"
- " --bz2=<command> set compressor and options for bzip2 format\n"
- " --gz=<command> set compressor and options for gzip format\n"
- " --lz=<command> set compressor and options for lzip format\n"
- " --xz=<command> set compressor and options for xz format\n" );
+ " -h, --help display this help and exit\n"
+ " -V, --version output version information and exit\n"
+ " -M, --format=<list> process only the formats in <list>\n"
+ " -N, --no-rcfile don't read runtime configuration file\n"
+ " -O, --force-format=<fmt> force given format (bz2, gz, lz, xz)\n"
+ " -q, --quiet suppress all messages\n"
+ " -r, --recursive operate recursively on directories\n"
+ " -R, --dereference-recursive recursively follow symbolic links\n"
+ " -v, --verbose be verbose (a 2nd -v gives more)\n"
+ " --bz2=<command> set compressor and options for bzip2 format\n"
+ " --gz=<command> set compressor and options for gzip format\n"
+ " --lz=<command> set compressor and options for lzip format\n"
+ " --xz=<command> set compressor and options for xz format\n" );
show_help_addr();
}
@@ -87,7 +89,7 @@ int open_instream( const std::string & input_filename )
{
const int infd = open( input_filename.c_str(), O_RDONLY | O_BINARY );
if( infd < 0 )
- show_error2( "Can't open input file", input_filename.c_str() );
+ show_file_error( input_filename.c_str(), "Can't open input file", errno );
return infd;
}
@@ -211,10 +213,8 @@ int ztest_file( const int infd, int format_index,
int main( const int argc, const char * const argv[] )
{
enum { bz2_opt = 256, gz_opt, lz_opt, xz_opt };
- int infd = -1;
int format_index = -1;
- bool recursive = false;
- std::string input_filename;
+ int recursive = 0; // 1 = '-r', 2 = '-R'
std::list< std::string > filenames;
std::vector< const char * > ztest_args; // args to ztest, maybe empty
invocation_name = argv[0];
@@ -222,19 +222,20 @@ int main( const int argc, const char * const argv[] )
const Arg_parser::Option options[] =
{
- { 'h', "help", Arg_parser::no },
- { 'M', "format", Arg_parser::yes },
- { 'N', "no-rcfile", Arg_parser::no },
- { 'O', "force-format", Arg_parser::yes },
- { 'q', "quiet", Arg_parser::no },
- { 'r', "recursive", Arg_parser::no },
- { 'v', "verbose", Arg_parser::no },
- { 'V', "version", Arg_parser::no },
- { bz2_opt, "bz2", Arg_parser::yes },
- { gz_opt, "gz", Arg_parser::yes },
- { lz_opt, "lz", Arg_parser::yes },
- { xz_opt, "xz", Arg_parser::yes },
- { 0 , 0, Arg_parser::no } };
+ { 'h', "help", Arg_parser::no },
+ { 'M', "format", Arg_parser::yes },
+ { 'N', "no-rcfile", Arg_parser::no },
+ { 'O', "force-format", Arg_parser::yes },
+ { 'q', "quiet", Arg_parser::no },
+ { 'r', "recursive", Arg_parser::no },
+ { 'R', "dereference-recursive", Arg_parser::no },
+ { 'v', "verbose", Arg_parser::no },
+ { 'V', "version", Arg_parser::no },
+ { bz2_opt, "bz2", Arg_parser::yes },
+ { gz_opt, "gz", Arg_parser::yes },
+ { lz_opt, "lz", Arg_parser::yes },
+ { xz_opt, "xz", Arg_parser::yes },
+ { 0 , 0, Arg_parser::no } };
const Arg_parser parser( argc, argv, options );
if( parser.error().size() ) // bad option
@@ -255,7 +256,8 @@ int main( const int argc, const char * const argv[] )
case 'N': break;
case 'O': format_index = parse_format_type( arg ); break;
case 'q': verbosity = -1; ztest_args.push_back( "-q" ); break;
- case 'r': recursive = true; break;
+ case 'r': recursive = 1; break;
+ case 'R': recursive = 2; break;
case 'v': if( verbosity < 4 ) ++verbosity;
ztest_args.push_back( "-v" ); break;
case 'V': show_version(); return 0;
@@ -275,13 +277,15 @@ int main( const int argc, const char * const argv[] )
for( ; argind < parser.arguments(); ++argind )
filenames.push_back( parser.argument( argind ) );
- if( filenames.empty() ) filenames.push_back( "-" );
+ if( filenames.empty() ) filenames.push_back( recursive ? "." : "-" );
+ std::string input_filename;
int retval = 0;
bool error = false;
bool stdin_used = false;
while( next_filename( filenames, input_filename, error, recursive ) )
{
+ int infd;
if( input_filename.empty() )
{
if( stdin_used ) continue; else stdin_used = true;
@@ -299,12 +303,12 @@ int main( const int argc, const char * const argv[] )
else tmp = ztest_file( infd, format_index, input_filename, ztest_args );
if( tmp > retval ) retval = tmp;
- if( input_filename.size() ) { close( infd ); infd = -1; }
+ if( input_filename.size() ) close( infd );
}
- if( std::fclose( stdout ) != 0 )
+ if( std::fclose( stdout ) != 0 ) // in case decompressor writes to stdout
{
- show_error( "Can't close stdout", errno );
+ show_error( "Error closing stdout", errno );
error = true;
}
if( error && retval == 0 ) retval = 1;
diff --git a/zupdate.cc b/zupdate.cc
index dcba0ec..dec65f4 100644
--- a/zupdate.cc
+++ b/zupdate.cc
@@ -1,5 +1,5 @@
/* Zupdate - recompress bzip2, gzip, xz files to lzip format
- Copyright (C) 2013-2018 Antonio Diaz Diaz.
+ Copyright (C) 2013-2019 Antonio Diaz Diaz.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -51,14 +51,15 @@ namespace {
void show_help()
{
- std::printf( "Zupdate recompresses files from bzip2, gzip, and xz formats to lzip format.\n"
- "The originals are compared with the new files and then deleted.\n"
+ std::printf( "Zupdate recompresses files from bzip2, gzip, and xz formats to lzip\n"
+ "format. Each original is compared with the new file and then deleted.\n"
"Only regular files with standard file name extensions are recompressed,\n"
- "other files are ignored.\n"
- "Compressed files are decompressed and then recompressed on the fly; no\n"
- "temporary files are created.\n"
- "The lzip format is chosen as destination because it is by far the most\n"
- "appropriate for long-term data archiving.\n"
+ "other files are ignored. Compressed files are decompressed and then\n"
+ "recompressed on the fly; no temporary files are created. The lzip format\n"
+ "is chosen as destination because it is the most appropriate for\n"
+ "long-term data archiving.\n"
+ "\nIf no files are specified, recursive searches examine the current\n"
+ "working directory, and nonrecursive searches do nothing.\n"
"\nIf the lzip compressed version of a file already exists, the file is\n"
"skipped unless the '--force' option is given. In this case, if the\n"
"comparison with the existing lzip version fails, an error is returned\n"
@@ -70,21 +71,22 @@ void show_help()
"recompressed (if needed), compared and deleted (if requested). Non-zero\n"
"otherwise.\n"
"\nOptions:\n"
- " -h, --help display this help and exit\n"
- " -V, --version output version information and exit\n"
- " -f, --force don't skip a file even if the .lz exists\n"
- " -k, --keep keep (don't delete) input files\n"
- " -l, --lzip-verbose pass a -v option to the lzip compressor\n"
- " -M, --format=<list> process only the formats in <list>\n"
- " -N, --no-rcfile don't read runtime configuration file\n"
- " -q, --quiet suppress all messages\n"
- " -r, --recursive operate recursively on directories\n"
- " -v, --verbose be verbose (a 2nd -v gives more)\n"
- " -0 .. -9 set compression level [default 9]\n"
- " --bz2=<command> set compressor and options for bzip2 format\n"
- " --gz=<command> set compressor and options for gzip format\n"
- " --lz=<command> set compressor and options for lzip format\n"
- " --xz=<command> set compressor and options for xz format\n" );
+ " -h, --help display this help and exit\n"
+ " -V, --version output version information and exit\n"
+ " -f, --force don't skip a file even if the .lz exists\n"
+ " -k, --keep keep (don't delete) input files\n"
+ " -l, --lzip-verbose pass a -v option to the lzip compressor\n"
+ " -M, --format=<list> process only the formats in <list>\n"
+ " -N, --no-rcfile don't read runtime configuration file\n"
+ " -q, --quiet suppress all messages\n"
+ " -r, --recursive operate recursively on directories\n"
+ " -R, --dereference-recursive recursively follow symbolic links\n"
+ " -v, --verbose be verbose (a 2nd -v gives more)\n"
+ " -0 .. -9 set compression level [default 9]\n"
+ " --bz2=<command> set compressor and options for bzip2 format\n"
+ " --gz=<command> set compressor and options for gzip format\n"
+ " --lz=<command> set compressor and options for lzip format\n"
+ " --xz=<command> set compressor and options for xz format\n" );
show_help_addr();
}
@@ -293,43 +295,43 @@ int zupdate_file( const std::string & name, const char * const lzip_name,
int main( const int argc, const char * const argv[] )
{
enum { bz2_opt = 256, gz_opt, lz_opt, xz_opt };
- std::string input_filename;
+ int recursive = 0; // 1 = '-r', 2 = '-R'
std::list< std::string > filenames;
std::vector< std::string > lzip_args2; // args to lzip, maybe empty
bool force = false;
bool keep_input_files = false;
bool no_rcfile = false;
- bool recursive = false;
invocation_name = argv[0];
program_name = "zupdate";
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 },
- { 'f', "force", Arg_parser::no },
- { 'h', "help", Arg_parser::no },
- { 'k', "keep", Arg_parser::no },
- { 'l', "lzip-verbose", Arg_parser::no },
- { 'M', "format", Arg_parser::yes },
- { 'N', "no-rcfile", Arg_parser::no },
- { 'q', "quiet", Arg_parser::no },
- { 'r', "recursive", Arg_parser::no },
- { 'v', "verbose", Arg_parser::no },
- { 'V', "version", Arg_parser::no },
- { bz2_opt, "bz2", Arg_parser::yes },
- { gz_opt, "gz", Arg_parser::yes },
- { lz_opt, "lz", Arg_parser::yes },
- { xz_opt, "xz", Arg_parser::yes },
- { 0 , 0, Arg_parser::no } };
+ { '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 },
+ { 'f', "force", Arg_parser::no },
+ { 'h', "help", Arg_parser::no },
+ { 'k', "keep", Arg_parser::no },
+ { 'l', "lzip-verbose", Arg_parser::no },
+ { 'M', "format", Arg_parser::yes },
+ { 'N', "no-rcfile", Arg_parser::no },
+ { 'q', "quiet", Arg_parser::no },
+ { 'r', "recursive", Arg_parser::no },
+ { 'R', "dereference-recursive", Arg_parser::no },
+ { 'v', "verbose", Arg_parser::no },
+ { 'V', "version", Arg_parser::no },
+ { bz2_opt, "bz2", Arg_parser::yes },
+ { gz_opt, "gz", Arg_parser::yes },
+ { lz_opt, "lz", Arg_parser::yes },
+ { xz_opt, "xz", Arg_parser::yes },
+ { 0 , 0, Arg_parser::no } };
const Arg_parser parser( argc, argv, options );
if( parser.error().size() ) // bad option
@@ -355,7 +357,8 @@ int main( const int argc, const char * const argv[] )
case 'M': parse_format_list( arg ); break;
case 'N': no_rcfile = true; break;
case 'q': verbosity = -1; lzip_args2.push_back( "-q" ); break;
- case 'r': recursive = true; break;
+ case 'r': recursive = 1; break;
+ case 'R': recursive = 2; break;
case 'v': if( verbosity < 4 ) ++verbosity; break;
case 'V': show_version(); return 0;
case bz2_opt: parse_compressor( arg, fmt_bz2, 1 ); break;
@@ -378,6 +381,9 @@ int main( const int argc, const char * const argv[] )
for( ; argind < parser.arguments(); ++argind )
filenames.push_back( parser.argument( argind ) );
+ if( filenames.empty() && recursive ) filenames.push_back( "." );
+
+ std::string input_filename;
int retval = 0;
bool error = false;
while( next_filename( filenames, input_filename, error, recursive, true ) )
diff --git a/zutils.cc b/zutils.cc
index 2b11532..74b7351 100644
--- a/zutils.cc
+++ b/zutils.cc
@@ -1,5 +1,5 @@
/* Zutils - Utilities dealing with compressed files
- Copyright (C) 2009-2018 Antonio Diaz Diaz.
+ Copyright (C) 2009-2019 Antonio Diaz Diaz.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -143,16 +143,17 @@ bool good_status( const Children & children, const bool finished )
const pid_t pid = children.pid[i];
if( pid )
{
- const char * const msg =
+ const char * const name =
( i & 1 ) ? children.compressor_name : "data feeder";
if( !finished )
{
- const int tmp = child_status( pid, msg );
- if( tmp < 0 ) kill( pid, SIGTERM ); // child not terminated
+ const int tmp = child_status( pid, name );
+ if( tmp < 0 ) // child not terminated
+ { kill( pid, SIGTERM ); wait_for_child( pid, name ); }
else if( tmp != 0 ) error = true; // child status != 0
}
else
- if( wait_for_child( pid, msg ) != 0 ) error = true;
+ if( wait_for_child( pid, name ) != 0 ) error = true;
}
}
return !error;
diff --git a/zutils.h b/zutils.h
index 37404ba..f05be62 100644
--- a/zutils.h
+++ b/zutils.h
@@ -1,5 +1,5 @@
/* Zutils - Utilities dealing with compressed files
- Copyright (C) 2009-2018 Antonio Diaz Diaz.
+ Copyright (C) 2009-2019 Antonio Diaz Diaz.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by