ftp.nice.ch/pub/next/tools/emulators/vice.0.15.0.NeXT.sd.tgz#/vice-0.15.0/src/zfile.c

This is zfile.c in view mode; [Download] [Up]

/*
 * zfile.c - Transparent handling of compressed files.
 *
 * Written by
 *  Ettore Perazzoli (ettore@comm2000.it)
 *
 * BZIP v2 support added by
 *  Andreas Boose (boose@rzgw.rz.fh-hannover.de)
 *
 * ARCHIVE, ZIPCODE and LYNX supports added by
 *  Teemu Rantanen (tvr@cs.hut.fi)
 *
 * This file is part of VICE, the Versatile Commodore Emulator.
 * See README for copyright notice.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 *  02111-1307  USA.
 *
 */

/* This code might be improved a lot...  */

#include "vice.h"

#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <string.h>
#include <stdarg.h>
#include <ctype.h>
#include <errno.h>

#include "utils.h"
#include "zfile.h"
#include "zipcode.h"

/* ------------------------------------------------------------------------- */

/* #define DEBUG_ZFILE */

#ifdef DEBUG_ZFILE
#define ZDEBUG(a) printf a
#else
#define ZDEBUG(a)
#endif

/* We could add more here...  */
enum compression_type {
    COMPR_NONE,
    COMPR_GZIP,
    COMPR_BZIP,
    COMPR_ARCHIVE,
    COMPR_ZIPCODE,
    COMPR_LYNX
};

/* This defines a linked list of all the compressed files that have been
   opened.  */
struct zfile {
    char *tmp_name;		/* Name of the temporary file.  */
    char *orig_name;		/* Name of the original file.  */
    int write_mode;		/* Non-zero if the file is open for writing.  */
    FILE *stream;		/* Associated stdio-style stream.  */
    int fd;			/* Associated file descriptor.  */
    enum compression_type type;	/* Compression algorithm.  */
    struct zfile *prev, *next;  /* Link to the previous and next nodes.  */
};
struct zfile *zfile_list = NULL;

/* ------------------------------------------------------------------------- */

static int zinit_done = 0;

static int zinit(void)
{
    struct zfile *p;

    /* Free the `zfile_list' if not empty.  */
    for (p = zfile_list; p != NULL; ) {
	struct zfile *next;

	free(p->orig_name);
	free(p->tmp_name);
	next = p->next;
	free(p);
	p = next;
    }

    zfile_list = NULL;
    zinit_done = 1;

    return 0;
}

/* Add one zfile to the list.  `orig_name' is automatically expanded to the
   complete path.  */
static void zfile_list_add(const char *tmp_name,
			   const char *orig_name,
			   enum compression_type type,
			   int write_mode,
			   FILE *stream, int fd)
{
    struct zfile *new_zfile = (struct zfile *)xmalloc(sizeof(struct zfile));

    /* Make sure we have the complete path of the file.  */

#ifdef __MSDOS__
    /* MS-DOS version.  */
    new_zfile->orig_name = _truename(orig_name, NULL);
    if (new_zfile->orig_name == NULL) {
	fprintf(stderr, "zfile_list_add: warning, illegal file name `%s'.\n",
		orig_name);
	new_zfile->orig_name = stralloc(orig_name);
    }
#else
    /* Unix version.  */
    if (*orig_name == '/') {
	new_zfile->orig_name = stralloc(orig_name);
    } else {
	static char *cwd;

	cwd = get_current_dir();
	new_zfile->orig_name = concat(cwd, "/", orig_name, NULL);
	free(cwd);
    }
#endif

    /* The new zfile becomes first on the list.  */
    new_zfile->tmp_name = stralloc(tmp_name);
    new_zfile->write_mode = write_mode;
    new_zfile->stream = stream;
    new_zfile->fd = fd;
    new_zfile->type = type;
    new_zfile->next = zfile_list;
    new_zfile->prev = NULL;
    if (zfile_list != NULL)
	zfile_list->prev = new_zfile;
    zfile_list = new_zfile;
}

/* ------------------------------------------------------------------------ */

/* Uncompression.  */

/* If `name' has a gzip-like extension, try to uncompress it into a temporary
   file using gzip.  If this succeeds, return the name of the temporary file;
   return NULL otherwise.  */
static char *try_uncompress_with_gzip(const char *name)
{
    static char tmp_name[L_tmpnam];
    int l = strlen(name);
    int exit_status;
    char *argv[4];

    /* Check whether the name sounds like a gzipped file by checking the
       extension.  The last case (3-character extensions whose last character
       is a `z' (or 'Z'), is the standard convention for the MS-DOS version
       of gzip.  */
    if ((l < 4 || strcasecmp(name + l - 3, ".gz") != 0)
	&& (l < 3 || strcasecmp(name + l - 2, ".z") != 0)
	&& (l < 4 || toupper(name[l - 1]) != 'Z' || name[l - 4] != '.'))
	return NULL;

    /* `exec*()' does not want these to be constant...  */
    argv[0] = stralloc("gzip");
    argv[1] = stralloc("-cd");
    argv[2] = stralloc(name);
    argv[3] = NULL;

    ZDEBUG(("try_uncompress_with_gzip: spawning gzip -cd %s\n", name));
    tmpnam(tmp_name);
    exit_status = spawn("gzip", argv, tmp_name, NULL);

    free(argv[0]);
    free(argv[1]);
    free(argv[2]);

    if (exit_status == 0) {
	ZDEBUG(("try_uncompress_with_gzip: OK\n"));
	return tmp_name;
    } else {
	ZDEBUG(("try_uncompress_with_gzip: failed\n"));
	unlink(tmp_name);
	return NULL;
    }
}

/* If `name' has a bzip-like extension, try to uncompress it into a temporary
   file using bzip.  If this succeeds, return the name of the temporary file;
   return NULL otherwise.  */
static char *try_uncompress_with_bzip(const char *name)
{
    static char tmp_name[L_tmpnam];
    int l = strlen(name);
    int exit_status;
    char *argv[4];

    /* Check whether the name sounds like a bzipped file by checking the
       extension.  MSDOS and UNIX variants of bzip v2 use the extension
       '.bz2'.  bzip v1 is obsolete.  */
    if (l < 5 || strcasecmp(name + l - 4, ".bz2") != 0)
	return NULL;

    /* `exec*()' does not want these to be constant...  */
    argv[0] = stralloc("bzip2");
    argv[1] = stralloc("-cd");
    argv[2] = stralloc(name);
    argv[3] = NULL;

    ZDEBUG(("try_uncompress_with_bzip: spawning bzip -cd %s\n", name));
    tmpnam(tmp_name);
    exit_status = spawn("bzip2", argv, tmp_name, NULL);

    free(argv[0]);
    free(argv[1]);
    free(argv[2]);

    if (exit_status == 0) {
	ZDEBUG(("try_uncompress_with_bzip: OK\n"));
	return tmp_name;
    } else {
	ZDEBUG(("try_uncompress_with_bzip: failed\n"));
	unlink(tmp_name);
	return NULL;
    }
}

/* is the name zipcode -name? */
static int is_zipcode_name(char *name)
{
    if (name[0] >= '1' && name[0] <= '4' && name[1] == '!')
	return 1;
    return 0;
}

/* Extensions we know about */
static char *extensions[] = {
    ".d64", ".x64", ".dsk", ".t64", ".p00", ".prg", ".lnx", NULL
};

static int is_valid_extension(char *end, int l, int nameoffset)
{
    int				i, len;
    /* zipcode testing is a special case */
    if (l > nameoffset + 2 && is_zipcode_name(end + nameoffset))
	return 1;
    /* others */
    for (i = 0; extensions[i]; i++)
    {
	len = strlen(extensions[i]);
	if (l < nameoffset + len)
	    continue;
	if (!strcasecmp(extensions[i], end + l - len))
	    return 1;
    }
    return 0;
}


/* If `name' has a correct extension, try to list its contents and search for
   the first file with a proper extension; if found, extract it.  If this
   succeeds, return the name of the temporary file; if the archive file is
   valid but `write_mode' is non-zero, return a zero-length string; in all
   the other cases, return NULL.  */
static char *try_uncompress_archive(const char *name, int write_mode,
				    char *program, char *listopts,
				    char *extractopts, char *extension,
				    char *search)
{
    static char tmp_name[L_tmpnam];
    int l = strlen(name), nameoffset, found = 0, len;
    int exit_status;
    char *argv[8];
    FILE *fd;
    char tmp[1024];

    /* Do we have correct extension?  */
    len = strlen(extension);
    if (l <= len || strcasecmp(name + l - len, extension) != 0)
	return NULL;

    /* First run listing and search for first recognizeable extension.  */
    argv[0] = stralloc(program);
    argv[1] = stralloc(listopts);
    argv[2] = stralloc(name);
    argv[3] = NULL;

    ZDEBUG(("try_uncompress_archive: spawning `%d %s %s'\n",
	    program, listopts, name));
    tmpnam(tmp_name);
    exit_status = spawn(program, argv, tmp_name, NULL);

    free(argv[0]);
    free(argv[1]);
    free(argv[2]);

    /* No luck?  */
    if (exit_status != 0) {
	ZDEBUG(("try_uncompress_archive: `%s %s' failed.\n", program,
		listopts));
	unlink(tmp_name);
	return NULL;
    }

    ZDEBUG(("try_uncompress_archive: `%s %s' successful.\n", program,
	    listopts));

    fd = fopen(tmp_name, "r");
    if (!fd) {
	ZDEBUG(("try_uncompress_archive: cannot read `%s %s' output.\n",
		program, archive));
	unlink(tmp_name);
	return NULL;
    }

    ZDEBUG(("try_uncompress_archive: searching for the first valid file.\n"));

    /* Search for `search' first (if any) to see the offset where
       filename begins, then search for first recognizeable file.  */
    nameoffset = search ? -1 : 0;
    len = search ? strlen(search) : 0;
    while (!feof(fd)) {
	fgets(tmp, 1024, fd);
	l = strlen(tmp);
	while (l > 0 && (tmp[l-1] == '\n' || tmp[l-1] == '\r'))
	    tmp[--l] = 0;
	if (nameoffset < 0 && l >= len &&
	    !strcasecmp(tmp + l - len, search) != 0) {
	    nameoffset = l - 4;
	}
	if (nameoffset >= 0 && is_valid_extension(tmp, l, nameoffset)) {
	    ZDEBUG(("try_uncompress_archive: found `%s'.\n",
		    tmp + nameoffset));
	    found = 1;
	    break;
	}
    }

    fclose(fd);
    unlink(tmp_name);
    if (!found) {
	ZDEBUG(("try_uncompress_archive: no valid file found.\n"));
	return NULL;
    }

    /* This would be a valid ZIP file, but we cannot handle ZIP files in
       write mode.  Return a null temporary file name to report this.  */
    if (write_mode) {
	ZDEBUG(("try_uncompress_archive: cannot open file in write mode.\n"));
	return "";
    }

    /* And then file inside zip.  If we have a zipcode extract all of them
       to the same file. */
    argv[0] = stralloc(program);
    argv[1] = stralloc(extractopts);
    argv[2] = stralloc(name);
    if (is_zipcode_name(tmp + nameoffset)) {
	argv[3] = stralloc(tmp + nameoffset);
	argv[4] = stralloc(tmp + nameoffset);
	argv[5] = stralloc(tmp + nameoffset);
	argv[6] = stralloc(tmp + nameoffset);
	argv[7] = NULL;
	argv[3][0] = '1';
	argv[4][0] = '2';
	argv[5][0] = '3';
	argv[6][0] = '4';
    } else {
	argv[3] = stralloc(tmp + nameoffset);
	argv[4] = NULL;
    }

    ZDEBUG(("try_uncompress_archive: spawning `%s %s %s %s'.\n",
	    program, extractopts, name, tmp + nameoffset));
    exit_status = spawn(program, argv, tmp_name, NULL);

    free(argv[0]);
    free(argv[1]);
    free(argv[2]);
    free(argv[3]);
    free(argv[4]);
    if (is_zipcode_name(tmp + nameoffset)) {
	free(argv[5]);
	free(argv[6]);
	free(argv[7]);
    }

    if (exit_status != 0) {
	ZDEBUG(("try_uncompress_archive: `%s %s' failed.",
		program, extractopts));
	unlink(tmp_name);
	return NULL;
    }

    ZDEBUG(("try_uncompress_archive: `%s %s' successful.", program,
	    archive));
    return tmp_name;
}

/* If this file looks like a zipcode, try to extract is using c1541. We have
   to figure this out by reading the contents of the file */
static char *try_uncompress_zipcode(const char *name, int write_mode)
{
    static char			 tmp_name[L_tmpnam];
    int				 fd, i, count, sector, sectors = 0;
    unsigned char		 tmp[256];
    char			*argv[5];
    int				 exit_status;

    /* can we read this file? */
    fd = open(name, O_RDONLY);
    if (fd < 0)
	return NULL;
    /* Read first track to see if this is zipcode */
    lseek(fd, 4, SEEK_SET);
    for (count = 1; count < 21; count++) {
	i = zipcode_read_sector(fd, 1, &sector, tmp);
	if (i || sector < 0 || sector > 20 || (sectors & (1 << sector))) {
	    close(fd);
	    return NULL;
	}
	sectors |= 1 << sector;
    }
    close(fd);

    /* it is a zipcode. We cannot support write_mode */
    if (write_mode)
	return "";

    /* format image first */
    tmpnam(tmp_name);
    argv[0] = stralloc("c1541");
    argv[1] = stralloc("-format");
    argv[2] = stralloc(tmp_name);
    argv[3] = stralloc("a,bc");
    argv[4] = NULL;

    exit_status = spawn("c1541", argv, NULL, NULL);

    free(argv[0]);
    free(argv[1]);
    free(argv[2]);
    free(argv[3]);

    if (exit_status) {
	unlink(tmp_name);
	return NULL;
    }

    /* ok, now extract the zipcode */
    argv[0] = stralloc("c1541");
    argv[1] = stralloc("-zcreate");
    argv[2] = stralloc(tmp_name);
    argv[3] = stralloc(name);
    argv[4] = NULL;

    exit_status = spawn("c1541", argv, NULL, NULL);

    free(argv[0]);
    free(argv[1]);
    free(argv[2]);
    free(argv[3]);

    if (exit_status) {
	unlink(tmp_name);
	return NULL;
    }
    /* everything ok */
    return tmp_name;
}

/* If the file looks like a lynx image, try to extract it using c1541. We have
   to figure this out by reading the contsnts of the file */
static char *try_uncompress_lynx(const char *name, int write_mode)
{
    static char			 tmp_name[L_tmpnam];
    int				 fd, i, count;
    unsigned char		 tmp[256];
    char			*argv[5];
    int				 exit_status;

    /* can we read this file? */
    fd = open(name, O_RDONLY);
    if (fd < 0)
	return NULL;
    /* is this lynx -image? */
    i = read(fd, tmp, 2);
    if (i != 2 || tmp[0] != 1 || tmp[1] != 8) {
	close(fd);
	return NULL;
    }
    count = 0;
    while (1) {
	i = read(fd, tmp, 1);
	if (i != 1) {
	    close(fd);
	    return NULL;
	}
	if (tmp[0])
	    count = 0;
	else
	    count++;
	if (count == 3)
	    break;
    }
    i = read(fd, tmp, 1);
    if (i != 1 || tmp[0] != 13) {
	close(fd);
	return NULL;
    }
    count = 0;
    while (1) {
	i = read(fd, &tmp[count], 1);
	if (i != 1 || count == 254) {
	    close(fd);
	    return NULL;
	}
	if (tmp[count++] == 13)
	    break;
    }
    tmp[count] = 0;
    if (!atoi(tmp)) {
	close(fd);
	return NULL;
    }
    /* XXX: this is not a full check, but perhaps enough? */

    close(fd);

    /* it is a lynx image. We cannot support write_mode */
    if (write_mode)
	return "";

    /* format image first */
    tmpnam(tmp_name);
    argv[0] = stralloc("c1541");
    argv[1] = stralloc("-format");
    argv[2] = stralloc(tmp_name);
    argv[3] = stralloc("a,bc");
    argv[4] = NULL;

    exit_status = spawn("c1541", argv, NULL, NULL);

    free(argv[0]);
    free(argv[1]);
    free(argv[2]);
    free(argv[3]);

    if (exit_status) {
	unlink(tmp_name);
	return NULL;
    }

    /* ok, now create the image */
    argv[0] = stralloc("c1541");
    argv[1] = stralloc("-unlynx");
    argv[2] = stralloc(tmp_name);
    argv[3] = stralloc(name);
    argv[4] = NULL;

    exit_status = spawn("c1541", argv, NULL, NULL);

    free(argv[0]);
    free(argv[1]);
    free(argv[2]);
    free(argv[3]);

    if (exit_status) {
	unlink(tmp_name);
	return NULL;
    }
    /* everything ok */
    return tmp_name;
}

/* List of archives we understand.  */
static struct {
    char	*program;
    char	*listopts;
    char	*extractopts;
    char	*extension;
    char	*search;
} valid_archives[] = {
#ifndef __MSDOS__
    { "unzip",	"-l",	"-p",		".zip",		"Name" },
    { "lha",	"lv",	"pq",		".lzh",		NULL },
    { "lha",	"lv",	"pq",		".lha",		NULL },
    /* Hmmm.  Did non-gnu tar have a -O -option?  */
    { "gtar",	"-tf",	"-xOf",		".tar",		NULL },
    { "tar",	"-tf",	"-xOf",		".tar",		NULL },
    { "gtar",	"-ztf",	"-zxOf",	".tar.gz",	NULL },
    { "tar",	"-ztf",	"-zxOf",	".tar.gz",	NULL },
    { "gtar",	"-ztf",	"-zxOf",	".tgz",		NULL },
    { "tar",	"-ztf",	"-zxOf",	".tgz",		NULL },
    /* this might be overkill, but adding this was sooo easy...  */
    { "zoo",	"lf1q",	"xpq",		".zoo",		NULL },
#endif
    { NULL }
};


/* Try to uncompress file `name' using the algorithms we now of.  If this is
   not possible, return `COMPR_NONE'.  Otherwise, uncompress the file into a
   temporary file, return the type of algorithm used and the name of the
   temporary file in `tmp_name'.  If `write_mode' is non-zero and the
   returned `tmp_name' has zero length, then the file cannot be accessed in
   write mode.  */
static enum compression_type try_uncompress(const char *name, char **tmp_name,
					    int write_mode)
{
    int i;

    for (i = 0; valid_archives[i].program; i++) {
	if ((*tmp_name = try_uncompress_archive(name, write_mode,
						valid_archives[i].program,
						valid_archives[i].listopts,
						valid_archives[i].extractopts,
						valid_archives[i].extension,
						valid_archives[i].search))
	    != NULL) {
	    return COMPR_ARCHIVE;
	}
    }

    /* need this order or .tar.gz is misunderstood */
    if ((*tmp_name = try_uncompress_with_gzip(name)) != NULL)
	return COMPR_GZIP;

    if ((*tmp_name = try_uncompress_with_bzip(name)) != NULL)
	return COMPR_BZIP;

    if ((*tmp_name = try_uncompress_zipcode(name, write_mode)) != NULL)
	return COMPR_ZIPCODE;

    if ((*tmp_name = try_uncompress_lynx(name, write_mode)) != NULL)
	return COMPR_LYNX;

    return COMPR_NONE;
}

/* ------------------------------------------------------------------------- */

/* Compression.  */

/* Compress `src' into `dest' using gzip.  */
static int compress_with_gzip(const char *src, const char *dest)
{
    static char *argv[4];
    int exit_status;

    /* `exec*()' does not want these to be constant...  */
    argv[0] = stralloc("gzip");
    argv[1] = stralloc("-c");
    argv[2] = stralloc(src);
    argv[3] = NULL;

    ZDEBUG(("compress_with_gzip: spawning gzip -c %s\n", src));
    exit_status = spawn("gzip", argv, dest, NULL);

    free(argv[0]);
    free(argv[1]);
    free(argv[2]);

    if (exit_status == 0) {
	ZDEBUG(("compress_with_gzip: OK.\n"));
	return 0;
    } else {
	ZDEBUG(("compress_with_gzip: failed.\n"));
	return -1;
    }
}

/* Compress `src' into `dest' using bzip.  */
static int compress_with_bzip(const char *src, const char *dest)
{
    static char *argv[4];
    int exit_status;

    /* `exec*()' does not want these to be constant...  */
    argv[0] = stralloc("bzip2");
    argv[1] = stralloc("-c");
    argv[2] = stralloc(src);
    argv[3] = NULL;

    ZDEBUG(("compress_with_bzip: spawning bzip -c %s\n", src));
    exit_status = spawn("bzip2", argv, dest, NULL);

    free(argv[0]);
    free(argv[1]);
    free(argv[2]);

    if (exit_status == 0) {
	ZDEBUG(("compress_with_bzip: OK.\n"));
	return 0;
    } else {
	ZDEBUG(("compress_with_bzip: failed.\n"));
	return -1;
    }
}

/* Compress `src' into `dest' using algorithm `type'.  */
static int compress(const char *src, const char *dest,
		    enum compression_type type)
{
    char *dest_backup_name;
    int retval;

    /* This shouldn't happen */
    if (type == COMPR_ARCHIVE)
    {
	fprintf(stderr, "compress: trying to compress archive -file\n");
	return -1;
    }

    /* This shouldn't happen */
    if (type == COMPR_ZIPCODE)
    {
	fprintf(stderr, "compress: trying to compress zipcode -file\n");
	return -1;
    }

    /* This shouldn't happen */
    if (type == COMPR_LYNX)
    {
	fprintf(stderr, "compress: trying to compress lynx -file\n");
	return -1;
    }

    /* Check whether `compression_type' is a known one.  */
    if (type != COMPR_GZIP && type != COMPR_BZIP) {
        ZDEBUG(("compress: unknown compression type\n"));
	return -1;
    }

    /* If we have no write permissions for `dest', give up.  */
    if (access(dest, W_OK) < 0) {
        ZDEBUG(("compress: no write permissions for `%s'\n",
                dest));
	return -1;
    }

    if (access(dest, R_OK) < 0) {
        ZDEBUG(("compress: no read permissions for `%s'\n",
               dest));
	perror(dest);
	dest_backup_name = NULL;
    } else {
	/* If `dest' exists, make a backup first.  */
	dest_backup_name = make_backup_filename(dest);
	if (dest_backup_name != NULL)
	    ZDEBUG(("compress: making backup %s... ", dest_backup_name));
	if (dest_backup_name != NULL && rename(dest, dest_backup_name) < 0) {
	    ZDEBUG(("failed.\n"));
	    perror("rename");
	    fprintf(stderr, "Could not make pre-compression backup.\n");
	    return -1;
	} else {
	    ZDEBUG(("OK\n"));
	}
    }

    switch (type) {
      case COMPR_GZIP:
	retval = compress_with_gzip(src, dest);
	break;
      case COMPR_BZIP:
	retval = compress_with_bzip(src, dest);
	break;
      default:
	retval = -1;
    }

    if (retval == -1) {
	/* Compression failed: restore original file.  */
	if (dest_backup_name != NULL && rename(dest_backup_name, dest) < 0) {
	    perror("rename");
	    fprintf(stderr, "Could not restore backup file after failed compression.\n");
	}
    } else {
	/* Compression succeeded: remove backup file.  */
	if (dest_backup_name != NULL && unlink(dest_backup_name) < 0) {
	    perror("unlink");
	    fprintf(stderr, "Warning: could not remove backup file.\n");
	    /* Do not return an error anyway (no data is lost).  */
	}
    }

    if (dest_backup_name)
	free(dest_backup_name);
    return retval;
}

/* ------------------------------------------------------------------------ */

/* Here we have the actual open/fopen and close/fclose wrappers.

   These functions work exactly like the standard library versions, but
   handle compression and decompression automatically.  When a file is
   opened, we check whether it looks like a compressed file of some kind.
   If so, we uncompress it and then actually open the uncompressed version.
   When a file that was opened for writing is closed, we re-compress the
   uncompressed version and update the original file.  */

/* `open()' wrapper.  */
int zopen(const char *name, mode_t opt, int flags)
{
    char *tmp_name;
    int fd;
    enum compression_type type;
    int write_mode;

    if (!zinit_done)
	zinit();

    /* Do we want to write to this file?  */
    write_mode = opt & (O_RDWR | O_WRONLY);

    /* Check for write permissions.  */
    if (write_mode && access(name, W_OK) < 0)
	return -1;

    type = try_uncompress(name, &tmp_name, write_mode);
    if (type == COMPR_NONE)
	return open(name, opt, flags);
    else if (*tmp_name == '\0') {
	errno = EACCES;
	return -1;
    }

    /* (Arghl...  The following code is very nice, except that it cannot work
       backwards, and it also clobbers `type' causing even "plain"
       compression not to work correctly anymore.)  */

#if 0
    /* OK, we managed to decompress that. Let's see if we can do that again.
       If we can, we can delete the previous tmpfile */
    while (1) {
	type = try_uncompress(tmp_name, &tmp_name2, write_mode);
	if (type == COMPR_NONE)
	    break;
	if (*tmp_name == '\0') {
	    unlink(tmp_name);
	    errno = EACCES;
	    return -1;
	}
	unlink(tmp_name);
	tmp_name = tmp_name2;
    }
#endif

    /* Open the uncompressed version of the file.  */
    fd = open(tmp_name, opt, flags);
    if (fd < 0)
	return fd;

    zfile_list_add(tmp_name, name, type, write_mode, NULL, fd);
    return fd;
}

/* `fopen()' wrapper.  */
FILE *zfopen(const char *name, const char *mode)
{
    char *tmp_name;
    FILE *stream;
    enum compression_type type;
    int write_mode;

    if (!zinit_done)
	zinit();

    /* Do we want to write to this file?  */
    write_mode = (strchr(mode, 'w') != NULL);

    /* Check for write permissions.  */
    if (write_mode && access(name, W_OK) < 0)
	return NULL;

    type = try_uncompress(name, &tmp_name, write_mode);
    if (type == COMPR_NONE)
	return fopen(name, mode);
    else if (*tmp_name == '\0') {
	errno = EACCES;
	return NULL;
    }

    /* Open the uncompressed version of the file.  */
    stream = fopen(tmp_name, mode);
    if (stream == NULL)
	return NULL;

    zfile_list_add(tmp_name, name, type, write_mode, stream, -1);
    return stream;
}

/* Handle close of a compressed file.  `ptr' points to the zfile to close.  */
static int handle_close(struct zfile *ptr)
{
    ZDEBUG(("handle_close: closing `%s' (`%s'), write_mode = %d\n",
            ptr->tmp_name, ptr->orig_name, ptr->write_mode));

    /* Recompress into the original file.  */
    if (ptr->write_mode
	&& compress(ptr->tmp_name, ptr->orig_name, ptr->type))
	return -1;

    /* Remove temporary file.  */
    if (unlink(ptr->tmp_name) < 0)
	perror(ptr->tmp_name);

    /* Remove item from list.  */
    if (ptr->prev != NULL)
	ptr->prev->next = ptr->next;
    else
	zfile_list = ptr->next;
    free(ptr->orig_name);
    free(ptr->tmp_name);
    free(ptr);

    return 0;
}

/* `close()' wrapper.  */
int zclose(int fd)
{
    struct zfile *ptr;

    if (!zinit_done) {
        ZDEBUG(("zclose: closing without init!?\n"));
	errno = EBADF;
	return -1;
    }

    ZDEBUG(("zclose: searching for the matching file...\n"));

    /* Search for the matching file in the list.  */
    for (ptr = zfile_list; ptr != NULL; ptr = ptr->next) {
	if (ptr->fd == fd) {

	    ZDEBUG(("zclose: file found, closing.\n"));

	    /* Close temporary file.  */
	    if (close(fd) == -1) {
	        ZDEBUG(("zclose: cannot close temporary file: %s\n",
	                strerror(errno)));
		return -1;
	    }

	    if (handle_close(ptr) < 0) {
		errno = EBADF;
		return -1;
	    }

	    return 0;
	}
    }

    ZDEBUG(("zclose: file descriptor not in the list, closing normally.\n"));
    return close(fd);
}

/* `fclose()' wrapper.  */
int zfclose(FILE *stream)
{
    struct zfile *ptr;

    if (!zinit_done) {
	errno = EBADF;
	return -1;
    }

    /* Search for the matching file in the list.  */
    for (ptr = zfile_list; ptr != NULL; ptr = ptr->next) {
	if (ptr->stream == stream) {

	    /* Close temporary file.  */
	    if (fclose(stream) == -1)
		return -1;

	    if (handle_close(ptr) < 0) {
		errno = EBADF;
		return -1;
	    }

	    return 0;
	}
    }

    return fclose(stream);
}

/* Close all files.  */
int zclose_all(void)
{
    struct zfile *p = zfile_list, *pnext;
    int ret = 0;

    if (!zinit_done)
	return 0;

    while (p != NULL) {
	if (p->stream != NULL) {
	    if (fclose(p->stream) == -1)
		ret = -1;
	} else if (p->fd != -1) {
	    if (close(p->fd) == -1)
		ret = -1;
	} else {
	    fprintf(stderr, "Inconsistent zfile list!\n");
	    continue;
	}
	/* Recompress into the original file.  */
	if (p->write_mode && compress(p->tmp_name, p->orig_name, p->type))
	    return -1;
	if (unlink(p->tmp_name) < 0)
	    perror(p->tmp_name);
	free(p->orig_name);
	free(p->tmp_name);
	pnext = p->next;
	free(p);
	p = pnext;
    }
    return ret;
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.