From uunet!rsalz Tue May 19 14:14:16 1992
Received: from uunet.UUCP by sparky.IMD.Sterling.COM (5.65c/IDA-1.4.4)
	id AA03480; Tue, 19 May 1992 14:14:09 -0500
Return-Path: <uunet!rsalz>
Received: by rodan.UU.NET (5.61/UUNET-mail-drop)
	id AA17539; Tue, 19 May 92 14:34:56 -0400
Date: Tue, 19 May 92 14:34:56 -0400
From: uunet!rsalz (Rich Salz)
Message-Id: <9205191834.AA17539@rodan.UU.NET>
To: kent@imd.sterling.com
Subject: cshar/Part01
Status: O

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then feed it
# into a shell via "sh file" or similar.  To overwrite existing files,
# type "sh file -c".
# The tool that generated this appeared in the comp.sources.unix newsgroup;
# send mail to comp-sources-unix@uunet.uu.net if you want that tool.
# Contents:  README MANIFEST makekit.c parser.c uuencode.c
# Wrapped by rsalz@rodan on Tue Apr  7 23:54:46 1992
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
echo If this archive is complete, you will see the following message:
echo '          "shar: End of archive 1 (of 6)."'
if test -f 'README' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'README'\"
else
  echo shar: Extracting \"'README'\" \(2710 characters\)
  sed "s/^X//" >'README' <<'END_OF_FILE'
X
XIntroduction
X------------
XThis set of tools is designed to make it easier to ship sources around.  I
Xwrote them because I do a lot of that as moderator of comp.sources.unix,
Xand nothing else did the job for me.  This set isn't perfect, but its
Xclose enough. :-)
X
XPrograms are included for the following tasks:
X    Find source files in a directory tree
X    Run uuencode on binary files, and split up large files
X    Partition files into reasonably-sized archives via a shipping list
X    Pull entries out of shipping lists
X    Make shell archives from shipping lists or named files
X    Strip mail/news headers from archives before feeding them to a shell
X    An interpreter of enough /bin/sh syntax to unpack them "sort of" safely
X
XThe sources in this distribution are being released into the public
Xdomain; do what you want, but let your conscience be your guide.  If you
Xsomehow enhance this package, please send it on to me so that others can
Xbenefit.
X
XInstallation
X------------
XPick a configuration file that comes close to matching your system, copy
Xit to config.h, and edit it as necessary.  Don't edit the distributed
Xversion because it will be harder to track patches to it.  Don't be scared
Xby your first look at all the parameters in the config.XXX files.  I agree
Xthat there are WAY too many, but it's really not that bad -- trust me.  If
Xyou find that you have to edit any other file, please let me know.   You
Xmight have to tweak with some of the lXXX.c files for bizarre systems, but
Xif you have to edit anything else then I goofed and I would like to hear
Xabout it.
X
XIt would be nice if I could use a Larry Wall-style Configure, but that
Xonly works on Unix (and Eunice).  Send me your config.h file so that
Xothers can benefit.
X
XYou might have to edit the Makefile, or perhaps even write one from scratch.
XIf you end up making significant changes, please let me know.  Apparently
X"make" and compilers in the micro world don't have the de-facto standards
Xthe way Unix and VMS do, so unless your changes are very general and
Xapplicable to a wide variety of machines, you needn't send them to me.
X
XCredits
X-------
XI freely stole ideas from a number of people who have been good enough to
Xput their stuff out on Usenet.  Particular thanks to Gary Perlman and
XLarry Wall for giving me something nice to reverse-engineer, Michael
XMauldin for unshar, and Phil Budne for what I turned into manipull.
X
XMore importantly, well over 50 people have sent me comments and bug reports
Xon previous versions of this.  The names are too long to list, but this
Xcode is dedicated to them.
X
XEnjoy!
X	Rich $alz
X	BBN Systems and Technology, Inc.
X	10 Moulton Street
X	Cambridge, MA  02238
X	rsalz@bbn.com
X	rsalz@uunet.uu.net
END_OF_FILE
  if test 2710 -ne `wc -c <'README'`; then
    echo shar: \"'README'\" unpacked with wrong size!
  fi
  # end of 'README'
fi
if test -f 'MANIFEST' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'MANIFEST'\"
else
  echo shar: Extracting \"'MANIFEST'\" \(3119 characters\)
  sed "s/^X//" >'MANIFEST' <<'END_OF_FILE'
X   File Name		Archive #	Description
X----------------------------------------------------------
XREADME                     1	Acknowledgements, installation notes
XMANIFEST                   1	This shipping list
XMakefile                   3	Control file for Unix make program
Xconfig.3b1                 3	Configuration for 3B1 machine
Xconfig.5r3                 3	Configuration for System V rel 3.1 and 3.2
Xconfig.bsd                 3	Configuration for BSD/Ultrix/Sun
Xconfig.dos                 3	Configuration for MS-DOS (Microsoft C5.1)
Xconfig.hpx                 3	Configuration for HP-UX 9000/300 9000/500
Xconfig.vms                 3	Configuration for VMS
Xconfig.x38                 2	configuration for Xenix on a 386
Xdirami.c                   5	A partial Amiga readdir package
Xdirami.h                   5	An <ndir.h> file for the Amiga
Xdirmsd.c                   5	An MS-DOS readdir package
Xdirmsd.h                   5	An <ndir.h> file for MS-DOS
Xdiros2.c                   5	A partial OS/2 readdir package
Xdiros2.h                   5	An <ndir.h> file for OS/2
Xdirvms.c                   4	A VMS readdir package
Xdirvms.h                   5	An <ndir.h> file for VMS
Xdodoc.sh                   5	Create formatted manpages
Xfindsrc.c                  2	Find source files, based on filename
Xfindsrc.man                5	Manual page for findsrc
Xglue.c                     5	Glue that so unshar uses my /bin/sh parser
Xlcwd.c                     5	Routines to find current directory
Xlexec.c                    4	Fork, exec, system, signal, etc., routines
Xlfiles.c                   5	File size and type routines
Xlhost.c                    5	Find our machine name
Xllib.c                     4	Stuff that should be in your C library
Xlmem.c                     5	Memory allocator, uses calloc
Xluser.c                    5	Get user's name
Xmakekit.c                  1	Partition files into reasonable-sized kits
Xmakekit.man                4	Manual page for makekit
Xmanipull.c                 4	Pull entries from a manifest
Xmanipull.man               3	Manual page for manipull
Xmaniscan.c                 2	Scan manifests for big or binary files
Xmaniscan.man               4	Manual page for maniscan
Xparser.c                   1	Interpreter for shell archives
Xparser.h                   5	Header file for archive interpreter
Xpatchlog.h                 6	Mistake recorder
Xshar.c                     2	Create script to create files
Xshar.h                     4	Header file, used by everyone
Xshar.man                   4	Manual page for makekit
Xshell.c                    5	Main routine for my shell interpreter
Xshell.man                  5	Manual page for shell
Xunshar.c                   2	Strip news, notes, mail headers from shar's
Xunshar.man                 5	Manual page for unshar
Xuudecode.c                 4	Printable->binary converter
Xuudecode.man               5	Manual page for uudecode
Xuuencode.c                 1	Binary->printable converter
Xuuencode.man               5	Manual page for uuencode
Xuuenmain.c                 5	Driver for uuencode program
Xwildmat.c                  4	Shell wildcard pattern matcher
END_OF_FILE
  if test 3119 -ne `wc -c <'MANIFEST'`; then
    echo shar: \"'MANIFEST'\" unpacked with wrong size!
  fi
  # end of 'MANIFEST'
fi
if test -f 'makekit.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'makekit.c'\"
else
  echo shar: Extracting \"'makekit.c'\" \(13020 characters\)
  sed "s/^X//" >'makekit.c' <<'END_OF_FILE'
X/*
X**  MAKEKIT
X**
X**  Split up source files into reasonably-sized shar lists.
X*/
X#include "shar.h"
Xstatic char RCS[] =
X	"$Header: makekit.c,v 2.2 88/06/06 22:04:45 rsalz Exp $";
X
X
X/*
X**  How much overhead a file takes up.
X*/
X#define OVERHEAD(Name)		(strlen(Name) * 8 + 325)
X
X/*
X**  Our block of information about the files we're doing.
X*/
Xtypedef struct _block {
X    char	*Name;			/* Filename			*/
X    char	*Text;			/* What it is			*/
X    int		Where;			/* Where it is			*/
X    int		Type;			/* Directory or file?		*/
X    long	Bsize;			/* Size in bytes		*/
X} BLOCK;
X
X
X/*
X**  Our block of information about the archives we're making.
X*/
Xtypedef struct _archive {
X    int		Count;			/* Number of files		*/
X    long	Asize;			/* Bytes used by archive	*/
X} ARCHIVE;
X
X
X/*
X**  Global variables.
X*/
Xstatic char	*InName;		/* File with list to pack	*/
Xstatic char	*OutName;		/* Where our output goes	*/
Xstatic char	*SharName = "Part";	/* Prefix for name of each shar	*/
Xstatic char	*Trailer;		/* Text for shar to pack in	*/
Xstatic char	TEMP[TEMPSIZE];		/* Temporary manifest file	*/
X#ifdef	USE_TEMP_MANIFEST
Xstatic char	FLST[TEMPSIZE];		/* File with list for shar	*/
X#endif	/* USE_TEMP_MANIFEST */
Xstatic int	ArkCount = 20;		/* Max number of archives	*/
Xstatic int	ExcludeIt;		/* Leave out the output file?	*/
Xstatic int	Header;			/* Lines of prolog in input	*/
Xstatic int	Preserve;		/* Preserve order for Manifest?	*/
Xstatic int	Working = TRUE;		/* Call shar when done?		*/
Xstatic long	Size = 55000;		/* Largest legal archive size	*/
X
X
X/*
X**  Sorting predicate to put README first, then MANIFEST, then directories,
X**  then larger files, then smaller files, which is how we want to pack
X**  stuff in archives.
X*/
Xstatic int
XSizeP(p1, p2)
X    char	*p1;
X    char	*p2;
X{
X    BLOCK	*t1;
X    BLOCK	*t2;
X    long	l;
X
X    t1 = (BLOCK *)p1;
X    t2 = (BLOCK *)p2;
X
X    if (t1->Type == F_DIR)
X	return t2->Type == F_DIR ? 0 : -1;
X    if (t2->Type == F_DIR)
X	return 1;
X    if (EQ(t1->Name, "PACKNOTES"))
X	return -1;
X    if (EQ(t2->Name, "PACKNOTES"))
X	return 1;
X    if (EQn(t1->Name, "README", 6))
X	return -1;
X    if (EQn(t2->Name, "README", 6))
X	return 1;
X    if (OutName && EQ(t1->Name, OutName))
X	return -1;
X    if (OutName && EQ(t2->Name, OutName))
X	return 1;
X    l = t1->Bsize - t2->Bsize;
X    /* NOSTRICT *//* "long assignment may lose accuracy" */
X    if (l != 0L)
X	return l < 0 ? 1 : -1;
X    return 0;
X}
X
X
X/*
X**  Sorting predicate to get things in alphabetical order, with the
X**  README and MANIFEST files as first and second, respectively.
X*/
Xstatic int
XReadmeP(p1, p2)
X    char	*p1;
X    char	*p2;
X{
X    BLOCK	*t1;
X    BLOCK	*t2;
X    int		i;
X
X    t1 = (BLOCK *)p1;
X    t2 = (BLOCK *)p2;
X    if (EQ(t1->Name, "PACKNOTES"))
X	return -1;
X    if (EQ(t2->Name, "PACKNOTES"))
X	return 1;
X    if (EQ(t1->Name, "README"))
X	return -1;
X    if (EQ(t2->Name, "README"))
X	return 1;
X    if (EQ(t1->Name, "MANIFEST"))
X	return -1;
X    if (EQ(t2->Name, "MANIFEST"))
X	return 1;
X    i = *t1->Name - *t2->Name;
X    return i ? i : strcmp(t1->Name, t2->Name);
X}
X
X
X/*
X**  Skip whitespace.
X*/
Xstatic char *
XSkip(p)
X    REGISTER char	*p;
X{
X    while (*p && WHITE(*p))
X	p++;
X    return p;
X}
X
X
X/*
X**  Signal handler.  Clean up and die.
X*/
Xstatic sigret_t
XCatch(s)
X    int		s;
X{
X    int		e;
X
X    e = errno;
X    if (TEMP[0])
X	(void)unlink(TEMP);
X#ifdef	USE_TEMP_MANIFEST
X    if (FLST[0])
X	(void)unlink(FLST);
X#endif	/* USE_TEMP_MANIFEST */
X    Fprintf(stderr, "Got signal %d, %s.\n", s, strerror(e));
X    exit(1);
X    /* NOTREACHED */
X}
X
X
Xint
Xmain(ac, av)
X    REGISTER int	ac;
X    char		*av[];
X{
X    REGISTER FILE	*F;
X    REGISTER FILE	*In;
X    REGISTER BLOCK	*t;
X    REGISTER ARCHIVE	*k;
X    REGISTER char	*p;
X    REGISTER int	i;
X    REGISTER int	lines;
X    REGISTER int	FoundOutname;
X    BLOCK		*Table;
X    BLOCK		*TabEnd;
X    ARCHIVE		*Ark;
X    ARCHIVE		*ArkEnd;
X    char		buff[BUFSIZ];
X    long		lsize;
X    int			LastOne;
X    int			Start;
X    int			Notkits;
X    int			Believer;
X    int			Oops;
X    char		EndArkNum[20];
X    char		CurArkNum[20];
X
X    /* Collect input. */
X    Believer = FALSE;
X    Notkits = FALSE;
X    for (Oops = FALSE; (i = getopt(ac, av, "v1beh:i:k:n:mo:ps:t:x")) != EOF; )
X	switch (i) {
X	default:
X	    Oops = TRUE;
X	    break;
X	case 'v':		/* Print version			*/
X	    Version(RCS);
X	    /* NOTREACHED */
X	case '1':		/* Pretend we are doing a single shar	*/
X	    Notkits = TRUE;
X	    break;
X	case 'b':		/* Use manifest's assignments		*/
X	    Believer = TRUE;
X	    break;
X	case 'e':		/* Don't include manifest in manifest	*/
X	    ExcludeIt = TRUE;
X	    break;
X	case 'h':		/* Lines of header to skip		*/
X	    Header = atoi(optarg);
X	    break;
X	case 'i':		/* Name of input manifest		*/
X	    InName = optarg;
X	    break;
X	case 'k':		/* Maximum number of kits to make	*/
X	    ArkCount = atoi(optarg);
X	    break;
X	case 'm':		/* Convenient option shorthand		*/
X	    InName = OutName = "MANIFEST";
X	    Header = 2;
X	    break;
X	case 'n':		/* Name for generated shar files	*/
X	    SharName = optarg;
X	    break;
X	case 'o':		/* Name for generated manifest file	*/
X	    OutName = optarg;
X	    break;
X	case 'p':		/* Preserve order in existing manifest	*/
X	    Preserve = TRUE;
X	    break;
X	case 's':		/* Target size for generated shars	*/
X	    Size = (long)atoi(optarg);
X	    if (IDX(optarg, 'k') || IDX(optarg, 'K'))
X		Size *= 1024;
X	    break;
X	case 't':		/* Text to print when all are unpacked	*/
X	    Trailer = optarg;
X	    break;
X	case 'x':		/* Don't invoke shar to do the packing	*/
X	    Working = FALSE;
X	    break;
X	}
X    ac -= optind;
X    av += optind;
X
X    if (Oops == FALSE && InName && av[0]) {
X	Fprintf(stderr,
X		"Can't list files on command line and use -i option.\n");
X	Oops = TRUE;
X    }
X
X    if (Oops) {
X	Fprintf(stderr, "Usage:\n  makekit %s\n          %s [files...]\n",
X		"[-1] [-b] [-e] [-x] [-k #] [-s #[k]] [-n Name] [-t Text]",
X		"[-p] [-m | -i MANIFEST -o MANIFEST -h 2]");
X	exit(1);
X	/* NOTREACHED */
X    }
X
X    /* Write the file list to a temp file. */
X    MakeTempName(TEMP, TEMP_NAME1);
X    if ((F = fopen(TEMP, "w")) == NULL) {
X	Fprintf(stderr, "Can't make tempfile %s, %s\n", TEMP, strerror(errno));
X	exit(1);
X    }
X    SetSigs(Catch);
X    if (av[0])
X	/* Got the arguments on the command line. */
X	while (*av)
X	    Fprintf(F, "%s\n", *av++);
X    else {
X	/* Got the name of the file from the command line. */
X	if (InName == NULL)
X	    In = stdin;
X	else if ((In = fopen(InName, "r")) == NULL) {
X	    Fprintf(stderr, "Can't read %s as manifest, %s.\n",
X		    InName, strerror(errno));
X	    exit(1);
X	    /* NOTREACHED */
X	}
X	/* Skip any possible prolog, then output rest of file. */
X	while (--Header >= 0 && fgets(buff, sizeof buff, In))
X	    continue;
X	if (feof(In)) {
X	    Fprintf(stderr, "Nothing but header lines in list!?\n");
X	    exit(1);
X	    /* NOTREACHED */
X	}
X	while (fgets(buff, sizeof buff, In))
X	    fputs(buff, F);
X	if (In != stdin)
X	    (void)fclose(In);
X    }
X    (void)fclose(F);
X
X    /* Count number of files, allow for NULL and our output file. */
X    F = fopen(TEMP, "r");
X    for (lines = 2; fgets(buff, sizeof buff, F); lines++)
X	continue;
X    rewind(F);
X
X    /* Read lines and parse lines, see if we found our OutFile. */
X    FoundOutname = FALSE;
X    Table = NEW(BLOCK, lines);		/* Initialized in loop, below. */
X    for (t = Table, lines = 0; fgets(buff, sizeof buff, F); ) {
X	/* Read line, skip first word, check for blank line. */
X	if ((p = IDX(buff, '\n')) == NULL)
X	    Fprintf(stderr, "Warning, line too long:\n\t%s\n", buff);
X	else
X	    *p = '\0';
X	p = Skip(buff);
X	if (*p == '\0')
X	    continue;
X
X	/* Copy the line, snip off the first word. */
X	for (p = t->Name = COPY(p); *p && !WHITE(*p); p++)
X	    continue;
X	if (*p)
X	    *p++ = '\0';
X
X	/* Skip <spaces><digits><spaces>; remainder is the file description. */
X	p = Skip(p);
X	t->Where = atoi(p);
X	while (*p && CTYPE(*p) && isdigit(*p))
X	    p++;
X	t->Text = Skip(p);
X
X	/* Get file type. */
X	if (!GetStat(t->Name)) {
X	    Fprintf(stderr, "Can't stat %s (%s), skipping.\n",
X		    t->Name, strerror(errno));
X	    continue;
X	}
X	t->Type = Ftype(t->Name);
X
X	/* Estimate how much space the file will take when archived. */
X	if (t->Type == F_FILE) {
X	    t->Bsize = OVERHEAD(t->Name);
X	    lsize = Fsize(t->Name);
X	    /* If we had a "wc" we could use it, but this is good enough. */
X	    if (IsProbablySource(t->Name))
X		/* Average chars/line in C, Pascal, Fortran.  Sort of. */
X		t->Bsize += lsize + lsize / 25;
X	    else
X		t->Bsize += lsize + lsize / 60;
X	}
X	else
X	    t->Bsize = strlen(t->Name) * 4 + 80;
X
X	if (t->Bsize > Size) {
X	    Fprintf(stderr,
X		"At %ld bytes, %s seems too big for any archive!\n",
X		t->Bsize, t->Name);
X	    exit(1);
X	    /* NOTREACHED */
X	}
X
X	/* Is our output file there? */
X	if (!FoundOutname && OutName && EQ(OutName, t->Name))
X	    FoundOutname = TRUE;
X
X	/* All done -- advance to next entry. */
X	t++;
X	lines++;
X    }
X    (void)fclose(F);
X    (void)unlink(TEMP);
X
X    /* Add our output file? */
X    if (!ExcludeIt && !FoundOutname && OutName) {
X	t->Name = OutName;
X	t->Text = "This shipping list";
X	t->Type = F_FILE;
X	t->Bsize = (lines + 3) * 60 + OVERHEAD(OutName);
X	t->Where = 0;
X	t++;
X    }
X
X    /* Sort by size. */
X    lines = t - Table;
X    TabEnd = &Table[lines];
X    if (!Preserve)
X	qsort((char *)Table, lines, sizeof Table[0], SizeP);
X
X    /* Get archive space, allow for initial overhead. */
X    Ark = NEW(ARCHIVE, ArkCount);
X    ArkEnd = &Ark[ArkCount];
X    for (k = Ark; k < ArkEnd; k++) {
X	k->Count = 0;
X	k->Asize = 1000;
X    }
X
X    /* The first archive gets extra overhead for introductory comments. */
X    Ark[0].Asize += 2000;
X
X    /* See if everyone has a place to be. */
X    if (Believer)
X	for (t = Table; t < TabEnd; t++) {
X	    if (t->Where == 0) {
X		Fprintf(stderr, "Can't believe the manifest assignments.\n");
X		Believer = FALSE;
X		break;
X	    }
X	    t->Where--;
X	}
X
X    /* Loop through the pieces, and put everyone into an archive. */
X    if (!Believer) {
X	for (t = Table; t < TabEnd; t++) {
X	    for (k = Ark; k < ArkEnd; k++)
X		if (t->Bsize + k->Asize <= Size) {
X		    k->Asize += t->Bsize;
X		    t->Where = k - Ark;
X		    k->Count++;
X		    break;
X		}
X	    if (k == ArkEnd) {
X		Fprintf(stderr,
X			"'%s' doesn't fit -- need more then %d archives.\n",
X			t->Name, ArkCount);
X		exit(1);
X		/* NOTREACHED */
X	    }
X
X	    /* Since our shar doesn't build sub-directories... */
X	    if (t->Type == F_DIR && k != Ark)
X		Fprintf(stderr, "Warning, directory '%s' is in archive %d.\n",
X			t->Name, k - Ark + 1);
X	}
X    }
X
X    /* Open the output file. */
X    if (OutName == NULL)
X	F = stdout;
X    else {
X	if (Fexists(OutName))
X	    SafeRename(OutName);
X	if ((F = fopen(OutName, "w")) == NULL) {
X	    Fprintf(stderr, "Can't open '%s' for output, %s.\n",
X		    OutName, strerror(errno));
X	    exit(1);
X	    /* NOTREACHED */
X	}
X    }
X
X    /* Sort the shipping list, then write it. */
X    if (!Preserve)
X	qsort((char *)Table, lines, sizeof Table[0], ReadmeP);
X    Fprintf(F, "   File Name\t\tArchive #\tDescription\n");
X    Fprintf(F, "----------------------------------------------------------\n");
X    for (t = Table; t < TabEnd; t++)
X	Fprintf(F, MANI_FORMAT, t->Name, t->Where + 1, t->Text);
X
X    /* Close output.  Are we done? */
X    if (F != stdout)
X	(void)fclose(F);
X    if (!Working)
X	exit(0);
X	/* NOTREACHED */
X
X    /* Find last archive number. */
X    for (i = 0, t = Table; t < TabEnd; t++)
X	if (i < t->Where)
X	    i = t->Where;
X    LastOne = i + 1;
X
X    /* Find archive with most files in it and build an argv vector. */
X    for (i = 0, k = Ark; k < ArkEnd; k++)
X	if (i < k->Count)
X	    i = k->Count;
X    av = NEW(char*, i + 10);
X
X    /* Build the fixed part of the argument vector. */
X    av[0] = "shar";
X    i = 1;
X    if (Trailer) {
X	av[i++] = "-t";
X	av[i++] = Trailer;
X    }
X    if (Notkits == FALSE) {
X	Sprintf(EndArkNum, "%d", LastOne);
X	av[i++] = "-e";
X	av[i++] = EndArkNum;
X	av[i++] = "-n";
X	av[i++] = CurArkNum;
X    }
X    av[i++] = "-o";
X    av[i++] = buff;			/* See sprintf call in loop below. */
X
X#ifdef	USE_TEMP_MANIFEST
X    av[i++] = "-i";
X    MakeTempName(FLST, TEMP_NAME2);
X    av[i++] = FLST;
X    av[i] = NULL;
X#endif	/* USE_TEMP_MANIFEST */
X
X    /* Call shar to package up each archive. */
X    for (Start = i, i = 0; i < LastOne; i++) {
X	Sprintf(CurArkNum, "%d", i + 1);
X	Sprintf(buff, NAME_FORMAT, SharName, i + 1);
X#ifndef	USE_TEMP_MANIFEST
X	for (lines = Start, t = Table; t < TabEnd; t++)
X	    if (t->Where == i)
X		av[lines++] = t->Name;
X	av[lines] = NULL;
X#else
X	if ((F = fopen(FLST, "w")) == NULL) {
X	    Fprintf(stderr, "Can't open list file '%s' for output, %s.\n",
X		    FLST, strerror(errno));
X	    exit(1);
X	    /* NOTREACHED */
X	}
X	for (t = Table; t < TabEnd; t++)
X	    if (t->Where == i)
X		Fprintf(F, "%s\n", t->Name);
X	(void)fclose(F);
X#endif /* USE_TEMP_MANIFEST */
X	Fprintf(stderr, "Packing kit %d...\n", i + 1);
X	if ((lines = Execute(av)) != 0)
X	    Fprintf(stderr, "Warning, shar returned status %d.\n", lines);
X    }
X
X#ifdef	USE_TEMP_MANIFEST
X    (void)unlink(FLST);
X#endif	/* USE_TEMP_MANIFEST */
X
X    /* That's all she wrote. */
X    exit(0);
X    /* NOTREACHED */
X}
END_OF_FILE
  if test 13020 -ne `wc -c <'makekit.c'`; then
    echo shar: \"'makekit.c'\" unpacked with wrong size!
  fi
  # end of 'makekit.c'
fi
if test -f 'parser.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'parser.c'\"
else
  echo shar: Extracting \"'parser.c'\" \(27174 characters\)
  sed "s/^X//" >'parser.c' <<'END_OF_FILE'
X/*
X**  Parser for /bin/sh language commonly used in writing shars.
X**
X**  Note that (void)Exec(...) is all over the place, even though every
X**  command returns the right exist status; I changed my mind about
X**  implementing $? "properly."
X**
X**  This code is big and ugly, and should probably be rewritten from the
X**  ground up.  It wasn't designed; it started with Argify and SynTable
X**  as a cute ten-minute hack and it just grew.
X*/
X#define PARSER_DATA
X#include "parser.h"
X#ifdef	RCSID
Xstatic char RCS[] =
X	"$Header: parser.c,v 2.1 88/06/03 11:39:11 rsalz Locked $";
X#endif	/* RCSID */
X
X
X/*
X**  Global variables.
X*/
X#ifdef	MAX_MKDIRS
Xstatic int	DirCount;		/* Number of directories made	*/
X#endif	/* MAX_MKDIRS */
X#ifdef	MAX_FOPENS
Xstatic int	CreatedCount;		/* Number of files created	*/
X#endif	/* MAX_FOPENS */
Xstatic VAR	VarList[MAX_VARS];	/* Our list of variables	*/
Xstatic char	Text[BUFSIZ];		/* Current text line		*/
Xstatic int	LineNum = 1;		/* Current line number		*/
Xstatic int	Running = TRUE;		/* Working, or skipping?	*/
Xstatic short	SynTable[256] = {	/* Syntax table			*/
X    /*	\0	001	002	003	004	005	006	007	*/
X	C_TERM,	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,
X    /*	\h	\t	\n	013	\f	\r	016	017	*/
X	C_WHIT,	C_WHIT,	C_TERM,	C_WHIT,	C_TERM,	C_TERM,	C_WHIT,	C_WHIT,
X    /*	020	021	022	023	024	025	026	027	*/
X	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,
X    /*	can	em	sub	esc	fs	gs	rs	us	*/
X	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,
X
X    /*	sp	!	"	#	$	%	&	'	*/
X	C_WHIT,	C_LETR,	C_QUOT,	C_TERM,	C_LETR,	C_LETR,	C_DUBL,	C_QUOT,
X    /*	(	)	*	+	,	-	.	/	*/
X	C_WORD,	C_WORD,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,
X    /*	0	1	2	3	4	5	6	7	*/
X	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,
X    /*	8	9	:	;	<	=	>	?	*/
X	C_LETR,	C_LETR,	C_LETR,	C_DUBL,	C_DUBL,	C_LETR,	C_DUBL,	C_LETR,
X
X    /*	@	A	B	C	D	E	F	G	*/
X	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,
X    /*	H	I	J	K	L	M	N	O	*/
X	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,
X    /*	P	Q	R	S	T	U	V	W	*/
X	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,
X    /*	X	Y	Z	[	\	]	^	_	*/
X	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_META,	C_LETR,	C_LETR,	C_LETR,
X
X    /*	`	a	b	c	d	e	f	g	*/
X	C_WORD,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,
X    /*	h	i	j	k	l	m	n	o	*/
X	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,
X    /*	p	q	r	s	t	u	v	w	*/
X	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,
X    /*	x	y	z	{	|	}	~	del	*/
X	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_DUBL,	C_LETR,	C_LETR,	C_WHIT,
X};
X
X/**
X***		E R R O R   R O U T I N E S
X**/
X
X
X/*
X**  Print message with current line and line number.
X*/
Xstatic void
XNote(text, arg)
X    char	*text;
X    char	*arg;
X{
X    Fprintf(stderr, "\nIn line %d of %s:\n\t", LineNum, File);
X    Fprintf(stderr, text, arg);
X    Fprintf(stderr, "Current line:\n\t%s\n", Text);
X    (void)fflush(stderr);
X}
X
X
X/*
X**  Print syntax message and die.
X*/
Xvoid
XSynErr(text)
X    char	*text;
X{
X    Note("Fatal syntax error in %s statement.\n", text);
X    exit(1);
X    /* NOTREACHED */
X}
X
X/**
X***		I N P U T   R O U T I N E S
X**/
X
X
X/*
X**  Get a yes/no/quit reply.
X*/
Xstatic int
XYNQreply(prompt)
X    char		*prompt;
X{
X    REGISTER FILE	*DEVTTY;
X    register int	value;
X    char		buff[LINE_SIZE];
X
X    DEVTTY = fopen(ctermid((char *)NULL), "r");
X    for (value = '\0'; !value; ) {
X	Fprintf(stderr, "%s:  ", prompt);
X	(void)fflush(stderr);
X	clearerr(DEVTTY);
X	if (fgets(buff, sizeof buff, DEVTTY) == NULL)
X	    value = 'Q';
X	else
X	    switch (buff[0]) {
X	    case 'q': case 'Q': case '\n':
X		value = 'Q';
X		break;
X	    case 't': case 'T':
X	    case 'y': case 'Y':
X		value = 'T';
X		break;
X	    case 'f': case 'F':
X	    case 'n': case 'N':
X		value = 'F';
X		break;
X	    }
X    }
X    (void)fclose(DEVTTY);
X    return value;
X}
X
X
X/*
X**  Miniscule regular-expression matcher; only groks the . meta-character.
X*/
Xstatic int
XMatches(p, text)
X    REGISTER char	*p;
X    REGISTER char	*text;
X{
X    for (; *p && *text; text++, p++)
X	if (*p != *text && *p != '.')
X	    return FALSE;
X    return TRUE;
X}
X
X
X
X/*
X**  Read input, possibly handling escaped returns.  Returns a value so
X**  we can do things like "while (GetLine(TRUE))", which is a hack.  This
X**  should also be split into two separate routines, and punt the Flag
X**  argument, but so it goes.
X*/
Xint
XGetLine(Flag)
X    REGISTER int	Flag;
X{
X    REGISTER char	*p;
X    REGISTER char	*q;
X    REGISTER int	i;
X    char		buf[LINE_SIZE];
X
X    if (Interactive) {
X	Fprintf(stderr, "Line %d%s>  ", LineNum, Running ? "" : "(SKIP)");
X	(void)fflush(stderr);
X    }
X    Text[0] = '\0';
X    for (q=Text; fgets(buf, sizeof buf, Input); q += strlen(strcpy(q, buf))) {
X	LineNum++;
X	i = strlen(buf);
X	p = &buf[i - 1];
X	if (*p != '\n') {
X	    /* If we hit EOF without a terminating \n, that's okay. */
X	    if (feof(Input))
X		p++;
X	    else {
X		Note("Input line too long.\n", (char *)NULL);
X		exit(1);
X		/* NOTREACHED */
X	    }
X	}
X
X	/* Make sure line isn't too long. */
X	if (q + i >= &Text[sizeof Text - 1]) {
X	    Note("Input line too big.\n", (char *)NULL);
X	    exit(1);
X	    /* NOTREACHED */
X	}
X
X	if (!Flag || p == buf || p[-1] != '\\') {
X	    (void)strcpy(q, buf);
X	    return 1;
X	}
X	p[-1] = '\0';
X	if (Interactive) {
X	    Fprintf(stderr, "PS2>  ");
X	    (void)fflush(stderr);
X	}
X    }
X    Note("RAN OUT OF INPUT.\n", (char *)NULL);
X    exit(1);
X    /* NOTREACHED */
X}
X
X
X/*
X**  Copy a sub-string of characters into dynamic space.
X*/
Xstatic char *
XCopyRange(Start, End)
X    char	*Start;
X    char	*End;
X{
X    char	*p;
X    int		i;
X
X    i = End - Start + 1;
X    p = strncpy(NEW(char, i + 1), Start, i);
X    p[i] = '\0';
X    return p;
X}
X
X
X/*
X**  Split a line up into shell-style "words."
X*/
Xint
XArgify(ArgV)
X    char		**ArgV;
X{
X    REGISTER char	**av;
X    REGISTER char	*p;
X    REGISTER char	*q;
X    
X    for (av = ArgV, p = Text; *p; p++) {
X	/* Skip whitespace, but treat "\ " as a letter. */
X	for (; ISwhit(*p); p++)
X	    if (ISmeta(*p))
X		p++;
X	if (ISterm(*p))
X	    break;
X	switch (SynTable[*p]) {
X	default:
X	    /* Gross type-casting for lint, oh well, shouldn't happen. */
X	    Note("Bad case %x in Argify.\n", (char *)SynTable[*p]);
X	    /* FALLTHROUGH */
X	case C_META:
X	    p++;
X	    /* FALLTHROUGH */
X	case C_WHIT:
X	case C_LETR:
X	    for (q = p; ISletr(*++q) || ISmeta(q[-1]); )
X		continue;
X	    *av++ = CopyRange(p, --q);
X	    p = q;
X	    break;
X	case C_DUBL:
X	    if (*p == p[1]) {
X		*av++ = CopyRange(p, p + 1);
X		p++;
X		break;
X	    }
X	    /* FALLTHROUGH */
X	case C_WORD:
X	    *av++ = CopyRange(p, p);
X	    break;
X	case C_QUOT:
X	    for (q = p; *++q; )
X		if (*q == *p && !ISmeta(q[-1]))
X		    break;
X	    *av++ = CopyRange(p + 1, q - 1);
X	    p = q;
X	    break;
X	}
X    }
X    *av = NULL;
X    if (av > &ArgV[MAX_WORDS - 1])
X	SynErr("TOO MANY WORDS IN LINE");
X    return av - ArgV;
X}
X
X/**
X***		V A R I A B L E   R O U T I N E S
X**/
X
X
X/*
X**  Return the value of a variable, or an empty string.
X*/
Xstatic char *
XGetVar(Name)
X    REGISTER char	*Name;
X{
X    REGISTER VAR	*Vptr;
X
X    for (Vptr = VarList; Vptr < &VarList[MAX_VARS]; Vptr++)
X	if (Vptr->Name && EQ(Vptr->Name, Name))
X	    return Vptr->Value;
X
X    /* Try the environment. */
X    Name = getenv(Name);
X    return Name ? Name : "";
X}
X
X
X/*
X**  Insert a variable/value pair into the list of variables.
X*/
Xvoid
XSetVar(Name, Value)
X    REGISTER char	*Name;
X    REGISTER char	*Value;
X{
X    REGISTER VAR	*Vptr;
X    REGISTER VAR	*FreeVar;
X
X    /* Skip leading whitespace in variable names, sorry... */
X    while (ISwhit(*Name))
X	Name++;
X
X    /* Try to find the variable in the table. */
X    for (Vptr = VarList, FreeVar = NULL; Vptr < &VarList[MAX_VARS]; Vptr++)
X	if (Vptr->Name) {
X	    if (EQ(Vptr->Name, Name)) {
X		free(Vptr->Value);
X		Vptr->Value = COPY(Value);
X		return;
X	    }
X	}
X	else if (FreeVar == NULL)
X	    FreeVar = Vptr;
X
X    if (FreeVar == NULL) {
X	Fprintf(stderr, "Overflow, can't do '%s=%s'\n", Name, Value);
X	SynErr("ASSIGNMENT");
X    }
X    FreeVar->Name = COPY(Name);
X    FreeVar->Value = COPY(Value);
X}
X
X
X/*
X**  Expand variable references inside a word that are of the form:
X**	foo${var}bar
X**	foo$$bar
X**  Returns a pointer to a static area which is overwritten every
X**  other time it is called, so that we can do EQ(Expand(a), Expand(b)).
X*/
Xstatic char *
XExpand(p)
X    REGISTER char	*p;
X{
X    static char		buff[2][VAR_VALUE_SIZE];
X    static int		Flag;
X    REGISTER char	*q;
X    REGISTER char	*n;
X    REGISTER char	Closer;
X    char		name[VAR_NAME_SIZE];
X
X    /* This is a hack, but it makes things easier in DoTEST, q.v. */
X    if (p == NULL)
X	return p;
X
X    /* Pick the "other" buffer then loop over the string to be expanded. */
X    for (Flag = 1 - Flag, q = buff[Flag]; *p; )
X	if (*p == '$')
X	    if (*++p == '$') {
X		Sprintf(name, "%d", Pid());
X		q += strlen(strcpy(q, name));
X		p++;
X	    }
X	    else if (*p == '?') {
X		/* Fake it -- all commands always succeed, here. */
X		*q++ = '0';
X		*q = '\0';
X		p++;
X	    }
X	    else {
X		if (*p == '{') {
X		    Closer = '}';
X		    p++;
X		}
X		else
X		    Closer = '\0';
X		for (n = name; *p && *p != Closer; )
X		    *n++ = *p++;
X		if (*p)
X		    p++;
X		*n = '\0';
X		q += strlen(strcpy(q, GetVar(name)));
X	    }
X	else
X	    *q++ = *p++;
X    *q = '\0';
X    return buff[Flag];
X}
X
X
X/*
X**  Do a variable assignment of the form:
X**	var=value
X**	var="quoted value"
X**	var="...${var}..."
X**	etc.
X*/
Xstatic void
XDoASSIGN(Name)
X    REGISTER char	*Name;
X{
X    REGISTER char	*Value;
X    REGISTER char	*q;
X    REGISTER char	Quote;
X
X    /* Split out into name=value strings, and deal with quoted values. */
X    Value = IDX(Name, '=');
X    *Value = '\0';
X    if (ISquot(*++Value))
X	for (Quote = *Value++, q = Value; *q && *q != Quote; q++)
X	    continue;
X    else
X	for (q = Value; ISletr(*q); q++)
X	    continue;
X    *q = '\0';
X
X    SetVar(Name, Expand(Value));
X}
X
X/**
X***		" O U T P U T "   C O M M A N D S
X**/
X
X#ifndef	OPEN_OVERHEAD
X
X#define CanOpenForWriting(name, mode)	fopen(name, mode)
X
X#else
X
X/*
X**  Translate bad characters in a name into something more palatable.
X**  This could be much more involved, but in practice it isn't very
X**  necessary to do too much work, here.
X*/
Xstatic void
XTranslate(Name)
X    char		*Name;
X{
X    REGISTER char	*p;
X    char		*Period;
X
X    /* If we have an extension, remember it. */
X    Period = RDX(Name, '.');
X
X    /* Loop over the file, transliterating. */
X    for (p = Period; *p; p++)
X	switch (*p) {
X	case '.': case ',':
X	case '#': case '@':
X	    *p = '_';
X	    break;
X	}
X
X    /* Restore the extension. */
X    if (Period)
X	*Period = '.';
X}
X
X
X/*
X**  Open a file for writing, "safely."
X*/
Xstatic FILE *
XCanOpenForWriting(Name, mode)
X    char		*Name;
X    char		*mode;
X{
X    register char	 *p;
X
X#ifdef	NAME_CHECK
X    Translate(Name);
X#endif	/* NAME_CHECK */
X
X    Fprintf(stderr, "Want to open \"%s\" for %s\n",
X	    Name, *mode == 'w' ? "writing" : "appending");
X#ifdef	MAX_FOPENS
X    if (++CreatedCount >= MAX_FOPENS) {
X	Fprintf(stderr, " -- would exceed limit of %d.\n", MAX_FOPENS);
X	SynErr("FILE CREATION");
X    }
X#endif	/* MAX_FOPENS */
X
X#ifdef	PATH_CHECK
X    /* Absolute pathname? */
X    if (Name[0] == '/'
X     && YNQreply("!!!Absolute pathname, confirm [ynq] (q)") != 'T')
X	SynErr("FILE CREATION");
X
X    /* Check for going up... */
X    for (bad = FALSE, p = Name; *p; p++)
X	if (p[0] == '.' && p[1] == '.') {
X	    bad++;
X	    break;
X	}
X    if (bad && YNQreply("!!!Going upwards, confirm [ynq] (q)") != 'T')
X	SynErr("FILE CREATION");
X
X    /* See if it exists. */
X    if (Fexists(Name) && YNQreply("!!!File exists, confirm [ynq] (q)") != 'T')
X	SynErr("FILE CREATION");
X#endif	/* PATH_CHECK */
X}
X
X#endif	/* OPEN_OVERHEAD */
X
X
X/*
X**  Do a cat command.  Understands the following:
X**	cat >arg1 <<arg2
X**	cat >>arg1 <<arg2
X**	cat >>arg1 /dev/null
X**  Except that arg2 is assumed to be quoted (no meta-char expansion
X**  inside the "here" document).  The IO redirection can be in any order.
X*/
X/* ARGSUSED */
Xstatic int
XDoCAT(ac, av)
X    int			ac;
X    REGISTER char	*av[];
X{
X    REGISTER FILE	*Out;
X    REGISTER char	*Ending;
X    REGISTER char	*Source;
X    REGISTER int	V;
X    REGISTER int	l;
X#ifdef	MAX_LINES
X    REGISTER long	c;
X#endif	/* MAX_LINES */
X
X    /* Parse the I/O redirecions. */
X    for (V = TRUE, Source = NULL, Out = NULL, Ending = NULL; *++av; )
X	if (EQ(*av, ">") && av[1]) {
X	    av++;
X	    /* This is a hack, but maybe MS-DOS doesn't have /dev/null? */
X	    Out = Running ? CanOpenForWriting(Expand(*av), "w") : stderr;
X	}
X	else if (EQ(*av, ">>") && av[1]) {
X	    av++;
X	    /* And besides, things are actually faster this way. */
X	    Out = Running ? CanOpenForWriting(Expand(*av), "a") : stderr;
X	}
X	else if (EQ(*av, "<<") && av[1]) {
X	    for (Ending = *++av; *Ending == '\\'; Ending++)
X		continue;
X	    l = strlen(Ending);
X	}
X	else if (!EQ(Source = *av, "/dev/null"))
X	    SynErr("CAT (bad input filename)");
X
X    if (Out == NULL || (Ending == NULL && Source == NULL)) {
X	Note("Missing parameter in CAT command.\n", (char *)NULL);
X	V = FALSE;
X    }
X
X    /* Read the input, spit it out. */
X    if (V && Running && Out != stderr) {
X#ifdef	MAX_LINES
X	c = 0;
X#endif	/* MAX_LINES */
X	if (Source == NULL)
X	    while (GetLine(FALSE) && !EQn(Text, Ending, l)) {
X#ifdef	MAX_LINES
X		if (++c == MAX_LINES) {
X		    Fprintf(stderr, "Line limit of %ld exceeded.\n",
X			    (long)MAX_LINES);
X		    SynErr("CAT");
X		}
X#endif	/* MAX_LINES */
X		(void)fputs(Text, Out);
X	    }
X	(void)fclose(Out);
X    }
X    else
X	while (GetLine(FALSE) && !EQn(Text, Ending, l))
X	    continue;
X
X    return V;
X}
X
X
X/*
X**  Do a SED command.  Understands the following:
X**	sed sX^yyyyXX >arg1 <<arg2
X**	sed -e sX^yyyyXX >arg1 <<arg2
X**  Where the yyyy is a miniscule regular expression; see Matches(), above.
X**  The "X" can be any single character and the ^ is optional (sigh).  No
X**  shell expansion is done inside the "here" document.  The IO redirection
X**  can be in any order.
X*/
X/* ARGSUSED */
Xstatic int
XDoSED(ac, av)
X    int			ac;
X    REGISTER char	*av[];
X{
X    REGISTER FILE	*Out;
X    REGISTER char	*Pattern;
X    REGISTER char	*Ending;
X    REGISTER char	*p;
X    REGISTER int	V;
X    REGISTER int	l;
X    REGISTER int	i;
X#ifdef	MAX_LINES
X    REGISTER long	c;
X#endif	/* MAX_LINES */
X
X    /* Parse IO redirection stuff. */
X    for (V = TRUE, Out = NULL, Pattern = NULL, Ending = NULL; *++av; )
X	if (EQ(*av, ">") && av[1]) {
X	    av++;
X	    Out = Running ? CanOpenForWriting(Expand(*av), "w") : stderr;
X	}
X	else if (EQ(*av, ">>") && av[1]) {
X	    av++;
X	    Out = Running ? CanOpenForWriting(Expand(*av), "a") : stderr;
X	}
X	else if (EQ(*av, "<<") && av[1]) {
X	    for (Ending = *++av; *Ending == '\\'; Ending++)
X		continue;
X	    l = strlen(Ending);
X	}
X	else
X	    Pattern = EQ(*av, "-e") && av[1] ? *++av : *av;
X
X    /* All there? */
X    if (Out == NULL || Ending == NULL || Pattern == NULL) {
X	Note("Missing parameter in SED command.\n", (char *)NULL);
X	V = FALSE;
X    }
X
X    /* Parse the substitute command and its pattern. */
X    if (*Pattern != 's') {
X	Note("Bad SED command -- not a substitute.\n", (char *)NULL);
X	V = FALSE;
X    }
X    else {
X	Pattern++;
X	p = Pattern + strlen(Pattern) - 1;
X	if (*p != *Pattern || *--p != *Pattern) {
X	    Note("Bad substitute pattern in SED command.\n", (char *)NULL);
X	    V = FALSE;
X	}
X	else {
X	    /* Now check the pattern. */
X	    if (*++Pattern == '^')
X		Pattern++;
X	    for (*p = '\0', i = strlen(Pattern), p = Pattern; *p; p++)
X		if (*p == '[' || *p == '*' || *p == '$') {
X		    Note("Bad meta-character in SED pattern.\n", (char *)NULL);
X		    V = FALSE;
X		}
X	}
X    }
X
X    /* Spit out the input. */
X    if (V && Running && Out != stderr) {
X#ifdef	MAX_LINES
X	c = 0;
X#endif	/* MAX_LINES */
X	while (GetLine(FALSE) && !EQn(Text, Ending, l)) {
X#ifdef	MAX_LINES
X	    if (++c == MAX_LINES) {
X		Fprintf(stderr, "Line limit of %ld exceeded.\n",
X			(long)MAX_LINES);
X		SynErr("SED");
X	    }
X#endif	/* MAX_LINES */
X	    (void)fputs(Matches(Pattern, Text) ? &Text[i] : Text, Out);
X	}
X	(void)fclose(Out);
X    }
X    else
X	while (GetLine(FALSE) && !EQn(Text, Ending, l))
X	    continue;
X
X    return V;
X}
X
X/**
X***		" S I M P L E "   C O M M A N D S
X**/
X
X
X/*
X**  Parse a cp command of the form:
X**	cp /dev/null arg
X**  We should check if "arg" is a safe file to clobber, but...
X*/
Xstatic int
XDoCP(ac, av)
X    int		ac;
X    char	*av[];
X{
X    FILE	*F;
X
X    if (Running) {
X	if (ac != 3 || !EQ(av[1], "/dev/null"))
X	    SynErr("CP");
X	if ((F = CanOpenForWriting(Expand(av[2]), "w")) != NULL) {
X	    (void)fclose(F);
X	    return TRUE;
X	}
X	Note("Can't create %s.\n", av[2]);
X    }
X    return FALSE;
X}
X
X
X/*
X**  Do a mkdir command of the form:
X**	mkdir args... [>name] [>>name] [2>name]
X*/
X/* ARGSUSED */
Xstatic int
XDoMKDIR(ac, av)
X    int		ac;
X    char	*av[];
X{
X    if (!Running)
X	return FALSE;
X
X    while (*++av) {
X#ifdef	MAX_MKDIRS
X	if (++DirCount >= MAX_MKDIRS) {
X	    Fprintf(stderr, "Directory limit of %ld exceeded for \"%s\".\n",
X		    MAX_MKDIRS, Expand(av[1]));
X	    SynErr("MKDIR");
X	}
X#endif	/* MAX_MKDIRS */
X 	if (EQ(*av, ">") || EQ(*av, ">>"))
X 	    av++;
X 	else if (EQ(*av, "2") && EQ(av[1], ">"))
X 	    av += 2;
X	else if (mkdir(Expand(av[1]), 0777) < 0)
X	    Note("Can't make directory %s.\n", av[1]);
X    }
X    return TRUE;
X}
X
X
X/*
X**  Do a cd command of the form:
X**	cd arg
X**	chdir arg
X*/
Xstatic int
XDoCD(ac, av)
X    int		ac;
X    char	*av[];
X{
X    if (Running) {
X	if (ac != 2)
X	    SynErr("CD");
X	if (chdir(Expand(av[1])) >= 0)
X	    return TRUE;
X	Note("Can't cd to %s.\n", av[1]);
X    }
X    return FALSE;
X}
X
X
X/*
X**  Do the echo command.  Understands the "-n" hack.
X*/
X/* ARGSUSED */
Xstatic int
XDoECHO(ac, av)
X    int		ac;
X    char	*av[];
X{
X    int		Flag;
X
X    if (Running) {
X	Flag = av[1] != NULL;
X	if (Flag && EQ(av[1], "-n"))
X	    av++;
X	while (*++av)
X	    Fprintf(stderr, "%s ", Expand(*av));
X	if (!Flag)
X	    Fprintf(stderr, "\n");
X	(void)fflush(stderr);
X    }
X    return TRUE;
X}
X
X
X/*
X**  Generic "handler" for commands we can't do.
X*/
Xstatic int
XDoIT(ac, av)
X    int		ac;
X    char	*av[];
X{
X    if (Running)
X	Fprintf(stderr, "You'll have to do this yourself:\n\t%s ", *av);
X    return DoECHO(ac, av);
X}
X
X
X/*
X**  Handler for the comment command, :
X*/
Xstatic int
XDoCOMMENT(ac, av)
X    int		ac;
X    char	*av[];
X{
X    if (Running)
X	Fprintf(stderr, "Shell comment:\n");
X    return DoECHO(ac, av);
X}
X
X
X/*
X**  Do an EXIT command.
X*/
Xstatic int
XDoEXIT(ac, av)
X    int		ac;
X    char	*av[];
X{
X    ac = *++av ? atoi(Expand(*av)) : 0;
X    Fprintf(stderr, "Exiting, with status %d\n", ac);
X#ifdef	USE_LONGJMP
X    longjmp(jEnv, 1);
X#else
X    return ac;
X#endif	/* USE_LONGJMP */
X}
X
X
X/*
X**  Do an EXPORT command.  Often used to make sure the archive is being
X**  unpacked with the Bourne (or Korn?) shell.  We look for:
X**	export PATH blah blah blah
X*/
Xstatic int
XDoEXPORT(ac, av)
X    int		ac;
X    char	*av[];
X{
X    if (ac < 2 || !EQ(av[1], "PATH"))
X	SynErr("EXPORT");
X    return TRUE;
X}
X
X/**
X***		F L O W - O F - C O N T R O L   C O M M A N D S
X**/
X
X
X/*
X**  Parse a "test" statement.  Returns TRUE or FALSE.  Understands the
X**  following tests:
X**	test {!} -f arg		Is arg {not} a plain file?
X**	test {!} -d arg		Is arg {not} a directory?
X**	test {!} $var -eq $var	Is the variable {not} equal to the variable?
X**	test {!} $var != $var	Is the variable {not} equal to the variable?
X**	test {!} ddd -ne `wc -c {<} arg`
X**				Is size of arg {not} equal to ddd in bytes?
X**	test -f arg -a $var -eq val
X**				Used by my shar, check for file clobbering
X**  These last two tests are starting to really push the limits of what is
X**  reasonable to hard-code, but they are common cliches in shell archive
X**  "programming."  We also understand the [ .... ] way of writing test.
X**  If we can't parse the test, we show the command and ask the luser.
X*/
Xstatic int
XDoTEST(ac, av)
X    REGISTER int	ac;
X    REGISTER char	*av[];
X{
X    REGISTER char	**p;
X    REGISTER char	*Name;
X    REGISTER int	V;
X    REGISTER int	i;
X
X    /* Quick test. */
X    if (!Running)
X	return FALSE;
X
X    /* See if we're called as "[ ..... ]" */
X    if (EQ(*av, "[")) {
X	for (i = 1; av[i] && !EQ(av[i], "]"); i++)
X	    continue;
X	if (av[i])
X	    free(av[i]);
X	av[i] = NULL;
X	ac--;
X    }
X
X    /* Ignore the "test" argument. */
X    av++;
X    ac--;
X
X    /* Inverted test? */
X    if (EQ(*av, "!")) {
X	V = FALSE;
X	av++;
X	ac--;
X    }
X    else
X	V = TRUE;
X
X    /* Testing for file-ness? */
X    if (ac == 2
X     && EQ(av[0], "-f")
X     && (Name = Expand(av[1])))
X	return GetStat(Name) && Ftype(Name) == F_FILE ? V : !V;
X
X    /* Testing for directory-ness? */
X    if (ac == 2
X     && EQ(av[0], "-d")
X     && (Name = Expand(av[1])))
X	return GetStat(Name) && Ftype(Name) == F_DIR ? V : !V;
X
X    /* Testing a variable's value? */
X    if (ac == 3
X     && (EQ(av[1], "-eq") || EQ(av[1], "=")))
X	return EQ(Expand(av[0]), Expand(av[2])) ? V : !V;
X    if (ac == 3
X     && (EQ(av[1], "-ne") || EQ(av[1], "!=")))
X	return !EQ(Expand(av[0]), Expand(av[2])) ? V : !V;
X
X    /* Testing a file's size? */
X    if (ac == (av[5] && EQ(av[5], "<") ? 8 : 7)
X     && CTYPE(av[0][0]) && isdigit(av[0][0])
X     && (EQ(av[1], "-ne") || EQ(av[1], "-eq"))
X     && EQ(av[2], "`")
X     && EQ(av[3], "wc")
X     && EQ(av[4], "-c")
X     && EQ(av[ac - 1], "`")) {
X	if (GetStat(av[ac - 2])) {
X	    if (EQ(av[1], "-ne"))
X		return Fsize(av[ac - 2]) != atol(av[0]) ? V : !V;
X	    return Fsize(av[ac - 2]) == atol(av[0]) ? V : !V;
X	}
X	Note("Can't get status of %s.\n", av[ac - 2]);
X    }
X
X    /* Testing for existing, but can clobber? */
X    if (ac == 6
X     && EQ(av[0], "-f")
X     && EQ(av[2], "-a")
X     && (EQ(av[4], "!=") || EQ(av[4], "-ne"))) {
X	/* Gotta simplify this for some compilers.  People, too. :-) */
X	Name = Expand(av[1]);
X	if (GetStat(Name)
X	 && Ftype(Name) == F_FILE
X	 && !EQ(Expand(av[3]), Expand(av[5])))
X	    return V;
X	return !V;
X    }
X
X    /* I give up -- print it out, and let's ask Mikey, he can do it... */
X    Fprintf(stderr, "Can't parse this test:\n\t");
X    for (i = FALSE, p = av; *p; p++) {
X	Fprintf(stderr, "%s ", *p);
X	if (p[0][0] == '$')
X	    i = TRUE;
X    }
X    if (i) {
X	Fprintf(stderr, "\n(Here it is with shell variables expanded...)\n\t");
X	for (p = av; *p; p++)
X	    Fprintf(stderr, "%s ", Expand(*p));
X    }
X    Fprintf(stderr, "\n");
X
X    switch (YNQreply("Is value true/false/quit [tfq] (q)")) {
X    case 'T':
X	return TRUE;
X    case 'F':
X	return FALSE;
X    }
X    SynErr("TEST");
X    /* NOTREACHED */
X}
X
X
X/*
X**  Do until we reach a specific terminator.
X*/
Xstatic void
XDoUntil(Terminator, NewVal)
X    char	*Terminator;
X    int		NewVal;
X{
X    char	*av[MAX_WORDS];
X    int		OldVal;
X
X    for (OldVal = Running, Running = NewVal; GetLine(TRUE); )
X	if (Argify(av)) {
X	    if (EQ(av[0], Terminator))
X		break;
X	    (void)Exec(av);
X	}
X
X    Running = OldVal;
X}
X
X
X/*
X**  Do an IF statement.
X*/
Xstatic int
XDoIF(ac, av)
X    REGISTER int	ac;
X    REGISTER char	*av[];
X{
X    REGISTER char	**p;
X    REGISTER int 	Flag;
X    char		*vec[MAX_WORDS];
X    char		**Pushed;
X
X    /* Skip first argument. */
X    if (!EQ(*++av, "[") && !EQ(*av, "test") && !EQ(*av, "/bin/test"))
X	SynErr("IF");
X    ac--;
X
X    /* Look for " ; then " on this line, or "then" on next line. */
X    for (Pushed = NULL, p = av; *p; p++) {
X	Flag = EQ(*p, ";");
X	if (Flag) {
X	    if (p[1] == NULL || !EQ(p[1], "then"))
X		SynErr("IF");
X	    *p = NULL;
X	    ac -= 2;
X	    break;
X	}
X    }
X    if (!Flag) {
X	(void)GetLine(TRUE);
X	if (Argify(vec) > 1)
X	    Pushed = &vec[1];
X	if (!EQ(vec[0], "then"))
X	    SynErr("IF (missing THEN)");
X    }
X
X    if (DoTEST(ac, av)) {
X	if (Pushed)
X	    (void)Exec(Pushed);
X	while (GetLine(TRUE)) {
X	    if ((ac = Argify(vec)) == 1 && EQ(vec[0], "fi"))
X		break;
X	    if (EQ(vec[0], "else")) {
X		DoUntil("fi", FALSE);
X		break;
X	    }
X	    (void)Exec(vec);
X	}
X    }
X    else
X	while (GetLine(TRUE)) {
X	    if ((ac = Argify(vec)) == 1 && EQ(vec[0], "fi"))
X		break;
X	    if (EQ(vec[0], "else")) {
X		if (ac > 1)
X		    (void)Exec(&vec[1]);
X		DoUntil("fi", Running);
X		break;
X	    }
X	}
X    return TRUE;
X}
X
X
X/*
X**  Do a FOR statement.
X*/
Xstatic int
XDoFOR(ac, av)
X    REGISTER int	ac;
X    REGISTER char	*av[];
X{
X    REGISTER char	*Var;
X    REGISTER char	**Values;
X    REGISTER int	Found;
X    long		Here;
X    char		*vec[MAX_WORDS];
X
X    /* Check usage, get variable name and eat noise words. */
X    if (ac < 4 || !EQ(av[2], "in"))
X	SynErr("FOR");
X    Var = av[1];
X    ac -= 3;
X    av += 3;
X
X    /* Look for "; do" on this line, or just "do" on next line. */
X    for (Values = av; *++av; ) {
X	Found = EQ(*av, ";");
X	if (Found) {
X	    if (av[1] == NULL || !EQ(av[1], "do"))
X		SynErr("FOR");
X	    *av = NULL;
X	    break;
X	}
X    }
X    if (!Found) {
X	(void)GetLine(TRUE);
X	if (Argify(vec) != 1 || !EQ(vec[0], "do"))
X	    SynErr("FOR (missing DO)");
X    }
X
X    for (Here = ftell(Input); *Values; ) {
X	SetVar(Var, *Values);
X	DoUntil("done", Running);
X	    ;
X	/* If we're not Running, only go through the loop once. */
X	if (!Running)
X	    break;
X	if (*++Values && (fseek(Input, Here, 0) < 0 || ftell(Input) != Here))
X	    SynErr("FOR (can't seek back)");
X    }
X
X    return TRUE;
X}
X
X
X/*
X**  Do a CASE statement of the form:
X**	case $var in
X**	    text1)
X**		...
X**		;;
X**	esac
X**  Where text1 is a simple word or an asterisk.
X**  In particular, these patterns aren't handled:
X**	    text1|text2)
X**	    text1) .... ;;
X*/
Xstatic int
XDoCASE(ac, av)
X    REGISTER int	ac;
X    REGISTER char	*av[];
X{
X    REGISTER int	FoundIt;
X    char		*vec[MAX_WORDS];
X    char		Value[VAR_VALUE_SIZE];
X
X    if (ac != 3 || !EQ(av[2], "in"))
X	SynErr("CASE");
X    (void)strcpy(Value, Expand(av[1]));
X
X    for (FoundIt = FALSE; GetLine(TRUE); ) {
X	ac = Argify(vec);
X	if (EQ(vec[0], "esac"))
X	    break;
X	/* This is for vi: (-; sigh. */
X	if (!EQ(vec[1], ")"))
X	    SynErr("CASE");
X	if (!FoundIt && (EQ(vec[0], Value) || EQ(vec[0], "*"))) {
X	    FoundIt = TRUE;
X	    if (Running && ac > 2)
X		(void)Exec(&vec[2]);
X	    DoUntil(";;", Running);
X	}
X	else
X	    DoUntil(";;", FALSE);
X    }
X    return TRUE;
X}
X
X
X
X/*
X**  Dispatch table of known commands.
X*/
Xstatic COMTAB	 Dispatch[] = {
X    {	"cat",		DoCAT		},
X    {	"case",		DoCASE		},
X    {	"cd",		DoCD		},
X    {	"chdir",	DoCD		},
X    {	"chmod",	DoIT		},
X    {	"cp",		DoCP		},
X    {	"echo",		DoECHO		},
X    {	"exit",		DoEXIT		},
X    {	"export",	DoEXPORT	},
X    {	"for",		DoFOR		},
X    {	"if",		DoIF		},
X    {	"mkdir",	DoMKDIR		},
X    {	"rm",		DoIT		},
X    {	"sed",		DoSED		},
X    {	"test",		DoTEST		},
X    {	"[",		DoTEST		},
X    {	":",		DoCOMMENT	},
X    {	"",		NULL		}
X};
X
X
X/*
X**  Dispatch on a parsed line.
X*/
Xint
XExec(av)
X    REGISTER char	*av[];
X{
X    REGISTER int	i;
X    REGISTER COMTAB	*p;
X
X    /* We have to re-calculate this because our callers can't always
X       pass the count down to us easily. */
X    for (i = 0; av[i]; i++)
X	continue;
X    if (i) {
X	/* Is this a command we know? */
X	for (p = Dispatch; p->Func; p++)
X	    if (EQ(av[0], p->Name)) {
X		i = (*p->Func)(i, av);
X		if (p->Func == DoEXIT)
X		    /* Sigh; this is a hack. */
X		    return OOB_FALSE;
X		break;
X	    }
X
X	/* If not a command, try it as a variable assignment. */
X	if (p->Func == NULL)
X	    /* Yes, we look for "=" in the first word, but pass down
X	       the whole line. */
X	    if (IDX(av[0], '='))
X		DoASSIGN(Text);
X	    else
X		Note("Command %s unknown.\n", av[0]);
X
X	/* Free the line. */
X	for (i = 0; av[i]; i++)
X	    free(av[i]);
X    }
X    return TRUE;
X}
END_OF_FILE
  if test 27174 -ne `wc -c <'parser.c'`; then
    echo shar: \"'parser.c'\" unpacked with wrong size!
  fi
  # end of 'parser.c'
fi
if test -f 'uuencode.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'uuencode.c'\"
else
  echo shar: Extracting \"'uuencode.c'\" \(2196 characters\)
  sed "s/^X//" >'uuencode.c' <<'END_OF_FILE'
X/*
X**  UUENCODE
X**
X**  Uuencode a file.  This is based on the public-domain implementation that
X**  Mark Horton released to mod.sources with the translation table written
X**  by Jean-Pierre H. Dumas.
X*/
X#include "shar.h"
X#ifndef	VMS
X#include <sys/stat.h>
X#else
X#include <stat.h>
X#endif	/* VMS */
X#ifdef	RCSID
Xstatic char RCS[] =
X	"$Header$";
X#endif	/* RCSID */
X
X/* Encode one character into printable. */
X#define ENC(C)		((C) ? ((C) & 077) + ' ': '`')
X
X
X/*
X**
X*/
Xvoid
Xuuencode(Name, Newname)
X    char		*Name;
X    char		*Newname;
X{
X    register char	*p;
X    register FILE	*Out;
X    register FILE	*In;
X    register int	i;
X    register int	c;
X    register int	n;
X    struct stat		Sb;
X    char		Outbuffer[BUFSIZ];
X    char		buff[UULINE_SIZE + 3];
X
X    /* Open input. */
X    if ((In = fopen(Name, "r")) == NULL) {
X	Fprintf(stderr, "Can't open \"%s\" for input, %s.\n",
X		Name, strerror(errno));
X	exit(1);
X	/* NOTREACHED */
X    }
X
X    /* Open the buffered output. */
X    if ((Out = fopen(Newname, "w")) == NULL) {
X	Fprintf(stderr, "Can't open \"%s\" for output, %s.\n",
X		buff, strerror(errno));
X	exit(1);
X	/* NOTREACHED */
X    }
X    setbuf(Out, Outbuffer);
X
X    /* Put out a translation table. */
X    Fprintf(Out, "table\n");
X    for (i = ' '; i <= '_'; i++) {
X	(void)putc(i, Out);
X	if (i == ' ' + 31)
X	    (void)putc('\n', Out);
X    }
X    (void)putc('\n', Out);
X
X    /* Print the file's mode and name. */
X    if (fstat(fileno(In), &Sb) < 0) {
X	Fprintf(stderr, "Can't get modes of \"%s\", %s.\n",
X		Name, strerror(errno));
X	exit(1);
X	/* NOTREACHED */
X    }
X    Fprintf(Out, "begin %o %s\n", Sb.st_mode & 0777, Name);
X
X    do {
X	/* Read one line, put out the length. */
X	n = fread(buff, sizeof (char), UULINE_SIZE, In);
X	(void)putc(ENC(n), Out);
X
X	/* Put out the line. */
X	for (p = buff, i = 0; i < n; i += 3, p += 3) {
X	    c = (p[0] >> 2);
X	    (void)putc(ENC(c), Out);
X	    c = (p[0] << 4) & 060 | (p[1] >> 4) & 017;
X	    (void)putc(ENC(c), Out);
X	    c = (p[1] << 2) & 074 | (p[2] >> 6) & 03;
X	    (void)putc(ENC(c), Out);
X	    c = p[2] & 077;
X	    (void)putc(ENC(c), Out);
X	}
X	(void)putc('\n', Out);
X    } while (n > 0);
X
X    Fprintf(Out, "end\n");
X
X    (void)fclose(In);
X    (void)fclose(Out);
X}
END_OF_FILE
  if test 2196 -ne `wc -c <'uuencode.c'`; then
    echo shar: \"'uuencode.c'\" unpacked with wrong size!
  fi
  # end of 'uuencode.c'
fi
echo shar: End of archive 1 \(of 6\).
cp /dev/null ark1isdone
MISSING=""
for I in 1 2 3 4 5 6 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 6 archives.
    rm -f ark[1-9]isdone
else
    echo You still must unpack the following archives:
    echo "        " ${MISSING}
fi
exit 0

