diff --git a/gnu/usr.bin/gzip/algorithm.doc b/gnu/usr.bin/gzip/algorithm.doc index 24f7619ab697..596faac18ab3 100644 --- a/gnu/usr.bin/gzip/algorithm.doc +++ b/gnu/usr.bin/gzip/algorithm.doc @@ -1,164 +1,164 @@ 1. Algorithm The deflation algorithm used by zip and gzip is a variation of LZ77 (Lempel-Ziv 1977, see reference below). It finds duplicated strings in the input data. The second occurrence of a string is replaced by a pointer to the previous string, in the form of a pair (distance, length). Distances are limited to 32K bytes, and lengths are limited to 258 bytes. When a string does not occur anywhere in the previous 32K bytes, it is emitted as a sequence of literal bytes. (In this description, 'string' must be taken as an arbitrary sequence of bytes, and is not restricted to printable characters.) Literals or match lengths are compressed with one Huffman tree, and match distances are compressed with another tree. The trees are stored in a compact form at the start of each block. The blocks can have any size (except that the compressed data for one block must fit in available memory). A block is terminated when zip determines that it would be useful to start another block with fresh trees. (This is somewhat similar to compress.) Duplicated strings are found using a hash table. All input strings of length 3 are inserted in the hash table. A hash index is computed for the next 3 bytes. If the hash chain for this index is not empty, all strings in the chain are compared with the current input string, and the longest match is selected. The hash chains are searched starting with the most recent strings, to favor small distances and thus take advantage of the Huffman encoding. The hash chains are singly linked. There are no deletions from the hash chains, the algorithm simply discards matches that are too old. To avoid a worst-case situation, very long hash chains are arbitrarily truncated at a certain length, determined by a runtime option (zip -1 to -9). So zip does not always find the longest possible match but generally finds a match which is long enough. zip also defers the selection of matches with a lazy evaluation mechanism. After a match of length N has been found, zip searches for a longer match at the next input byte. If a longer match is found, the previous match is truncated to a length of one (thus producing a single literal byte) and the longer match is emitted afterwards. Otherwise, the original match is kept, and the next match search is attempted only N steps later. The lazy match evaluation is also subject to a runtime parameter. If the current match is long enough, zip reduces the search for a longer match, thus speeding up the whole process. If compression ratio is more important than speed, zip attempts a complete second search even if the first match is already long enough. -The lazy match evaluation is no performed for the fastest compression +The lazy match evaluation is not performed for the fastest compression modes (speed options -1 to -3). For these fast modes, new strings are inserted in the hash table only when no match was found, or when the match is not too long. This degrades the compression ratio but saves time since there are both fewer insertions and fewer searches. 2. gzip file format The pkzip format imposes a lot of overhead in various headers, which are useful for an archiver but not necessary when only one file is compressed. gzip uses a much simpler structure. Numbers are in little endian format, and bit 0 is the least significant bit. A gzip file is a sequence of compressed members. Each member has the following structure: 2 bytes magic header 0x1f, 0x8b (\037 \213) 1 byte compression method (0..7 reserved, 8 = deflate) 1 byte flags bit 0 set: file probably ascii text bit 1 set: continuation of multi-part gzip file bit 2 set: extra field present bit 3 set: original file name present bit 4 set: file comment present bit 5 set: file is encrypted bit 6,7: reserved 4 bytes file modification time in Unix format 1 byte extra flags (depend on compression method) 1 byte operating system on which compression took place 2 bytes optional part number (second part=1) 2 bytes optional extra field length ? bytes optional extra field ? bytes optional original file name, zero terminated ? bytes optional file comment, zero terminated 12 bytes optional encryption header ? bytes compressed data 4 bytes crc32 4 bytes uncompressed input size modulo 2^32 The format was designed to allow single pass compression without any backwards seek, and without a priori knowledge of the uncompressed input size or the available size on the output media. If input does not come from a regular disk file, the file modification time is set to the time at which compression started. The time stamp is useful mainly when one gzip file is transferred over a network. In this case it would not help to keep ownership attributes. In the local case, the ownership attributes are preserved by gzip when compressing/decompressing the file. A time stamp of zero is ignored. Bit 0 in the flags is only an optional indication, which can be set by a small lookahead in the input data. In case of doubt, the flag is cleared indicating binary data. For systems which have different file formats for ascii text and binary data, the decompressor can use the flag to choose the appropriate format. The extra field, if present, must consist of one or more subfields, each with the following format: subfield id : 2 bytes subfield size : 2 bytes (little-endian format) subfield data The subfield id can consist of two letters with some mnemonic value. Please send any such id to jloup@chorus.fr. Ids with a zero second byte are reserved for future use. The following ids are defined: Ap (0x41, 0x70) : Apollo file type information The subfield size is the size of the subfield data and does not include the id and the size itself. The field 'extra field length' is the total size of the extra field, including subfield ids and sizes. It must be possible to detect the end of the compressed data with any compression format, regardless of the actual size of the compressed data. If the compressed data cannot fit in one file (in particular for diskettes), each part starts with a header as described above, but only the last part has the crc32 and uncompressed size. A decompressor may prompt for additional data for multipart compressed files. It is desirable but not mandatory that multiple parts be extractable independently so that partial data can be recovered if one of the parts is damaged. This is possible only if no compression state is kept from one part to the other. The compression-type dependent flags can indicate this. If the file being compressed is on a file system with case insensitive names, the original name field must be forced to lower case. There is no original file name if the data was compressed from standard input. Compression is always performed, even if the compressed file is slightly larger than the original. The worst case expansion is a few bytes for the gzip file header, plus 5 bytes every 32K block, or an expansion ratio of 0.015% for large files. Note that the actual number of used disk blocks almost never increases. The encryption is that of zip 1.9. For the encryption check, the last byte of the decoded encryption header must be zero. The time stamp of an encrypted file might be set to zero to avoid giving a clue about the construction of the random header. Jean-loup Gailly jloup@chorus.fr References: [LZ77] Ziv J., Lempel A., "A Universal Algorithm for Sequential Data Compression", IEEE Transactions on Information Theory", Vol. 23, No. 3, pp. 337-343. APPNOTE.TXT documentation file in PKZIP 1.93a. It is available by ftp in ftp.cso.uiuc.edu:/pc/exec-pc/pkz193a.exe [128.174.5.59] Use "unzip pkz193a.exe APPNOTE.TXT" to extract. diff --git a/gnu/usr.bin/gzip/gzexe b/gnu/usr.bin/gzip/gzexe index 27b697b21c9e..b6107261fe35 100644 --- a/gnu/usr.bin/gzip/gzexe +++ b/gnu/usr.bin/gzip/gzexe @@ -1,150 +1,158 @@ #!/bin/sh # gzexe: compressor for Unix executables. # Use this only for binaries that you do not use frequently. # # The compressed version is a shell script which decompresses itself after # skipping $skip lines of shell commands. We try invoking the compressed # executable with the original name (for programs looking at their name). # We also try to retain the original file permissions on the compressed file. # For safety reasons, gzexe will not create setuid or setgid shell scripts. # WARNING: the first line of this file must be either : or #!/bin/sh # The : is required for some old versions of csh. # On Ultrix, /bin/sh is too buggy, change the first line to: #!/bin/sh5 +# +# $Id$ x=`basename $0` if test $# = 0; then echo compress executables. original file foo is renamed to foo~ echo usage: ${x} [-d] files... echo " -d decompress the executables" exit 1 fi -tmp=gz$$ +tmp=`/usr/bin/mktemp gzXXXXXXXXXX` || exit 1 trap "rm -f $tmp; exit 1" 1 2 3 5 10 13 15 decomp=0 res=0 test "$x" = "ungzexe" && decomp=1 if test "x$1" = "x-d"; then decomp=1 shift fi -echo hi > zfoo1$$ -echo hi > zfoo2$$ -if test -z "`(${CPMOD-cpmod} zfoo1$$ zfoo2$$) 2>&1`"; then +zfoo1=`/usr/bin/mktemp zfoo1XXXXXXXXXX` || exit 1 +zfoo2=`/usr/bin/mktemp zfoo2XXXXXXXXXX` || exit 1 +echo hi > $zfoo1 +echo hi > $zfoo2 +if test -z "`(${CPMOD-cpmod} $zfoo1 $zfoo2) 2>&1`"; then cpmod=${CPMOD-cpmod} fi -rm -f zfoo[12]$$ +rm -f $zfoo1 $zfoo2 tail="" IFS="${IFS= }"; saveifs="$IFS"; IFS="${IFS}:" for dir in $PATH; do test -z "$dir" && dir=. if test -f $dir/tail; then tail="$dir/tail" break fi done IFS="$saveifs" if test -z "$tail"; then echo cannot find tail exit 1 fi for i do if test ! -f "$i" ; then echo ${x}: $i not a file res=1 continue fi if test $decomp -eq 0; then if sed -e 1d -e 2q "$i" | grep "^skip=[0-9]*$" >/dev/null; then echo "${x}: $i is already gzexe'd" continue fi fi if ls -l "$i" | grep '^...[sS]' > /dev/null; then echo "${x}: $i has setuid permission, unchanged" continue fi if ls -l "$i" | grep '^......[sS]' > /dev/null; then echo "${x}: $i has setgid permission, unchanged" continue fi case "`basename $i`" in - sh | gzip | tail | chmod | ln | sleep | rm) + sh | gzip | tail | chmod | ln | sleep | rm | mktemp) echo "${x}: $i would depend on itself"; continue ;; esac if test -z "$cpmod"; then cp -p "$i" $tmp 2>/dev/null || cp "$i" $tmp if test -w $tmp 2>/dev/null; then writable=1 else writable=0 chmod u+w $tmp 2>/dev/null fi fi if test $decomp -eq 0; then sed 1q $0 > $tmp sed "s|^if tail|if $tail|" >> $tmp <<'EOF' -skip=18 -if tail +$skip $0 | gzip -cd > /tmp/gztmp$$; then - chmod 700 /tmp/gztmp$$ +skip=22 +gztmp=`/usr/bin/mktemp /tmp/gztmpXXXXXXXXXX` || exit 1 +if tail +$skip $0 | gzip -cd > $gztmp; then + chmod 700 $gztmp prog="`echo $0 | sed 's|^.*/||'`" - if /bin/ln /tmp/gztmp$$ "/tmp/$prog" 2>/dev/null; then - trap '/bin/rm -f /tmp/gztmp$$ "/tmp/$prog"; exit $res' 0 - (/bin/sleep 5; /bin/rm -f /tmp/gztmp$$ "/tmp/$prog") 2>/dev/null & + progtmp=`/usr/bin/mktemp /tmp/${prog}XXXXXXXXXX` || exit 1 + if /bin/ln $gztmp $progtmp 2>/dev/null; then + trap '/bin/rm -f $gztmp $progtmp; exit $res' 0 + (/bin/sleep 5; /bin/rm -f $gztmp $progtmp) 2>/dev/null & /tmp/"$prog" ${1+"$@"}; res=$? else - trap '/bin/rm -f /tmp/gztmp$$; exit $res' 0 - (/bin/sleep 5; /bin/rm -f /tmp/gztmp$$) 2>/dev/null & - /tmp/gztmp$$ ${1+"$@"}; res=$? + trap '/bin/rm -f $gztmp exit $res' 0 + (/bin/sleep 5; /bin/rm -f $gztmp) 2>/dev/null & + $gztmp ${1+"$@"}; res=$? fi else - echo Cannot decompress $0; exit 1 + echo Cannot decompress $0 + rm -f $gztmp + exit 1 fi; exit $res EOF gzip -cv9 "$i" >> $tmp || { /bin/rm -f $tmp echo ${x}: compression not possible for $i, file unchanged. res=1 continue } else # decompression skip=18 if sed -e 1d -e 2q "$i" | grep "^skip=[0-9]*$" >/dev/null; then eval `sed -e 1d -e 2q "$i"` fi if tail +$skip "$i" | gzip -cd > $tmp; then : else echo ${x}: $i probably not in gzexe format, file unchanged. res=1 continue fi fi rm -f "$i~" mv "$i" "$i~" || { echo ${x}: cannot backup $i as $i~ rm -f $tmp res=1 continue } mv $tmp "$i" || cp -p $tmp "$i" 2>/dev/null || cp $tmp "$i" || { echo ${x}: cannot create $i rm -f $tmp res=1 continue } rm -f $tmp if test -n "$cpmod"; then $cpmod "$i~" "$i" 2>/dev/null elif test $writable -eq 0; then chmod u-w $i 2>/dev/null fi done exit $res diff --git a/gnu/usr.bin/gzip/gzip.c b/gnu/usr.bin/gzip/gzip.c index 7a666baba17e..08803e3c1395 100644 --- a/gnu/usr.bin/gzip/gzip.c +++ b/gnu/usr.bin/gzip/gzip.c @@ -1,1749 +1,1755 @@ /* gzip (GNU zip) -- compress files with zip algorithm and 'compress' interface * Copyright (C) 1992-1993 Jean-loup Gailly * The unzip code was written and put in the public domain by Mark Adler. * Portions of the lzw code are derived from the public domain 'compress' * written by Spencer Thomas, Joe Orost, James Woods, Jim McKie, Steve Davies, * Ken Turkowski, Dave Mack and Peter Jannesen. * * See the license_msg below and the file COPYING for the software license. * See the file algorithm.doc for the compression algorithms and file formats. */ static char *license_msg[] = { " Copyright (C) 1992-1993 Jean-loup Gailly", " This program is free software; you can redistribute it and/or modify", " it under the terms of the GNU General Public License as published by", " the Free Software Foundation; either version 2, or (at your option)", " any later version.", "", " This program is distributed in the hope that it will be useful,", " but WITHOUT ANY WARRANTY; without even the implied warranty of", " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the", " GNU General Public License for more details.", "", " You should have received a copy of the GNU General Public License", " along with this program; if not, write to the Free Software", " Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.", 0}; /* Compress files with zip algorithm and 'compress' interface. * See usage() and help() functions below for all options. * Outputs: * file.gz: compressed file with same mode, owner, and utimes * or stdout with -c option or if stdin used as input. * If the output file name had to be truncated, the original name is kept * in the compressed file. * On MSDOS, file.tmp -> file.tmz. On VMS, file.tmp -> file.tmp-gz. * * Using gz on MSDOS would create too many file name conflicts. For * example, foo.txt -> foo.tgz (.tgz must be reserved as shorthand for * tar.gz). Similarly, foo.dir and foo.doc would both be mapped to foo.dgz. * I also considered 12345678.txt -> 12345txt.gz but this truncates the name * too heavily. There is no ideal solution given the MSDOS 8+3 limitation. * * For the meaning of all compilation flags, see comments in Makefile.in. */ #ifdef RCSID -static char rcsid[] = "$Id: gzip.c,v 1.7 1997/03/15 22:43:58 guido Exp $"; +static char rcsid[] = "$Id: gzip.c,v 1.4 1998/11/22 20:03:21 deraadt Exp $"; #endif #include #include #include #include #include #include "tailor.h" #include "gzip.h" #include "lzw.h" #include "revision.h" #include "getopt.h" /* configuration */ #ifdef NO_TIME_H # include #else # include #endif #ifndef NO_FCNTL_H # include #endif #ifdef HAVE_UNISTD_H # include #endif #if defined(STDC_HEADERS) || !defined(NO_STDLIB_H) # include #else extern int errno; #endif #if defined(DIRENT) # include typedef struct dirent dir_type; # define NLENGTH(dirent) ((int)strlen((dirent)->d_name)) # define DIR_OPT "DIRENT" #else # define NLENGTH(dirent) ((dirent)->d_namlen) # ifdef SYSDIR # include typedef struct direct dir_type; # define DIR_OPT "SYSDIR" # else # ifdef SYSNDIR # include typedef struct direct dir_type; # define DIR_OPT "SYSNDIR" # else # ifdef NDIR # include typedef struct direct dir_type; # define DIR_OPT "NDIR" # else # define NO_DIR # define DIR_OPT "NO_DIR" # endif # endif # endif #endif #ifndef NO_UTIME # ifndef NO_UTIME_H # include # define TIME_OPT "UTIME" # else # ifdef HAVE_SYS_UTIME_H # include # define TIME_OPT "SYS_UTIME" # else struct utimbuf { time_t actime; time_t modtime; }; # define TIME_OPT "" # endif # endif #else # define TIME_OPT "NO_UTIME" #endif #if !defined(S_ISDIR) && defined(S_IFDIR) # define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) #endif #if !defined(S_ISREG) && defined(S_IFREG) # define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) #endif typedef RETSIGTYPE (*sig_type) OF((int)); #ifndef O_BINARY # define O_BINARY 0 /* creation mode for open() */ #endif #ifndef O_CREAT /* Pure BSD system? */ # include # ifndef O_CREAT # define O_CREAT FCREAT # endif # ifndef O_EXCL # define O_EXCL FEXCL # endif #endif #ifndef S_IRUSR # define S_IRUSR 0400 #endif #ifndef S_IWUSR # define S_IWUSR 0200 #endif #define RW_USER (S_IRUSR | S_IWUSR) /* creation mode for open() */ #ifndef MAX_PATH_LEN # define MAX_PATH_LEN 1024 /* max pathname length */ #endif #ifndef SEEK_END # define SEEK_END 2 #endif #ifdef NO_OFF_T typedef long off_t; off_t lseek OF((int fd, off_t offset, int whence)); #endif /* Separator for file name parts (see shorten_name()) */ #ifdef NO_MULTIPLE_DOTS # define PART_SEP "-" #else # define PART_SEP "." #endif /* global buffers */ DECLARE(uch, inbuf, INBUFSIZ +INBUF_EXTRA); DECLARE(uch, outbuf, OUTBUFSIZ+OUTBUF_EXTRA); DECLARE(ush, d_buf, DIST_BUFSIZE); DECLARE(uch, window, 2L*WSIZE); #ifndef MAXSEG_64K DECLARE(ush, tab_prefix, 1L< 4 && strequ(progname+proglen-4, ".exe")) { progname[proglen-4] = '\0'; } /* Add options in GZIP environment variable if there is one */ env = add_envopt(&argc, &argv, OPTIONS_VAR); if (env != NULL) args = argv; foreground = signal(SIGINT, SIG_IGN) != SIG_IGN; if (foreground) { (void) signal (SIGINT, (sig_type)abort_gzip); } #ifdef SIGTERM if (signal(SIGTERM, SIG_IGN) != SIG_IGN) { (void) signal(SIGTERM, (sig_type)abort_gzip); } #endif #ifdef SIGHUP if (signal(SIGHUP, SIG_IGN) != SIG_IGN) { (void) signal(SIGHUP, (sig_type)abort_gzip); } #endif #ifndef GNU_STANDARD /* For compatibility with old compress, use program name as an option. * If you compile with -DGNU_STANDARD, this program will behave as * gzip even if it is invoked under the name gunzip or zcat. * * Systems which do not support links can still use -d or -dc. * Ignore an .exe extension for MSDOS, OS/2 and VMS. */ if ( strncmp(progname, "un", 2) == 0 /* ungzip, uncompress */ || strncmp(progname, "gun", 3) == 0) { /* gunzip */ decompress = 1; } else if (strequ(progname+1, "cat") /* zcat, pcat, gcat */ || strequ(progname, "gzcat")) { /* gzcat */ decompress = to_stdout = 1; } #endif strncpy(z_suffix, Z_SUFFIX, sizeof(z_suffix)-1); z_len = strlen(z_suffix); while ((optc = getopt_long (argc, argv, "ab:cdfhH?lLmMnNqrS:tvVZ123456789", longopts, (int *)0)) != EOF) { switch (optc) { case 'a': ascii = 1; break; case 'b': maxbits = atoi(optarg); break; case 'c': to_stdout = 1; break; case 'd': decompress = 1; break; case 'f': force++; break; case 'h': case 'H': case '?': help(); do_exit(OK); break; case 'l': list = decompress = to_stdout = 1; break; case 'L': license(); do_exit(OK); break; case 'm': /* undocumented, may change later */ no_time = 1; break; case 'M': /* undocumented, may change later */ no_time = 0; break; case 'n': no_name = no_time = 1; break; case 'N': no_name = no_time = 0; break; case 'q': quiet = 1; verbose = 0; break; case 'r': #ifdef NO_DIR fprintf(stderr, "%s: -r not supported on this system\n", progname); usage(); do_exit(ERROR); break; #else recursive = 1; break; #endif case 'S': #ifdef NO_MULTIPLE_DOTS if (*optarg == '.') optarg++; #endif z_len = strlen(optarg); - strcpy(z_suffix, optarg); + if (z_len > sizeof(z_suffix)-1) { + fprintf(stderr, "%s: -S suffix too long\n", progname); + usage(); + do_exit(ERROR); + } + strncpy(z_suffix, optarg, sizeof z_suffix-1); + z_suffix[sizeof z_suffix-1] = '\0'; break; case 't': test = decompress = to_stdout = 1; break; case 'v': verbose++; quiet = 0; break; case 'V': version(); do_exit(OK); break; case 'Z': #ifdef LZW do_lzw = 1; break; #else fprintf(stderr, "%s: -Z not supported in this version\n", progname); usage(); do_exit(ERROR); break; #endif case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': level = optc - '0'; break; default: /* Error message already emitted by getopt_long. */ usage(); do_exit(ERROR); } } /* loop on all arguments */ /* By default, save name and timestamp on compression but do not * restore them on decompression. */ if (no_time < 0) no_time = decompress; if (no_name < 0) no_name = decompress; file_count = argc - optind; #if O_BINARY #else if (ascii && !quiet) { fprintf(stderr, "%s: option --ascii ignored on this system\n", progname); } #endif if ((z_len == 0 && !decompress) || z_len > MAX_SUFFIX) { fprintf(stderr, "%s: incorrect suffix '%s'\n", progname, optarg); do_exit(ERROR); } if (do_lzw && !decompress) work = lzw; /* Allocate all global buffers (for DYN_ALLOC option) */ ALLOC(uch, inbuf, INBUFSIZ +INBUF_EXTRA); ALLOC(uch, outbuf, OUTBUFSIZ+OUTBUF_EXTRA); ALLOC(ush, d_buf, DIST_BUFSIZE); ALLOC(uch, window, 2L*WSIZE); #ifndef MAXSEG_64K ALLOC(ush, tab_prefix, 1L< 1) { do_list(-1, -1); /* print totals */ } do_exit(exit_code); return exit_code; /* just to avoid lint warning */ } /* ======================================================================== * Compress or decompress stdin */ local void treat_stdin() { if (!force && !list && isatty(fileno((FILE *)(decompress ? stdin : stdout)))) { /* Do not send compressed data to the terminal or read it from * the terminal. We get here when user invoked the program * without parameters, so be helpful. According to the GNU standards: * * If there is one behavior you think is most useful when the output * is to a terminal, and another that you think is most useful when * the output is a file or a pipe, then it is usually best to make * the default behavior the one that is useful with output to a * terminal, and have an option for the other behavior. * * Here we use the --force option to get the other behavior. */ fprintf(stderr, "%s: compressed data not %s a terminal. Use -f to force %scompression.\n", progname, decompress ? "read from" : "written to", decompress ? "de" : ""); fprintf(stderr,"For help, type: %s -h\n", progname); do_exit(ERROR); } if (decompress || !ascii) { SET_BINARY_MODE(fileno(stdin)); } if (!test && !list && (!decompress || !ascii)) { SET_BINARY_MODE(fileno(stdout)); } strcpy(ifname, "stdin"); strcpy(ofname, "stdout"); /* Get the time stamp on the input file. */ time_stamp = 0; /* time unknown by default */ #ifndef NO_STDIN_FSTAT if (list || !no_time) { if (fstat(fileno(stdin), &istat) != 0) { error("fstat(stdin)"); } # ifdef NO_PIPE_TIMESTAMP if (S_ISREG(istat.st_mode)) # endif time_stamp = istat.st_mtime; #endif /* NO_STDIN_FSTAT */ } ifile_size = -1L; /* convention for unknown size */ clear_bufs(); /* clear input and output buffers */ to_stdout = 1; part_nb = 0; if (decompress) { method = get_method(ifd); if (method < 0) { do_exit(exit_code); /* error message already emitted */ } } if (list) { do_list(ifd, method); return; } /* Actually do the compression/decompression. Loop over zipped members. */ for (;;) { if ((*work)(fileno(stdin), fileno(stdout)) != OK) return; if (!decompress || last_member || inptr == insize) break; /* end of file */ method = get_method(ifd); if (method < 0) return; /* error message already emitted */ bytes_out = 0; /* required for length check */ } if (verbose) { if (test) { fprintf(stderr, " OK\n"); } else if (!decompress) { display_ratio(bytes_in-(bytes_out-header_bytes), bytes_in, stderr); fprintf(stderr, "\n"); #ifdef DISPLAY_STDIN_RATIO } else { display_ratio(bytes_out-(bytes_in-header_bytes), bytes_out,stderr); fprintf(stderr, "\n"); #endif } } } /* ======================================================================== * Compress or decompress the given file */ local void treat_file(iname) char *iname; { /* Accept "-" as synonym for stdin */ if (strequ(iname, "-")) { int cflag = to_stdout; treat_stdin(); to_stdout = cflag; return; } /* Check if the input file is present, set ifname and istat: */ if (get_istat(iname, &istat) != OK) return; /* If the input name is that of a directory, recurse or ignore: */ if (S_ISDIR(istat.st_mode)) { #ifndef NO_DIR if (recursive) { struct stat st; st = istat; treat_dir(iname); /* Warning: ifname is now garbage */ # ifndef NO_UTIME reset_times (iname, &st); # endif } else #endif WARN((stderr,"%s: %s is a directory -- ignored\n", progname, ifname)); return; } if (!S_ISREG(istat.st_mode)) { WARN((stderr, "%s: %s is not a directory or a regular file - ignored\n", progname, ifname)); return; } if (istat.st_nlink > 1 && !to_stdout && !force) { WARN((stderr, "%s: %s has %d other link%c -- unchanged\n", progname, ifname, (int)istat.st_nlink - 1, istat.st_nlink > 2 ? 's' : ' ')); return; } ifile_size = istat.st_size; time_stamp = no_time && !list ? 0 : istat.st_mtime; /* Generate output file name. For -r and (-t or -l), skip files * without a valid gzip suffix (check done in make_ofname). */ if (to_stdout && !list && !test) { strcpy(ofname, "stdout"); } else if (make_ofname() != OK) { return; } /* Open the input file and determine compression method. The mode * parameter is ignored but required by some systems (VMS) and forbidden * on other systems (MacOS). */ ifd = OPEN(ifname, ascii && !decompress ? O_RDONLY : O_RDONLY | O_BINARY, RW_USER); if (ifd == -1) { fprintf(stderr, "%s: ", progname); perror(ifname); exit_code = ERROR; return; } clear_bufs(); /* clear input and output buffers */ part_nb = 0; if (decompress) { method = get_method(ifd); /* updates ofname if original given */ if (method < 0) { close(ifd); return; /* error message already emitted */ } } if (list) { do_list(ifd, method); close(ifd); return; } /* If compressing to a file, check if ofname is not ambiguous * because the operating system truncates names. Otherwise, generate * a new ofname and save the original name in the compressed file. */ if (to_stdout) { ofd = fileno(stdout); /* keep remove_ofname as zero */ } else { if (create_outfile() != OK) return; if (!decompress && save_orig_name && !verbose && !quiet) { fprintf(stderr, "%s: %s compressed to %s\n", progname, ifname, ofname); } } /* Keep the name even if not truncated except with --no-name: */ if (!save_orig_name) save_orig_name = !no_name; if (verbose) { fprintf(stderr, "%s:\t%s", ifname, (int)strlen(ifname) >= 15 ? "" : ((int)strlen(ifname) >= 7 ? "\t" : "\t\t")); } /* Actually do the compression/decompression. Loop over zipped members. */ for (;;) { if ((*work)(ifd, ofd) != OK) { method = -1; /* force cleanup */ break; } if (!decompress || last_member || inptr == insize) break; /* end of file */ method = get_method(ifd); if (method < 0) break; /* error message already emitted */ bytes_out = 0; /* required for length check */ } close(ifd); if (!to_stdout && close(ofd)) { write_error(); } if (method == -1) { if (!to_stdout) unlink (ofname); return; } /* Display statistics */ if(verbose) { if (test) { fprintf(stderr, " OK"); } else if (decompress) { display_ratio(bytes_out-(bytes_in-header_bytes), bytes_out,stderr); } else { display_ratio(bytes_in-(bytes_out-header_bytes), bytes_in, stderr); } if (!test && !to_stdout) { fprintf(stderr, " -- replaced with %s", ofname); } fprintf(stderr, "\n"); } /* Copy modes, times, ownership, and remove the input file */ if (!to_stdout) { copy_stat(&istat); } } /* ======================================================================== * Create the output file. Return OK or ERROR. * Try several times if necessary to avoid truncating the z_suffix. For * example, do not create a compressed file of name "1234567890123." * Sets save_orig_name to true if the file name has been truncated. * IN assertions: the input file has already been open (ifd is set) and * ofname has already been updated if there was an original name. * OUT assertions: ifd and ofd are closed in case of error. */ local int create_outfile() { struct stat ostat; /* stat for ofname */ int flags = O_WRONLY | O_CREAT | O_EXCL | O_BINARY; if (ascii && decompress) { flags &= ~O_BINARY; /* force ascii text mode */ } for (;;) { /* Make sure that ofname is not an existing file */ if (check_ofname() != OK) { close(ifd); return ERROR; } /* Create the output file */ remove_ofname = 1; ofd = OPEN(ofname, flags, RW_USER); if (ofd == -1) { perror(ofname); close(ifd); exit_code = ERROR; return ERROR; } /* Check for name truncation on new file (1234567890123.gz) */ #ifdef NO_FSTAT if (stat(ofname, &ostat) != 0) { #else if (fstat(ofd, &ostat) != 0) { #endif fprintf(stderr, "%s: ", progname); perror(ofname); close(ifd); close(ofd); unlink(ofname); exit_code = ERROR; return ERROR; } if (!name_too_long(ofname, &ostat)) return OK; if (decompress) { /* name might be too long if an original name was saved */ WARN((stderr, "%s: %s: warning, name truncated\n", progname, ofname)); return OK; } close(ofd); unlink(ofname); #ifdef NO_MULTIPLE_DOTS /* Should never happen, see check_ofname() */ fprintf(stderr, "%s: %s: name too long\n", progname, ofname); do_exit(ERROR); #endif shorten_name(ofname); } } /* ======================================================================== * Use lstat if available, except for -c or -f. Use stat otherwise. * This allows links when not removing the original file. */ local int do_stat(name, sbuf) char *name; struct stat *sbuf; { errno = 0; #if (defined(S_IFLNK) || defined (S_ISLNK)) && !defined(NO_SYMLINK) if (!to_stdout && !force) { return lstat(name, sbuf); } #endif return stat(name, sbuf); } /* ======================================================================== * Return a pointer to the 'z' suffix of a file name, or NULL. For all * systems, ".gz", ".z", ".Z", ".taz", ".tgz", "-gz", "-z" and "_z" are * accepted suffixes, in addition to the value of the --suffix option. * ".tgz" is a useful convention for tar.z files on systems limited * to 3 characters extensions. On such systems, ".?z" and ".??z" are * also accepted suffixes. For Unix, we do not want to accept any * .??z suffix as indicating a compressed file; some people use .xyz * to denote volume data. * On systems allowing multiple versions of the same file (such as VMS), * this function removes any version suffix in the given name. */ local char *get_suffix(name) char *name; { int nlen, slen; char suffix[MAX_SUFFIX+3]; /* last chars of name, forced to lower case */ static char *known_suffixes[] = {z_suffix, ".gz", ".z", ".taz", ".tgz", "-gz", "-z", "_z", #ifdef MAX_EXT_CHARS "z", #endif NULL}; char **suf = known_suffixes; if (strequ(z_suffix, "z")) suf++; /* check long suffixes first */ #ifdef SUFFIX_SEP /* strip a version number from the file name */ { char *v = strrchr(name, SUFFIX_SEP); if (v != NULL) *v = '\0'; } #endif nlen = strlen(name); if (nlen <= MAX_SUFFIX+2) { strcpy(suffix, name); } else { strcpy(suffix, name+nlen-MAX_SUFFIX-2); } strlwr(suffix); slen = strlen(suffix); do { int s = strlen(*suf); if (slen > s && suffix[slen-s-1] != PATH_SEP && strequ(suffix + slen - s, *suf)) { return name+nlen-s; } } while (*++suf != NULL); return NULL; } /* ======================================================================== * Set ifname to the input file name (with a suffix appended if necessary) * and istat to its stats. For decompression, if no file exists with the * original name, try adding successively z_suffix, .gz, .z, -z and .Z. * For MSDOS, we try only z_suffix and z. * Return OK or ERROR. */ local int get_istat(iname, sbuf) char *iname; struct stat *sbuf; { int ilen; /* strlen(ifname) */ static char *suffixes[] = {z_suffix, ".gz", ".z", "-z", ".Z", NULL}; char **suf = suffixes; char *s; #ifdef NO_MULTIPLE_DOTS char *dot; /* pointer to ifname extension, or NULL */ #endif if (strlen(iname) >= sizeof(ifname) - 3) { errno = ENAMETOOLONG; perror(iname); exit_code = ERROR; return ERROR; } strcpy(ifname, iname); /* If input file exists, return OK. */ if (do_stat(ifname, sbuf) == 0) return OK; if (!decompress || errno != ENOENT) { perror(ifname); exit_code = ERROR; return ERROR; } /* file.ext doesn't exist, try adding a suffix (after removing any * version number for VMS). */ s = get_suffix(ifname); if (s != NULL) { perror(ifname); /* ifname already has z suffix and does not exist */ exit_code = ERROR; return ERROR; } #ifdef NO_MULTIPLE_DOTS dot = strrchr(ifname, '.'); if (dot == NULL) { strcat(ifname, "."); dot = strrchr(ifname, '.'); } #endif ilen = strlen(ifname); if (strequ(z_suffix, ".gz")) suf++; /* Search for all suffixes */ do { s = *suf; #ifdef NO_MULTIPLE_DOTS if (*s == '.') s++; #endif #ifdef MAX_EXT_CHARS strcpy(ifname, iname); /* Needed if the suffixes are not sorted by increasing length */ if (*dot == '\0') strcpy(dot, "."); dot[MAX_EXT_CHARS+1-strlen(s)] = '\0'; #endif strcat(ifname, s); if (do_stat(ifname, sbuf) == 0) return OK; ifname[ilen] = '\0'; } while (*++suf != NULL); /* No suffix found, complain using z_suffix: */ #ifdef MAX_EXT_CHARS strcpy(ifname, iname); if (*dot == '\0') strcpy(dot, "."); dot[MAX_EXT_CHARS+1-z_len] = '\0'; #endif strcat(ifname, z_suffix); perror(ifname); exit_code = ERROR; return ERROR; } /* ======================================================================== * Generate ofname given ifname. Return OK, or WARNING if file must be skipped. * Sets save_orig_name to true if the file name has been truncated. */ local int make_ofname() { char *suff; /* ofname z suffix */ strcpy(ofname, ifname); /* strip a version number if any and get the gzip suffix if present: */ suff = get_suffix(ofname); if (decompress) { if (suff == NULL) { /* Whith -t or -l, try all files (even without .gz suffix) * except with -r (behave as with just -dr). */ if (!recursive && (list || test)) return OK; /* Avoid annoying messages with -r */ if (verbose || (!recursive && !quiet)) { WARN((stderr,"%s: %s: unknown suffix -- ignored\n", progname, ifname)); } return WARNING; } /* Make a special case for .tgz and .taz: */ strlwr(suff); if (strequ(suff, ".tgz") || strequ(suff, ".taz")) { strcpy(suff, ".tar"); } else { *suff = '\0'; /* strip the z suffix */ } /* ofname might be changed later if infile contains an original name */ } else if (suff != NULL) { /* Avoid annoying messages with -r (see treat_dir()) */ if (verbose || (!recursive && !quiet)) { fprintf(stderr, "%s: %s already has %s suffix -- unchanged\n", progname, ifname, suff); } if (exit_code == OK) exit_code = WARNING; return WARNING; } else { save_orig_name = 0; #ifdef NO_MULTIPLE_DOTS suff = strrchr(ofname, '.'); if (suff == NULL) { strcat(ofname, "."); # ifdef MAX_EXT_CHARS if (strequ(z_suffix, "z")) { strcat(ofname, "gz"); /* enough room */ return OK; } /* On the Atari and some versions of MSDOS, name_too_long() * does not work correctly because of a bug in stat(). So we * must truncate here. */ } else if (strlen(suff)-1 + z_len > MAX_SUFFIX) { suff[MAX_SUFFIX+1-z_len] = '\0'; save_orig_name = 1; # endif } #endif /* NO_MULTIPLE_DOTS */ strcat(ofname, z_suffix); } /* decompress ? */ return OK; } /* ======================================================================== * Check the magic number of the input file and update ofname if an * original name was given and to_stdout is not set. * Return the compression method, -1 for error, -2 for warning. * Set inptr to the offset of the next byte to be processed. * Updates time_stamp if there is one and --no-time is not used. * This function may be called repeatedly for an input file consisting * of several contiguous gzip'ed members. * IN assertions: there is at least one remaining compressed member. * If the member is a zip file, it must be the only one. */ local int get_method(in) int in; /* input file descriptor */ { uch flags; /* compression flags */ char magic[2]; /* magic header */ ulg stamp; /* time stamp */ /* If --force and --stdout, zcat == cat, so do not complain about * premature end of file: use try_byte instead of get_byte. */ if (force && to_stdout) { magic[0] = (char)try_byte(); magic[1] = (char)try_byte(); /* If try_byte returned EOF, magic[1] == 0xff */ } else { magic[0] = (char)get_byte(); magic[1] = (char)get_byte(); } method = -1; /* unknown yet */ part_nb++; /* number of parts in gzip file */ header_bytes = 0; last_member = RECORD_IO; /* assume multiple members in gzip file except for record oriented I/O */ if (memcmp(magic, GZIP_MAGIC, 2) == 0 || memcmp(magic, OLD_GZIP_MAGIC, 2) == 0) { method = (int)get_byte(); if (method != DEFLATED) { fprintf(stderr, "%s: %s: unknown method %d -- get newer version of gzip\n", progname, ifname, method); exit_code = ERROR; return -1; } work = unzip; flags = (uch)get_byte(); if ((flags & ENCRYPTED) != 0) { fprintf(stderr, "%s: %s is encrypted -- get newer version of gzip\n", progname, ifname); exit_code = ERROR; return -1; } if ((flags & CONTINUATION) != 0) { fprintf(stderr, "%s: %s is a a multi-part gzip file -- get newer version of gzip\n", progname, ifname); exit_code = ERROR; if (force <= 1) return -1; } if ((flags & RESERVED) != 0) { fprintf(stderr, "%s: %s has flags 0x%x -- get newer version of gzip\n", progname, ifname, flags); exit_code = ERROR; if (force <= 1) return -1; } stamp = (ulg)get_byte(); stamp |= ((ulg)get_byte()) << 8; stamp |= ((ulg)get_byte()) << 16; stamp |= ((ulg)get_byte()) << 24; if (stamp != 0 && !no_time) time_stamp = stamp; (void)get_byte(); /* Ignore extra flags for the moment */ (void)get_byte(); /* Ignore OS type for the moment */ if ((flags & CONTINUATION) != 0) { unsigned part = (unsigned)get_byte(); part |= ((unsigned)get_byte())<<8; if (verbose) { fprintf(stderr,"%s: %s: part number %u\n", progname, ifname, part); } } if ((flags & EXTRA_FIELD) != 0) { unsigned len = (unsigned)get_byte(); len |= ((unsigned)get_byte())<<8; if (verbose) { fprintf(stderr,"%s: %s: extra field of %u bytes ignored\n", progname, ifname, len); } while (len--) (void)get_byte(); } /* Get original file name if it was truncated */ if ((flags & ORIG_NAME) != 0) { if (no_name || (to_stdout && !list) || part_nb > 1) { /* Discard the old name */ char c; /* dummy used for NeXTstep 3.0 cc optimizer bug */ do {c=get_byte();} while (c != 0); } else { /* Copy the base name. Keep a directory prefix intact. */ char *p = basename(ofname); char *base = p; for (;;) { *p = (char)get_char(); if (*p++ == '\0') break; if (p >= ofname+sizeof(ofname)) { error("corrupted input -- file name too large"); } } /* If necessary, adapt the name to local OS conventions: */ if (!list) { MAKE_LEGAL_NAME(base); if (base) list=0; /* avoid warning about unused variable */ } } /* no_name || to_stdout */ } /* ORIG_NAME */ /* Discard file comment if any */ if ((flags & COMMENT) != 0) { while (get_char() != 0) /* null */ ; } if (part_nb == 1) { header_bytes = inptr + 2*sizeof(long); /* include crc and size */ } } else if (memcmp(magic, PKZIP_MAGIC, 2) == 0 && inptr == 2 && memcmp((char*)inbuf, PKZIP_MAGIC, 4) == 0) { /* To simplify the code, we support a zip file when alone only. * We are thus guaranteed that the entire local header fits in inbuf. */ inptr = 0; work = unzip; if (check_zipfile(in) != OK) return -1; /* check_zipfile may get ofname from the local header */ last_member = 1; } else if (memcmp(magic, PACK_MAGIC, 2) == 0) { work = unpack; method = PACKED; } else if (memcmp(magic, LZW_MAGIC, 2) == 0) { work = unlzw; method = COMPRESSED; last_member = 1; } else if (memcmp(magic, LZH_MAGIC, 2) == 0) { work = unlzh; method = LZHED; last_member = 1; } else if (force && to_stdout && !list) { /* pass input unchanged */ method = STORED; work = copy; inptr = 0; last_member = 1; } if (method >= 0) return method; if (part_nb == 1) { fprintf(stderr, "\n%s: %s: not in gzip format\n", progname, ifname); exit_code = ERROR; return -1; } else { WARN((stderr, "\n%s: %s: decompression OK, trailing garbage ignored\n", progname, ifname)); return -2; } } /* ======================================================================== * Display the characteristics of the compressed file. * If the given method is < 0, display the accumulated totals. * IN assertions: time_stamp, header_bytes and ifile_size are initialized. */ local void do_list(ifd, method) int ifd; /* input file descriptor */ int method; /* compression method */ { ulg crc; /* original crc */ static int first_time = 1; static char* methods[MAX_METHODS] = { "store", /* 0 */ "compr", /* 1 */ "pack ", /* 2 */ "lzh ", /* 3 */ "", "", "", "", /* 4 to 7 reserved */ "defla"}; /* 8 */ char *date; if (first_time && method >= 0) { first_time = 0; if (verbose) { printf("method crc date time "); } if (!quiet) { printf("compressed uncompr. ratio uncompressed_name\n"); } } else if (method < 0) { if (total_in <= 0 || total_out <= 0) return; if (verbose) { printf(" %9lu %9lu ", total_in, total_out); } else if (!quiet) { printf("%9ld %9ld ", total_in, total_out); } display_ratio(total_out-(total_in-header_bytes), total_out, stdout); /* header_bytes is not meaningful but used to ensure the same * ratio if there is a single file. */ printf(" (totals)\n"); return; } crc = (ulg)~0; /* unknown */ bytes_out = -1L; bytes_in = ifile_size; #if RECORD_IO == 0 if (method == DEFLATED && !last_member) { /* Get the crc and uncompressed size for gzip'ed (not zip'ed) files. * If the lseek fails, we could use read() to get to the end, but * --list is used to get quick results. * Use "gunzip < foo.gz | wc -c" to get the uncompressed size if * you are not concerned about speed. */ bytes_in = (long)lseek(ifd, (off_t)(-8), SEEK_END); if (bytes_in != -1L) { uch buf[8]; bytes_in += 8L; if (read(ifd, (char*)buf, sizeof(buf)) != sizeof(buf)) { read_error(); } crc = LG(buf); bytes_out = LG(buf+4); } } #endif /* RECORD_IO */ date = ctime((time_t*)&time_stamp) + 4; /* skip the day of the week */ date[12] = '\0'; /* suppress the 1/100sec and the year */ if (verbose) { printf("%5s %08lx %11s ", methods[method], crc, date); } printf("%9ld %9ld ", bytes_in, bytes_out); if (bytes_in == -1L) { total_in = -1L; bytes_in = bytes_out = header_bytes = 0; } else if (total_in >= 0) { total_in += bytes_in; } if (bytes_out == -1L) { total_out = -1L; bytes_in = bytes_out = header_bytes = 0; } else if (total_out >= 0) { total_out += bytes_out; } display_ratio(bytes_out-(bytes_in-header_bytes), bytes_out, stdout); printf(" %s\n", ofname); } /* ======================================================================== * Return true if the two stat structures correspond to the same file. */ local int same_file(stat1, stat2) struct stat *stat1; struct stat *stat2; { return stat1->st_ino == stat2->st_ino && stat1->st_dev == stat2->st_dev #ifdef NO_ST_INO /* Can't rely on st_ino and st_dev, use other fields: */ && stat1->st_mode == stat2->st_mode && stat1->st_uid == stat2->st_uid && stat1->st_gid == stat2->st_gid && stat1->st_size == stat2->st_size && stat1->st_atime == stat2->st_atime && stat1->st_mtime == stat2->st_mtime && stat1->st_ctime == stat2->st_ctime #endif ; } /* ======================================================================== * Return true if a file name is ambiguous because the operating system * truncates file names. */ local int name_too_long(name, statb) char *name; /* file name to check */ struct stat *statb; /* stat buf for this file name */ { int s = strlen(name); char c = name[s-1]; struct stat tstat; /* stat for truncated name */ int res; tstat = *statb; /* Just in case OS does not fill all fields */ name[s-1] = '\0'; res = stat(name, &tstat) == 0 && same_file(statb, &tstat); name[s-1] = c; Trace((stderr, " too_long(%s) => %d\n", name, res)); return res; } /* ======================================================================== * Shorten the given name by one character, or replace a .tar extension * with .tgz. Truncate the last part of the name which is longer than * MIN_PART characters: 1234.678.012.gz -> 123.678.012.gz. If the name * has only parts shorter than MIN_PART truncate the longest part. * For decompression, just remove the last character of the name. * * IN assertion: for compression, the suffix of the given name is z_suffix. */ local void shorten_name(name) char *name; { int len; /* length of name without z_suffix */ char *trunc = NULL; /* character to be truncated */ int plen; /* current part length */ int min_part = MIN_PART; /* current minimum part length */ char *p; len = strlen(name); if (decompress) { if (len <= 1) error("name too short"); name[len-1] = '\0'; return; } p = get_suffix(name); if (p == NULL) error("can't recover suffix\n"); *p = '\0'; save_orig_name = 1; /* compress 1234567890.tar to 1234567890.tgz */ if (len > 4 && strequ(p-4, ".tar")) { strcpy(p-4, ".tgz"); return; } /* Try keeping short extensions intact: * 1234.678.012.gz -> 123.678.012.gz */ do { p = strrchr(name, PATH_SEP); p = p ? p+1 : name; while (*p) { plen = strcspn(p, PART_SEP); p += plen; if (plen > min_part) trunc = p-1; if (*p) p++; } } while (trunc == NULL && --min_part != 0); if (trunc != NULL) { do { trunc[0] = trunc[1]; } while (*trunc++); trunc--; } else { trunc = strrchr(name, PART_SEP[0]); if (trunc == NULL) error("internal error in shorten_name"); if (trunc[1] == '\0') trunc--; /* force truncation */ } strcpy(trunc, z_suffix); } /* ======================================================================== * If compressing to a file, check if ofname is not ambiguous * because the operating system truncates names. Otherwise, generate * a new ofname and save the original name in the compressed file. * If the compressed file already exists, ask for confirmation. * The check for name truncation is made dynamically, because different * file systems on the same OS might use different truncation rules (on SVR4 * s5 truncates to 14 chars and ufs does not truncate). * This function returns -1 if the file must be skipped, and * updates save_orig_name if necessary. * IN assertions: save_orig_name is already set if ofname has been * already truncated because of NO_MULTIPLE_DOTS. The input file has * already been open and istat is set. */ local int check_ofname() { struct stat ostat; /* stat for ofname */ #ifdef ENAMETOOLONG /* Check for strictly conforming Posix systems (which return ENAMETOOLONG * instead of silently truncating filenames). */ errno = 0; while (stat(ofname, &ostat) != 0) { if (errno != ENAMETOOLONG) return 0; /* ofname does not exist */ shorten_name(ofname); } #else if (stat(ofname, &ostat) != 0) return 0; #endif /* Check for name truncation on existing file. Do this even on systems * defining ENAMETOOLONG, because on most systems the strict Posix * behavior is disabled by default (silent name truncation allowed). */ if (!decompress && name_too_long(ofname, &ostat)) { shorten_name(ofname); if (stat(ofname, &ostat) != 0) return 0; } /* Check that the input and output files are different (could be * the same by name truncation or links). */ if (same_file(&istat, &ostat)) { if (strequ(ifname, ofname)) { fprintf(stderr, "%s: %s: cannot %scompress onto itself\n", progname, ifname, decompress ? "de" : ""); } else { fprintf(stderr, "%s: %s and %s are the same file\n", progname, ifname, ofname); } exit_code = ERROR; return ERROR; } /* Ask permission to overwrite the existing file */ if (!force) { char response[80]; strcpy(response,"n"); fprintf(stderr, "%s: %s already exists;", progname, ofname); if (foreground && isatty(fileno(stdin))) { fprintf(stderr, " do you wish to overwrite (y or n)? "); fflush(stderr); (void)fgets(response, sizeof(response)-1, stdin); } if (tolow(*response) != 'y') { fprintf(stderr, "\tnot overwritten\n"); if (exit_code == OK) exit_code = WARNING; return ERROR; } } if (unlink(ofname)) { fprintf(stderr, "%s: ", progname); perror(ofname); exit_code = ERROR; return ERROR; } return OK; } #ifndef NO_UTIME /* ======================================================================== * Set the access and modification times from the given stat buffer. */ local void reset_times (name, statb) char *name; struct stat *statb; { struct utimbuf timep; /* Copy the time stamp */ timep.actime = statb->st_atime; timep.modtime = statb->st_mtime; /* Some systems (at least OS/2) do not support utime on directories */ if (utime(name, &timep) && !S_ISDIR(statb->st_mode)) { WARN((stderr, "%s: ", progname)); if (!quiet) perror(ofname); } } #endif /* ======================================================================== * Copy modes, times, ownership from input file to output file. * IN assertion: to_stdout is false. */ local void copy_stat(ifstat) struct stat *ifstat; { #ifndef NO_UTIME if (decompress && time_stamp != 0 && ifstat->st_mtime != time_stamp) { ifstat->st_mtime = time_stamp; if (verbose > 1) { fprintf(stderr, "%s: time stamp restored\n", ofname); } } reset_times(ofname, ifstat); #endif /* Copy the protection modes */ if (chmod(ofname, ifstat->st_mode & 07777)) { WARN((stderr, "%s: ", progname)); if (!quiet) perror(ofname); } #ifndef NO_CHOWN chown(ofname, ifstat->st_uid, ifstat->st_gid); /* Copy ownership */ #endif remove_ofname = 0; /* It's now safe to remove the input file: */ if (unlink(ifname)) { WARN((stderr, "%s: ", progname)); if (!quiet) perror(ifname); } } #ifndef NO_DIR /* ======================================================================== * Recurse through the given directory. This code is taken from ncompress. */ local void treat_dir(dir) char *dir; { dir_type *dp; DIR *dirp; char nbuf[MAX_PATH_LEN]; int len; dirp = opendir(dir); if (dirp == NULL) { fprintf(stderr, "%s: %s unreadable\n", progname, dir); exit_code = ERROR; return ; } /* ** WARNING: the following algorithm could occasionally cause ** compress to produce error warnings of the form ".gz ** already has .gz suffix - ignored". This occurs when the ** .gz output file is inserted into the directory below ** readdir's current pointer. ** These warnings are harmless but annoying, so they are suppressed ** with option -r (except when -v is on). An alternative ** to allowing this would be to store the entire directory ** list in memory, then compress the entries in the stored ** list. Given the depth-first recursive algorithm used here, ** this could use up a tremendous amount of memory. I don't ** think it's worth it. -- Dave Mack ** (An other alternative might be two passes to avoid depth-first.) */ while ((dp = readdir(dirp)) != NULL) { if (strequ(dp->d_name,".") || strequ(dp->d_name,"..")) { continue; } len = strlen(dir); if (len + NLENGTH(dp) + 1 < MAX_PATH_LEN - 1) { strcpy(nbuf,dir); if (len != 0 /* dir = "" means current dir on Amiga */ #ifdef PATH_SEP2 && dir[len-1] != PATH_SEP2 #endif #ifdef PATH_SEP3 && dir[len-1] != PATH_SEP3 #endif ) { nbuf[len++] = PATH_SEP; } strcpy(nbuf+len, dp->d_name); treat_file(nbuf); } else { fprintf(stderr,"%s: %s/%s: pathname too long\n", progname, dir, dp->d_name); exit_code = ERROR; } } closedir(dirp); } #endif /* ? NO_DIR */ /* ======================================================================== * Free all dynamically allocated variables and exit with the given code. */ local void do_exit(exitcode) int exitcode; { static int in_exit = 0; if (in_exit) exit(exitcode); in_exit = 1; if (env != NULL) free(env), env = NULL; if (args != NULL) free((char*)args), args = NULL; FREE(inbuf); FREE(outbuf); FREE(d_buf); FREE(window); #ifndef MAXSEG_64K FREE(tab_prefix); #else FREE(tab_prefix0); FREE(tab_prefix1); #endif exit(exitcode); } /* ======================================================================== * Signal and error handler. */ RETSIGTYPE abort_gzip() { if (remove_ofname) { close(ofd); unlink (ofname); } do_exit(ERROR); } diff --git a/gnu/usr.bin/gzip/zdiff b/gnu/usr.bin/gzip/zdiff index 84e65d329efb..a8ba5fe6324c 100644 --- a/gnu/usr.bin/gzip/zdiff +++ b/gnu/usr.bin/gzip/zdiff @@ -1,69 +1,70 @@ #!/bin/sh # sh is buggy on RS/6000 AIX 3.2. Replace above line with #!/bin/ksh # Zcmp and zdiff are used to invoke the cmp or the diff pro- # gram on compressed files. All options specified are passed # directly to cmp or diff. If only 1 file is specified, then # the files compared are file1 and an uncompressed file1.gz. # If two files are specified, then they are uncompressed (if # necessary) and fed to cmp or diff. The exit status from cmp # or diff is preserved. +# +# $Id$ -PATH="/usr/local/bin:$PATH"; export PATH prog=`echo $0 | sed 's|.*/||'` case "$prog" in *cmp) comp=${CMP-cmp} ;; *) comp=${DIFF-diff} ;; esac OPTIONS= FILES= for ARG do case "$ARG" in -*) OPTIONS="$OPTIONS $ARG";; *) if test -f "$ARG"; then FILES="$FILES $ARG" else echo "${prog}: $ARG not found or not a regular file" exit 1 fi ;; esac done if test -z "$FILES"; then echo "Usage: $prog [${comp}_options] file [file]" exit 1 fi set $FILES if test $# -eq 1; then FILE=`echo "$1" | sed 's/[-.][zZtga]*$//'` gzip -cd "$1" | $comp $OPTIONS - "$FILE" STAT="$?" elif test $# -eq 2; then case "$1" in *[-.]gz* | *[-.][zZ] | *.t[ga]z) case "$2" in *[-.]gz* | *[-.][zZ] | *.t[ga]z) F=`echo "$2" | sed 's|.*/||;s|[-.][zZtga]*||'` gzip -cdfq "$2" > /tmp/"$F".$$ gzip -cdfq "$1" | $comp $OPTIONS - /tmp/"$F".$$ STAT="$?" /bin/rm -f /tmp/"$F".$$;; *) gzip -cdfq "$1" | $comp $OPTIONS - "$2" STAT="$?";; esac;; *) case "$2" in *[-.]gz* | *[-.][zZ] | *.t[ga]z) gzip -cdfq "$2" | $comp $OPTIONS "$1" - STAT="$?";; *) $comp $OPTIONS "$1" "$2" STAT="$?";; esac;; esac exit "$STAT" else echo "Usage: $prog [${comp}_options] file [file]" exit 1 fi diff --git a/gnu/usr.bin/gzip/zforce b/gnu/usr.bin/gzip/zforce index 17258a4855f9..808b0d0f9c4f 100644 --- a/gnu/usr.bin/gzip/zforce +++ b/gnu/usr.bin/gzip/zforce @@ -1,41 +1,42 @@ #!/bin/sh # zforce: force a gz extension on all gzip files so that gzip will not # compress them twice. # # This can be useful for files with names truncated after a file transfer. # 12345678901234 is renamed to 12345678901.gz +# +# $Id$ -PATH="/usr/local/bin:$PATH"; export PATH x=`basename $0` if test $# = 0; then echo "force a '.gz' extension on all gzip files" echo usage: $x files... exit 1 fi res=0 for i do if test ! -f "$i" ; then echo ${x}: $i not a file res=1 continue fi test `expr "$i" : '.*[.-]z$'` -eq 0 || continue test `expr "$i" : '.*[.-]gz$'` -eq 0 || continue test `expr "$i" : '.*[.]t[ag]z$'` -eq 0 || continue if gzip -l < "$i" 2>/dev/null | grep '^defl' > /dev/null; then if test `expr "$i" : '^............'` -eq 12; then new=`expr "$i" : '\(.*\)...$`.gz else new="$i.gz" fi if mv "$i" "$new" 2>/dev/null; then echo $i -- replaced with $new continue fi res=1; echo ${x}: cannot rename $i to $new fi done exit $res diff --git a/gnu/usr.bin/gzip/zgrep b/gnu/usr.bin/gzip/zgrep index bcc10cc1dba2..32a27457de0c 100644 --- a/gnu/usr.bin/gzip/zgrep +++ b/gnu/usr.bin/gzip/zgrep @@ -1,72 +1,72 @@ #!/bin/sh # zgrep -- a wrapper around a grep program that decompresses files as needed # Adapted from a version sent by Charles Levert - -PATH="/usr/local/bin:$PATH"; export PATH +# +# $Id$ prog=`echo $0 | sed 's|.*/||'` case "$prog" in *egrep) grep=${EGREP-egrep} ;; *fgrep) grep=${FGREP-fgrep} ;; *) grep=${GREP-grep} ;; esac A= fileno=0 pat="" while test $# -ne 0; do case "$1" in -e | -f) opt="$opt $1"; shift; pat="$1" if test "$grep" = grep; then # grep is buggy with -e on SVR4 grep=egrep fi;; -*) opt="$opt $1";; *) if test -z "$pat"; then pat="$1" else fileno=`expr $fileno + 1` eval A$fileno=\$1 A="$A \"\$A$fileno\"" fi ;; esac shift done if test -z "$pat"; then echo "grep through gzip files" echo "usage: $prog [grep_options] pattern [files]" exit 1 fi list=0 silent=0 op=`echo "$opt" | sed -e 's/ //g' -e 's/-//g'` case "$op" in *l*) list=1 esac case "$op" in *h*) silent=1 esac if test $fileno -eq 0; then gzip -cdfq | $grep $opt "$pat" exit $? fi eval set "$A" # files in $1, $2 ... res=0 for i do if test $list -eq 1; then gzip -cdfq "$i" | $grep $opt "$pat" > /dev/null && echo $i r=$? elif test $# -eq 1 -o $silent -eq 1; then gzip -cdfq "$i" | $grep $opt "$pat" r=$? else gzip -cdfq "$i" | $grep $opt "$pat" | sed "s|^|${i}:|" r=$? fi test "$r" -ne 0 && res="$r" done exit $res diff --git a/gnu/usr.bin/gzip/zmore b/gnu/usr.bin/gzip/zmore index ca933c711821..2590423333c2 100644 --- a/gnu/usr.bin/gzip/zmore +++ b/gnu/usr.bin/gzip/zmore @@ -1,51 +1,52 @@ #!/bin/sh +# +# $Id$ -PATH="/usr/local/bin:$PATH"; export PATH if test "`echo -n a`" = "-n a"; then # looks like a SysV system: n1=''; n2='\c' else n1='-n'; n2='' fi oldtty=`stty -g 2>/dev/null` if stty -cbreak 2>/dev/null; then cb='cbreak'; ncb='-cbreak' else # 'stty min 1' resets eof to ^a on both SunOS and SysV! cb='min 1 -icanon'; ncb='icanon eof ^d' fi if test $? -eq 0 -a -n "$oldtty"; then trap 'stty $oldtty 2>/dev/null; exit' 0 2 3 5 10 13 15 else trap 'stty $ncb echo 2>/dev/null; exit' 0 2 3 5 10 13 15 fi if test $# = 0; then if test -t 0; then echo usage: zmore files... else gzip -cdfq | eval ${PAGER-more} fi else FIRST=1 for FILE do if test $FIRST -eq 0; then echo $n1 "--More--(Next file: $FILE)$n2" stty $cb -echo 2>/dev/null ANS=`dd bs=1 count=1 2>/dev/null` stty $ncb echo 2>/dev/null echo " " if test "$ANS" = 'e' -o "$ANS" = 'q'; then exit fi fi if test "$ANS" != 's'; then echo "------> $FILE <------" gzip -cdfq "$FILE" | eval ${PAGER-more} fi if test -t; then FIRST=0 fi done fi diff --git a/gnu/usr.bin/gzip/zmore.1 b/gnu/usr.bin/gzip/zmore.1 index f7f1843de01a..3c9d928cfd99 100644 --- a/gnu/usr.bin/gzip/zmore.1 +++ b/gnu/usr.bin/gzip/zmore.1 @@ -1,145 +1,147 @@ +.\" $Id$ +.\" .TH ZMORE 1 .SH NAME zmore \- file perusal filter for crt viewing of compressed text .SH SYNOPSIS .B zmore [ name ... ] .SH DESCRIPTION .I Zmore is a filter which allows examination of compressed or plain text files one screenful at a time on a soft-copy terminal. .I zmore works on files compressed with .I compress, pack or .I gzip, and also on uncompressed files. If a file does not exist, .I zmore looks for a file of the same name with the addition of a .gz, .z or .Z suffix. .PP .I Zmore normally pauses after each screenful, printing --More-- at the bottom of the screen. If the user then types a carriage return, one more line is displayed. If the user hits a space, another screenful is displayed. Other possibilities are enumerated later. .PP .I Zmore looks in the file -.I /etc/termcap +.I /usr/share/misc/termcap to determine terminal characteristics, and to determine the default window size. On a terminal capable of displaying 24 lines, the default window size is 22 lines. To use a pager other than the default .I more, set environment variable PAGER to the name of the desired program, such as .I less. .PP Other sequences which may be typed when .I zmore pauses, and their effects, are as follows (\fIi\fP is an optional integer argument, defaulting to 1) : .PP .IP \fIi\|\fP display .I i more lines, (or another screenful if no argument is given) .PP .IP ^D display 11 more lines (a ``scroll''). If .I i is given, then the scroll size is set to \fIi\|\fP. .PP .IP d same as ^D (control-D) .PP .IP \fIi\|\fPz same as typing a space except that \fIi\|\fP, if present, becomes the new window size. Note that the window size reverts back to the default at the end of the current file. .PP .IP \fIi\|\fPs skip \fIi\|\fP lines and print a screenful of lines .PP .IP \fIi\|\fPf skip \fIi\fP screenfuls and print a screenful of lines .PP .IP "q or Q" quit reading the current file; go on to the next (if any) .PP .IP "e or q" When the prompt --More--(Next file: .IR file ) is printed, this command causes zmore to exit. .PP .IP s When the prompt --More--(Next file: .IR file ) is printed, this command causes zmore to skip the next file and continue. .PP .IP = Display the current line number. .PP .IP \fIi\|\fP/expr search for the \fIi\|\fP-th occurrence of the regular expression \fIexpr.\fP If the pattern is not found, .I zmore goes on to the next file (if any). Otherwise, a screenful is displayed, starting two lines before the place where the expression was found. The user's erase and kill characters may be used to edit the regular expression. Erasing back past the first column cancels the search command. .PP .IP \fIi\|\fPn search for the \fIi\|\fP-th occurrence of the last regular expression entered. .PP .IP !command invoke a shell with \fIcommand\|\fP. The character `!' in "command" are replaced with the previous shell command. The sequence "\\!" is replaced by "!". .PP .IP ":q or :Q" quit reading the current file; go on to the next (if any) (same as q or Q). .PP .IP . (dot) repeat the previous command. .PP The commands take effect immediately, i.e., it is not necessary to type a carriage return. Up to the time when the command character itself is given, the user may hit the line kill character to cancel the numerical argument being formed. In addition, the user may hit the erase character to redisplay the --More-- message. .PP At any time when output is being sent to the terminal, the user can hit the quit key (normally control\-\\). .I Zmore will stop sending output, and will display the usual --More-- prompt. The user may then enter one of the above commands in the normal manner. Unfortunately, some output is lost when this is done, due to the fact that any characters waiting in the terminal's output queue are flushed when the quit signal occurs. .PP The terminal is set to .I noecho mode by this program so that the output can be continuous. What you type will thus not show on your terminal, except for the / and ! commands. .PP If the standard output is not a teletype, then .I zmore acts just like .I zcat, except that a header is printed before each file. .SH FILES .DT /etc/termcap Terminal data base .SH "SEE ALSO" more(1), gzip(1), zdiff(1), zgrep(1), znew(1), zforce(1), gzexe(1) diff --git a/gnu/usr.bin/gzip/znew b/gnu/usr.bin/gzip/znew index 5c832e826ac2..53226c31ca87 100644 --- a/gnu/usr.bin/gzip/znew +++ b/gnu/usr.bin/gzip/znew @@ -1,145 +1,146 @@ #!/bin/sh +# +# $Id$ -PATH="/usr/local/bin:$PATH"; export PATH check=0 pipe=0 opt= files= keep=0 res=0 old=0 new=0 block=1024 # block is the disk block size (best guess, need not be exact) warn="(does not preserve modes and timestamp)" tmp=/tmp/zfoo.$$ echo hi > $tmp.1 echo hi > $tmp.2 if test -z "`(${CPMOD-cpmod} $tmp.1 $tmp.2) 2>&1`"; then cpmod=${CPMOD-cpmod} warn="" fi if test -z "$cpmod" && ${TOUCH-touch} -r $tmp.1 $tmp.2 2>/dev/null; then cpmod="${TOUCH-touch}" cpmodarg="-r" warn="(does not preserve file modes)" fi # check if GZIP env. variable uses -S or --suffix gzip -q $tmp.1 ext=`echo $tmp.1* | sed "s|$tmp.1||"` rm -f $tmp.[12]* if test -z "$ext"; then echo znew: error determining gzip extension exit 1 fi if test "$ext" = ".Z"; then echo znew: cannot use .Z as gzip extension. exit 1 fi for arg do case "$arg" in -*) opt="$opt $arg"; shift;; *) break;; esac done if test $# -eq 0; then echo "recompress .Z files into $ext (gzip) files" echo usage: `echo $0 | sed 's,^.*/,,'` "[-tv9KP]" file.Z... echo " -t tests the new files before deleting originals" echo " -v be verbose" echo " -9 use the slowest compression method (optimal compression)" echo " -K keep a .Z file when it is smaller than the $ext file" echo " -P use pipes for the conversion $warn" exit 1 fi opt=`echo "$opt" | sed -e 's/ //g' -e 's/-//g'` case "$opt" in *t*) check=1; opt=`echo "$opt" | sed 's/t//g'` esac case "$opt" in *K*) keep=1; opt=`echo "$opt" | sed 's/K//g'` esac case "$opt" in *P*) pipe=1; opt=`echo "$opt" | sed 's/P//g'` esac if test -n "$opt"; then opt="-$opt" fi for i do n=`echo $i | sed 's/.Z$//'` if test ! -f "$n.Z" ; then echo $n.Z not found res=1; continue fi test $keep -eq 1 && old=`wc -c < "$n.Z"` if test $pipe -eq 1; then if gzip -d < "$n.Z" | gzip $opt > "$n$ext"; then # Copy file attributes from old file to new one, if possible. test -n "$cpmod" && $cpmod $cpmodarg "$n.Z" "$n$ext" 2> /dev/null else echo error while recompressing $n.Z res=1; continue fi else if test $check -eq 1; then if cp -p "$n.Z" "$n.$$" 2> /dev/null || cp "$n.Z" "$n.$$"; then : else echo cannot backup "$n.Z" res=1; continue fi fi if gzip -d "$n.Z"; then : else test $check -eq 1 && mv "$n.$$" "$n.Z" echo error while uncompressing $n.Z res=1; continue fi if gzip $opt "$n"; then : else if test $check -eq 1; then mv "$n.$$" "$n.Z" && rm -f "$n" echo error while recompressing $n else # compress $n (might be dangerous if disk full) echo error while recompressing $n, left uncompressed fi res=1; continue fi fi test $keep -eq 1 && new=`wc -c < "$n$ext"` if test $keep -eq 1 -a `expr \( $old + $block - 1 \) / $block` -lt \ `expr \( $new + $block - 1 \) / $block`; then if test $pipe -eq 1; then rm -f "$n$ext" elif test $check -eq 1; then mv "$n.$$" "$n.Z" && rm -f "$n$ext" else gzip -d "$n$ext" && compress "$n" && rm -f "$n$ext" fi echo "$n.Z smaller than $n$ext -- unchanged" elif test $check -eq 1; then if gzip -t "$n$ext" ; then rm -f "$n.$$" "$n.Z" else test $pipe -eq 0 && mv "$n.$$" "$n.Z" rm -f "$n$ext" echo error while testing $n$ext, $n.Z unchanged res=1; continue fi elif test $pipe -eq 1; then rm -f "$n.Z" fi done exit $res