[net.sources] "Everyone's" shell

rs@mirror.UUCP (03/25/87)

This is the set of tools I've been using recently in mod.sources.  It's
designed to be portable to non-Unix systems, and includes a program
that should let yuckky MS-DOS, iccky AMIGA-DOS, and similar beasties
unpack shell archives.  It does pretty well at doing many of the items
in the mod.sources archive.

Please beat on this and send me feedback, porting notes, and other
brickbats; I'll publish this in mod.sources in a couple of weeks.
	/r$
#! /bin/sh
# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
# If this archive is complete, you will see the message:
#		"End of archive 1 (of 2)."
# Contents:  MANIFEST Makefile README cwd.c findsrc.c findsrc.man
#   getopt.c glue.c host.c makekit.man mem.c shar.c shar.h shar.man
#   shell.c shell.man sysfuncs.c unshar.man
# Wrapped by rs@mirror on Tue Mar 24 18:12:02 1987
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
echo shar: Extracting \"MANIFEST\" \(1276 characters\)
if test -f MANIFEST ; then 
  echo shar: Will not over-write existing file \"MANIFEST\"
else
sed "s/^X//" >MANIFEST <<'END_OF_MANIFEST'
X   File Name		Archive #	Description
X-----------------------------------------------------------
X MANIFEST                  1	This shipping list
X Makefile                  1	Guess
X README                    1	Acknowledgements, installation notes
X cwd.c                     1	Routines to find current directory
X findsrc.c                 1	Find source files, based on filename
X findsrc.man               1	Manpage for findsrc
X getopt.c                  1	For those that need it
X glue.c                    1	Glue for "safe" unshar
X host.c                    1	Find our machine name
X makekit.c                 2	Partition files into reasonable-sized kits
X makekit.man               1	Manpage for makekit
X mem.c                     1	Memory allocator, uses calloc
X parser.c                  2	Interpreter for shell archives
X shar.c                    1	Create script to create files
X shar.h                    1	Header file, used by everyone
X shar.man                  1	Manpage for makekit
X shell.c                   1	Main routine for my shell interpreter
X shell.man                 1	Manpage for shell
X sysfuncs.c                1	System-dependent routines
X unshar.c                  2	Strip news, notes, mail headers from shar's
X unshar.man                1	Manpage for unshar
END_OF_MANIFEST
if test 1276 -ne `wc -c <MANIFEST`; then
    echo shar: \"MANIFEST\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"Makefile\" \(2618 characters\)
if test -f Makefile ; then 
  echo shar: Will not over-write existing file \"Makefile\"
else
sed "s/^X//" >Makefile <<'END_OF_Makefile'
X##
X##  SOURCE-SHIPPING TOOLS MAKEFILE
X##  $Header: Makefile,v 1.16 87/03/18 13:12:39 rs Exp $
X##
X
X##  Edit appropriately.
XCFLAGS	= -O
X
X##  Use these two lines if you use ranlib...
XRANLIB	= ranlib lib.a
XAR_OBJ	= $(LOBJ)
X##  ...or use these two if you don't.
X#AR_OBJ	= `lorder $(LOBJ) | tsort`
X#RANLIB	= @echo
X
X##  Use this line if you have ctags (free from mod.sources)...
XCTAGS	= ctags -tw $(SRCS) $H
X# ...or this line if you don't.
X#CTAGS	= @echo
X
X##  Where executables should be put.
XDESTDIR	= /usr/local/bin
X
X##  The manpage for "foo" goes in $(MANDIR)/foo.$1
XMANDIR	= /usr/man/man1
X1	= 1
X#MANDIR	= /usr/man/u_man/manl
X#1	= 1L
X
X##
X##  END OF CONFIGURATION SECTION
X##
X
X##  Shorthands for sources, objects, etc.
XH	= shar.h
XL	= lib.a
XPROGS	= findsrc makekit shar unshar shell
XSRCS	= findsrc.c getopt.c makekit.c mem.c shar.c unshar.c
XLSRC	= cwd.c getopt.c host.c sysfuncs.c glue.c parser.c mem.c
XLOBJ	= cwd.o getopt.o host.o sysfuncs.o glue.o parser.o mem.o
XMAN	= findsrc.$1 makekit.$1 shar.$1 unshar.$1 shell.$1
X
X
Xall:		programs tags
X
Xinstall:	newprog newman
X
X
X##  You might want to change these actions...
Xnewprog:	$(PROGS)
X	cd $(DESTDIR) ; rm -f $(PROGS)
X	cp $(PROGS) $(DESTDIR)
X	cd $(DESTDIR) ; strip $(PROGS) ; chmod 755 $(PROGS)
X#	cd $(DESTDIR) ; /etc/chown root $(PROGS)
X
Xnewman:		$(MAN)
X	cd $(MANDIR) ; rm -f $(MAN)
X	cp $(MAN) $(MANDIR)
X
X
X##  CREATING MANPAGES
X.SUFFIXES:	.$1 .man
X.man.$1:
X	cp $< $@
X	chmod 444 $@
X
X##  PROGRAM TARGETS
Xprograms:	$(PROGS)
X
Xfindsrc:	findsrc.o $H $L
X	$(CC) $(CFLAGS) -o findsrc findsrc.o $L
X
Xmakekit:	makekit.o $H $L
X	$(CC) $(CFLAGS) -o makekit makekit.o $L
X
Xshar:		shar.o $H $L
X	$(CC) $(CFLAGS) -o shar shar.o $L
X
Xshell:		shell.o $H $L
X	$(CC) $(CFLAGS) -o shell shell.o $L
X
X##  Special -- gotta build these from the .c files...
Xunshar:		unshar.c $H $L
X	$(CC) $(CFLAGS) -o unshar unshar.c $L
Xunshar.safe:	unshar.c $H $L
X	$(CC) $(CFLAGS) -DUSE_MY_SHELL -o unshar unshar.c $L
X
X
X##  UTILITY TARGETS
Xlib.a:		$(LOBJ)
X	@rm -f lib.a
X	ar r lib.a $(AR_OBJ)
X	$(RANLIB)
X$(LOBJ):	$H
X
Xtags:		$(SRCS) $H
X	$(CTAGS)
X
Xclean:
X	rm -f *.[oa] *.$1 *.BAK $(PROGS) unshar.safe
X	rm -f lint lib.ln tags core foo a.out
X
X
X##  LINT (probably only good for 4.[23]BSD Unices)
X#LINTF	= -p -ahb
XLINTF	= -ahb
Xlint:		programs lib.ln
X	exec lint $(LINTF)u  >lint $(LSRC)
X	exec lint $(LINTF)  >>lint findsrc.c lib.ln
X	exec lint $(LINTF)  >>lint makekit.c lib.ln
X	exec lint $(LINTF)  >>lint shar.c    lib.ln
X	exec lint $(LINTF)  >>lint shell.c   lib.ln
X	exec lint $(LINTF)  >>lint unshar.c  lib.ln
X#	exec lint $(LINTF) -DUSE_MY_SHELL >>lint unshar.c  lib.ln
X	
X
Xlib.ln:		$(LSRC)
X	lint -CX $(LSRC)
X	mv llib-lX.ln lib.ln
END_OF_Makefile
if test 2618 -ne `wc -c <Makefile`; then
    echo shar: \"Makefile\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"README\" \(4350 characters\)
if test -f README ; then 
  echo shar: Will not over-write existing file \"README\"
else
sed "s/^X//" >README <<'END_OF_README'
XThis set of tools is designed to make it easier to ship sources around.
XI wrote them because I do a lot of that as moderator of mod.sources, and
Xnothing else did the job for me.  This set isn't perfect, but's very
Xclose.  Included are a program to find source files, a program to partition
Xthem into reasonable sizes, a program to make shell archives out of
Xthem, a program to strip mail, news, and notes headers from archives
Xbefore feeding them to a shell, and a program to simulate /bin/sh for
Xnon-Unix system.
X
XThe sources in this distribution are being released into the public
Xdomain; do what you want, but let your conscience be your guide.
XIf you somehow enhance this package, please send it on to me so that
Xothers can benefit.
X
XI'll try to answer any questions or problems that come up -- send me
Xelectronic mail.
X
XTo install, edit the Makefile and shar.h as necessary (see detailed list
Xof parameters, below), then run make; doing make install will put the
Xmanpages and executables where you told it to.  I don't think "make lint"
Xwill work anywhere other than a 4.[23]BSD system.
X
XIf you aren't running on Unix, then you will have to write replacements
Xfor the functions in sysfuncs.c; everything else should be OK.  If you
Xdon't have a Unix-like make available, you will have to write a command
Xscript or otherwise work out something with your compiler.
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 Larry
XWall for giving me something nice to reverse-engineer, and Michael Mauldin
Xfor unshar.  It may have been a mistake to take people's names out of the
Xcode; if I upset anyone by doing this, please let me know.
X
XThere are a couple of things that could still be done, but I'm tired of
Xworking on this, and I don't need them often enough to want to come up
Xwith general solutions.  Specifically:
X	    Automatically splitting up large text files;
X	    Automatically invoking uuencode when sending binaries; and
X	    Perhaps arranging for the archive to invoke uudecode.
X
XEnjoy!
X	Richard $alz
X	Mirror Systems
X	2067 Massachusetts Avenue
X	Cambridge, MA  02140
X	{ihnp4, harvard!wjh12, cca, cbosgd, seismo}!mirror!rs
X	rs%mirror.UUCP@cca
X	rs@mirror.TMC.COM
X
XMy, my, my, aren't we anal:
X	$Header: README,v 1.7 87/03/24 18:09:04 rs Exp $
X
XCOMPILATION PARAMETERS
X----------------------
X[The Makefile is ... er ... self-documented.]
X
XCAN_POPEN
X    If #define'd, then findsrc will do a popen() to file(1) to see if
X    the file is executable, etc.
X
XDEF_SAVEIT
X    This is used by unshar, and sets whether or not to save the headers
X    by default.
X
XGETWD GETCWD PWDPOPEN PWDGETENV
X    These are used in cwd.c to determine how to find out the current
X    directory, which unshar sometimes needs to know.
X
XGETHOSTNAME HOST UNAME UUNAME WHOAMI
X    There are used in host.c to determine how to find out the host name,
X    which shar needs to know.
X
XIDX, RDX
X    Some Unices call index/rindex strchr/strrchr.
X
XIN_SYS_DIR IN_SYS_NDIR IN_NDIR
X    The findsrc program uses the BSD-style directory-reading routines.
X    These routines, and the structures they use, are typically defined in
X    one of <sys/dir.h> <sys/ndir.h> or "ndir.h", respectively.
X
XNEED_MKDIR
X    In sysfuncs, this includes a subroutine that calls system() to do a
X    mkdir command; otherwise mkdir() must be in your C library.
X
XNEED_GETOPT
X    Define this if getopt is not in your C library.
X
XSYS_WAIT
X    Define this if you have <sys/wait.h> and the vfork system call.
X
XTHE_TTY
X    This should be a "device" to open so we can read input from the
X    user's terminal if standard input is, e.g., coming from a file.
X    Unix systems should use the default "/dev/tty"; MS-DOS and would
X    be "CON:" I guess?
X
XUSE_MY_SHELL
X    Used in unshar.  If #define'd, then the archive is passed to my shell
X    interpreter sub-routine.  The default is to do a popen() to /bin/sh.
X
XUSER_ENV
X    The name of the environment variable that holds the user's name,
X    which shar needs to know.
X
Xtypedef int	*align_t
X    This is a worst-case alignment, to get lint to shut up about malloc'd
X    space.  For ANSI C, e.g., this should be "typedef void *align_t".
X
Xtypedef long	time_t
X    What datatype is needed to hold a time value.
X
Xtypedef long	off_t
X    What datatype is needed to hold a file size.
END_OF_README
if test 4350 -ne `wc -c <README`; then
    echo shar: \"README\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"cwd.c\" \(941 characters\)
if test -f cwd.c ; then 
  echo shar: Will not over-write existing file \"cwd.c\"
else
sed "s/^X//" >cwd.c <<'END_OF_cwd.c'
X/*
X**  Return current working directory.  Something for everyone.
X*/
X/* LINTLIBRARY */
X#include "shar.h"
XRCS("$Header: cwd.c,v 1.3 87/03/02 11:03:21 rs Exp $")
X
X
X#ifdef	PWDGETENV
X/* ARGSUSED */
Xchar *
XCwd(p, i)
X    char	*p;
X    int		 i;
X{
X    char	*q;
X
X    return((q = getenv(PWDGETENV)) ? strcpy(p, q) : NULL);
X}
X#endif	/* PWDGETENV */
X
X
X#ifdef	GETWD
X/* ARGSUSED1 */
Xchar *
XCwd(p, size)
X    char	*p;
X    int		 size;
X{
X    return(getwd(p) ? p : NULL);
X}
X#endif	/* GETWD */
X
X
X#ifdef	GETCWD
Xchar *
XCwd(p, size)
X    char	*p;
X    int		 size;
X{
X    return(getcwd(p, size) ? p : NULL);
X}
X#endif	/* GETCWD */
X
X
X#ifdef	PWDPOPEN
Xextern FILE	*popen();
Xchar *
XCwd(p, size)
X    char	*p;
X    int		 size;
X{
X    FILE	*F;
X    int		 i;
X
X    if (F = popen("exec pwd", "r")) {
X	if (fgets(p, size, F) && p[i = strlen(p) - 1] == '\n') {
X	    p[i] = '\0';
X	    (void)fclose(F);
X	    return(p);
X	}
X	(void)fclose(F);
X    }
X    return(NULL);
X}
X#endif	/* PWDPOPEN */
END_OF_cwd.c
if test 941 -ne `wc -c <cwd.c`; then
    echo shar: \"cwd.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"findsrc.c\" \(6863 characters\)
if test -f findsrc.c ; then 
  echo shar: Will not over-write existing file \"findsrc.c\"
else
sed "s/^X//" >findsrc.c <<'END_OF_findsrc.c'
X/*
X**  FINDSRC
X**  Walk directories, trying to find source files.
X**
X**  Options:
X**	-d [yn]		Set default answer to yes or no
X**	-.		Do .cshrc, .profile, etc.
X**	-o file		Redirect stdout
X**	-R		Do RCS and SCCS files and directories
X**	-S		Do SCCS and RCS files and directories
X**	-v		Verbose, include those that were rejected
X*/
X#include "shar.h"
XRCS("$Header: findsrc.c,v 1.18 87/03/18 14:03:03 rs Exp $")
X
X
X/*
X**  How many levels to walk down?
X*/
X#define MAX_LEVELS	6
X
X
X/*
X**  Global variables.
X*/
Xint	 DoDOTFILES;			/* Do .newsrc and friends?	*/
Xint	 DoRCS;				/* Do RCS and SCCS files?	*/
Xint	 Default;			/* Default answer from user	*/
Xint	 Verbose;			/* List rejected files, too?	*/
Xchar	 Dname[] = "/tmp/findDXXXXXX";	/* Filename of directory list	*/
Xchar	 Fname[] = "/tmp/findFXXXXXX";	/* Filename of file list	*/
XFILE	*Dfile;				/* List of directories found	*/
XFILE	*Ffile;				/* List of files found		*/
XFILE	*DEVTTY;			/* The tty, if in filter mode	*/
X
X
X/*
X**  Signal handler.  Clean up and die.
X*/
Xstatic
XCatch(s)
X    int		 s;
X{
X    int		 e;
X
X    e = errno;
X    (void)unlink(Dname);
X    (void)unlink(Fname);
X    fprintf(stderr, "Got signal %d, %s.\n", s, Ermsg(e));
X    exit(1);
X}
X
X
X/*
X**  Given a filename, apply heuristics to see if we want it.
X*/
Xstatic int
XWanted(Name)
X    register char	*Name;
X{
X    register FILE	*F;
X    register char	*s;
X    register char	*p;
X    register char	*d;
X    char		 buff[BUFSIZ];
X
X    /* Get down to brass tacks. */
X    s = (p = RDX(Name, '/')) ? p + 1 : Name;
X
X    /* Only do directories other than . and .. and regular files. */
X    if ((Ftype(Name) != F_DIR && Ftype(Name) != F_FILE)
X     || EQ(s, ".") || EQ(s, ".."))
X	return(FALSE);
X
X    /* Common cruft we never want. */
X    if (EQ(s, "foo") || EQ(s, "core") || EQ(s, "tags") || EQ(s, "lint"))
X	return(FALSE);
X
X    /* Disregard stuff with bogus suffixes. */
X    d = RDX(s, '.');
X    if ((p = d)
X     && (EQ(++p, "out") || EQ(p, "orig") || EQ(p, "rej") || EQ(p, "BAK")
X      || EQ(p, "CKP") || EQ(p, "old") || EQ(p, "o")))
X	return(FALSE);
X
X    /* Want .cshrc, .newsrc, etc.? */
X    if (*s == '.' && isalpha(s[1]))
X	return(DoDOTFILES);
X
X    /* RCS or SCCS file or directory? */
X    if (EQ(s, "RCS")
X     || ((p = RDX(s, ',')) && *++p == 'v' && *++p == '\0')
X     || EQ(s, "SCCS") || (s[0] == 's' && s[1] == '.'))
X	return(DoRCS);
X
X    /* Mlisp (yes to .ml, no to .mo)? */
X    if ((p = d) && *++p == 'm' && p[2] == '\0')
X	return(*++p = 'l');
X
X    /* C source or manpage? */
X    if ((p = d) && (*++p == 'c' || *p == 'h' || isdigit(*p))
X     && *++p == '\0')
X	return(TRUE);
X
X    /* Make control file? */
X    if ((*s == 'M' || *s == 'm') && EQ(s + 1, "akefile"))
X	return(TRUE);
X
X    /* Convert to lowercase, and see if it's a README or MANIFEST. */
X    for (p = strcpy(buff, s); *p; p++)
X	if (isupper(*p))
X	    *p = tolower(*p);
X    if (EQ(buff, "readme") || EQ(buff, "read_me") || EQ(buff, "read-me")
X     || EQ(buff, "manifest"))
X	return(TRUE);
X
X    /* If we have a default, give it back. */
X    if (Default)
X	return(Default == 'y');
X
X#ifdef	CAN_POPEN
X    /* See what file(1) has to say; if it says executable, punt. */
X    (void)sprintf(buff, "exec file '%s'", Name);
X    if (F = popen(buff, "r")) {
X	(void)fgets(buff, sizeof buff, F);
X	(void)pclose(F);
X	for (p = buff; p = IDX(p, 'e'); p++)
X	    if (PREFIX(p, "executable"))
X		return(FALSE);
X	(void)fputs(buff, stderr);
X    }
X#endif	/* CAN_POPEN */
X
X    /* Add it? */
X    while (TRUE) {
X	if (DEVTTY == NULL)
X	    DEVTTY = fopen(THE_TTY, "r");
X	fprintf(stderr, "Add this one (y or n)[y]?  ");
X	(void)fflush(stderr);
X	if (fgets(buff, sizeof buff, DEVTTY) == NULL
X	 || buff[0] == '\n' || buff[0] == 'y' || buff[0] == 'Y')
X	    break;
X	if (buff[0] == 'n' || buff[0] == 'N')
X	    return(FALSE);
X	if (buff[0] == '!' )
X	    (void)system(&buff[1]);
X	fprintf(stderr, "--------------------\n%s:  ", Name);
X	clearerr(DEVTTY);
X    }
X    return(TRUE);
X}
X
X
X/*
X**  Quick and dirty recursive routine to walk down directory tree.
X**  Could be made more general, but why bother?
X*/
Xstatic void
XProcess(p, level)
X    register char	 *p;
X    register int	  level;
X{
X    register char	 *q;
X    DIR			 *Dp;
X    struct direct	 *E;
X    char		  buff[BUFSIZ];
X
X    if (!GetStat(p))
X	fprintf(stderr, "Can't walk down %s, %s.\n", Ermsg(errno));
X    else {
X	/* Skip leading ./ which find(1), e.g., likes to put out. */
X	if (p[0] == '.' && p[1] == '/')
X	    p += 2;
X
X	if (Wanted(p))
X	    fprintf(Ftype(p) == F_FILE ? Ffile : Dfile, "%s\n", p);
X	else if (Verbose)
X	    fprintf(Ftype(p) == F_FILE ? Ffile : Dfile, "PUNTED %s\n", p);
X
X	if (Ftype(p) == F_DIR)
X	    if (++level == MAX_LEVELS)
X		fprintf(stderr, "Won't walk down %s -- more than %d levels.\n",
X			p, level);
X	    else if (Dp = opendir(p)) {
X		q = buff + strlen(strcpy(buff, p));
X		for (*q++ = '/'; E = readdir(Dp); )
X		    if (!EQ(E->d_name, ".") && !EQ(E->d_name, "..")) {
X			(void)strcpy(q, E->d_name);
X			Process(buff, level);
X		    }
X		(void)closedir(Dp);
X	    }
X	    else
X		fprintf(stderr, "Can't open directory %s, %s.\n",
X			p, Ermsg(errno));
X    }
X}
X
X
Xmain(ac, av)
X    register int	 ac;
X    register char	*av[];
X{
X    register char	*p;
X    register int	 i;
X    register int	 Oops;
X    char		 buff[BUFSIZ];
X
X    /* Parse JCL. */
X    for (Oops = 0; (i = getopt(ac, av, ".d:o:RSv")) != EOF; )
X	switch (i) {
X	    default:
X		Oops++;
X		break;
X	    case '.':
X		DoDOTFILES++;
X		break;
X	    case 'd':
X		switch (optarg[0]) {
X		    default:
X			Oops++;
X		    case 'y':
X		    case 'n':
X			break;
X		    case 'Y':
X			Default = 'y';
X			break;
X		    case 'N':
X			Default = 'n';
X			break;
X		}
X		break;
X	    case 'o':
X		if (freopen(optarg, "w", stdout) == NULL) {
X		    fprintf(stderr, "Can't open %s for output, %s.\n",
X			    optarg, Ermsg(errno));
X		    exit(1);
X		}
X	    case 'R':
X	    case 'S':
X		DoRCS++;
X		break;
X	    case 'v':
X		Verbose++;
X		break;
X	}
X    if (Oops) {
X	fprintf(stderr, "Usage: findsrc [-d{yn}] [-.] [-{RS}] [-v] files...\n");
X	exit(1);
X    }
X    av += optind;
X
X    /* Set signal catcher, open temp files. */
X    SetSigs(TRUE, Catch);
X    Dfile = fopen(mktemp(Dname), "w");
X    Ffile = fopen(mktemp(Fname), "w");
X
X    /* Read list of files, determine their status. */
X    if (*av)
X	for (DEVTTY = stdin; *av; av++)
X	    Process(*av, 0);
X    else
X	while (fgets(buff, sizeof buff, stdin)) {
X	    if (p = IDX(buff, '\n'))
X		*p = '\0';
X	    else
X		fprintf(stderr, "Warning, line too long:\n\t%s\n", buff);
X	    Process(buff, 0);
X	}
X
X    /* First print directories. */
X    if (freopen(Dname, "r", Dfile)) {
X	while (fgets(buff, sizeof buff, Dfile))
X	    (void)fputs(buff, stdout);
X	(void)fclose(Dfile);
X    }
X
X    /* Now print regular files. */
X    if (freopen(Fname, "r", Ffile)) {
X	while (fgets(buff, sizeof buff, Ffile))
X	    (void)fputs(buff, stdout);
X	(void)fclose(Ffile);
X    }
X
X    /* That's all she wrote. */
X    (void)unlink(Dname);
X    (void)unlink(Fname);
X    exit(0);
X}
END_OF_findsrc.c
if test 6863 -ne `wc -c <findsrc.c`; then
    echo shar: \"findsrc.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"findsrc.man\" \(1865 characters\)
if test -f findsrc.man ; then 
  echo shar: Will not over-write existing file \"findsrc.man\"
else
sed "s/^X//" >findsrc.man <<'END_OF_findsrc.man'
X.TH FINDSRC 1 LOCAL
X.\" $Header: findsrc.man,v 1.2 87/03/06 14:02:49 rs Exp $
X.SH NAME
Xfindsrc \- walk directories, trying to find source files
X.SH SYNOPSIS
X.B findsrc
X[
X.B \-.
X] [
X.BI \-d y_or_n
X] [
X.BI \-o output_file
X] [
X.B \-R
X] [
X.B \-S
X] [
X.B \-v
X] [ file... ]
X.SH DESCRIPTION
X.I Findsrc
Xrecursively examines all directories and files specified on the command
Xline, and determines, based on the file name, whether the file contains
Xsource code or not.
XAll directories are listed first, followed by all regular files,
Xwith one item per line.
X.PP
XIf
X.I findsrc
Xis unable to make a decision, it invokes the
X.IR file (1)
Xcommand, and prompts the user for a decision.
XIn reply to the prompt, type the letter ``y'' or ``n'' (either case);
XRETURN means yes.
XIf the reply starts with an exclamation point, the rest of the line
Xis passed off to a sub-shell and the question is repeated.
XThe ``\-d'' option may be used with an argument of ``y'' or ``n''
Xto by-pass the interaction, and provide a default answer.
X.PP
XThe ``\-o'' option may be used to specify an output filename.
XThis is designed to prevent confusion if a command like the following
Xis executed:
X.RS
Xfindsrc . * >LIST
X.RE
X.PP
XBy default,
X.I findsrc
Xignores files whose names begin with a period, like ``.newsrc'' or
X``.tags''; such files may be included by using the ``\-.'' option.
X.I Findsrc
Xalso normally ignores
X.SM RCS
Xand
X.SM SCCS
Xfiles and directories; using either the ``\-R'' or ``\-S'' option causes
Xboth to be included.
X.PP
X.I Findsrc
Xnormally lists only the names of those files that have been selected.
XIf the ``\-v'' option is used, rejected files are also listed preceeded
Xby the word ``PUNTED.''
X.PP
XIf no files are specified on the command line, the program operates as
Xa filter, reading a list of files and directories from the standard
Xinput, one per line.
X.SH "SEE ALSO"
Xmakekit(1L).
END_OF_findsrc.man
if test 1865 -ne `wc -c <findsrc.man`; then
    echo shar: \"findsrc.man\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"getopt.c\" \(1422 characters\)
if test -f getopt.c ; then 
  echo shar: Will not over-write existing file \"getopt.c\"
else
sed "s/^X//" >getopt.c <<'END_OF_getopt.c'
X/*
X**  Return options and their values from the command line.
X**
X**  This comes from the AT&T public-domain getopt published in mod.sources.
X*/
X/* LINTLIBRARY */
X#include "shar.h"
XRCS("$Header: getopt.c,v 1.4 87/03/02 11:03:24 rs Exp $")
X
X#ifdef	NEED_GETOPT
X
X
X#define TYPE	int
X
X#define ERR(s, c)	if(opterr){\
X	char errbuf[2];\
X	errbuf[0] = c; errbuf[1] = '\n';\
X	(void) write(2, argv[0], (TYPE)strlen(argv[0]));\
X	(void) write(2, s, (TYPE)strlen(s));\
X	(void) write(2, errbuf, 2);}
X
Xextern int strcmp();
X
Xint	opterr = 1;
Xint	optind = 1;
Xint	optopt;
Xchar	*optarg;
X
Xint
Xgetopt(argc, argv, opts)
Xint	argc;
Xchar	**argv, *opts;
X{
X	static int sp = 1;
X	register int c;
X	register char *cp;
X
X	if(sp == 1)
X		if(optind >= argc ||
X		   argv[optind][0] != '-' || argv[optind][1] == '\0')
X			return(EOF);
X		else if(strcmp(argv[optind], "--") == NULL) {
X			optind++;
X
X		}
X	optopt = c = argv[optind][sp];
X	if(c == ':' || (cp=IDX(opts, c)) == NULL) {
X		ERR(": illegal option -- ", c);
X		if(argv[optind][++sp] == '\0') {
X			optind++;
X			sp = 1;
X		}
X		return('?');
X	}
X	if(*++cp == ':') {
X		if(argv[optind][sp+1] != '\0')
X			optarg = &argv[optind++][sp+1];
X		else if(++optind >= argc) {
X			ERR(": option requires an argument -- ", c);
X			sp = 1;
X			return('?');
X		} else
X			optarg = argv[optind++];
X		sp = 1;
X	} else {
X		if(argv[optind][++sp] == '\0') {
X			sp = 1;
X			optind++;
X		}
X		optarg = NULL;
X	}
X	return(c);
X}
X
X#endif	/* NEED_GETOPT */
END_OF_getopt.c
if test 1422 -ne `wc -c <getopt.c`; then
    echo shar: \"getopt.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"glue.c\" \(897 characters\)
if test -f glue.c ; then 
  echo shar: Will not over-write existing file \"glue.c\"
else
sed "s/^X//" >glue.c <<'END_OF_glue.c'
X/*
X**  Subroutine to call the shell archive parser.  This is "glue"
X**  between unshar and the parser proper.
X*/
X#include "shar.h"
XRCS("$Header: glue.c,v 1.1 87/03/09 16:24:17 rs Exp $")
X
X
X/*
X**  Copy the input to a temporary file, then call the shell parser.
X*/
XBinSh(Name, Stream, Pushback)
X    char		*Name;
X    register FILE	*Stream;
X    char		*Pushback;
X{
X    static char		 TEMP[] = "/tmp/shellXXXXXX";
X    register FILE	*F;
X    char		 buff[BUFSIZ];
X    char		*vec[MAX_WORDS];
X
X    Interactive = Name == NULL;
X    File = mktemp(TEMP);
X    F = fopen(File, "w");
X    (void)fputs(Pushback, F);
X    while (fgets(buff, sizeof buff, Stream))
X	(void)fputs(buff, F);
X    (void)fclose(Stream);
X
X    if ((Input = fopen(TEMP, "r")) == NULL)
X	fprintf(stderr, "Can't open %s, %s!?\n", TEMP, Ermsg(errno));
X    else
X	while (GetLine(TRUE))
X	    if (Argify(vec))
X		(void)Exec(vec);
X
X    (void)unlink(TEMP);
X}
END_OF_glue.c
if test 897 -ne `wc -c <glue.c`; then
    echo shar: \"glue.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"host.c\" \(1645 characters\)
if test -f host.c ; then 
  echo shar: Will not over-write existing file \"host.c\"
else
sed "s/^X//" >host.c <<'END_OF_host.c'
X/*
X**  Return name of this host.  Something for everyone.
X*/
X/* LINTLIBRARY */
X#include "shar.h"
XRCS("$Header: host.c,v 1.5 87/03/02 11:03:26 rs Exp $")
X
X
X#ifdef	HOST
Xchar *
XHost()
X{
X    return(HOST);
X}
X#endif	/* HOST */
X
X
X#ifdef	GETHOSTNAME
Xchar *
XHost()
X{
X    static char		 buff[64];
X
X    return(gethostname(buff, sizeof buff) < 0 ? "SITE" : buff);
X}
X#endif	/* GETHOSTNAME */
X
X
X#ifdef	UNAME
X#include <sys/utsname.h>
Xchar *
XHost()
X{
X    static struct utsname	 U;
X
X    return(uname(&U) < 0 ? "SITE" : U.nodename);
X}
X#endif	/* UNAME */
X
X
X#ifdef	UUNAME
Xextern FILE	*popen();
Xchar *
XHost()
X{
X    static char		 buff[50];
X    char		*p;
X
X    if (F = popen("exec uuname -l", "r")) {
X	if (fgets(buff, sizeof buff, F) == buff && (p = IDX(name, '\n'))) {
X	    (void)pclose(F);
X	    *p = '\0';
X	    return(buff);
X	}
X	(void)pclose(F);
X    }
X    return("SITE");
X}
X#endif	/* UUNAME */
X
X
X#ifdef	WHOAMI
Xchar *
XHost()
X{
X    static char		 name[64];
X    register FILE	*F;
X    register char	*p;
X    char		 buff[100];
X
X    /* Try /etc/whoami; look for a single well-formed line. */
X    if (F = fopen("/etc/whoami", "r")) {
X	if (fgets(name, sizeof name, F) && (p = IDX(name, '\n')))
X	    (void)fclose(F);
X	    *p = '\0';
X	    return(name);
X	}
X	(void)fclose(F);
X    }
X
X    /* Try /usr/include/whoami.h; look for #define sysname "foo" somewhere. */
X    if (F = fopen("/usr/include/whoami.h", "r")) {
X	while (fgets(buff, sizeof buff, F))
X	    /* I don't like sscanf, nor do I trust it.  Sigh. */
X	    if (sscanf(buff, "#define sysname \"%[^\"]\"", name) == 1) {
X		(void)fclose(F);
X		return(name);
X	    }
X	(void)fclose(F);
X    }
X    return("SITE");
X}
X#endif /* WHOAMI */
END_OF_host.c
if test 1645 -ne `wc -c <host.c`; then
    echo shar: \"host.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"makekit.man\" \(3676 characters\)
if test -f makekit.man ; then 
  echo shar: Will not over-write existing file \"makekit.man\"
else
sed "s/^X//" >makekit.man <<'END_OF_makekit.man'
X.TH MAKEKIT 1 LOCAL
X.\" $Header: makekit.man,v 1.7 87/03/13 12:56:33 rs Exp $
X.SH NAME
Xmakekit \- split files up into shell archive packages
X.SH SYNOPSIS
X.B makekit
X[
X.B -e
X] [
X.BI -h #
X] [
X.BI -i name
X] [
X.BI -k #
X] [
X.B -m
X] [
X.BI -n name
X] [
X.BI -o name
X] [
X.B -p
X] [
X.BI -s #[k]
X] [
X.BI -t text
X] [
Xfile...
X]
X.SH DESCRIPTION
X.I Makekit
Xreads a list of files and directories, determines their sizes,
Xand parcels them up into a series of shell archives such that all the
Xarchives are of reasonable size.
XIt then invokes
X.IR shar (1L)
Xto actually create the archives.
X.PP
XBy default, no archive will be larger than about 50,000 bytes; this may be
Xchanged by using the ``\-s'' option.
XIf the number given with the ``\-s'' option ends with the letter ``k''
Xthen the size is multiplied by 1024, otherwise it is taken to be the
Xdesired maximum size, in bytes.
XEach archive will have a name that looks like
X.IR Part nn,
Xwhere ``nn'' represents the two-digit sequence number (with leading zero
Xif needed).
XThe leader part of the archive name may be changed with the ``\-n'' option.
XThe ``\-n'' is also useful when write permission to the directory being
Xarchive is defined; e.g., ``\-n/tmp/KERNEL.''
X.PP
X.I Makekit
Xreads its list of files on the command line, or standard input
Xif none are given.
XIt is also possible to specify an input filename with the ``\-i'' option.
XThe input should contain a list of files, one to a line, to separate.
XIn addition, if each input line looks like this:
X.RS
Xfilename\ \ \ whitespaces\ \ \ optional-digits\ \ \ whitespaces\ \ \ text
X.RE
Xthen
X.I makekit
Xwill ignore the spaces and digits, but remember the text associated with
Xeach file, and output it with the filename when generating the ``shipping
Xmanifest.''
XFurther, the ``\-h'' option may be given to have the program skip the
Xindicated number of lines in the input; this option is provided so that
X.I makekit
Xcan more easily re-parse the manifests it has generated.
X.PP
XThe generated manifest will be sent to the standard output.
XAn alternate output file may be given by using the ``\-o'' option; if
Xthe output file exists,
X.I makekit
Xwill try to rename it with an extension of
X.IR \&.BAK \&.
XIf the ``\-o'' option is used,
X.I makekit
Xwill add that name to the list of files to be archived; the ``\-e''
Xoption may be given to exclude the manifest from the list.
X.PP
XThe ``\-m'' option is the same as given the options,
X\&``-iMANIFEST -oMANIFEST -h2.''
X.PP
XAfter partitioning the files and directories,
X.I makekit
Xcalls
X.I shar
Xwith the proper options to generate archives in a series.
XEach resultant archive will, when executed, check to see if all the parts
Xare present.
XBy using the ``\-t'' option, you can specify a line of starting instructions
Xto display to the recipient when all pieces have been unpacked.
XSee
X.I shar
Xfor more information on multi-part archives.
XIf the ``\-x'' option is used,
X.I shar
Xis not called, but the manifest is still created.
X.PP
X.I Makekit
Xnormally reorders its input so that the archives are as ``dense'' as
Xpossible, with the exception that directories are given priority over
Xfiles, and a file named
X.I README
Xis the first of all.
XThe manifest is also sorted in alphabetical order; this makes it easy
Xto locate ``missing'' files when the distribution is a large one.
XThe ``\-p'' option may be used to override both sortings, however,
Xand preserve the original order of the input list in generating
Xboth the manifest, and the shell archives.
X.SH NOTES
X.I Makekit
Xtries to partition the files so that all directories are in the first archive.
XThis usually means the first archive must be the first one to be unpacked.
X.SH "SEE ALSO"
Xfindsrc(1L), shar(1L)
END_OF_makekit.man
if test 3676 -ne `wc -c <makekit.man`; then
    echo shar: \"makekit.man\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"mem.c\" \(460 characters\)
if test -f mem.c ; then 
  echo shar: Will not over-write existing file \"mem.c\"
else
sed "s/^X//" >mem.c <<'END_OF_mem.c'
X/*
X**  Get some memory or die trying.
X*/
X/* LINTLIBRARY */
X#include "shar.h"
XRCS("$Header: mem.c,v 1.6 87/03/04 14:51:20 rs Exp $")
X
X
Xalign_t
Xgetmem(i, j)
X    unsigned int	 i;
X    unsigned int	 j;
X{
X    extern char		*calloc();
X    align_t		 p;
X
X    if ((p = (align_t)calloc(i, j)) == NULL) {
X	/* Print the unsigned's as int's so ridiculous values show up. */
X	fprintf(stderr, "Can't Calloc(%d,%d), %s.\n", i, j, Ermsg(errno));
X	exit(1);
X    }
X    return(p);
X}
END_OF_mem.c
if test 460 -ne `wc -c <mem.c`; then
    echo shar: \"mem.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"shar.c\" \(6386 characters\)
if test -f shar.c ; then 
  echo shar: Will not over-write existing file \"shar.c\"
else
sed "s/^X//" >shar.c <<'END_OF_shar.c'
X/*
X**  SHAR
X**  Make a shell archive of a list of files.
X**
X**  Options:
X**	-b		Strip leading directories off names before packing
X**	-n #		Set number of this archive in a series
X**	-o foo		Output goes to file foo, not stdout
X**	-e #		Set ending archive number of series
X**	-t "See README"	Set instructions for when all archives are done
X*/
X#include "shar.h"
XRCS("$Header: shar.c,v 1.18 87/03/18 14:10:21 rs Exp $")
X
X
X/*
X**  This prolog is output before the archive.
X*/
Xstatic char	 *Prolog[] = {
X  "! /bin/sh",
X  " This is a shell archive.  Remove anything before this line, then unpack",
X  " it by saving it into a file and typing \"sh file\".  To overwrite existing",
X  " files, type \"sh file -c\".  You can also feed this as standard input via",
X  " unshar, or by typing \"sh <file\", e.g..  If this archive is complete, you",
X  " will see the following message at the end:",
X  NULL
X};
X
X
X/*
X**  Package up one file or directory.
X*/
Xstatic void
Xshar(file, Basename)
X    char		*file;
X    int			 Basename;
X{
X    register char	*s;
X    register char	*Name;
X    register int	 Bads;
X    register int	 Lastchar;
X    register off_t	 Size;
X    char		 buf[BUFSIZ];
X
X    /* Just in case. */
X    if (EQ(file, ".") || EQ(file, ".."))
X	return;
X
X    Size = Fsize(file);
X    Name =  Basename && (Name = RDX(file, '/')) ? Name + 1 : file;
X
X    /* Making a directory? */
X    if (Ftype(file) == F_DIR) {
X	printf("if test ! -d %s ; then\n", Name);
X	printf("    echo shar: Creating directory \\\"%s\\\"\n", Name);
X	printf("    mkdir %s\n", Name);
X	printf("fi\n");
X    }
X    else {
X	if (freopen(file, "r", stdin) == NULL) {
X	    fprintf(stderr, "Can't open %s, %s\n", file, Ermsg(errno));
X	    exit(1);
X	}
X
X	/* Emit the per-file prolog. */
X	printf("if test -f %s -a \"${1}\" != \"-c\" ; then \n", Name);
X	printf("  echo shar: Will not over-write existing file \\\"%s\\\"\n",
X	       Name);
X	printf("else\n");
X	printf("echo shar: Extracting \\\"%s\\\" \\(%ld character%s\\)\n",
X	       Name, (long)Size, Size > 1 ? "s" : "");
X	printf("sed \"s/^X//\" >%s <<'END_OF_%s'\n", Name, Name);
X
X	/* Output the file contents. */
X	for (Bads = 0, Lastchar = '\n'; fgets(buf, BUFSIZ, stdin); ) {
X	    printf("X");
X	    if (buf[0]) {
X		for (s = buf; *s; s++) {
X		    if (BADCHAR(*s))
X			Bads++;
X		    (void)putchar(*s);
X		}
X		Lastchar = s[-1];
X	    }
X	    else
X		Lastchar = 'X';
X	}
X
X	/* Tell about missing \n and control characters. */
X	if (Lastchar != '\n')
X	    (void)putchar('\n');
X	printf("END_OF_%s\n", Name);
X	if (Lastchar != '\n') {
X	    printf("echo shar: Missing newline added to \\\"%s\\\"\n", Name);
X	    fprintf(stderr, "Missing newline added to \"%s\"\n", Name);
X	}
X	if (Bads) {
X	    printf(
X	"echo shar: %d control character%s may be missing from \\\"%s\\\"\n",
X		   Bads, Bads > 1 ? "s" : "", Name);
X	    fprintf(stderr, "Found %d control char%s in \"%s\"\n",
X		    Bads, Bads > 1 ? "s" : "", Name);
X	}
X
X	/* Output size check. */
X	printf("if test %ld -ne `wc -c <%s`; then\n", (long)Size, Name);
X	printf("    echo shar: \\\"%s\\\" unpacked with wrong size!\n", Name);
X	printf("fi\n");
X
X	/* Executable? */
X	if (Fexecute(file))
X	    printf("chmod +x %s\n", Name);
X
X	printf("# end of overwriting check\nfi\n");
X    }
X}
X
X
Xmain(ac, av)
X    int			 ac;
X    register char	*av[];
X{
X    register char	*Trailer;
X    register char	*p;
X    register int	 i;
X    register int	 length;
X    register int	 Oops;
X    register int	 Knum;
X    register int	 Kmax;
X    time_t		 clock;
X    int			 Basename;
X
X    /* Parse JCL. */
X    Basename = 0;
X    Knum = 0;
X    Kmax = 0;
X    Trailer = NULL;
X    for (Oops = 0; (i = getopt(ac, av, "be:n:o:t:")) != EOF; )
X	switch (i) {
X	    default:
X		Oops++;
X		break;
X	    case 'b':
X		Basename++;
X		break;
X	    case 'e':
X		Kmax = atoi(optarg);
X		break;
X	    case 'n':
X		Knum = atoi(optarg);
X		break;
X	    case 'o':
X		if (freopen(optarg, "w", stdout) == NULL) {
X		    fprintf(stderr, "Can't open %s for output, %s.\n",
X			    optarg, Ermsg(errno));
X		    Oops++;
X		}
X		break;
X	    case 't':
X		Trailer = optarg;
X		break;
X	}
X    av += optind;
X    if (*av == NULL) {
X	fprintf(stderr, "No input files\n");
X	Oops++;
X    }
X    if (Oops) {
X	fprintf(stderr, "USAGE: shar [-b] [-o:] [-n:e:t:] file... >SHAR\n");
X	exit(1);
X    }
X
X    /* Everything readable and reasonably-named? */
X    for (Oops = 0, i = 0; p = av[i]; i++)
X	if (freopen(p, "r", stdin) == NULL) {
X	    fprintf(stderr, "Can't read %s, %s.\n", p, Ermsg(errno));
X	    Oops++;
X	}
X	else
X	    for (; *p; p++)
X		if (!isascii(*p)
X		 || (!isalnum(*p) && IDX(OK_CHARS, *p) == NULL)) {
X		    fprintf(stderr, "Bad character '%c' in '%s'.\n", *p, av[i]);
X		    Oops++;
X		}
X    if (Oops)
X	exit(2);
X
X    /* Prolog. */
X    for (i = 0; p = Prolog[i]; i++)
X	printf("#%s\n", p);
X    if (Knum && Kmax)
X	printf("#\t\t\"End of archive %d (of %d).\"\n", Knum, Kmax);
X    else
X        printf("#\t\t\"End of shell archive.\"\n");
X    printf("# Contents: ");
X    for (length = 12, i = 0; p = av[i++]; length += strlen(p) + 1)
X	if (length + strlen(p) + 1 < WIDTH)
X	    printf(" %s", p);
X	else {
X	    printf("\n#   %s", p);
X	    length = 4;
X	}
X    printf("\n");
X    clock = time((time_t *)NULL);
X    printf("# Wrapped by %s@%s on %s", User(), Host(), ctime(&clock));
X    printf("PATH=/bin:/usr/bin:/usr/ucb ; export PATH\n");
X
X    /* Do it. */
X    while (*av)
X	shar(*av++, Basename);
X
X    /* Epilog. */
X    if (Knum && Kmax) {
X	printf("echo shar: End of archive %d \\(of %d\\).\n", Knum, Kmax);
X	printf("cp /dev/null ark%disdone\n", Knum);
X	printf("MISSING=\"\"\n");
X	printf("for I in");
X	for (i = 0; i < Kmax; i++)
X	    printf(" %d", i + 1);
X	printf(" ; do\n");
X	printf("    if test ! -f ark${I}isdone ; then\n");
X	printf("\tMISSING=\"${MISSING} ${I}\"\n");
X	printf("    fi\n");
X	printf("done\n");
X	printf("if test \"${MISSING}\" = \"\" ; then\n");
X	if (Kmax == 2)
X	    printf("    echo You have unpacked both archives.\n");
X	else
X	    printf("    echo You have unpacked all %d archives.\n", Kmax);
X	if (Trailer && *Trailer)
X	    printf("    echo \"%s\"\n", Trailer);
X	printf("    rm -f ark[1-9]isdone%s\n",
X	       Kmax >= 9 ? " ark[1-9][0-9]isdone" : "");
X	printf("else\n");
X	printf("    echo You still need to unpack the following archives:\n");
X	printf("    echo \"        \" ${MISSING}\n");
X	printf("fi\n");
X	printf("##  End of shell archive.\n");
X    }
X    else
X        printf("echo shar: End of shell archive.\n");
X
X    printf("exit 0\n");
X
X    exit(0);
X}
END_OF_shar.c
if test 6386 -ne `wc -c <shar.c`; then
    echo shar: \"shar.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"shar.h\" \(4373 characters\)
if test -f shar.h ; then 
  echo shar: Will not over-write existing file \"shar.h\"
else
sed "s/^X//" >shar.h <<'END_OF_shar.h'
X/*
X**  Header file for shar and friends.
X**
X**  $Header: shar.h,v 1.25 87/03/18 14:03:27 rs Exp $
X*/
X
X
X/*
X**  Edit as necessary.
X*/
X
X/* Variances in local dialects. */
X#define IDX		index		/* Maybe strchr?		*/
X#define RDX		rindex		/* Maybe strrchr?		*/
X/*efine NEED_MKDIR			/* Don't have mkdir(2)?		*/
X/*efine NEED_QSORT			/* Don't have qsort(3)?		*/
X#define NEED_GETOPT			/* Need local getopt object?	*/
X#define CAN_POPEN			/* Can invoke file(1) command?	*/
X/*efine USE_MY_SHELL			/* Don't popen("/bin/sh")?	*/
X
Xtypedef int		*align_t;	/* Worst-case alignment, for lint */
X/* typedef long		time_t		/* Needed for non-BSD sites?	*/
X/* typedef long		off_t		/* Needed for non-BSD sites?	*/
X
X#define DEF_SAVEIT	1		/* Save headers by default?	*/
X
X/* Where is BSD-compatible directory header file?   Pick one. */
X#define IN_SYS_DIR			/* <sys/dir.h>			*/
X/*efine IN_SYS_NDIR			/* <sys/ndir.h>			*/
X/*efine IN_DIR				/* "dir.h"			*/
X
X/* Do you have <sys/wait.h>?  If so, then you have vfork, too. */
X#define SYS_WAIT
X
X/* Login name from environment; pick one. */
X#define USER_ENV	"USER"		/* .. */
X/*efine USER_ENV	"LOGNAME"	/* .. */
X/*efine USER_ENV	"NAME"		/* .. */
X
X/* What we should fopen() if stdin is not the tty. */
X#define THE_TTY		"/dev/tty"	/* Maybe "con:" for MS-DOS?	*/
X
X/* Name of the machine we're running on; pick one. */
X#define GETHOSTNAME			/* Use gethostname(2) call	*/
X/*efine UNAME				/* Use uname(2) call		*/
X/*efine UUNAME				/* Invoke "uuname -l"		*/
X/*efine	WHOAMI				/* Try /etc/whoami & <whoami.h>	*/
X/*efine HOST		"SITE"		/* If all else fails		*/
X
X/* How do we find the current working directory? */
X#define GETWD				/* Use getwd(3) routine		*/
X/* fine GETCWD				/* Use getcwd(3) routine	*/
X/* fine PWDPOPEN			/* Invoke "pwd"			*/
X/* fine PWDGETENV	"PWD"		/* Get $PWD from environment	*/
X
X/* Prefixes for first two lines of saved NOTESFILES articles. */
X#define NOTES1		"/* Written "
X#define NOTES2		"/* ---"
X
X/* Legal characters for filenames.  Note that shar doesn't do quoting... */
X#define OK_CHARS	"@%_-+=.,/"
X
X/*
X**  END OF CONFIGURATION SECTION
X*/
X
X#include <stdio.h>
X#include <sys/types.h>
X#include <ctype.h>
X
X#ifdef	IN_SYS_DIR
X#include <sys/dir.h>
X#endif	/* IN_SYS_DIR */
X#ifdef	IN_SYS_NDIR
X#include <sys/ndir.h>
X#endif	/* IN_SYS_NDIR */
X#ifdef	IN_NDIR
X#include "ndir.h"
X#endif	/* IN_DIR */
X
X
X/*
X**  Handy shorthands.
X*/
X#define TRUE		1
X#define FALSE		0
X#define WIDTH		72
X#define F_DIR		'$'		/* Something is a directory	*/
X#define F_FILE		'A'		/* Something is a regular file	*/
X#define S_IGNORE	'L'		/* Ignore this signal		*/
X#define S_RESET		'Z'		/* Reset signal to default	*/
X
X/* These are used by the archive parser. */
X#define MAX_LINE_SIZE	200		/* Length of physical input line*/
X#define MAX_VAR_NAME	 30		/* Length of a variable's name	*/
X#define MAX_VAR_VALUE	128		/* Length of a variable's value	*/
X#define MAX_VARS	 20		/* Number of shell vars allowed	*/
X#define MAX_WORDS	 30		/* Make words in command lnes	*/
X
X
X/*
X**  Keep RCS stuff away from lint.
X*/
X#ifdef	lint
X#define RCS(text)	/* NULL */
X#else
X#define RCS(text)	static char ID[] = text;
X#endif	/* lint */
X
X
X/*
X**  Memory hacking.
X*/
X#define NEW(T, count)	((T *)getmem(sizeof(T), (unsigned int)(count)))
X#define ALLOC(n)	getmem(1, (unsigned int)(n))
X#define COPY(s)		strcpy(NEW(char, strlen((s)) + 1), (s))
X
X
X/*
X**  Macros.
X*/
X#define BADCHAR(c)	(iscntrl((c)) && !isspace((c)))
X#define EQ(a, b)	(strcmp((a), (b)) == 0)
X#define EQn(a, b, n)	(strncmp((a), (b), (n)) == 0)
X#define PREFIX(a, b)	(EQn((a), (b), sizeof b - 1))
X#define WHITE(c)	((c) == ' ' || (c) == '\t')
X
X
X/*
X**  Linked in later.
X*/
Xextern int	 errno;
Xextern int	 optind;
Xextern char	*optarg;
X
X/* From your C run-time library. */
Xextern FILE	*popen();
Xextern time_t	 time();
Xextern long	 atol();
Xextern char	*IDX();
Xextern char	*RDX();
Xextern char	*ctime();
Xextern char	*gets();
Xextern char	*mktemp();
Xextern char	*strcat();
Xextern char	*strcpy();
Xextern char	*strncpy();
Xextern char   	*getenv();
X
X/* From our local library. */
Xextern align_t	 getmem();
Xextern off_t	 Fsize();
Xextern char	*Copy();
Xextern char	*Cwd();
Xextern char	*Ermsg();
Xextern char	*Host();
Xextern char	*User();
X
X/* Exported by the archive parser. */
Xextern FILE	*Input;			/* Current input stream		*/
Xextern char	*File;			/* Input filename		*/
Xextern int	 Interactive;		/* isatty(fileno(stdin))?	*/
Xextern void	 SynErr();		/* Fatal syntax error		*/
END_OF_shar.h
if test 4373 -ne `wc -c <shar.h`; then
    echo shar: \"shar.h\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"shar.man\" \(2834 characters\)
if test -f shar.man ; then 
  echo shar: Will not over-write existing file \"shar.man\"
else
sed "s/^X//" >shar.man <<'END_OF_shar.man'
X.TH SHAR 1 LOCAL
X.\" $Header: shar.man,v 1.1 87/02/27 13:45:27 rs Exp $
X.SH NAME
Xshar \- create shell archive file for extraction by /bin/sh
X.SH SYNOPSIS
X.B shar
X[
X.B \-b
X] [
X.BI \-n seq_number
X] [
X.BI \-e seq_end
X] [
X.BI \-o output_file
X] [
X.BI \-t final_text
X] files
X.SH DESCRIPTION
X.I Shar
Xtakes a list of files, and generates a
X.IR /bin/sh
Xscript that, when executed, will re-create those files in a different
Xdirectory or on a different machine.
XThe resultant script will use
X.IR wc (1)
Xto do a mild error-check, and will warn about possibly-omitted
Xcontrol characters.
X.PP
X.I Shar
Xgenerates scripts that will make directories and plain files.
XIt will not try to generate intermediate filenames, however, so
X.RS
Xshar foo/bar/file
X.RE
Xwill not work.  Do
X.RS
Xshar foo foo/bar foo/bar/file
X.RE
Xinstead.
X.PP
XThe script is normally sent to standard output; the ``\-o'' option may be
Xused to specify an output filename.
XThis is designed to prevent filling up the disk if
X.RS
Xshar * >SHAR
X.RE
Xcommand is done; do
X.RS
Xshar -o SHAR *
X.RE
Xinstead.
X.PP
XThe ``\-b'' option says that all leading directory names should be stripped
Xfrom the file when they are packed into the archive.
XFor example,
X.RS
Xshar -b /etc/termcap
X.RE
Xcreates an archive that, when executed, creates a file named
X``termcap'' in the current directory, rather than overwrite the
Xhost system file.
XNote, however, that the scripts generated by
X.I shar
Xrefuse to overwrite pre-existing files; the ``\-b'' option is
Xfor convenience, and may not strictly be necessary.
X.SS "Multi\-part Archives"
XMost larger software packages are usually sent out in two or more shell
Xarchives.
XThe ``\-n,'' ``\-e,'' and ``\-t'' options are used to make an archive
Xthat is part of a series.
XThe individual archives are often called ``kits'' when this is done.
XThe ``\-n'' option specifies the archive number; the ``\-e'' option species
Xthe highest number in the series.
XWhen executed, the generated archives will then echo messages like
X.RS
Xshar: End of archive 3 of 9.
X.RE
Xat their end.
X.PP
XIn addition, each shar will generate a file named
X.IR ark X isdone .
XEach script will contain a loop to check for the presence of these
Xfiles, and indicate to the recipient which archives still need to be
Xexecuted.
XThe ``\-t'' option may be used to give starting instructions to the recipient.
XWhen the scripts determine that all the archives have been unpacked,
Xthe text specified with this flag is displayed.
XFor example,
X.RS
Xshar -n1 -k9 -t "Now do 'sh ./Configure'" *.c >SHAR
X.RE
XAdds commands to output the following when all the archives have been unpacked:
X.RS
X.nf
XYou have run archive 1.
X\&    .
X\&    .
X\&    .
XYou have run archive 9.
XYou have run all 9 archives.
XNow do 'sh ./Configure'
X.fi
X.RE
X.SH "SEE ALSO"
Xecho(1), findsrc(1L), makekit(1L), mkdir(1), sh(1), test(1), unshar(1L),
Xwc(1).
END_OF_shar.man
if test 2834 -ne `wc -c <shar.man`; then
    echo shar: \"shar.man\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"shell.c\" \(767 characters\)
if test -f shell.c ; then 
  echo shar: Will not over-write existing file \"shell.c\"
else
sed "s/^X//" >shell.c <<'END_OF_shell.c'
X/*
X**  Stand-alone driver for shell.
X*/
X#include "shar.h"
XRCS("$Header: shell.c,v 1.4 87/03/24 16:19:56 rs Exp $")
X
X
Xextern void	 SetVar();
X
X
Xmain(ac, av)
X    register int	 ac;
X    register char	*av[];
X{
X    char		*vec[MAX_WORDS];
X    char		 buff[MAX_VAR_VALUE];
X
X    if (Interactive = ac == 1) {
X	fprintf(stderr, "Testing shell interpreter...\n");
X	Input = stdin;
X	File = "stdin";
X    }
X    else {
X	if ((Input = fopen(File = av[1], "r")) == NULL)
X	    SynErr("UNREADABLE INPUT");
X	/* Build the positional parameters. */
X	for (ac = 1; av[ac]; ac++) {
X	    (void)sprintf(buff, "%d", ac - 1);
X	    SetVar(buff, av[ac]);
X	}
X    }
X
X    /* Read, parse, and execute. */
X    while (GetLine(TRUE))
X	if (Argify(vec))
X	    (void)Exec(vec);
X
X    /* That's it. */
X    exit(0);
X}
END_OF_shell.c
if test 767 -ne `wc -c <shell.c`; then
    echo shar: \"shell.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"shell.man\" \(1091 characters\)
if test -f shell.man ; then 
  echo shar: Will not over-write existing file \"shell.man\"
else
sed "s/^X//" >shell.man <<'END_OF_shell.man'
X.TH SHELL 1 LOCAL
X.\" $Header: shell.man,v 1.2 87/03/09 16:55:28 rs Exp $
X.SH NAME
Xshell \- Interpreter for shell archives
X.SH SYNOPSIS
X.B shell
X[
X.B file
X]
X.SH DESCRIPTION
XThis program interprets enough UNIX shell syntax, and command usage,
Xto enable it to unpack many different types of UNIX shell archives,
Xor ``shar's.''
XIt is primarily intended to be used on non-UNIX systems that need to
Xunpack such archives.
X.PP
X.I Shell
Xdoes
X.B not
Xcheck for security holes, and will blithely execute commands like
X.RS
Xcp /dev/null /etc/passwd
X.RE
Xwhich, if executed by the super-user, can be disastrous.
X.PP
XThe
X.I shell
Xparser is line-based, where lines are then split into tokens; it is not a
Xtrue token-based parser.
XIn general, it is best if all
X.I sh
Xkeywords that can appear alone on a line do so, and that compound
Xcommands (i.e., using a semi-colon) be avoided.
XFor more details on the syntax, see the source (sorry...).
X.SH BUGS
XIt is probably easier to write a true portable replacement for /bin/sh
Xthan it is to write something which understands all shar formats.
X.SH SEE ALSO
Xshar(1L).
END_OF_shell.man
if test 1091 -ne `wc -c <shell.man`; then
    echo shar: \"shell.man\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"sysfuncs.c\" \(4828 characters\)
if test -f sysfuncs.c ; then 
  echo shar: Will not over-write existing file \"sysfuncs.c\"
else
sed "s/^X//" >sysfuncs.c <<'END_OF_sysfuncs.c'
X/*
X**  System-specific stuff.  This module will need to be ported to
X**  other systems.
X*/
X/* LINTLIBRARY */
X#include "shar.h"
X#include <signal.h>
X#include <pwd.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X#ifdef	SYS_WAIT
X#include <sys/wait.h>
X#else
X#endif	/* SYS_WAIT */
XRCS("$Header: sysfuncs.c,v 1.7 87/03/13 13:08:34 rs Exp $")
X
X
X/* How to fork(), what to wait with. */
X#ifdef	SYS_WAIT
X#define FORK()		 vfork()
X#define W_VAL(w)	 ((w).w_retcode)
Xtypedef union wait	 WAITER;
X#else
X#define FORK()		 fork()
X#define W_VAL(w)	 ((w) >> 8)
Xtypedef int		 WAITER;
X#endif	/* SYS_WAIT */
X
X
X/* Mask of executable bits. */
X#define	EXE_MASK	(S_IEXEC | (S_IEXEC >> 3) | (S_IEXEC >> 6))
X
X/* Stat buffer for last file. */
Xstatic struct stat	 Sb;
X
X
X/*
X**  Get user name.  Not secure, but who sends nastygrams as shell archives?
X*/
Xchar *
XUser()
X{
X    extern struct passwd	*getpwuid();
X    struct passwd		*p;
X    char			*g;
X
X    if (g = getenv(USER_ENV))
X	return(g);
X    return((p = getpwuid(getuid())) ? p->pw_name : "USER");
X}
X
X
X/*
X**  Set up a signal handler.
X*/
XSetSigs(What, Func)
X    int		  What;
X    int		(*Func)();
X{
X    if (What == S_IGNORE)
X	Func = SIG_IGN;
X    else if (What == S_RESET)
X	Func = SIG_DFL;
X    if (signal(SIGINT, SIG_IGN) != SIG_IGN)
X	(void)signal(SIGINT, Func);
X    if (signal(SIGQUIT, SIG_IGN) != SIG_IGN)
X	(void)signal(SIGQUIT, Func);
X}
X
X
X/*
X**  Stat the file if it's not the one we did last time.
X*/
Xint
XGetStat(p)
X    char		*p;
X{
X    static char		 Name[BUFSIZ];
X
X    if (*p == Name[0] && EQ(p, Name))
X	return(TRUE);
X    return(stat(strcpy(Name, p), &Sb) < 0 ? FALSE : TRUE);
X}
X
X
X/*
X**  Return the file type -- directory or regular file.
X*/
Xint
XFtype(p)
X    char	*p;
X{
X    return(GetStat(p) && ((Sb.st_mode & S_IFMT) == S_IFDIR) ? F_DIR : F_FILE);
X}
X
X
X/*
X**  Return the file size.
X*/
Xoff_t
XFsize(p)
X    char	*p;
X{
X    return(GetStat(p) ? Sb.st_size : 0);
X}
X
X
X/*
X**  Is a file executable?
X*/
Xint
XFexecute(p)
X    char	*p;
X{
X    return(GetStat(p) && (Sb.st_mode & EXE_MASK) ? TRUE : FALSE);
X}
X
X
X/*
X**  Return the process ID.
X*/
Xint
XPid()
X{
X    static int	 X;
X
X    if (X == 0)
X	X = getpid();
X    return(X);
X}
X
X
X/*
X**  Return the text string that corresponds to errno.
X*/
Xchar *
XErmsg(e)
X    int			 e;
X{
X    extern int		 sys_nerr;
X    extern char		*sys_errlist[];
X    static char		 buff[30];
X
X    if (e > 0 && e < sys_nerr)
X	return(sys_errlist[e]);
X    (void)sprintf(buff, "Error code %d", e);
X    return(buff);
X}
X
X
X/*
X**  Fork off a command.
X*/
Xint
XExecute(av)
X    char		*av[];
X{
X    register int	 i;
X    register int	 j;
X    WAITER		 W;
X
X    if ((i = FORK()) == 0) {
X	SetSigs(S_RESET, (int (*)())NULL);
X	(void)execvp(av[0], av);
X	perror(av[0]);
X	_exit(1);
X    }
X
X    SetSigs(S_IGNORE, (int (*)())NULL);
X    while ((j = wait(&W)) < 0 && j != i)
X	;
X    SetSigs(S_RESET, (int (*)())NULL);
X    return(W_VAL(W));
X}
X
X
X#ifdef	NEED_MKDIR
X/*
X**  Quick and dirty mkdir routine for them that's need it.
X*/
Xint
Xmkdir(name, mode)
X    char	*name;
X    int		 mode;
X{
X    char	*av[3];
X    int		 i;
X
X    av[0] = "mkdir";
X    av[1] = name;
X    av[2] = NULL;
X    U = umask(~mode);
X    i = Execute(av);
X    (void)umask(U);
X    return(i ? -1 : 0);
X}
X#endif	/* NEED_MKDIR */
X
X
X#ifdef	NEED_QSORT
X/*
X**  Bubble sort an array of arbitrarily-sized elements.  This routine
X**  can be used as an (inefficient) replacement for the Unix qsort
X**  routine, hence the name.  If I were to put this into my C library,
X**  I'd do two things:
X**	-Make it be a quicksort;
X**	-Have a front routine which called specialized routines for
X**	 cases where Width equals sizeof(int), sizeof(char *), etc.
X*/
Xqsort(Table, Number, Width, Compare)
X    register char	 *Table;
X    register int	  Number;
X    register int	  Width;
X    register int	(*Compare)();
X{
X    register char	 *i;
X    register char	 *j;
X
X    for (i = &Table[Number * Width]; (i -= Width) >= &Table[Width]; )
X	for (j = i; (j -= Width) >= &Table[0]; )
X	    if ((*Compare)(i, j) < 0) {
X		register char	*p;
X		register char	*q;
X		register int	 t;
X		register int	 w;
X
X		/* Swap elements pointed to by i and j. */
X		for (w = Width, p = i, q = j; --w >= 0; *p++ = *q, *q++ = t)
X		    t = *p;
X	    }
X}
X#endif	/* NEED_QSORT */
X
X
X#undef NOTDEF
X#ifdef	NOTDEF
X/*
X**  Cons all the arguments together into a single command line and hand
X**  it off to the shell to execute.  This is only here for someone to use
X**  as the basis of, say, an MSDOS port.
X*/
Xint
XExecute(av)
X    register char	*av[];
X{
X    register char	**v;
X    register char	 *p;
X    register char	 *q;
X    register int	 i;
X
X    /* Get length of command line. */
X    for (i = 5, v = av; *v; v++)
X	i += strlen(*v) + 1;
X
X    /* Create command line and execute it. */
X    p = NEW(char, i);
X    for (q = p + strlen(strcpy(p, "exec")), v = av; *v; v++) {
X	*q++ = ' ';
X	q += strlen(strcpy(q, *v));
X    }
X
X    return(system(p));
X}
X#endif	/* NOTDEF */
END_OF_sysfuncs.c
if test 4828 -ne `wc -c <sysfuncs.c`; then
    echo shar: \"sysfuncs.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"unshar.man\" \(2178 characters\)
if test -f unshar.man ; then 
  echo shar: Will not over-write existing file \"unshar.man\"
else
sed "s/^X//" >unshar.man <<'END_OF_unshar.man'
X.TH UNSHAR 1 LOCAL
X.\" $Header: unshar.man,v 1.1 87/02/27 13:45:34 rs Exp $
X.SH NAME
Xunshar \- unpack shell archives from news, mail, notes, etc.
X.SH SYNOPSIS
X.B unshar
X[
X.BI \-c\| directory
X] [
X.BI \-d\| directory
X] [
X.B \-f
X] [
X.B \-n
X] [
X.B \-s
X] [files]
X.SH DESCRIPTION
X.I Unshar
Xremoves mail and news header lines from its input, and feeds the remainder
Xto
X.IR /bin/sh (1)
Xso that a shell archive can be properly unpacked.
XIf no files are specified,
X.I unshar
Xreads from standard input.
XThe program is designed to be useful when unpacking archives directly
Xfrom the news or mail systems (e.g., s | unshar).
X.PP
X.I Unshar
Xnormally unpacks its files in the current directory.
XUse the ``\-c'' option to have the program change to a new directory
Xbefore invoking the shell.
XIf the directory does not exist, it will try to create it.
XIf the directory name starts with a question mark, then
X.I unshar
Xwill ask for the directory name before doing anything; this is most useful
Xwith the environment variable UNSHAREDIR.
XIf the directory name starts with a tilde, then the value of the HOME
Xenvironment variable is inserted in place of that character.
XFor convenience, the ``\-d'' option is a synonym for the ``\-c'' option.
X.PP
X.I Unshar
Xnormally complains if the input looks like something other than a shar file.
X(Among other things, it checks for files that resemble C, and Pascal code).
XIt can be fooled, however, by nonstandard versions of news, notes, etc.
XThe ``\-f'' option forces
X.I unshar
Xto try unpacking files, even if they look like something else.
X.PP
XDepending on how the program is installed,
X.I unshar
Xmay or may not try to preserve the header part of file ``foo''
Xinto the name ``foo.hdr'' (if the file is standard input, the name
Xwill be ``UNSHAR.HDR'').
XUsing the
XUsing the ``\-s'' option forces the program to save the headers, while
Xusing the ``\-n'' option forces it to discard the headers.
XThe file is appended to, if it already exists, so all headers can be easily
Xsaved in one file.
X.SH ENVIRONMENT
X.ta \w'UNSHAREDIR  'u
XHOME	Value used if a leading tilde is given in directory name.
X.br
XUNSHAREDIR	Default value for ``\-c'' option.
X.SH SEE ALSO
Xshar(1).
END_OF_unshar.man
if test 2178 -ne `wc -c <unshar.man`; then
    echo shar: \"unshar.man\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: End of archive 1 \(of 2\).
cp /dev/null ark1isdone
MISSING=""
for I in 1 2 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked both archives.
    echo "See the README"
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0
-- 
--
Rich $alz					"Drug tests p**s me off"
Mirror Systems, Cambridge Massachusetts		rs@mirror.TMC.COM
{adelie, mit-eddie, ihnp4, harvard!wjh12, cca, cbosgd, seismo}!mirror!rs

rs@mirror.UUCP (03/25/87)

#! /bin/sh
# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
# If this archive is complete, you will see the message:
#		"End of archive 2 (of 2)."
# Contents:  makekit.c parser.c unshar.c
# Wrapped by rs@mirror on Tue Mar 24 18:12:09 1987
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
echo shar: Extracting \"makekit.c\" \(9987 characters\)
if test -f makekit.c ; then 
  echo shar: Will not over-write existing file \"makekit.c\"
else
sed "s/^X//" >makekit.c <<'END_OF_makekit.c'
X/*
X**  MAKEKIT
X**  Split up source files into reasonably-sized shar lists.
X**
X**  Options:
X**	-e		Leave our output file out
X**	-h #		Number of header lines in input file
X**	-i name		Input file name
X**	-k #		Maximum number of archives desired
X**	-m		Same as "-i Manifest -o Manifest -s2"
X**	-n name		Name for resultant archives
X**	-o name		Output file name
X**	-p		Preserve original input order
X**	-s #[k]		Maximum size of each archvie
X**	-t text		Set final instructions after all are unpacked
X**	-x		Don't actually do the shar'ing
X*/
X#include "shar.h"
XRCS("$Header: makekit.c,v 1.15 87/03/13 12:56:41 rs Exp $")
X
X
X/*
X**  Our block of information about the files we're doing.
X*/
Xtypedef struct {
X    char	*Name;			/* Filename			*/
X    char	*Text;			/* What it is			*/
X    int		 Where;			/* Where it is			*/
X    int		 Type;			/* Directory or file?		*/
X    long	 Size;			/* Size in bytes		*/
X} BLOCK;
X
X
X/*
X**  Our block of information about the archives we're making.
X*/
Xtypedef struct {
X    int		 Count;			/* Number of files		*/
X    long	 Size;			/* Bytes used by archive	*/
X} ARCHIVE;
X
X
X/*
X**  We re-use parts of one buffer to hold three different strings in our
X**  argument vector to exec().
X*/
X#define SEG0		 0		/* Ending archive number	*/
X#define SEG1		10		/* Current archive number	*/
X#define SEG2		20		/* Output name (rest of buff)	*/
X
X/*
X**  Format strings; these are strict K&R so you shouldn't have to change them.
X*/
X#define FORMAT1		" %-25s%2d\t%s\n"
X#define FORMAT2		"%s%2.2d"
X
X
X/*
X**  Global variables.
X*/
Xchar	*InName;			/* File with list to pack	*/
Xchar	*OutName;			/* Where our output goes	*/
Xchar	*SharName = "Part";		/* Prefix for name of each shar	*/
Xchar	*Trailer;			/* Text for shar to pack in	*/
Xchar	 TEMP[] = "/tmp/arkXXXXXX";	/* Temporary manifest file	*/
Xint	 ArchCount = 20;		/* Max number of archives	*/
Xint	 ExcludeIt;			/* Leave out the output file?	*/
Xint	 Header;			/* Lines of prolog in input	*/
Xint	 Preserve;			/* Preserve order for Manifest?	*/
Xint	 Working = TRUE;		/* Call shar when done?		*/
Xlong	 Size = 55000;			/* Largest legal archive size	*/
X
X
X/*
X**  Sorting predicate to put README first, then directories, then large
X**  files, then smaller files, which is how we want to assign things to
X**  the archives.
X*/
Xstatic int
XSizeP(t1, t2)
X    BLOCK	*t1;
X    BLOCK	*t2;
X{
X    long	 i;
X
X    if (EQ(t1->Name, "README") || EQ(t1->Name, "readme"))
X	return(-1);
X    if (EQ(t2->Name, "README") || EQ(t1->Name, "readme"))
X	return(1);
X    if (t1->Type != t2->Type)
X	return(t1->Type == F_DIR ? 1 : -1);
X    return((i = t1->Size - t2->Size) == 0L ? 0 : (i < 0L ? -1 : 1));
X}
X
X
X/*
X**  Sorting predicate to get things in alphabetical order, which is how
X**  we write the Manifest file.
X*/
Xstatic int
XNameP(t1, t2)
X    BLOCK	*t1;
X    BLOCK	*t2;
X{
X    int		 i;
X
X    return((i = *t1->Name - *t2->Name) ? 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
XCatch(s)
X    int		 s;
X{
X    int		 e;
X
X    e = errno;
X    (void)unlink(TEMP);
X    fprintf(stderr, "Got signal %d, %s.\n", s, Ermsg(e));
X    exit(1);
X}
X
X
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	 Value;
X    BLOCK		*Table;
X    BLOCK		*TabEnd;
X    ARCHIVE		*Ark;
X    ARCHIVE		*ArkEnd;
X    char		 buff[BUFSIZ];
X    int			 LastOne;
X    int			 Start;
X
X    /* Collect input. */
X    Value = FALSE;
X    while ((i = getopt(ac, av, "eh:i:k:n:mop:s:t:x")) != EOF)
X	switch (i) {
X	    default:
X		exit(1);
X	    case 'e':
X		ExcludeIt = TRUE;
X		break;
X	    case 'h':
X		Header = atoi(optarg);
X		break;
X	    case 'i':
X		InName = optarg;
X		break;
X	    case 'k':
X		ArchCount = atoi(optarg);
X		break;
X	    case 'm':
X		InName = OutName = "MANIFEST";
X		Header = 2;
X		break;
X	    case 'n':
X		SharName = optarg;
X		break;
X	    case 'o':
X		OutName = optarg;
X		break;
X	    case 'p':
X		Preserve = TRUE;
X		break;
X	    case 's':
X		Size = atoi(optarg);
X		if (IDX(optarg, 'k') || IDX(optarg, 'K'))
X		    Size *= 1024;
X		break;
X	    case 't':
X		Trailer = optarg;
X		break;
X	    case 'x':
X		Working = FALSE;
X		break;
X	}
X    ac -= optind;
X    av += optind;
X
X    /* Write the file list to a temp file. */
X    F = fopen(mktemp(TEMP), "w");
X    SetSigs(TRUE, 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, Ermsg(errno));
X	    exit(1);
X	}
X	/* Skip any possible prolog, then output rest of file. */
X	while (--Header >= 0 && fgets(buff, sizeof buff, In))
X	    ;
X	if (feof(In)) {
X	    fprintf(stderr, "Nothing but header lines in list!?\n");
X	    exit(1);
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	;
X    rewind(F);
X
X    /* Read lines and parse lines, see if we found our OutFile. */
X    Table = NEW(BLOCK, lines);
X    for (t = Table, Value = FALSE, lines = 0; fgets(buff, sizeof buff, F); ) {
X	/* Read line, skip first word, check for blank line. */
X	if (p = IDX(buff, '\n'))
X	    *p = '\0';
X	else
X	    fprintf(stderr, "Warning, line truncated:\n%s\n", buff);
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	    ;
X	if (*p)
X	    *p++ = '\0';
X
X	/* Skip <spaces><digits><spaces>; remainder is the file description. */
X	for (p = Skip(p); *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, Ermsg(errno));
X	    continue;
X	}
X	t->Type = Ftype(t->Name);
X
X	/* Guesstimate it's size when archived. */
X	t->Size = strlen(t->Name) * 3 + 200;
X	if (t->Type == F_FILE) {
X	    i = Fsize(t->Name);
X	    t->Size += i + i / 60;
X	}
X	if (t->Size > Size) {
X	    fprintf(stderr, "At %ld bytes, %s is too big for any archive!\n",
X		    t->Size, t->Name);
X	    exit(1);
X	}
X
X	/* Is our ouput file there? */
X	if (!Value && OutName && EQ(OutName, t->Name))
X	    Value = TRUE;
X
X	/* All done -- advance to next entry. */
X	t++;
X    }
X    (void)fclose(F);
X    (void)unlink(TEMP);
X    SetSigs(S_RESET, (int (*)())NULL);
X
X    /* Add our output file? */
X    if (!ExcludeIt && !Value && OutName) {
X	t->Name = OutName;
X	t->Text = "This shipping list";
X	t->Type = F_FILE;
X	t->Size = lines * 60;
X	t++;
X    }
X
X    /* Sort by size, get archive space. */
X    lines = t - Table;
X    TabEnd = &Table[lines];
X    if (!Preserve)
X	qsort((char *)Table, lines, sizeof Table[0], SizeP);
X    Ark = NEW(ARCHIVE, ArchCount);
X    ArkEnd = &Ark[ArchCount];
X
X    /* Loop through the pieces, and put everyone into an archive. */
X    for (t = Table; t < TabEnd; t++) {
X	for (k = Ark; k < ArkEnd; k++)
X	    if (t->Size + k->Size < Size) {
X		k->Size += t->Size;
X		t->Where = k - Ark;
X		k->Count++;
X		break;
X	    }
X	if (k == ArkEnd) {
X	    fprintf(stderr, "'%s' doesn't fit -- need more then %d archives.\n",
X		    t->Name, ArchCount);
X	    exit(1);
X	}
X	/* Since our share 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    /* Open the output file. */
X    if (OutName == NULL)
X	F = stdout;
X    else {
X	if (GetStat(OutName)) {
X	    /* Handle /foo/bar/VeryLongFileName.BAK for non-BSD sites. */
X	    (void)sprintf(buff, "%s.BAK", OutName);
X	    p = (p = RDX(buff, '/')) ? p + 1 : buff;
X	    if (strlen(p) > 14)
X		/* ... well, sort of handle it. */
X		(void)strcpy(&p[10], ".BAK");
X	    fprintf(stderr, "Renaming %s to %s\n", OutName, buff);
X	    (void)unlink(buff);
X	    (void)link(OutName, buff);
X	    (void)unlink(OutName);
X	}
X	if ((F = fopen(OutName, "w")) == NULL) {
X	    fprintf(stderr, "Can't open '%s' for output, %s.\n",
X		    OutName, Ermsg(errno));
X	    exit(1);
X	}
X    }
X
X    /* Sort the shipping list, then write it. */
X    if (!Preserve)
X	qsort((char *)Table, lines, sizeof Table[0], NameP);
X    fprintf(F, "   File Name\t\tArchive #\tDescription\n");
X    fprintf(F, "-----------------------------------------------------------\n");
X    for (t = Table; t < TabEnd; t++)
X	fprintf(F, FORMAT1, 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
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. */
X    for (i = 0, k = Ark; k < ArkEnd; k++)
X	if (i < k->Count)
X	    i = k->Count;
X
X    /* Build the fixed part of the argument vector. */
X    av = NEW(char*, i + 10);
X    av[0] = "shar";
X    i = 1;
X    if (Trailer) {
X	av[i++] = "-t";
X	av[i++] = Trailer;
X    }
X    (void)sprintf(&buff[SEG0], "%d", LastOne);
X    av[i++] = "-e";
X    av[i++] = &buff[SEG0];
X    av[i++] = "-n";
X    av[i++] = &buff[SEG1];
X    av[i++] = "-o";
X    av[i++] = &buff[SEG2];
X
X    /* Call shar to package up each archive. */
X    for (Start = i, i = 0; i < LastOne; i++) {
X	(void)sprintf(&buff[SEG1], "%d", i + 1);
X	(void)sprintf(&buff[SEG2], FORMAT2, SharName, i + 1);
X	for (lines = Start, t = Table; t < TabEnd; t++)
X	    if (t->Where == i)
X		av[lines++] = t->Name;
X	av[lines] = NULL;
X	fprintf(stderr, "Packing kit %d...\n", i + 1);
X	if (lines = Execute(av))
X	    fprintf(stderr, "Warning:  shar returned status %d.\n", lines);
X    }
X
X    /* That's all she wrote. */
X    exit(0);
X}
END_OF_makekit.c
if test 9987 -ne `wc -c <makekit.c`; then
    echo shar: \"makekit.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"parser.c\" \(23917 characters\)
if test -f parser.c ; then 
  echo shar: Will not over-write existing file \"parser.c\"
else
sed "s/^X//" >parser.c <<'END_OF_parser.c'
X/*
X**  An interpreter that can unpack many /bin/sh shell archives.
X**  This program should really be split up into a couple of smaller
X**  files; it started with Argify and SynTable as a cute 10-minute
X**  hack and it just grew.
X**
X**  Also, note that (void) casts abound, and that every command goes
X**  to some trouble to return a value.  That's because I decided
X**  not to implement $? "properly."
X*/
X#include "shar.h"
XRCS("$Header: parser.c,v 1.7 87/03/24 17:56:13 rs Exp $")
X
X
X/*
X**  DATATYPES
X*/
X
X/* Command dispatch table. */
Xtypedef struct {
X    char	  Name[10];		/* Text of command name		*/
X    int		(*Func)();		/* Function that implements it	*/
X} COMTAB;
X
X/* A shell variable.  We only have a few of these. */
Xtypedef struct {
X    char	 *Name;
X    char	 *Value;
X} VAR;
X
X
X/*
X**  Manifest constants, handy shorthands.
X*/
X
X/* Character classes used in the syntax table. */
X#define C_LETR		1		/* A letter within a word	*/
X#define C_WHIT		2		/* Whitespace to separate words	*/
X#define C_WORD		3		/* A single-character word	*/
X#define C_DUBL		4		/* Something like <<, e.g.	*/
X#define C_QUOT		5		/* Quotes to group a word	*/
X#define C_META		6		/* Heavy magic character	*/
X#define C_TERM		7		/* Line terminator		*/
X
X/* Macros used to query character class. */
X#define ISletr(c)	(SynTable[(c)] == C_LETR)
X#define ISwhit(c)	(SynTable[(c)] == C_WHIT)
X#define ISword(c)	(SynTable[(c)] == C_WORD)
X#define ISdubl(c)	(SynTable[(c)] == C_DUBL)
X#define ISquot(c)	(SynTable[(c)] == C_QUOT)
X#define ISmeta(c)	(SynTable[(c)] == C_META)
X#define ISterm(c)	(SynTable[(c)] == C_TERM)
X
X
X/*
X**  Global variables.
X*/
X
XFILE		*Input;			/* Current input stream		*/
Xchar		*File;			/* Input filename		*/
Xint		 Interactive;		/* isatty(fileno(stdin))?	*/
X
Xextern COMTAB	 Dispatch[];		/* Defined below...		*/
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_TERM,	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}
X
X/**
X***		I N P U T   R O U T I N E S
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    char		 buf[MAX_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	p = &buf[strlen(buf) - 1];
X	if (*p != '\n') {
X	    Note("Input line too long.\n", (char *)NULL);
X	    exit(1);
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		Note("Bad case %x in Argify.\n", (char *)SynTable[*p]);
X	    case C_META:
X		p++;
X	    case C_WHIT:
X	    case C_LETR:
X		for (q = p; ISletr(*++q) || ISmeta(q[-1]); )
X		    ;
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	    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 (EQ(Vptr->Name, Name))
X	    return(Vptr->Value);
X
X    /* Try the environment. */
X    return((Name = getenv(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][MAX_VAR_VALUE];
X    static int		 Flag;
X    register char	*q;
X    register char	*n;
X    register char	 Closer;
X    char		 name[MAX_VAR_NAME];
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		(void)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		/* Read this line carefully... */
X		if (Closer = *p == '{' ? '}' : '\0')
X		    p++;
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; )
X	    ;
X    else
X	for (q = Value; ISletr(*q); q++)
X	    ;
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
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 -- i.e., no expansion of meta-chars
X**  inside the "here" document is done.  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
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 ? fopen(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 ? fopen(Expand(*av), "a") : stderr;
X	}
X	else if (EQ(*av, "<<") && av[1]) {
X	    for (Ending = *++av; *Ending == '\\'; Ending++)
X		;
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	if (Source == NULL)
X	    while (GetLine(FALSE) && !EQn(Text, Ending, l))
X		(void)fputs(Text, Out);
X	(void)fclose(Out);
X    }
X    else
X	while (GetLine(FALSE) && !EQn(Text, Ending, l))
X	    ;
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
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 ? fopen(Expand(*av), "w") : stderr;
X	}
X	else if (EQ(*av, ">>") && av[1]) {
X	    av++;
X	    Out = Running ? fopen(Expand(*av), "a") : stderr;
X	}
X	else if (EQ(*av, "<<") && av[1]) {
X	    for (Ending = *++av; *Ending == '\\'; Ending++)
X		;
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	while (GetLine(FALSE) && !EQn(Text, Ending, l))
X	    (void)fputs(Matches(Pattern, Text) ? &Text[i] : Text, Out);
X	(void)fclose(Out);
X    }
X    else
X	while (GetLine(FALSE) && !EQn(Text, Ending, l))
X	    ;
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 = fopen(Expand(av[2]), "w")) {
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 arg
X*/
Xstatic int
XDoMKDIR(ac, av)
X    int		 ac;
X    char	*av[];
X{
X    if (Running) {
X	if (ac != 2)
X	    SynErr("MKDIR");
X	if (mkdir(Expand(av[1]), 0777) >= 0)
X	    return(TRUE);
X	Note("Can't make directory %s.\n", av[1]);
X    }
X    return(FALSE);
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	if (Flag = av[1] != NULL && 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**  Do an EXIT command.
X*/
Xstatic
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    exit(ac);
X    /* NOTREACHED */
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 FILE	 *DEVTTY;
X    register int	  V;
X    register int	  i;
X    char		  buff[MAX_LINE_SIZE];
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	    ;
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 && EQ(av[0], "-f") && (Name = Expand(av[1])))
X	return(GetStat(Name) && Ftype(Name) == F_FILE ? V : !V);
X
X    /* Testing for directory-ness? */
X    if (ac == 2 && EQ(av[0], "-d") && (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 && (EQ(av[1], "-eq") || EQ(av[1], "=")))
X	return(EQ(Expand(av[0]), Expand(av[2])) ? V : !V);
X    if (ac == 3 && (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], "<") ? 7 : 6) && isdigit(av[0][0])
X     && (EQ(av[1], "-ne") || EQ(av[1], "-eq"))
X     && EQ(av[2], "`") && EQ(av[3], "wc")
X     && EQ(av[4], "-c") && 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 && EQ(av[0], "-f") && EQ(av[2], "-a") && EQ(av[4], "-eq")
X     && GetStat(Name = Expand(av[1])) && Ftype(Name) == F_FILE)
X	return(EQ(Expand(av[3]), Expand(av[5])) ? V : !V);
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    DEVTTY = fopen(THE_TTY, "r");
X    do {
X	fprintf(stderr, "Is value true/false/quit [tfq] (q):  ");
X	(void)fflush(stderr);
X	clearerr(DEVTTY);
X	if (fgets(buff, sizeof buff, DEVTTY) == NULL
X	 || buff[0] == 'q' || buff[0] == 'Q' || buff[0] == '\n')
X	    SynErr("TEST");
X	if (buff[0] == 't' || buff[0] == 'T') {
X	    (void)fclose(DEVTTY);
X	    return(TRUE);
X	}
X    } while (buff[0] != 'f' && buff[0] != 'F');
X    (void)fclose(DEVTTY);
X    return(FALSE);
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"))
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	if (Flag = EQ(*p, ";")) {
X	    if (p[1] == NULL || !EQ(p[1], "then"))
X		SynErr("IF");
X	    *p = NULL;
X	    ac -= 2;
X	    break;
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	if (Found = EQ(*av, ";")) {
X	    if (av[1] == NULL || !EQ(av[1], "do"))
X		SynErr("FOR");
X	    *av = NULL;
X	    break;
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*/
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[MAX_VAR_VALUE];
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 (ac != 2 || !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    {	":",		DoIT		},
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    {	"",		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	;
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		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}
X
X
X/*
X**  Do until we reach a specific terminator.
X*/
Xstatic
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}
END_OF_parser.c
if test 23917 -ne `wc -c <parser.c`; then
    echo shar: \"parser.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"unshar.c\" \(8109 characters\)
if test -f unshar.c ; then 
  echo shar: Will not over-write existing file \"unshar.c\"
else
sed "s/^X//" >unshar.c <<'END_OF_unshar.c'
X/*
X**  UNSHAR
X**  Unpack shell archives that might have gone through mail, notes, news, etc.
X**
X**  Options:
X**	-c dir	Change to directory 'dir' before starting
X**	-d dir	Change to directory 'dir' before starting
X**	-f	Don't try to intuit file type
X**	-s	Save pre-shar headers into a file
X**	-n	Don't save pre-shar headers into a file
X*/
X
X#include "shar.h"
XRCS("$Header: unshar.c,v 1.16 87/03/18 14:03:19 rs Exp $")
X
X
X/*
X**  Print error message and die.
X*/
Xstatic void
XQuit(text)
X    char	*text;
X{
X    int		 e;
X
X    e = errno;
X    fprintf(stderr, "unshar:  %s", text);
X    if (e)
X	fprintf(stderr, ", %s", Ermsg(e));
X    fprintf(stderr, ".\n");
X    exit(1);
X}
X
X
X/*
X**  Does this look like a mail header line?
X*/
Xstatic int
XIsHeader(p)
X    register char	*p;
X{
X    register int	 i;
X
X    if (*p == '\0' || *p == '\n')
X	return(FALSE);
X    if (WHITE(*p))
X	return(TRUE);
X    for (i = 0; *p == '-' || *p == '_' || *p == '.' || isalnum(*p); i++)
X	p++;
X    return(i && *p == ':');
X}
X
X
X
X/*
X**  Is this a /bin/sh comment line?  We check that because some shars
X**  output comments before the CUT line.
X*/
Xstatic int
XIsSHcomment(p)
X    register char	*p;
X{
X    while (isalpha(*p) || WHITE(*p) || *p == '\n' || *p == ',' || *p == '.')
X	p++;
X    return(*p == '\0');
X}
X
X
X/*
X**  Return TRUE if p has wd1 and wd2 as words (i.e., no preceeding or
X**  following letters).
X*/
Xstatic int
XHas(p, wd1, wd2)
X    register char	*p;
X    register char	*wd1;
X    register char	*wd2;
X{
X    register char	*wd;
X    register int	 first;
X
X    wd = wd1;
X    first = TRUE;
Xagain: 
X    while (*p) {
X	if (!isalpha(*p)) {
X	    p++;
X	    continue;
X	}
X	while (*p++ == *wd++) {
X	    if (*wd == '\0') {
X		if (!isalpha(*p)) {
X		    if (!first)
X			return(TRUE);
X		    first = FALSE;
X		    wd = wd2;
X		    goto again;
X		}
X		break;
X	    }
X	}
X	while (isalpha(*p))
X	    p++;
X	wd = first ? wd1 : wd2;
X    }
X    return(FALSE);
X}
X
X
X/*
X**  Here's where the work gets done.  Skip headers and try to intuit
X**  if the file is, e.g., C code, etc.
X*/
Xstatic int
XFound(Name, buff, Forced, Stream, Header)
X    register char	*Name;
X    register char	*buff;
X    register int	 Forced;
X    register FILE	*Stream;
X    register FILE	*Header;
X{
X    register char	*p;
X    register int	 InHeader;
X    char		 lower[BUFSIZ];
X
X    if (Header)
X	InHeader = TRUE;
X
X    while (TRUE) {
X	/* Read next line, fail if no more */
X	if (fgets(buff, BUFSIZ, Stream) == NULL) {
X	    fprintf(stderr, "unshar:  No shell commands in %s.\n", Name);
X	    return(FALSE);
X	}
X
X	/* See if it looks like another language. */
X	if (!Forced) {
X	    if (PREFIX(buff, "#include") || PREFIX(buff, "# include")
X	     || PREFIX(buff, "#define") || PREFIX(buff, "# define")
X	     || PREFIX(buff, "#ifdef") || PREFIX(buff, "# ifdef")
X	     || PREFIX(buff, "#ifndef") || PREFIX(buff, "# ifndef")
X	     || (PREFIX(buff, "/*")
X	      && !PREFIX(buff, NOTES1) && !PREFIX(buff, NOTES2)))
X		p = "C code";
X	    else if (PREFIX(buff, "(*"))		/* For vi :-) */
X		p = "PASCAL code";
X	    else if (buff[0] == '.' && isalpha(buff[1]) && isalpha(buff[2])
X		  && !isalpha(buff[3]))
X		p = "TROFF source";
X	    else
X		p = NULL;
X	    if (p) {
X		fprintf(stderr, "unshar:  %s is %s, not a shell archive.\n",
X			Name, p);
X		return(FALSE);
X	    }
X	}
X
X	/* Does this line start with a shell command or comment? */
X	if ((buff[0] == '#' && !IsSHcomment(buff + 1))
X	 || buff[0] == ':' || PREFIX(buff, "echo ")
X	 || PREFIX(buff, "sed ") || PREFIX(buff, "cat ")) {
X	    return(TRUE);
X	}
X
X	/* Does this line say "Cut here"? */
X	for (p = strcpy(lower, buff); *p; p++)
X	    if (isascii(*p) && islower(*p))
X		*p = toupper(*p);
X	if (PREFIX(buff, "-----") || Has(lower, "cut", "here")
X	 || Has(lower, "cut", "cut") || Has(lower, "tear", "here")) {
X	    /* Get next non-blank line. */
X	    do {
X		if (fgets(buff, BUFSIZ, Stream) == NULL) {
X		    fprintf(stderr, "unshar:  cut line is last line of %s\n",
X			    Name);
X		    return(FALSE);
X		}
X	    } while (*buff == '\n');
X
X	    /* If it starts with a comment or lower-case letter we win. */
X	    if (*buff == '#' || *buff == ':' || islower(*buff))
X		return(TRUE);
X
X	    /* The cut message lied. */
X	    fprintf(stderr, "unshar: %s is not a shell archive,\n", Name);
X	    fprintf(stderr, "        the 'cut' line was followed by: %s", buff);
X	    return(FALSE);
X	}
X
X	if (Header) {
X	    (void)fputs(buff, Header);
X	    if (InHeader && !IsHeader(buff))
X		InHeader = FALSE;
X	}
X    }
X}
X
X
X/*
X**  Create file for the header, find true start of the archive,
X**  and send it off to the shell.
X*/
Xstatic void
XUnshar(Name, Stream, Saveit, Forced)
X    char		*Name;
X    register FILE 	*Stream;
X    int			 Saveit;
X    int			 Forced;
X{
X    register FILE	*Header;
X#ifndef	USE_MY_SHELL
X    register FILE	*Pipe;
X#endif	/* USE_MY_SHELL */
X    char		*p;
X    char		 buff[BUFSIZ];
X
X    if (Saveit) {
X	/* Create a name for the saved header. */
X	if (Name) {
X	    p = RDX(Name, '/');
X	    (void)strncpy(buff, p ? p + 1 : Name, 14);
X	    buff[10] = 0;
X	    (void)strcat(buff, ".hdr");
X	}
X	else
X	    (void)strcpy(buff, "UNSHAR.HDR");
X
X	/* Tell user, and open the file. */
X	fprintf(stderr, "unshar:  Sending header to %s.\n", buff);
X	if ((Header = fopen(buff, "a")) == NULL)
X	    Quit("Can't open file for header");
X    }
X    else
X	Header = NULL;
X
X    /* If name is NULL, we're being piped into... */
X    p = Name ? Name : "the standard input";
X    printf("unshar:  Doing %s:\n", p);
X
X    if (Found(p, buff, Forced, Stream, Header)) {
X#ifdef	USE_MY_SHELL
X	BinSh(Name, Stream, buff);
X#else
X	if ((Pipe = popen("/bin/sh", "w")) == NULL)
X	    Quit("Can't open pipe to /bin/sh process");
X
X	(void)fputs(buff, Pipe);
X	while (fgets(buff, sizeof buff, Stream))
X	    (void)fputs(buff, Pipe);
X
X	(void)pclose(Pipe);
X#endif	/* USE_MY_SHELL */
X    }
X
X    /* Close the headers. */
X    if (Saveit)
X	(void)fclose(Header);
X}
X
X
Xmain(ac, av)
X    register int	 ac;
X    register char	*av[];
X{
X    register FILE	*Stream;
X    register int	 i;
X    char		*p;
X    char		 cwd[BUFSIZ];
X    char		 dir[BUFSIZ];
X    char		 buff[BUFSIZ];
X    int			 Saveit;
X    int			 Forced;
X
X    /* Parse JCL. */
X    p = getenv("UNSHARDIR");
X    Saveit = DEF_SAVEIT;
X    for (Forced = 0; (i = getopt(ac, av, "c:d:fns")) != EOF; )
X	switch (i) {
X	    default: 
X		Quit("Usage: unshar [-fs] [-c directory] [input files]");
X	    case 'c': 
X	    case 'd': 
X		p = optarg;
X		break;
X	    case 'f':
X		Forced++;
X		break;
X	    case 'n':
X		Saveit = 0;
X	    case 's': 
X		Saveit++;
X		break;
X	}
X    av += optind;
X
X    /* Going somewhere? */
X    if (p) {
X	if (*p == '?') {
X	    /* Ask for name; go to THE_TTY if we're being piped into. */
X	    Stream = isatty(fileno(stdin)) ? stdin : fopen(THE_TTY, "r");
X	    if (Stream == NULL)
X		Quit("Can't open tty to ask for directory");
X	    printf("unshar:  what directory?  ");
X	    (void)fflush(stdout);
X	    if (fgets(buff, sizeof buff, Stream) == NULL
X	     || buff[0] == '\n' || (p = IDX(buff, '\n')) == NULL)
X		Quit("Okay, cancelled");
X	    *p = '\0';
X	    p = buff;
X	    if (Stream != stdin)
X		(void)fclose(Stream);
X	}
X
X	/* If name is ~/blah, he means $HOME/blah. */
X	if (*p == '~') {
X	    if (getenv("HOME") == NULL)
X		Quit("You have no $HOME?");
X	    (void)sprintf(dir, "%s/%s", getenv("HOME"), p + 1);
X	    p = dir;
X	}
X
X	/* If we're gonna move, first remember where we were. */
X	if (Cwd(cwd, sizeof cwd) == NULL) {
X	    fprintf(stderr, "unshar warning:  Can't get current directory.\n");
X	    cwd[0] = '\0';
X	}
X
X	/* Got directory; try to go there. */
X	while (chdir(p) < 0)
X	    if (mkdir(p, 0777) < 0)
X		Quit("Cannot chdir nor mkdir desired directory");
X    }
X    else
X	cwd[0] = '\0';
X
X    /* No buffering. */
X    (void)setbuf(stdout, (char *)NULL);
X    (void)setbuf(stderr, (char *)NULL);
X
X    /* Process args. */
X    if (*av)
X	for (; *av; av++) {
X	    if (cwd[0] && av[0][0] != '/') {
X		(void)sprintf(buff, "%s/%s", cwd, *av);
X		*av = buff;
X	    }
X	    if ((Stream = fopen(*av, "r")) == NULL)
X		fprintf(stderr, "unshar:  File '%s' not found.\n", *av);
X	    else {
X		Unshar(*av, Stream, Saveit, Forced);
X		(void)fclose(Stream);
X	    }
X	}
X    else
X	Unshar((char *)NULL, stdin, Saveit, Forced);
X
X    /* That's all she wrote. */
X    exit(0);
X}
END_OF_unshar.c
if test 8109 -ne `wc -c <unshar.c`; then
    echo shar: \"unshar.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: End of archive 2 \(of 2\).
cp /dev/null ark2isdone
MISSING=""
for I in 1 2 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked both archives.
    echo "See the README"
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0
-- 
--
Rich $alz					"Drug tests p**s me off"
Mirror Systems, Cambridge Massachusetts		rs@mirror.TMC.COM
{adelie, mit-eddie, ihnp4, harvard!wjh12, cca, cbosgd, seismo}!mirror!rs