/*
 * logres.c
 *
 * parse all input fields, truncate fields that are too long.
 * resolve IP addresses into host names, when possible.
 * write clean record to new log file.  Parse errors go to message
 * file specified with -m.  Otherwise they go to /dev/null.
 * keeps all IP addresses (found and not found) in a hash table.
 * write the hash table to disk file when finished.
 */

#include <stdio.h>
#include <string.h>
#include <ctype.h>			/* for isdigit */
#include <unistd.h>			/* for getuid on Solaris */
#include <sys/types.h>			/* for lookuphost */
#include <sys/socket.h>			/* for lookuphost */
#include <netdb.h>			/* for lookuphost */
#include <netinet/in.h>			/* for in_addr */
#include <arpa/inet.h>			/* for gethostbyaddr on Solaris */
#include <fcntl.h>			/* for open */
#include <sys/signal.h>			/* for signal */
#include <sys/stat.h>			/* for stat */
#include <sys/time.h>			/* for timeval, used by resource.h */
#include <sys/resource.h>		/* for setrlimit */
#include <time.h>
#include "gs-parse.h"			/* for ParseAccSTD */
#include "getstats.h"
#include "logres.h"

#define VERSION		"3.0"

					/* print message if request takes this long */
#define DNS_LONGTIME 10
					/* give up if request takes this long */
#define DNS_TOOLONG  14
					/* erase the hash file when it gets this big */
#define MAX_HASH_ENTRIES   500000

int	debug = FALSE;			/* debug, filesopen, logcopy required by gs-parse.c */
int	filesopen = 0;
char	logcopy[MAXLINE+1];		/* current input line, for error messages */

char	*gzipbin=GZIPBIN;
char	*hashfilnam="/www/data/httpd/pl/iptable.bin";

static struct IPtable *iptable[HASH_SIZE] = {0};
int	nhashed=0,
	timedout=0,			/* number of DNS lookups timed out */
	dns_timed_out;			/* flag set by interrupt handler */

int truncline, truncreq, truncref;
int failshort, faildate, failmin, failhost, failprefix, failreq;
char *infilnam=NULL, *outfilnam=NULL, *msgfilnam=NULL, *cmdfilnam=NULL;
int hashwritable=1,			/* hash file can be overwritten */
    killinput=0;			/* remove input file after reading */
off_t hashfilsiz=-1;			/* size of hash file */
uid_t hashfilown=-1,			/* uid that owns hash file */
      myuid=-1;				/* uid of this process */


int main(argc, argv)
int argc;
char **argv;
{
    char *word[NCMD_FIELDS+1];
    char *msg;

    do_args(argc, argv);
    printf("logres    v%s\n\n", VERSION);
    myuid = getuid();
    gDoLoadCommand();			/* load hash table */
    if (cmdfilnam) {
	greadcmdfile(cmdfilnam);
	return(0);
    }
    word[0] = "resolve";		/* no command file specified */
    word[1] = infilnam;			/* use the -i and -o arguments */
    word[2] = outfilnam;
    word[3] = msgfilnam;
    gDoResCommand(word, 4);
    return(0);
} /* main */



int do_args(argc, argv)
int argc;
char **argv;
{
    extern int optind;
    extern char *optarg;
    int c;

    while ((c = getopt(argc, argv, "i:o:c:rk")) != EOF)
      switch(c) {
	case 'i':
	  infilnam = optarg;		/* get input file name */
	  break;
	case 'o':
	  outfilnam = optarg;		/* get output file name */
	  break;
	case 'm':
	  msgfilnam = optarg;		/* get message file name */
	  break;
	case 'c':
	  cmdfilnam = optarg;		/* get command file name */
	  break;
	case 'r':
	  hashwritable = 0;		/* hash file is read-only */
	  break;
	case 'k':
	  killinput = 1;		/* remove input file */
	  break;
	case '?':
	  usage();
	  break;
	}
    if (optind < argc)			/* command options not valid */
	usage();

    /* check for required arguments */
    if (((infilnam==NULL) || (outfilnam==NULL)) && (cmdfilnam==NULL))
	usage();
    return;
}


int usage(void)
{
    fprintf(stderr,
"Usage:
     logres [options] -c cmdfil
or   logres [options] -i infil -o outfil [-m msgfil]

     if msgfil is specified the parse errors go there
     otherwise they go to /dev/null

     options are:
     -k  remove input files after processing them
     -r  do not overwrite the hash file (read-only)
");
    exit(3);
}



/* add this IP address and domain name to the hash table
 * call this routine after you know it's not in the hash table
 */
void addip(hashval, ipnum, domain)
unsigned int hashval;
u_long ipnum;
char *domain;
{
    struct IPtable *ip = (struct IPtable *) emalloc(sizeof(*ip));
    ip->ipaddr = ipnum;
    if (domain)	ip->domain = e_strdup(domain);
    else	ip->domain = NULL;
    ip->next = iptable[hashval];
    iptable[hashval] = ip;
    nhashed++;
    return;
}



/* lookup this IP address in the hash table
 *
 * returns 0 if not found in hash table
 *         1 if found, but no corresponding host name
 *         2 if found, with host name returned in *domain
 */
int find_domain(hashval, ipnum, domain)
unsigned int hashval;
u_long ipnum;
char **domain;
{
    struct IPtable *ip;
    *domain = NULL;
    for (ip = iptable[hashval]; ip != NULL; ip = ip->next)
	if (ipnum == ip->ipaddr) {		/* found in hash */
	    if (ip->domain==NULL)
		return 1;
	    else {
		*domain = ip->domain;
		return 2;
	    }
	}
    return 0;					/* not found */
}



/* write the hash table to disk, overwriting the old hash file
 * returns  -1   nothing to write, hash table empty
 *          -2   can't overwrite hash file
 *           n   number of entries in the largest bucket
 */
int dump_hash_table(hasfilnam)
char *hasfilnam;
{
    FILE *has;
    int bucket, bigbucket=0, nwritten=0;
    struct IPtable *ip;

    if (!nhashed)
	return -1;			/* hash table is empty */
    if (hashwritable)
	if ((has = fopen(hasfilnam, "w")) == NULL) {
	    fprintf(stderr, "can't overwrite hash file %s ", hasfilnam);
	    perror("because");
	    return -2;
	}

    /* write IP address and domain name pairs
	ignore IP addresses that were not found by named */
    for (bucket = 0; bucket < HASH_SIZE; bucket++) {
	int e=0;			/* count entries in this bucket */
	for (ip = iptable[bucket]; ip != NULL; ip = ip->next) {
	  e++;
	  if (hashwritable && ip->domain) {	/* 4-byte IP address */
	    fwrite(ip, 1, sizeof(ip->ipaddr), has);
	    fputs(ip->domain, has);		/* domain name */
	    putc('\n', has);			/* newline */
	    nwritten++;
	  }
	}
	if (e > bigbucket) bigbucket = e;	/* size of biggest bucket */
    }
    if (hashwritable) {
	fclose(has);
	printf("%d out of %d hash table entries written to %s\n",
			nwritten, nhashed, hasfilnam);
    }
    return bigbucket;
} /* dump_hash_table */



/* read IP,host pairs from the disk file, add them to hash table
 * returns the number of entries added to the hash table
 */
int load_hash_table(hasfilnam)
char *hasfilnam;
{
    FILE *has;
    int xhashed = nhashed;			/* entries before loading */
    int nrecs = 0;				/* records in hash file */
    u_long ipnum;				/* IP address in 4 bytes */
    char domain[DOMAIN_LEN];

    if ((has = fopen(hasfilnam, "r")) == NULL) {
	fprintf(stderr, "can't open hash file %s ", hasfilnam);
	perror("because");
	return 0;				/* can't open hash file */
    }
    if (xhashed)
	printf("hash table contains %d entries before loading\n", xhashed);
    while (fread(&ipnum, 1, sizeof(ipnum), has)) { /* read 4 byte IP addr */
	unsigned int hashval;
	char *nl;
	nrecs++;
	if (!fgets(domain, DOMAIN_LEN, has)) {	/* read domain name */
	    printf("hash record %d is invalid\n", nrecs);
	    exit(5);
	}
	nl = strchr(domain, '\n');
	if (nl) *nl = '\0';
	hashval = hash(ipnum);			/* bucket number */
	addip(hashval, ipnum, domain);		/* add hash table entry */
    }
    fclose(has);
    printf("%d hash table entries loaded from %s\n",
						nhashed-xhashed, hasfilnam);
    if (xhashed)
	printf("hash table now contains %d entries\n", nhashed);
    return nhashed-xhashed;
} /* load_hash_table */



char *e_strdup(estring)
char *estring;
{
    char *p = (char *) malloc(strlen(estring)+1);
    if (p == NULL) {
	printf("not enough memory! [%s] %d entries hashed\n",
					estring, nhashed);
	exit(5);
    }
    strcpy(p, estring);
    return p;
}



void *emalloc(i)
int i;
{
    void *p  = (void *) malloc(i);
    if (p == NULL) {
	printf("not enough memory! %d entries hashed\n", nhashed);
	exit(5);
    }
    return p;
}



/* handle SIGALRM interrupt */
void *dns_timeout(void) {
    dns_timed_out = 1;
    timedout++;
    return 0;
}



/* use DNS to find the host name that corresponds to this IP address
 * returns the host name, or NULL
 */
char *lookuphost(pin, ipnam, err)
struct in_addr *pin;
char *ipnam;
FILE *err;
{
    int start, nsecs;
    struct hostent *host;

    signal(SIGALRM, dns_timeout);		/* wait this long for */
    alarm(DNS_TOOLONG);				/*  name server response */
    dns_timed_out = 0;
    start = (int) time(NULL);
    host = gethostbyaddr((char *) pin, sizeof(*pin), AF_INET);
    alarm(0);
    if (dns_timed_out) {
	if (err)
	    fprintf(err, "%d second timeout looking for %s\n",
				DNS_TOOLONG, ipnam);
	return(NULL);				/* failed */
    }
    nsecs = (int) time(NULL) - start;
    if (err && (nsecs >= DNS_LONGTIME)) {
	fprintf(err, "%d seconds to find %s", nsecs, ipnam);
	if (host && host->h_name)
	    fprintf(err, " %s", host->h_name);
	fprintf(err, "\n");
    }
    if (host && host->h_name) {
	makelower(host->h_name);		/* cvt to lower case */
	return(host->h_name);			/* success */
    }
    return(NULL);				/* failed */
} /* lookuphost */



void makelower(string)
char *string;
{
    while (*string)
	*string++ = tolower(*string);
}



/* read a line up to '\n', ignoring NULLs
 * control characters are translated to ^X format
 * returns TRUE on EOF
 */
int greadaline(logline, bufsize, fp)
char logline[];
int bufsize;
FILE *fp;
{
    int c, ip = 0;
    do {
	c = getc(fp);

	if ((c=='\n') || (c==EOF))	/* do not echo newline or EOF */
	    ;
	else if (ip >= bufsize-1)	/* ignore excess characters */
	    ;
	else if (c & 0200)		/* translate garbage to '!' */
	    logline[ip++] = '!';
	else if (c == 0177) {		/* translate <DEL> to "^?" */
	    logline[ip++] = '^';
	    logline[ip++] = '?';
	} else if (c == '\0')		/* ignore NULL */
	    ;
	else if (c < ' ') {		/* translate control characters */
	    logline[ip++] = '^'; 	/* to "^X" */
	    logline[ip++] = c + 'A' - 1;
	} else				/* normal char, keep it */
	    logline[ip++] = c;
    } while (c != '\n' && c != EOF);
    logline[ip] = '\0';
    if (ip >= bufsize-1)
	truncline++;
    return (c == EOF);
} /* greadaline */



/* parse and execute each line of the command file
 *
 */
void greadcmdfile(cmdfilnam)
char *cmdfilnam;
{
    char cmdline[CMD_LEN];
    char *cmd_result = NULL;
    FILE *cmd;

    if (strcmp(cmdfilnam, "-") == 0)
	cmd = stdin;
    else if ((cmd = fopen(cmdfilnam, "r")) == NULL) {
	fprintf(stderr, "Can't open command file %s ", cmdfilnam);
	perror("because");
	exit(3);
    }
    while (fgets(cmdline, CMD_LEN, cmd)) { /* read a line */
	int i, nwords;
	char *word[NCMD_FIELDS+1],
	     *c, *command;

	c = strchr(cmdline, '\n');	/* erase the trailing newline */
	if (c) *c = '\0';
	if (*cmdline != '#') {		/* ignore comments */
	    ggetwords(cmdline, word, &nwords);
	    command = word[0];
	    if (!strcmp(command, "resolve"))
		gDoResCommand(word, nwords);
	    else
		printf("invalid command: %s\n", cmdline);
	} /* ignore comments */
    } /* read a line */
    if (strcmp(cmdfilnam, "-"))
	fclose(cmd);
    return;
} /* greadcmdfile */



/* ggetwords
 *
 * create an array of pointers to individual command line fields
 * if any field begins with a doublequote, strip the quotes and
 * allow it to contain spaces.  any fields not surrounded by quotes
 * are converted to lower case.
 */
void ggetwords(buffer, word, nwords)
char *buffer;
char *word[];
int *nwords;
{
    static char bcopy[MAX_LINE_IN+1];
    char *c = bcopy;
    int i;
   
    strcpy(bcopy, buffer);
    for (i = 0; i <= NCMD_FIELDS && *c; i++) {
	int done;
	char stopchar = ' ';
	if (*c == '"') {
	    stopchar = '"';
	    c++;
	}
	word[i] = c;			/* begin */
	done = FALSE;
	while (!done) {			/* parse one field */
	    char *d;
	    if (*c == '\0')
		  done = TRUE;
	    else if (*c == stopchar) {
		*c++ = '\0';		/* end */
		done = TRUE;
		while (*c == ' ')
		      *c++ = '\0';
	    } else
		  c++;
	} /* while not done */
	if (stopchar != '"')		/* if not a quoted field */
	    makelower(word[i]);		/* cvt to lower case */
    } /* for */
    *nwords = i;
} /* ggetwords */



/* parse and execute the LOAD command
 *
 * every eight weeks or so, when the hash table reaches
 * MAX_HASH_ENTRIES, the hash file is erased, and starts building again.
 * this ensures that stale entries are removed periodically.
 */
void gDoLoadCommand(void)
{
    struct stat s;
    int ql=0;
    FILE *has;
    prtime();				/* print current time */
    if (stat(hashfilnam, &s)==0) {	/* found it */
	printf("load from %s\n", hashfilnam);
	ql=load_hash_table(hashfilnam); /* load into memory */
    } else {				/* create empty file */
	if ((has=fopen(hashfilnam, "w"))==0)
	    return;
	printf("new file created %s\n", hashfilnam);
	fclose(has);
	if (stat(hashfilnam, &s)) return;
    }
    hashfilsiz = s.st_size;		/* remember file size */
    hashfilown = s.st_uid;		/* remember the owner */
    if (ql>MAX_HASH_ENTRIES) {
	if ((has=fopen(hashfilnam, "w"))==0)
	    return;
	printf("hash file erased %s\n", hashfilnam);
	printf("  because it has more than %d entries\n",
					MAX_HASH_ENTRIES);
	fclose(has);
	/* do not update hashfilsiz so the hash table will not be dumped
	 * at the conclusion of this run.  we want the next job to
	 * start with an empty hash file.
	 */
    }
    return;
} /* gDoLoadCommand */



/* parse and execute the RESOLVE command */
void gDoResCommand(word, nwords)
char *word[];
int nwords;
{
    int ip_found=0, ip_not_found=0, found_in_hash=0, nrecs=0;
    FILE *in=NULL, *out=NULL, *err=stderr;
    char buf[MAX_LINE_IN+1];
    char temp[DATELEN];
    char *infilnam  = word[1],
	 *outfilnam = word[2],
	 *msgfilnam = word[3];

    if (nwords != 4) {
	fprintf(stderr, "RESOLVE command requires 4 arguments\n");
	return;
    }
    prtime();				/* print current time */
    printf("resolve to %s\n", outfilnam);

					/* OPEN MESSAGE FILE */
    /* if there was no -m on the command line redirect parse errors
     * to /dev/null
     */
    if (msgfilnam)
	printf("parse errors to %s\n", msgfilnam);
    if (!msgfilnam) {
      if ((err=fopen("/dev/null", "w"))==NULL) {
	fprintf(stderr, "can't write to /dev/null ");
	perror("because");
	return;
      }
    } else if (strstr(msgfilnam, ".gz")) { /* pipe through gzip */
	write_compressed(&err, msgfilnam);
	if (!err) return;		/* error */
    } else if ((err=fopen(msgfilnam, "w"))==NULL) {
	fprintf(stderr, "can't create %s ", msgfilnam);
	perror("because");
	return;
    }
					/* OPEN INPUT FILE */
    if (strstr(infilnam, ".gz")) {	/* pipe through gunzip */
	read_uncompressed(&in, infilnam);
	if (!in) return;		/* error */
    } else if ((in = fopen(infilnam, "r")) == NULL) {
	fprintf(stderr, "can't open %s ", infilnam);
	perror("because");
	return;
    }

					/* OPEN OUTPUT FILE */
    if (strstr(outfilnam, ".gz")) {	/* pipe through gzip */
	write_compressed(&out, outfilnam);
	if (!out) return;		/* error */
    } else if ((out = fopen(outfilnam, "w")) == NULL) {
	fprintf(stderr, "can't create %s ", outfilnam);
	perror("because");
	return;
    }

    truncline=0; truncreq=0; truncref=0;
    failshort=0; faildate=0; failmin=0; failhost=0; failprefix=0;
    failreq=0;
    timedout=0;				/* number of DNS lookups timed out */

    while (!greadaline(buf, MAX_LINE_IN, in)) {
	char	tmpreq[REQUESTLEN*2],	/* leave room for "(GET ... )" */
		tmpref[REQUESTLEN+20],	/* leave room for "ref ..." */
		tmphos[REQUESTLEN+20],	/* leave room for "vhost ..." */
		tmpcli[REQUESTLEN+20];	/* leave room for "client ..." */
	int k = 0;
	logr r;
	unsigned int hashval;
	struct in_addr in;

	nrecs++;
	if (!ParseAccSTD(buf, &r, err))
	    continue;

	in.s_addr = inet_addr(r.address);	/* separate octets */
	if (in.s_addr != 0xffffffffu) {		/* numeric address */
	    int hresult;
	    char *thisdomain;
	    hashval = hash(in.s_addr);		/* bucket number */
						/* look in hash table */
	    hresult = find_domain(hashval, in.s_addr, &thisdomain);
	    if (hresult == 2) {			/* found domain name in hash */
		found_in_hash++;
		strcpy(r.address, thisdomain);
	    } else if (hresult == 1)		/* found empty hash entry */
		found_in_hash++;		/* r.address contains IP */
	    else if (thisdomain = lookuphost(&in, r.address, NULL)) {
						/* ask DNS */
		ip_found++;
		addip(hashval, in.s_addr, thisdomain); /* insert in hash table */
		strcpy(r.address, thisdomain);
	    } else {				/* not registered with DNS */
		ip_not_found++;
		addip(hashval, in.s_addr, NULL); /* insert IP address only */
	    }
	} /* numeric */
	if (*r.rprefix || *r.request || *r.rsuffix)
	    sprintf(tmpreq, "\"(%s %s %s)\"",
			r.rprefix, r.request, r.rsuffix);
	else
	    strcpy(tmpreq, "\"(-)\"");
	if (*r.referer)	sprintf(tmpref, " \"(ref %s)\"", r.referer);
	else		*tmpref = '\0';
	if (*r.vhost)	sprintf(tmphos, " \"vhost %s\"", r.vhost);
	else		*tmphos = '\0';
	if (*r.client)	sprintf(tmpcli, " \"(client %s)\"", r.client);
	else		*tmpcli = '\0';
	fprintf(out, "%s %s %s [%s] %s %s %s%s%s%s\n",
		r.address, r.ident, r.user, r.date, tmpreq,
		r.status, r.nbytstr, tmpref, tmphos, tmpcli);
    } /* while not EOF */

    printf("%8d records read from %s\n", nrecs, infilnam);
    if (truncline)
	printf("%8d input records truncated, longer than %d bytes (MAX_LINE_IN)\n",
	   truncline, MAX_LINE_IN);
    if (truncreq)
	printf(
	   "%8d req fields truncated, longer than %d bytes (REQUESTLEN)\n",
	   truncreq, REQUESTLEN);
    if (truncref)
	printf(
	   "%8d ref fields truncated, longer than %d bytes (REQUESTLEN)\n",
	   truncref, REQUESTLEN);
    if (timedout)
	printf(
	   "%8d DNS timeouts, longer than %d seconds (DNS_TOOLONG)\n",
	   timedout, DNS_TOOLONG);
    printf("%8d + %d + %d DNS lookups succeeded + failed + saved\n",
			ip_found, ip_not_found, found_in_hash);

    if (failshort + faildate + failmin + failprefix + failhost + failreq)
	printf("       ");
    if (failshort)	printf("too short: %d   ", failshort);
    if (faildate)	printf("bad date: %d   ", faildate);
    if (failmin)	printf("fewer than 7 fields: %d   ", failmin);
    if (failprefix)	printf("invalid prefix: %d   ", failprefix);
    if (failhost)	printf("hostname too long: %d   ", failhost);
    if (failreq)	printf("null request: %d", failreq);
    if (failshort + faildate + failmin + failprefix + failhost + failreq)
	putc('\n', stdout);

    if (in)  fclose(in);
    if (out) fclose(out);
    if (err) fclose(err);
    if (killinput) {			/* -k option: remove input file */
	if (unlink(infilnam)) {
	    fprintf(stderr, "unable to remove %s ", infilnam);
	    perror("because");
	} else
	    printf("removed input file %s\n", infilnam);
    }
    gDoDumpCommand();
    return;
} /* gDoResCommand */



/* dump the contents of the hash table to disk */
void gDoDumpCommand(void)
{
    struct stat s;
    int bigbucket;


    if (myuid != hashfilown)		/* existing hash file owned by me? */
	return;				/* no, return */
			/* yes, overwrite it with a new (bigger) version */
    prtime();				/* print current time */
    if (hashwritable) {
	printf("dump to %s\n", hashfilnam);
	if (stat(hashfilnam, &s) != 0)
	    printf("creating a new %s\n", hashfilnam);
	else if ((hashfilsiz != -1) && (hashfilsiz != s.st_size)) {
	    printf("%s has changed, not overwriting\n", hashfilnam);
	    return;
	}
    } else
	printf("checking hash statistics\n");

    if ((bigbucket=dump_hash_table(hashfilnam)) < 0)
	return;
    
    if (hashwritable) {
	if (stat(hashfilnam, &s) != 0) {
	    fprintf(stderr, "can't stat new hash file %s ", hashfilnam);
	    perror("because");
	} else
	    hashfilsiz = s.st_size;		/* remember new size */
    }
    printf("   # %d buckets, %d entries, %.1f entries per bucket\n",
		HASH_SIZE, nhashed, 1.0 * nhashed / HASH_SIZE);
    printf("   # the most crowded bucket has %d ", bigbucket);
    if (bigbucket == 1)	printf("entry\n");
    else		printf("entries\n");
    return;
} /* gDoDumpCommand */



void prtime(void)
{
    char *datfmt = "%d/%b/%Y %T";         /* "31/Jan/1995 01:02:03 -0500" */
    char date[90];
    time_t tp;

    time(&tp);
    if (TRUE)
	strftime(date, 90, datfmt, localtime(&tp));
					/* 05/Jan/1997 01:16:36 */
    else
	strcpy(date, asctime(localtime(&tp)));
					/* Sun Jan  5 01:15:28 1997 */
    printf("\n--------------------\n%s   ", date);
    return;
}



void set_ulimit(void)			/* no limit to memory size */
{
    struct rlimit rs;

    if (getrlimit(RLIMIT_DATA, &rs) != 0) perror("getrlimit");
    rs.rlim_cur = RLIM_INFINITY;	/* ulimit -d unlimited */
    if (setrlimit(RLIMIT_DATA, &rs) != 0) perror("setrlimit");

    if (getrlimit(RLIMIT_STACK, &rs) != 0) perror("getrlimit");
    rs.rlim_cur = rs.rlim_max = RLIM_INFINITY; /* ulimit -m unlimited */
    if (setrlimit(RLIMIT_STACK, &rs) != 0) perror("setrlimit");
    return;
}



unsigned int hash(addr)
u_long addr;
{
    unsigned int lefthalf  = (addr>>16) & 0xffff; /* first two octets */
    unsigned int righthalf = addr & 0xffff;	/* last two octets */
    lefthalf += righthalf;
    return (((lefthalf<<16) | righthalf) % HASH_SIZE);
}



#define MAXARGS 4
/* fork a process for output using gzip for compression
 *
 * returns -1 on error
 *          0 on success
 */
int write_compressed(zip, outfilnam)
FILE **zip;
char *outfilnam;
{
    char *a[MAXARGS];
    int pipe0[2], pipe1[2], pipe2[2];
    int out;
    FILE *fp2;
    struct stat s;

    if (!outfilnam || !(*outfilnam)) {
	printf("write_compressed requires outfilnam\n");
	return -1;
    }
    if (stat(outfilnam, &s) == 0)	/* found it */
	printf("overwriting %s\n", outfilnam);
    if ((out = open(outfilnam, O_WRONLY|O_CREAT|O_TRUNC, 0640)) <= 0) {
	fprintf(stderr, "can't create %s ", outfilnam);
	perror("because");
	return -1;
    }
    a[0] = e_strdup(gzipbin);
    a[1] = NULL;
    pipe(pipe0);
    pipe(pipe1);
    pipe(pipe2);
    switch(fork()) {
	case -1:
	    perror("fork() failed");
	    exit(3);
	case 0:
	    close(pipe0[1]);
	    close(pipe1[0]);
	    close(pipe2[0]);
	    dup2(pipe0[0], 0);		/* stdin */
	    dup2(out,      1);		/* stdout */
	    dup2(pipe2[1], 2);		/* stderr */
	    execv(gzipbin, a);
	    perror("execv() failed");
	    exit(3);
	default:
	    close(pipe0[0]);
	    close(pipe1[1]);
	    close(pipe2[1]);
	    *zip = fdopen(pipe0[1], "w");	/* write to his stdin */
	    fp2  = fdopen(pipe2[0], "r");	/* read from his stderr */
	    return 0;				/* success */
    }
} /* write_compressed */
