| /* 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.3 2005/02/12 21:03:28 olh Exp $"; |
| #endif |
| |
| #include <ctype.h> |
| #include <sys/types.h> |
| #include <signal.h> |
| #include <sys/stat.h> |
| #include <errno.h> |
| |
| #include "tailor.h" |
| #include "gzip.h" |
| #include "revision.h" |
| |
| #include <time.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| |
| #include <utime.h> |
| |
| typedef void (*sig_type) OF((int)); |
| |
| #define RW_USER (S_IRUSR | S_IWUSR) /* creation mode for open() */ |
| |
| #ifndef MAX_PATH_LEN |
| # define MAX_PATH_LEN 1024 /* max pathname length */ |
| #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); |
| DECLARE(ush, tab_prefix, 1L<<BITS); |
| |
| /* local variables */ |
| |
| #ifndef SUPPORT_ZIP |
| #define decompress 1 |
| #else |
| int level = 6; /* compression level */ |
| #endif |
| |
| int to_stdout; /* output to stdout (-c) */ |
| #ifndef decompress |
| int decompress; /* decompress (-d) */ |
| #endif |
| int force; /* don't ask questions, compress links (-f) */ |
| int no_name = -1; /* don't save or restore the original file name */ |
| int no_time = -1; /* don't save or restore the original file time */ |
| int verbose; /* be verbose (-v) */ |
| int quiet; /* be very quiet (-q) */ |
| int test; /* test .gz file integrity */ |
| int foreground; /* set if program run in foreground */ |
| char *progname; /* program name */ |
| int method = DEFLATED;/* compression method */ |
| int exit_code = OK; /* program exit code */ |
| int save_orig_name; /* set if original name must be saved */ |
| int last_member; /* set for .zip and .Z files */ |
| int part_nb; /* number of parts in .gz file */ |
| time_t time_stamp; /* original time stamp (modification time) */ |
| long ifile_size; /* input file size, -1 for devices (debug only) */ |
| char *env; /* contents of GZIP env variable */ |
| char **args = NULL; /* argv pointer if GZIP env variable defined */ |
| char z_suffix[MAX_SUFFIX+1]; /* default suffix (can be set with --suffix) */ |
| int z_len; /* strlen(z_suffix) */ |
| |
| long header_bytes; /* number of bytes in gzip header */ |
| long bytes_in; /* number of input bytes */ |
| long bytes_out; /* number of output bytes */ |
| long total_in; /* input bytes for all files */ |
| long total_out; /* output bytes for all files */ |
| char ifname[MAX_PATH_LEN]; /* input file name */ |
| char ofname[MAX_PATH_LEN]; /* output file name */ |
| int remove_ofname; /* remove output file on error */ |
| struct stat istat; /* status for input file */ |
| int ifd; /* input file descriptor */ |
| int ofd; /* output file descriptor */ |
| unsigned insize; /* valid bytes in inbuf */ |
| unsigned inptr; /* index of next byte to be processed in inbuf */ |
| unsigned outcnt; /* bytes in output buffer */ |
| |
| /* local functions */ |
| |
| local void usage OF((void)); |
| local void help OF((void)); |
| local void license OF((void)); |
| local void version OF((void)); |
| local void treat_stdin OF((void)); |
| local void treat_file OF((char *iname)); |
| local int create_outfile OF((void)); |
| local int do_stat OF((char *name, struct stat *sbuf)); |
| local char *get_suffix OF((char *name)); |
| local int get_istat OF((char *iname, struct stat *sbuf)); |
| local int make_ofname OF((void)); |
| local int same_file OF((struct stat *stat1, struct stat *stat2)); |
| local int name_too_long OF((char *name, struct stat *statb)); |
| local void shorten_name OF((char *name)); |
| local int get_method OF((void)); |
| local int check_ofname OF((void)); |
| local void copy_stat OF((struct stat *ifstat)); |
| local void do_exit OF((int exitcode)); |
| int main OF((int argc, char **argv)); |
| int (*work) OF((int infile, int outfile)) |
| #ifdef SUPPORT_ZIP |
| = zip; /* function to call */ |
| #else |
| = unzip; |
| #endif |
| local void reset_times OF((char *name, struct stat *statb)); |
| |
| #define strequ(s1, s2) (strcmp((s1),(s2)) == 0) |
| |
| /* ======================================================================== */ |
| local void usage() |
| { |
| fprintf(stderr, "usage: %s [-cdfhlLnNtvV19] [-S suffix] [file ...]\n", |
| progname); |
| } |
| |
| /* ======================================================================== */ |
| local void help() |
| { |
| static char *help_msg[] = { |
| " -c --stdout write on standard output, keep original files unchanged", |
| " -d --decompress decompress", |
| " -f --force force overwrite of output file and compress links", |
| " -h --help give this help", |
| " -L --license display software license", |
| #ifdef UNDOCUMENTED |
| " -m --no-time do not save or restore the original modification time", |
| " -M --time save or restore the original modification time", |
| #endif |
| " -n --no-name do not save or restore the original name and time stamp", |
| " -N --name save or restore the original name and time stamp", |
| " -q --quiet suppress all warnings", |
| " -S .suf --suffix .suf use suffix .suf on compressed files", |
| " -t --test test compressed file integrity", |
| " -v --verbose verbose mode", |
| " -V --version display version number", |
| #ifdef SUPPORT_ZIP |
| " -1 --fast compress faster", |
| " -9 --best compress better", |
| " file... files to (de)compress. If none given, use standard input.", |
| #else |
| " file... files to decompress. If none given, use standard input.", |
| #endif |
| 0}; |
| char **p = help_msg; |
| |
| fprintf(stderr,"%s %s (%s)\n", progname, VERSION, REVDATE); |
| usage(); |
| while (*p) fprintf(stderr, "%s\n", *p++); |
| } |
| |
| /* ======================================================================== */ |
| local void license() |
| { |
| char **p = license_msg; |
| |
| fprintf(stderr,"%s %s (%s)\n", progname, VERSION, REVDATE); |
| while (*p) fprintf(stderr, "%s\n", *p++); |
| } |
| |
| /* ======================================================================== */ |
| local void version() |
| { |
| fprintf(stderr,"%s %s (%s)\n", progname, VERSION, REVDATE); |
| |
| fprintf(stderr, "Compilation options: UTIME STDC_HEADERS" |
| #ifndef SUPPORT_ZIP |
| " DECOMPRESS_ONLY" |
| #endif |
| "\n"); |
| } |
| |
| /* ======================================================================== */ |
| int main (argc, argv) |
| int argc; |
| char **argv; |
| { |
| int file_count; /* number of files to precess */ |
| int optc; /* current option */ |
| |
| progname = basename(argv[0]); |
| |
| /* Add options in GZIP environment variable if there is one */ |
| env = add_envopt(&argc, &argv, OPTIONS_VAR); |
| if (env != NULL) args = argv; |
| |
| foreground = sysv_signal(SIGINT, SIG_IGN) != SIG_IGN; |
| if (foreground) { |
| (void) sysv_signal(SIGINT, (sig_type)abort_gzip); |
| } |
| #ifdef SIGTERM |
| if (sysv_signal(SIGTERM, SIG_IGN) != SIG_IGN) { |
| (void) sysv_signal(SIGTERM, (sig_type)abort_gzip); |
| } |
| #endif |
| #ifdef SIGHUP |
| if (sysv_signal(SIGHUP, SIG_IGN) != SIG_IGN) { |
| (void) sysv_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. |
| */ |
| #ifndef decompress |
| if ( strncmp(progname, "un", 2) == 0 /* ungzip, uncompress */ |
| || strncmp(progname, "gun", 3) == 0) { /* gunzip */ |
| decompress = 1; |
| } |
| #endif |
| if (strequ(progname+1, "cat") /* zcat, pcat, gcat */ |
| || strequ(progname, "gzcat")) { /* gzcat */ |
| #ifndef decompress |
| decompress = 1; |
| #endif |
| to_stdout = 1; |
| } |
| #endif |
| |
| strncpy(z_suffix, Z_SUFFIX, sizeof(z_suffix)-1); |
| z_len = strlen(z_suffix); |
| |
| while ((optc = getopt(argc, argv, "cdfhH?LmMnNqrS:tvV123456789")) != EOF) { |
| switch (optc) { |
| case 'c': |
| to_stdout = 1; break; |
| case 'd': |
| #ifndef decompress |
| decompress = 1; |
| #endif |
| break; |
| case 'f': |
| force++; break; |
| case 'h': case 'H': case '?': |
| help(); do_exit(OK); 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 'S': |
| z_len = strlen(optarg); |
| strcpy(z_suffix, optarg); |
| break; |
| case 't': |
| test = to_stdout = 1; |
| #ifndef decompress |
| decompress = 1; |
| #endif |
| break; |
| case 'v': |
| verbose++; quiet = 0; break; |
| case 'V': |
| version(); do_exit(OK); break; |
| #ifdef SUPPORT_ZIP |
| case '1': case '2': case '3': case '4': |
| case '5': case '6': case '7': case '8': case '9': |
| level = optc - '0'; |
| break; |
| #endif |
| 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 ((z_len == 0 && !decompress) || z_len > MAX_SUFFIX) { |
| fprintf(stderr, "%s: incorrect suffix '%s'\n", |
| progname, optarg); |
| do_exit(ERROR); |
| } |
| |
| /* 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); |
| ALLOC(ush, tab_prefix, 1L<<BITS); |
| |
| /* And get to work */ |
| if (file_count != 0) { |
| while (optind < argc) { |
| treat_file(argv[optind++]); |
| } |
| } else { /* Standard input */ |
| treat_stdin(); |
| } |
| do_exit(exit_code); |
| return exit_code; /* just to avoid lint warning */ |
| } |
| |
| /* ======================================================================== |
| * Compress or decompress stdin |
| */ |
| local void treat_stdin() |
| { |
| if (!force && |
| 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); |
| } |
| |
| strcpy(ifname, "stdin"); |
| strcpy(ofname, "stdout"); |
| |
| /* Get the time stamp on the input file. */ |
| time_stamp = 0; /* time unknown by default */ |
| |
| if (!no_time) { |
| if (fstat(fileno(stdin), &istat) != 0) { |
| error("fstat(stdin)"); |
| } |
| time_stamp = istat.st_mtime; |
| } |
| 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(); |
| if (method < 0) { |
| do_exit(exit_code); /* error message already emitted */ |
| } |
| } |
| |
| /* 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(); |
| 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)) { |
| 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 ? 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 && !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, !decompress ? O_RDONLY : O_RDONLY, |
| 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(); /* updates ofname if original given */ |
| if (method < 0) { |
| close(ifd); |
| return; /* error message already emitted */ |
| } |
| } |
| |
| /* 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(); |
| 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; |
| |
| 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) */ |
| if (fstat(ofd, &ostat) != 0) { |
| 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); |
| 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 (!to_stdout && !force) { |
| return lstat(name, sbuf); |
| } |
| 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", |
| NULL}; |
| char **suf = known_suffixes; |
| |
| if (strequ(z_suffix, "z")) suf++; /* check long suffixes first */ |
| |
| 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; |
| |
| 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; |
| } |
| ilen = strlen(ifname); |
| if (strequ(z_suffix, ".gz")) suf++; |
| |
| /* Search for all suffixes */ |
| do { |
| s = *suf; |
| strcat(ifname, s); |
| if (do_stat(ifname, sbuf) == 0) return OK; |
| ifname[ilen] = '\0'; |
| } while (*++suf != NULL); |
| |
| /* No suffix found, complain using z_suffix: */ |
| 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 (test) return OK; |
| |
| /* Avoid annoying messages with -r */ |
| if (verbose || !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 || !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; |
| 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() |
| { |
| 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 || 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); |
| for (;;) { |
| *p = (char)get_char(); |
| if (*p++ == '\0') break; |
| if (p >= ofname+sizeof(ofname)) { |
| error("corrupted input -- file name too large"); |
| } |
| } |
| } /* 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 (force && to_stdout) { /* 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; |
| } |
| } |
| |
| |
| /* ======================================================================== |
| * 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, "."); |
| 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, '.'); |
| 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) { |
| #if 0 |
| 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)? "); |
| (void)fgets(response, sizeof(response)-1, stdin); |
| } |
| if (tolow(*response) != 'y') { |
| fprintf(stderr, "\tnot overwritten\n"); |
| #endif |
| if (exit_code == OK) exit_code = WARNING; |
| return ERROR; |
| #if 0 |
| } |
| #endif |
| } |
| (void) chmod(ofname, 0777); |
| if (unlink(ofname)) { |
| fprintf(stderr, "%s: ", progname); |
| perror(ofname); |
| exit_code = ERROR; |
| return ERROR; |
| } |
| return OK; |
| } |
| |
| |
| /* ======================================================================== |
| * 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); |
| } |
| } |
| |
| |
| /* ======================================================================== |
| * Copy modes, times, ownership from input file to output file. |
| * IN assertion: to_stdout is false. |
| */ |
| local void copy_stat(ifstat) |
| struct stat *ifstat; |
| { |
| 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); |
| |
| /* Copy the protection modes */ |
| if (chmod(ofname, ifstat->st_mode & 07777)) { |
| WARN((stderr, "%s: ", progname)); |
| if (!quiet) perror(ofname); |
| } |
| |
| chown(ofname, ifstat->st_uid, ifstat->st_gid); /* Copy ownership */ |
| |
| remove_ofname = 0; |
| /* It's now safe to remove the input file: */ |
| (void) chmod(ifname, 0777); |
| if (unlink(ifname)) { |
| WARN((stderr, "%s: ", progname)); |
| if (!quiet) perror(ifname); |
| } |
| } |
| |
| /* ======================================================================== |
| * 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); |
| FREE(tab_prefix); |
| exit(exitcode); |
| } |
| |
| /* ======================================================================== |
| * Signal and error handler. |
| */ |
| void abort_gzip() |
| { |
| if (remove_ofname) { |
| close(ofd); |
| unlink (ofname); |
| } |
| do_exit(ERROR); |
| } |