[comp.sources.unix] v17i068: Zoo archive program, Part05/10

rsalz@uunet.uu.net (Rich Salz) (02/03/89)

Submitted-by: Rahul Dhesi <bsu-cs!dhesi>
Posting-number: Volume 17, Issue 68
Archive-name: zoo2/part05

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 5 (of 10)."
# Wrapped by rsalz@papaya.bbn.com on Thu Feb  2 18:04:00 1989
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'comment.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'comment.c'\"
else
echo shar: Extracting \"'comment.c'\" \(9637 characters\)
sed "s/^X//" >'comment.c' <<'END_OF_FILE'
X#ifndef LINT
Xstatic char sccsid[]="@(#) comment.c 2.14 88/01/24 12:42:13";
X#endif /* LINT */
X
X/*
XCopyright (C) 1986, 1987 Rahul Dhesi -- All rights reserved
X(C) Copyright 1988 Rahul Dhesi -- All rights reserved
X*/
X
X#include "options.h"
X#include "portable.h"
X/* comment() */
X/* Updates comments */
X
X/* buffer size for any one comment line */
X#define  COMMENT_LINE_SIZE 76
X
X#define  MAX_COMMENT_SIZE  65535
X#include "zooio.h"
X#include "various.h"
X
X#ifndef NOSIGNAL
X#include <signal.h>
X#endif
X
X#include "zoo.h"
X#include "zoofns.h"
X#include "errors.i"
X
X#ifdef LINT_ARGS
Xvoid show_comment (struct direntry *, ZOOFILE, int, char *);
Xget_comment (struct direntry *, ZOOFILE, char *);
Xint needed (char *, struct direntry *, struct zoo_header *);
X#else
Xvoid show_comment ();
Xget_comment ();
Xint needed ();
X#endif
X
Xvoid comment(zoo_path, option)
Xchar *zoo_path, *option;
X{
X#ifndef NOSIGNAL  
Xint (*oldsignal)();
X#endif
XZOOFILE zoo_file;                         /* stream for open archive */
Xlong next_ptr;                            /* pointers to within archive */
Xlong this_dir_offset;                     /* pointers to within archive */
Xstruct direntry direntry;                 /* directory entry */
Xstruct zoo_header zoo_header;
Xint matched = 0;                          /* any files matched? */
Xunsigned int zoo_date, zoo_time;          /* for restoring archive timestamp */
Xchar whichname[PATHSIZE];                 /* which name to use */
X#ifdef ZOOCOMMENT
Xint acmt = 0;										/* if changing archive comment */
X#endif
X
X/* on entry option points to first letter */
Xoption++;											/* skip 'c' */
X#ifdef ZOOCOMMENT
Xwhile (*option != '\0') {
X	if (*option == 'A') {
X		acmt++;										/* changing archive comment */
X		option++;
X	} else
X   	prterror ('f', inv_option, *option);
X}
X#else
Xif (*option != '\0')
X	prterror ('f', inv_option, *option);
X#endif /* ZOOCOMMENT */
X
Xif ((zoo_file = zooopen (zoo_path, Z_RDWR)) == NOFILE)
X   prterror ('f', could_not_open, zoo_path);
X
X/* save archive timestamp */
X#ifdef GETUTIME
Xgetutime (zoo_path, &zoo_date, &zoo_time);
X#else
Xgettime (zoo_file, &zoo_date, &zoo_time);
X#endif
X
X/* read header and rewrite with updated version numbers, but ask user to pack
Xarchive first if archive comment is to be added and header type is 0 */
X#ifdef ZOOCOMMENT
Xif (acmt)
X	rwheader (&zoo_header, zoo_file, 0);
Xelse
X	rwheader (&zoo_header, zoo_file, 1);
X#else
Xrwheader (&zoo_header, zoo_file, 1);
X#endif
X
X#ifdef ZOOCOMMENT
X/* if archive comment being added, handle it and return */
Xif (acmt) {
X	void do_acmt PARMS ((struct zoo_header *, ZOOFILE, char *));
X	do_acmt (&zoo_header, zoo_file, zoo_path);
X#ifdef NIXTIME
X	zooclose (zoo_file);
X	setutime (zoo_path, zoo_date, zoo_time);	/* restore timestamp */
X#else
X	settime (zoo_file, zoo_date, zoo_time);	/* restore timestamp */
X	zooclose (zoo_file);
X#endif
X	return;
X}
X#endif /* ZOOCOMMENT */
X
X/* Loop through and add comments for matching files */
Xwhile (1) {
X   this_dir_offset = zootell (zoo_file);  /* save pos'n of this dir entry */
X   readdir (&direntry, zoo_file, 1);      /* read directory entry */
X   next_ptr = direntry.next;              /* ptr to next dir entry */
X
X   /* exit on end of directory chain or end of file */
X   if (next_ptr == 0L || feof(stdin))
X      break;
X
X	strcpy (whichname, fullpath (&direntry));		/* full pathname */
X	add_version (whichname, &direntry);				/* add version suffix */
X   /* add comments for matching non-deleted files */
X   if (!direntry.deleted && needed (whichname, &direntry, &zoo_header)) {
X      matched++;
X      show_comment (&direntry, zoo_file, 1, whichname);
X      get_comment (&direntry, zoo_file, whichname);
X      zooseek (zoo_file, this_dir_offset, 0);
X#ifndef NOSIGNAL
X      oldsignal = signal (SIGINT, SIG_IGN);
X#endif
X      fwr_dir (&direntry, zoo_file);
X#ifndef NOSIGNAL
X      signal (SIGINT, oldsignal);
X#endif
X   }
X   zooseek (zoo_file, next_ptr, 0);   /* ..seek to next dir entry */
X} /* end while */
X
X#ifdef NIXTIME
Xzooclose (zoo_file);
Xsetutime (zoo_path, zoo_date, zoo_time);	/* restore timestamp */
X#else
Xsettime (zoo_file, zoo_date, zoo_time);	/* restore timestamp */
Xzooclose (zoo_file);
X#endif
X
Xif (!matched)
X   printf ("Zoo:  %s", no_match);
X} /* comment */
X
X/* show_comment() */
X/* shows comment on screen.  If show=1, says "Current comment is..." */
X
Xvoid show_comment (direntry, zoo_file, show, name)
Xstruct direntry *direntry;
XZOOFILE zoo_file;
Xint show;
Xchar *name;       /* name of file for which comment is being added */
X{
X   if (direntry->cmt_size != 0) {
X      unsigned int i;
X      char ch;
X      int newline = 1;
X      zooseek (zoo_file, direntry->comment, 0);   
X      if (show)
X         printf ("Current comment for %s is:\n", name);
X      for (i = 0; i < direntry->cmt_size; i++) {/* show it */
X         ch = zgetc (zoo_file) & 0x7f;          /* 7 bits only */
X         if (newline)
X            printf (" |");    /* indent and mark comment lines thus */
X         zputchar (ch);
X         if (ch == '\n')
X            newline = 1;
X         else
X            newline = 0;
X      }
X      if (!newline)              /* always terminate with newline */
X         zputchar ('\n');
X   }
X} /* show_comment() */
X
X
X/* get_comment() */
X/* Shows user old comment and updates it */
X
X/* INPUT:
X   direntry points to current directory entry.
X   zoo_file is archive file.
X   this_path is full pathname of file being updated/added.
X
X   OUTPUT:
X   Comment is added to file and supplied directory entry is updated
X   with comment size and seek position but directory entry is
X   not written to file.  Exceptions:  If RETURN is hit as first line,
X   previous comment is left unchanged.  If /END is hit, previous
X   comment is superseded, even if new comment is null.
X*/
X
Xchar cmt_prompt[]="[Enter %scomment for %s then type /END]\n";
X
Xget_comment (direntry, zoo_file, this_path)  /* update comment */
Xregister struct direntry *direntry;
XZOOFILE zoo_file;
Xchar *this_path;
X{
X   unsigned int line_count = 0;        /* count of new comment lines */
X
X   zooseek (zoo_file, 0L, 2);            /* ready to append new comment */
X#if 0
X   fprintf (stderr, "[Enter comment for %s then type /END]\n", this_path);
X#else
X   fprintf (stderr, cmt_prompt, "", this_path);
X#endif
X   while (1) {
X      char cmt_line[COMMENT_LINE_SIZE];
X      int cmt_size;
X      if (fgets (cmt_line, sizeof(cmt_line), stdin) == NULL)
X         break;
X      line_count++;
X      if (line_count == 1) {                 /* first line typed */
X         if (!strcmp (cmt_line, "\n"))   /* exit if first line blank */
X            break;
X         direntry->comment = zootell (zoo_file);
X         direntry->cmt_size = 0;
X      }
X      if (!strcmpi (cmt_line, "/end\n"))
X         break;
X      cmt_size = strlen (cmt_line);
X      if (MAX_COMMENT_SIZE - direntry->cmt_size > cmt_size) {
X         direntry->cmt_size += (unsigned int) cmt_size;
X         if (zoowrite (zoo_file, cmt_line, cmt_size) < cmt_size)
X            prterror ('f', disk_full);
X      }
X   } /* end while */
X} /* get_comment() */
X
X#ifdef ZOOCOMMENT
X/*
Xdo_acmt() updates archive comment by showing it to user and 
Xrequesting a new one.  Typed input terminates as with file comment,
Xi.e., empty initial line leaves comment unchanged, case-insensitive
X"/end" terminates input comment.
X*/
Xvoid do_acmt (zoo_header, zoo_file, zoo_path)
Xstruct zoo_header *zoo_header;
XZOOFILE zoo_file;
Xchar *zoo_path;
X{
X   unsigned int line_count = 0;        /* count of new comment lines */
X	void show_acmt PARMS ((struct zoo_header *, ZOOFILE, int));
X
X	show_acmt (zoo_header, zoo_file, 1);	/* show current archive comment */
X   zooseek (zoo_file, 0L, 2);            /* ready to append new comment */
X#if 0
X   fprintf (stderr, "[Enter archive comment for %s then type /END]\n", 
X							zoo_path);
X#else
X   fprintf (stderr, cmt_prompt, "archive ", zoo_path);
X#endif
X
X   while (1) {
X      char cmt_line[COMMENT_LINE_SIZE];
X      int cmt_size;
X      if (fgets (cmt_line, sizeof(cmt_line), stdin) == NULL)
X         break;
X      line_count++;
X      if (line_count == 1) {                 /* first line typed */
X         if (!strcmp (cmt_line, "\n"))   /* exit if first line blank */
X            break;
X         zoo_header->acmt_pos = zootell (zoo_file);
X         zoo_header->acmt_len = 0;
X      }
X      if (!strcmpi (cmt_line, "/end\n"))
X         break;
X      cmt_size = strlen (cmt_line);
X      if (MAX_COMMENT_SIZE - zoo_header->acmt_len > cmt_size) {
X         zoo_header->acmt_len += (unsigned int) cmt_size;
X         if (zoowrite (zoo_file, cmt_line, cmt_size) < cmt_size)
X            prterror ('f', disk_full);
X      }
X   } /* end while */
X	zooseek (zoo_file, 0L, 0);					/* seek back to beginning */
X	fwr_zooh (zoo_header, zoo_file);			/* write update zoo_header */
X} /* do_acmt() */
X#endif /* ZOOCOMMENT */
X
X/* Prints archive comment.  If show==1, says "Current archive comment is:" */
Xvoid show_acmt (zoo_header, zoo_file, show)
Xstruct zoo_header *zoo_header;
XZOOFILE zoo_file;
Xint show;
X{
X   if (zoo_header->zoo_start != FIXED_OFFSET && zoo_header->acmt_len > 0) {
X      unsigned int i;
X      char ch;
X      int newline = 1;
X      zooseek (zoo_file, zoo_header->acmt_pos, 0);   
X		if (show)
X      	printf ("Current archive comment is:\n");
X      for (i = 0; i < zoo_header->acmt_len; i++) {/* show it */
X         ch = zgetc (zoo_file) & 0x7f;          /* 7 bits only */
X         if (newline)
X            printf (">> ");		/* indent and mark comment lines thus */
X         zputchar (ch);
X         if (ch == '\n')
X            newline = 1;
X         else
X            newline = 0;
X      }
X      if (!newline)              /* always terminate with newline */
X         zputchar ('\n');
X   }
X} /* show_acmt() */
END_OF_FILE
if test 9637 -ne `wc -c <'comment.c'`; then
    echo shar: \"'comment.c'\" unpacked with wrong size!
fi
# end of 'comment.c'
fi
if test -f 'misc.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'misc.c'\"
else
echo shar: Extracting \"'misc.c'\" \(9670 characters\)
sed "s/^X//" >'misc.c' <<'END_OF_FILE'
X#ifndef LINT
X/* @(#) misc.c 2.6 88/08/15 16:17:23 */
Xstatic char sccsid[]="@(#) misc.c 2.6 88/08/15 16:17:23";
X#endif /* LINT */
X
X/*
XCopyright (C) 1986, 1987 Rahul Dhesi -- All rights reserved
X(C) Copyright 1988 Rahul Dhesi -- All rights reserved
X*/
X#include "options.h"
X/* Miscellaneous functions needed by Zoo but not by Ooz */
X
X#include "zoo.h"
X#include "zooio.h"
X#include "various.h"
X
X#include "errors.i"
X#include "zoofns.h"
X#ifndef NOSIGNAL
X#include <signal.h>
X#endif
X
X#ifdef NEEDCTYP
X#include <ctype.h>
X#else
X#include "portable.h"
X#endif
X
X#ifdef LINT_ARGS
Xint ver_too_high (struct zoo_header *);
X#else
Xint ver_too_high ();
X#endif
X
X
X/*
Xcalc_ofs() is given a string that (supposedly) begins with a string
Xof digits.  It returns a corresponding numeric value.  If no such
Xstring, it aborts the program with a fatal error message.
X*/
Xlong calc_ofs(str)
Xchar *str;
X{
X   long retval;
X   char *p;
X   retval = 0L;
X   p = str; /* save for error message */
X   while (isdigit(*str)) {
X      retval = retval * 10L + (*str-'0');
X      str++;
X   }
X   if (*str != '\0')
X      prterror ('f', "Invalid number %s\n", p);
X   return (retval);
X}
X
X/*
Xchoosefname() decides which filename to use.  If a long filename is present,
Xand if the syntax is that of UNIX, MS-DOS or the portable form, we use it;
Xelse we use the short filename. 
X*/
X
Xchar *choosefname(direntry)
Xstruct direntry *direntry;
X{
X   char *retptr;                 /* pointer to name that we will return */
X   switch (direntry->system_id) {
X      case SYSID_NIX:
X      case SYSID_PORTABLE:
X      case SYSID_MS:
X         retptr = (direntry->namlen != 0) ? direntry->lfname : direntry->fname;
X         break;
X      default:
X         retptr = direntry->fname;
X         break;
X   }
X   return (retptr);
X} /* choosefname() */
X
X/* 
Xcombine() combines a directory name and a filename, making sure the
Xtwo are separated by a path separator 
X*/
Xchar *combine(result, dirname, fname)
Xchar result[], *dirname, *fname;
X{
X   *result = '\0';
X   if (*dirname != '\0') {
X#ifdef DIR_LBRACK /* hack for VMS */
X      strcat (result, DIR_LBRACK);
X
X      /*  "/" => "[",  "./" => "[."  others => "[." */
X
X      if (dirname[0] == '/') {        /* absolute path => "[" */
X         strcat (result, dirname + 1);
X      } else if (dirname[0] == '.' && dirname[1] == '/') {
X         strcat (result, CUR_DIR);
X         strcat (result, dirname + 2);
X      } else {
X         strcat (result, CUR_DIR);
X         strcat (result, dirname);
X      }
X
X/* folowing #ifdef block ought to be outside #ifdef DIR_LBRACK, and
X   for loop should then start with p=result.  This is currently
X   just a hack for VMS.
X*/
X#ifdef DIR_SEP
X   if (DIR_SEP != '/') {   /* if char separating dirs is not "/",  */
X      char *p;
X      for (p = result+2;  *p != '\0';  p++) /* change it to underscore */
X        if (*p == DIR_SEP)
X           *p = '_';
X   }
X#endif
X
X      {
X         char *p;
X         for (p = result; *p != '\0';  p++)
X         if (*p == '/')
X            *p = '.';
X      }
X#else
X      strcat (result, dirname);
X#endif
X      if (*lastptr(result) != *PATH_CH)
X         strcat(result, PATH_CH);
X   }
X
X   strcat(result, fname);
X   return (result);
X}
X
X/*
Xfullpath() accepts a pointer to a directory entry and returns the
Xcombined directory name + filename.  The long filename is used
Xif available, else the short filename is used.
X*/
Xchar *fullpath (direntry)
Xstruct direntry *direntry;
X{
X	static char result[PATHSIZE];
X	combine (result,
X				direntry->dirlen != 0 ? direntry->dirname : "", 
X				(direntry->namlen != 0) ? direntry->lfname : direntry->fname
X			  );
X	return (result);
X}
X
X/* 
Xver_too_high returns true if version of provided archive header is
Xtoo high for us to manipulate archive
X*/
X
Xint ver_too_high (header)
Xstruct zoo_header *header;
X{
X   return (header->major_ver > MAJOR_VER ||
X            (header->major_ver == MAJOR_VER &&
X             header->minor_ver > MINOR_VER));
X}
X
X/* 
Xrwheader() reads archive header, checks consistency, makes sure its
Xversion number is not too high, updates it if too low, and seeks to
Xbeginning of first directory entr.  If `preserve' is 1, it preserves
Xthe header type;  if `preserve' is 0, it gives a fatal error message
Xif type is 0.
X*/
X
Xvoid rwheader (header, zoo_file, preserve)
Xregister struct zoo_header *header;
XZOOFILE zoo_file;
Xint preserve;
X{
X
X   frd_zooh (header, zoo_file);
X
X   if ((header->zoo_start + header->zoo_minus) != 0L)
X      prterror ('f', failed_consistency);
X   if (ver_too_high (header))
X      prterror ('f', wrong_version, header->major_ver, header->minor_ver);
X
X	if (preserve == 0 && header->type == 0)
X		prterror ('f', packfirst);
X
X   /* We reach here if the archive version is not too high.  Now, if it
X   isn't the same as ours, we bring it up to ours so the modified archive
X   will be safe from previous versions of Zoo */
X
X   if (header->major_ver != MAJOR_VER || header->minor_ver != MINOR_VER) {
X      header->major_ver = MAJOR_VER;
X      header->minor_ver = MINOR_VER;
X      zooseek (zoo_file, 0L, 0);            /* seek to beginning */
X      fwr_zooh (header, zoo_file);
X   }
X   zooseek (zoo_file, header->zoo_start, 0); /* seek to where data begins */
X} /* rwheader */
X
X/*
Xwritedir() write a directory entry with keyboard interrupt disabled
X*/
Xvoid writedir (direntry, zoo_file)
Xstruct direntry *direntry;
XZOOFILE zoo_file;
X{
X#ifndef NOSIGNAL  
X   int (*oldsignal)();
X   oldsignal = signal (SIGINT, SIG_IGN);
X#endif
X   if (fwr_dir (direntry, zoo_file) == -1)
X      prterror ('f', disk_full);
X#ifndef NOSIGNAL
X   signal (SIGINT, oldsignal);
X#endif
X}
X
X/* 
Xreaddir() reads a directory entry from an archive.  If the directory
Xentry is invalid and if fail is 1, it causes a fatal error;
Xelse it returns.  Return value is 0 if no error else -1;
X*/
X
Xint readdir (direntry, zoo_file, fail)    /* read directory entry */
Xregister struct direntry *direntry;
XZOOFILE zoo_file;
Xint fail;                              /* 0 -> return, 1 -> abort on error */
X{
X   if (frd_dir (direntry, zoo_file) < 0) {
X      if (fail) {
X         prterror ('f', bad_directory);
X      } else
X         return (-1);
X   }
X   if (direntry->zoo_tag != ZOO_TAG) {
X      if (fail)
X         prterror ('f', bad_directory);
X      else
X         return (-1);
X   }
X   return (0);
X}
X
X/* use pointer version below */
X#ifdef COMMENT
X/* instr() searches a string for a substring */
Xinstr (s, t)      /* return index of string t in string s, -1 if none */
Xchar s[], t[];    /*  .. from K&R page 67 */
X{
X   int i;
X   register int j, k;
X   for (i = 0; s[i] != '\0'; i++) {
X      for (j = i, k = 0; t[k] != '\0' && s[j]==t[k]; j++, k++)
X         ;
X      if (t[k] == '\0')
X         return (i);
X   }
X   return (-1);
X}
X#endif COMMENT
X
X/* instr() searches a string for a substring */
X/* from J. Brian Waters */
Xint instr (s, t)   		/* return the position of t in s, -1 if none */
Xchar *s, *t;				/*  a pointer version of K&R index function p.67 */
X{               /* renamed to instr() to avoid conflicts with C RTL - JBW */
X
X   register char *i, *j, *k;
X
X   for (i = s; *i; i++) {
X      for (j = i, k = t; (*k) && (*j++ == *k); k++)
X         ;
X       if (!*k)
X         return ((int) (i - s));
X   }
X   return(-1);
X}
X
X/* cfactor() calculates the compression factor given a directory entry */
Xint cfactor (org_size, size_now)
Xlong org_size, size_now;
X{
X   register int size_factor;
X   if ((unsigned long) org_size > 1000000) { /* avoid later overflow */
X      org_size = (unsigned long) org_size / 1024;
X      size_now = (unsigned long) size_now / 1024;
X   }
X   if (org_size == 0)         /* avoid division by zero */
X      size_factor = 0;
X   else {
X      size_factor = (int)
X         (
X            (1000 * 
X               ((unsigned long) org_size - (unsigned long) size_now)
X            ) / org_size + 5
X         ) / 10;
X   }
X   return (size_factor);
X}
X
X#ifndef STRDUP
X/***********
Xstrdup() duplicates a string using dynamic memory.
X*/
X
Xchar *strdup (str)
Xregister char *str;
X{
X   return (strcpy (emalloc (strlen(str)+1), str));
X}
X#endif /* STRDUP */
X
X/**************
Xcmpnum() compares two pairs of unsigned integers and returns a negative,
Xzero, or positive value as the comparison yields less than, equal, or
Xgreater than result.  Each pair of unsigned integers is considered to be the
Xmore significant and the less significant half of a longer unsigned number.
X
XNote:  cmpnum is used to compare dates and times.
X*/
X
Xint cmpnum (hi1, lo1, hi2, lo2)
Xregister unsigned int hi1, hi2;
Xunsigned int lo1, lo2;
X{
X   if (hi1 != hi2)
X      return (hi1 > hi2 ? 1 : -1);
X   else {
X		if (lo1 == lo2)
X			return (0);
X		else
X			return (lo1 > lo2 ? 1 : -1);
X	}
X}
X
X/*******************/
X/* writenull() */
X/* writes a null directory entry to an open archive */
Xvoid writenull (file, length)
XZOOFILE file;
Xint length;
X{
X#ifndef NOSIGNAL
X   int (*oldsignal)();
X#endif
X   struct direntry newentry;
X   memset ((char *) &newentry, 0, sizeof (newentry));
X   newentry.zoo_tag = ZOO_TAG;
X   newentry.type = 2;
X   /* Force entry to be the required length plus possibly 2 stray bytes
X   by dividing up the needed padding into dirlen and namlen. */
X   if (length > SIZ_DIRL)
X      newentry.dirlen = newentry.namlen = (length-SIZ_DIRL)/2 + 2;
X   else
X      newentry.dirlen = newentry.namlen = 0;
X#ifndef NOSIGNAL
X   oldsignal = signal (SIGINT, SIG_IGN);
X#endif
X   if (fwr_dir (&newentry, file) == -1)
X      prterror ('f', disk_full);
X#ifndef NOSIGNAL
X   signal (SIGINT, oldsignal);
X#endif
X}
X
X#ifdef FORCESLASH
X/*******************/
X/*
Xfixslash() changes all "\" characters in the supplied string to "/".
X*/
X
Xvoid fixslash (str)
Xchar *str;
X{
X   register char *p;
X   for (p = str; *p != '\0'; p++)
X      if (*p == '\\')
X         *p = '/';
X}
X#endif /* FORCESLASH */
END_OF_FILE
if test 9670 -ne `wc -c <'misc.c'`; then
    echo shar: \"'misc.c'\" unpacked with wrong size!
fi
# end of 'misc.c'
fi
if test -f 'vms.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'vms.c'\"
else
echo shar: Extracting \"'vms.c'\" \(9468 characters\)
sed "s/^X//" >'vms.c' <<'END_OF_FILE'
X#ifndef LINT
X/* @(#) vms.c 1.4 87/07/26 22:48:45 */
X/* @(#) vms.c 2.2 88/01/09 03:47:52 */
X#endif /* LINT */
X
X/* machine.c for VMS */
X
X/****************
XDate and time functions are standard UNIX-style functions, except that
XVAX/VMS does not know about timezones.  "nixtime.i" will be included 
Xby machine.c.
X*/
X
X#include <stat.h>
X#include <time.h>
X
X/* Function isuadir() returns 1 if the supplied filename is a directory, 
Xelse it returns 0.  
X*/
X
Xint isuadir (file)
Xchar *file;
X{
X   struct stat buf;           /* buffer to hold file information */
X   if (stat (file, &buf) == -1) {
X      return (0);             /* inaccessible -- assume not dir */
X   } else {
X      if (buf.st_mode & S_IFDIR)
X         return (1);
X      else
X         return (0);
X   }
X}
X
X/****************
XFunction fixfname() converts the supplied filename to a syntax
Xlegal for the host system.  It is used during extraction.  We
Xallow a maximum of one dot in the filename, and it must not
Xbe at the beginning.  We also truncate the number of charac-
Xters preceding and following the dot to at most 39.
X*/
X
Xchar *strchr();
X
Xchar *fixfname(fname)
Xchar *fname;
X{
X   char *p;
X   char *dotpos;
X   static char legal[] = 
X      "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_.0123456789";
X
X   /* convert all characters to legal characters */
X   for (p = fname;  *p != '\0';  p++)
X      if (strchr (legal, *p) == 0) {
X         if (*p == '-' || *p == ' ')
X            *p = '_';
X         else
X            *p = legal [ *p % 26 ];
X      }
X
X   /* first char can't be dot */
X   if (*fname == '.')
X      *fname = 'X';
X
X   /* find embedded dot if any */
X   dotpos = strchr (fname, '.');
X
X   if (dotpos != NULL) {
X      for (p = dotpos+1;  *p != '\0';  p++)  /* remove 2nd dot onwards */
X         if (*p == '.')
X            *p = '_';
X      if (dotpos - fname + 1 > 39) {  /* more than 39 before dot not allowed */
X         char *q;
X         p = fname + 39;
X         q = dotpos;
X         while (*p++ = *q++)  /* the notational convenience is considerable */
X            ;
X         dotpos = strchr (fname, '.');
X      }
X      if (strlen (dotpos + 1) > 39)  /* more than 39 after dot not allowed */
X         *(dotpos + 39 + 1) = '\0';
X   } else
X      *(fname + 39) = '\0'; /* no dots, just truncate to 39 characters */
X   return (fname);
X}
X
X/*
XFunction gettz(), returns the offset from GMT in seconds of the
Xlocal time, taking into account daylight savings time -- or it
Xwould, if VAX/VMS knew about timezones!  It's just a no-op.
X*/
Xlong gettz()
X{
X   return 0L;
X}
X
X/* Standard UNIX-compatible time functions */
X#include "nixtime.i"
X
X/*
XFunction utime() is just a stub.  VMS C doesn't have it or
Xan equivalent.
X*/
Xint utime (fname) char *fname; {}
X
X/*
XFunction unlink() will delete a file using the VMS C delete()
Xfunction.
X*/
Xint unlink (fname)
Xchar *fname;
X{
X   return (delete (fname));
X}
X
X/*
XFunction zooexit() receives attempts at exit.  It always invokes
Xexit() with status=1.
X*/
Xint zooexit (status)
Xint status;
X{
X   exit (1);
X}
X
X
X/*
XFunction rename() renames a file.  
XThanks to Owen Anthony <bsu-cs!anthony>.
X*/
X
X#include <descrip.h>
X
X#ifndef VMS_RENAME /* if not using VMS 4.6 library rename() */
Xint rename (new_name, old_name)
Xchar *new_name, *old_name;
X{
X   int status;
X   struct dsc$descriptor_s file1, file2;
X   file1.dsc$w_length = strlen (old_name);  /* descriptor for old name */
X   file1.dsc$a_pointer = old_name;
X   file1.dsc$b_class = DSC$K_CLASS_S;
X   file1.dsc$b_dtype = DSC$K_DTYPE_T;
X   file2.dsc$w_length = strlen (new_name);  /* descriptor for new name */
X   file2.dsc$a_pointer = new_name;
X   file2.dsc$b_class = DSC$K_CLASS_S;
X   file2.dsc$b_dtype = DSC$K_DTYPE_T;
X
X   status = LIB$RENAME_FILE (&file1, &file2);
X
X   return ((status & ~1) == 1);
X}
X#endif /* VMS_RENAME */
X
X/*
XFunction specfname() modifies filenames before they are stored
Xin an archive.  Currently we remove any trailing version field,
Xand then any trailing dot.
X*/
Xchar *specfname (fname)
Xchar *fname;
X{
X   char *p;
X   p = strchr (fname, ';');
X   if (p != NULL)
X      *p = '\0';
X   if (*fname != '\0') {
X      p = fname + strlen (fname) - 1; /* point to last char */
X      if (*p == '.')                  /* remove any trailing dot */
X         *p = '\0';
X   }
X   return (fname);
X}
X
X/* 
XFunction specdir() modifies directory names before they are stored
Xin an archive.  We remove any leading device name or logical
Xname and and the [ and ] that bracket any directory name.
XThen we change any dots in the directory name to slashes.
X*/
X
X#if 0
X/* test stub that just truncates dir to null string */
Xchar *specdir (fname) char *fname;
X{ *fname = '\0'; return (fname); }
X#else
X
Xchar *specdir (fname)
Xchar *fname;
X{
X   char *p;
X   char tmpstr[LFNAMESIZE];
X
X   p = strchr (fname, ':');      /* remove chars upto and including : */
X   if (p != NULL) {
X      strcpy (tmpstr, p+1);
X      strcpy (fname, tmpstr);
X   }
X
X   p = strchr (fname, '[');      /* remove chars upto and including [ */
X   if (p != NULL) {
X      strcpy (tmpstr, p+1);
X      strcpy (fname, tmpstr);
X   }
X
X   p = strchr (fname, ']');      /* truncate at ] */
X   if (p != NULL) {
X      if (*(p+1) != '\0')
X         prterror ('w', "Trailing garbage in directory name\n");
X      *p = '\0';
X   }
X
X   for (p = fname;  *p != '\0';  p++)   /* change dots to slashes */
X      if (*p == '.')
X         *p = '/';
X
X   /* make sure there is a leading slash -- just a hack for now */
X   if (*fname != '/') {
X      strcpy (tmpstr, fname);
X      strcpy (fname, "/");
X      strcat (fname, tmpstr);
X   }
X
X#ifdef DEBUG
Xprintf ("dir name transformed to \"%s\"\n", fname);
X#endif
X}
X#endif
X
X#define  FMAX  3        /* Number of different filename patterns */
X
Xchar *nextfile (what, filespec, fileset)
Xint what;                        /* whether to initialize or match      */
Xregister char *filespec;         /* filespec to match if initializing   */
Xregister int fileset;            /* which set of files                  */
X{
X   int status;
X   char *p;                      /* temp ptr */
X   struct dsc$descriptor_s d_fwild, d_ffound;
X   static int first_time [FMAX+1];
X   static char saved_fspec [FMAX+1][PATHSIZE];  /* our own copy of filespec */
X   static char found_fspec [FMAX+1][PATHSIZE];  /* matched filename */
X   static unsigned long context [FMAX+1];	/* needed by VMS */
X   if (what == 0) {
X      strcpy (saved_fspec[fileset], filespec);  /* save the filespec */
X      first_time[fileset] = 1;
X      return (0);
X   }
X
X   /* Reach here if what is not 0, so it must be 1 */
X
X   /* Create a descriptor for the wildcarded filespec */
X   d_fwild.dsc$w_length = strlen (saved_fspec[fileset]);
X   d_fwild.dsc$a_pointer = saved_fspec[fileset];
X   d_fwild.dsc$b_class = DSC$K_CLASS_S;
X   d_fwild.dsc$b_dtype = DSC$K_DTYPE_T;
X
X   d_ffound.dsc$w_length = sizeof (found_fspec[fileset]);
X   d_ffound.dsc$a_pointer = found_fspec[fileset];
X   d_ffound.dsc$b_class = DSC$K_CLASS_S;
X   d_ffound.dsc$b_dtype = DSC$K_DTYPE_T;
X
X   if (first_time[fileset]) {
X      first_time[fileset] = 0;
X      context[fileset] = 0L;   /* tell VMS this is first search */
X   }
X   status = LIB$FIND_FILE (&d_fwild, &d_ffound, &context[fileset]);
X   status = status & 1;	/* use only lowest bit */
X
X   if (status == 0) {
X      LIB$FIND_FILE_END (&context[fileset]);
X      return ((char *) 0);
X   } else {
X      found_fspec[fileset][d_ffound.dsc$w_length] = '\0'; /* just in case */
X      p = found_fspec[fileset];
X      while (*p != ' ' && *p != '\0')
X         p++;
X      if (*p != '\0')
X         *p = '\0';
X      return (found_fspec[fileset]);
X   }
X}
X
X/*
XFunction vmsmkdir() converts the received directory name into VMS
Xformat and then creates a directory.
X*/
Xint vmsmkdir (subdir)
Xchar *subdir;
X{
X   char *lastptr();
X   char *p;
X   char tmp[LFNAMESIZE];
X
X   p = subdir;
X
X   /* leading "/" => "[", otherwise => "[." */
X   if (*p == '/') {
X      strcpy (tmp, "[");
X      p++;
X   } else {
X      strcpy (tmp, "[.");
X      while (*p == '/' || *p == '.')
X         p++;
X   }
X
X   strcat (tmp, p);
X
X   /* 
X   VMS doesn't like dots in directory names, so we convert them to
X   underscores.  Leave first two characters untouched, because
X   we don't want to corrupt a leading "[." into "[_".
X   */
X   for (p = tmp + 2;  *p != '\0';  p++)
X      if (*p == '.')
X         *p = '_';
X
X   /* convert all slashes to dots */
X   for (p = tmp; *p != '\0';  p++)
X      if (*p == '/')
X         *p = '.';
X
X   /* Remove any trailing dot */
X   p = lastptr (tmp);
X   if (*p == '.')
X      *p = '\0';
X
X   /* append closing bracket */
X   strcat (tmp, "]");
X#if 0
X   printf ("\nmaking directory \"%s\"\n", tmp);
X#endif
X   return (mkdir (tmp, 0));
X}
X
X/*
XFunction spec_wild() transforms a pattern supplied on the command line into one
Xsuitable for wildcard expansion in the most efficient way possible.  We change
Xeach "?" to "%" but let "*" remain unchanged.  We also append a ".*" if the
Xpattern contains no dot, so "*" will be interpreted as "*.*" for VMS globbing. 
X*/
Xchar *spec_wild (arg)
Xchar *arg;
X{
X   char *p;
X#ifdef DEBUG
X   printf ("spec_wild: arg = [%s]\n", arg);
X#endif
X   if (*lastptr (arg) == ']')      /* add *.* if no filename */
X      strcat (arg, "*.*");
X   p = nameptr (arg);              /* point p to filename part */
X
X   /* if no dot in name append ".*" */
X   if (strchr (p, '.') == NULL)
X      strcat (p, ".*");
X
X   for ( ; *p != '\0';  p++)        /* change every "?" to "%" */
X      if (*p == '?')
X         *p = '%';
X#ifdef DEBUG
X   printf ("spec_wild: arg changed to [%s]\n", arg);
X#endif
X   return (arg);
X}
END_OF_FILE
if test 9468 -ne `wc -c <'vms.c'`; then
    echo shar: \"'vms.c'\" unpacked with wrong size!
fi
# end of 'vms.c'
fi
if test -f 'zoo.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'zoo.c'\"
else
echo shar: Extracting \"'zoo.c'\" \(9863 characters\)
sed "s/^X//" >'zoo.c' <<'END_OF_FILE'
X#ifndef LINT
X/* @(#) zoo.c 2.24 88/01/29 00:55:09 */
Xstatic char sccsid[]="@(#) zoo.c 2.24 88/01/29 00:55:09";
X#endif /* LINT */
X
X#if 0
X#define TRACEI(item)	printf("line %d: %s= %d\n", __LINE__, #item, item)
X#define TRACES(item)	printf("line %d: %s= [%s]\n", __LINE__, #item, item)
X#endif
X
Xextern char version[];
X
X/*
XCopyright (C) 1986, 1987 Rahul Dhesi -- All rights reserved
X(C) Copyright 1988 Rahul Dhesi -- All rights reserved
X*/
X#include "options.h"
X#include "zooio.h"
X#include "various.h"
X
X#include "zoo.h"
X#include "zoofns.h"
X
X#include "errors.i"
X#include "zoomem.h"
X
X#ifdef TRACE_IO
Xint verbose = 0;
X#endif
X
X#ifdef LINT_ARGS
Xint instr (char *, char *);
X#else
Xint instr ();
X#endif
X
Xchar *out_buf_adr;      /* points to memory allocated for output buffer(s) */
Xchar *in_buf_adr;       /* points to memory allocated for input buffer */
X
X/* static declarations */
Xint quiet = 0;             /* whether to be quiet */
Xint next_arg = FIRST_ARG; /* filenames start at this position */
Xint arg_count;          /* count of arguments supplied to program */
Xchar **arg_vector;      /* vector of arguments supplied to program */
X
Xmain(argc,argv)
Xregister int argc;
Xregister char **argv;
X{
X   char *zooname;          /* synonym for argv[2] -- to make life easier */
X#ifndef OOZ
X   static char incorrect_args[] = "Incorrect number of arguments.\n";
X   int filecount;          /* how many filespecs supplied */
X#endif /* OOZ */
X
X#ifdef OOZ
X#else
X/* else not OOZ */
X      static char usage[] = "Usage: zoo {acDeglLPTuUvx}[aAcCdEfInmMNoOpPqu1:/.@n] archive file\n(\"zoo h\" for help)\n";
X      static char nov_usage[] =
X          "\nNovice usage:  zoo -cmd archive[.zoo] file...  where -cmd is one of these:\n";
X      char *option;
X
X      static char nov_cmds[] =
X         /* ADD=0EXT=5    MOV=14TES=20PRI=26 DEL=33  LIS=41UPD=47  FRE=55   COMMENT=64 */
X           "-add -extract -move -test -print -delete -list -update -freshen -comment\n";
X
X#ifdef NOENUM
X#define NONE   -1
X#define ADD    0
X#define EXTRACT 5
X#define MOVE   14
X#define TEST   20
X#define PRINT  26
X#define DELETE 33
X#define LIST   41
X#define UPDATE 47
X#define FRESHEN   55
X#define COMMENT   64
X
Xint cmd = NONE;
X
X#else
X   enum choice {
X      NONE=-1, ADD=0, EXTRACT=5, MOVE=14, TEST=20, PRINT=26,
X      DELETE=33, LIST=41, UPDATE=47, FRESHEN=55, COMMENT=64
X   };
X   enum choice cmd = NONE;          /* assume no Novice command */
X#endif
X
X#endif /* end of not OOZ */
X
X#ifdef SPECINIT
X	void spec_init PARMS ((NOTHING));
X	spec_init();							/* system-specific startup code */
X#endif
X
X   arg_count = argc;
X   arg_vector = argv;
X   zooname = argv[FIRST_ARG-1];     /* points to name or archive */
X
X#ifdef OOZ
X   if (argc < 2) {
X      putstr (usage1);
X      putstr (usage2);
X      zooexit (1);
X   }
X#else
X/* else not OOZ */
X   if (argc < 2)
X      goto show_usage;
X   filecount = argc - 3;
X   option = strdup(argv[1]);
X
X#ifdef TRACE_IO
X   if (*option == ':') {         /* for debugging output */
X      verbose++;
X      option++;                  /* hide the : from other functions */
X   }
X#endif
X
X#ifdef WAIT_PROMPT
X   if (*option == 'w') {
X		option++;						/* hide w from other functions */
X		printf ("Hit RETURN when ready: ");
X		while (getchar() != '\n')
X			;
X	}
X#endif /* WAIT_PROMPT */
X
X   if (*option == 'h' || *option == 'H')
X      goto bigusage;
X	if (strchr("-acDegflLPTuUvVx", *option) == NULL)
X		goto give_list;
X
X   if (*option == '-') {
X
X#ifdef NOENUM
X      cmd = instr (nov_cmds, strlwr(option));
X#else
X      cmd = (enum choice) instr (nov_cmds, strlwr(option));
X#endif
X
X      if (strlen(option) < 2 || cmd == NONE)
X         goto show_usage;
X      if (  ((cmd == ADD || cmd == MOVE || cmd == FRESHEN ||
X                  cmd == UPDATE || cmd == DELETE) && argc < 4) ||
X            ((cmd == EXTRACT || cmd == TEST || cmd == LIST ||
X                     cmd == PRINT || cmd == COMMENT) && argc < 3)) {
X         fprintf (stderr, incorrect_args);
X         goto show_usage;
X      }
X   } else {
X		char *wheresI;		/* will be null if I option not supplied */
X		if	(
X				(
X					strchr("au",*option) &&
X					(
X						(((wheresI = strchr(option,'I')) != 0) &&
X							argc != 3) ||
X						wheresI==NULL && argc < 4
X					)
X				) ||
X				 strchr("DU",*option) && argc < 4 ||
X             strchr("cexlvVL",*option) && argc < 3 ||
X             strchr("TP",*option)   && argc != 3 ||
X				 (*option == 'f' && argc != 2) ||
X				 (*option == 'g' &&
X					(strchr(option,'A') == NULL && argc < 4 ||
X					 strchr(option,'A') != NULL && argc != 3
X					)
X				 )
X			) {
X         fprintf (stderr, incorrect_args);
X         goto show_usage;
X      }
X   }
X#endif /* end of not OOZ */
X
X#ifndef OOZ
X   /* if not doing a list and no extension in archive name, add default
X   extension */
X   if (*option != 'f' && cmd != LIST && strchr("lvVL", *option) == NULL &&
X         strchr(nameptr (zooname), EXT_CH) == NULL)
X      zooname = newcat (zooname, EXT_DFLT);
X#endif
X
X/*
XHere we allocate a large block of memory for the duration of the program.
Xlzc() and lzd() will use half of it each.  Routine getfile() will use all
Xof it.
X*/
X/* fudge factor to avoid off-by-one errors */
X#define  FUDGE    10
X
X/*                          fudge/2           fudge/2
X**             [______________||________________|]
X**               output buffer    input buffer
X*/
X   out_buf_adr = emalloc (OUT_BUF_SIZE + IN_BUF_SIZE + FUDGE);
X
X   /* input buffer is in top of allocated block */
X   in_buf_adr = out_buf_adr + OUT_BUF_SIZE + (FUDGE/2);
X
X#ifdef OOZ
Xzooext(zooname, "\0");     /* just extract -- no fancy stuff   */
Xzooexit (0);                  /* and exit normally                */
X#else
X/* else not OOZ -- parse command line and invoke a routine */
X   if (cmd != NONE) {
X      switch (cmd) {
X
X         case ADD:      zooadd (zooname, filecount, &argv[3], "aP:"); break;
X         case FRESHEN:  zooadd (zooname, filecount, &argv[3], "auP:"); break;
X         case UPDATE:   zooadd (zooname, filecount, &argv[3], "aunP:"); break;
X         case MOVE:     zooadd (zooname, filecount, &argv[3], "aMP:"); break;
X
X         case EXTRACT:  zooext (zooname, "x"); break;
X         case TEST:     zooext (zooname, "xNd"); break;
X         case PRINT:    zooext (zooname, "xp"); break;
X
X         case DELETE:   zoodel (zooname, "DP",1); break;
X         case LIST:     zoolist (&argv[2], "VC", argc-2); break;
X         case COMMENT:  comment (zooname, "c"); break;
X         default: goto show_usage;
X      }
X   } else
X      switch (*option) {
X
X         case 'a':
X         case 'u':
X         case 'T':   
X            zooadd (zooname, filecount, &argv[3], option); break;
X#ifdef FILTER
X			case 'f':
X				zoofilt (option);  break;
X#endif /* FILTER */
X         case 'D':
X            zoodel (zooname, option, 1); break;
X         case 'U':
X            zoodel (zooname, option, 0); break;
X			case 'g':
X				zoodel (zooname, option, 2); break;
X         case 'v':
X			case 'V':
X         case 'l': 
X            zoolist(&argv[2], option, 1); break;
X         case 'L': 
X            zoolist(&argv[2], option, argc-2); break;
X         case 'e':
X         case 'x': 
X            zooext(zooname, option); break;
X         case 'P':
X            zoopack (zooname, option); break;
X         case 'c':
X            comment (zooname, option); break;
X         default:
X            goto give_list;
X      }
Xzooexit (0);      /* don't fall through */
X
X/* usage list including Novice commands */
Xshow_usage:
X   fprintf (stderr, "%s%s%s", usage, nov_usage, nov_cmds); zooexit (1);
X
X/* brief usage list */
Xgive_list:
X	fprintf (stderr, usage); zooexit (1);
X
X/* help screen */
Xbigusage:
X
Xprintf ("Zoo archiver, %s\n", version);
Xprintf("(C) Copyright 1988 Rahul Dhesi -- Noncommercial use permitted\n");
X
Xprintf (usage);
Xprintf ("\nChoose a command from within {} and zero or more modifiers from within [].\n");
X
Xprintf ("E.g.:  `zoo a save /bin/*' will archive all files in /bin into save.zoo.\n");
Xprintf ("(Please see the user manual for a complete description of commands.)\n\n");
X
Xprintf (" Commands in {} mean:         |Modifiers in [] mean:\n");
X
Xprintf ("  a     add files             | a     show archive name(s) in listing\n");
Xprintf ("  c     update comments       | A     apply g or c to archive\n");
Xprintf ("  D     delete stored files   | c     add/list comments\n");
Xprintf ("  e,x   extract files         | d     extract/list deleted files too\n");
Xprintf ("  g     adj. gen. limit/count | dd    extract/list only deleted files\n");
Xprintf ("  l,L,v,V list filenames      | E     erase backup after packing\n");
Xprintf ("  P     pack archive          | f     fast add (no compression) or list\n");
Xprintf ("  T     fix archive datestamp | M     move when adding (erase original)\n");
Xprintf ("  u     add only newer files  | n     add only files not already in archive\n");
Xprintf ("  U     undelete stored files | N     send extracted data to Neverland\n");
X#ifdef FILTER
Xprintf ("  f     act as filter         | c/u   compress/uncompress as filter\n");
X#endif /* FILTER */
Xprintf (" -----------------------------  O     don't ask \"Overwrite?\"\n");
Xprintf ("  q     be quiet                p     pipe extracted data to standard output\n");
Xprintf ("  :     don't store dir names   /,//  extract full pathnames\n");
Xprintf ("  .     pack to current dir     I     add filenames read from stdin\n");
Xprintf ("  C     show file CRC value     +/-   enable/disable generations\n");
Xprintf ("  S     overwrite newer files   g     list generation limits\n");
Xprintf ("  P     pack after adding       @n    start extract/list at position n\n");
X
X#ifdef FATTR
Xprintf ("  m     list file modes         OO    overwrite read-only files\n");
X#endif /* FATTR */
X
Xprintf (nov_usage);
Xprintf (nov_cmds);
X#endif /* end of not OOZ */
X
X/* NOTE:  if allowed to fall through and return without an exit() statement,
X   it was printing garbage--corrupted stack?  Why--bug in Microsoft C? */
Xzooexit (1);
X}
END_OF_FILE
if test 9863 -ne `wc -c <'zoo.c'`; then
    echo shar: \"'zoo.c'\" unpacked with wrong size!
fi
# end of 'zoo.c'
fi
echo shar: End of archive 5 \(of 10\).
cp /dev/null ark5isdone
MISSING=""
for I in 1 2 3 4 5 6 7 8 9 10 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 10 archives.
    rm -f ark[1-9]isdone ark[1-9][0-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0
-- 
Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.