[comp.sources.atari.st] v02i013: shar -- Pack and unpack shell archives

koreth@ssyx.ucsc.edu (Steven Grimm) (02/25/89)

Submitted-by: John.Altstadt@moliman.molienergy.bc.ca (John Altstadt)
Posting-number: Volume 2, Issue 13
Archive-name: shar

[I know, this is like posting a uuencoded binary for "uudecode", but... -sg]

Here is a somewhat updated version of shar (including unshar) that was
posted to the net many years ago.  I have included the original readme
file (complete with typo's) plus many typo's of my own.  If somebody
wants to clean up the code, or add more features, be my guest.  Binaries
have been sent to the binaries group.

#	This is a shell archive.
#	Remove everything above and including the cut line.
#	Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar:	Shell Archiver
#	Run the following text with /bin/sh to create:
#	readme
#	makefile
#	shar.c
# This archive created: 31-January-1989 20:09:20 ENOENV
# By:	ENOENV (ENOENV)
echo shar: extracting readme
sed 's/^X//' << \SHAR_EOF > readme
X 
XThe next two files make up a shar/unshare program for the ST and vax VMS.  I
Xhave tested both versions and they work the same on each system.  I've had
Xthis source for quite a while and have been using it on VMS, but hadn't
Xbothered to port it to the ST until recently.  The first file is the shar
Xarchive for source and the link68 linkfile.  The second file is the uuencoded
Xbinary shar.prg.  I haven't prefixed the lines so that you can easily
Xextract the files without the progrm itself.  The original was posted to
Xnet.sources last year.  This one has had some bugs removed and most of the
Xamiga/cpm stuff deleted (it made it difficult to decipher the code).  But I
Xleft the VMS conditionals in place to make it easy to get versions running
Xon the two machines.  Hope you can find a use for this.  I sure have.
X 
SHAR_EOF
if test 819 -ne "`wc -c readme`"
then
echo shar: error transmitting readme '(should have been 819 characters)'
fi
echo shar: extracting makefile
sed 's/^X//' << \SHAR_EOF > makefile
XCFLAGS	= -A -VSMALL
X
Xshar.ttp : shar.c makefile
X	cc shar.c $(CFLAGS) -o shar.ttp
X	strip shar.ttp
SHAR_EOF
if test 97 -ne "`wc -c makefile`"
then
echo shar: error transmitting makefile '(should have been 97 characters)'
fi
echo shar: extracting shar.c
sed 's/^X//' << \SHAR_EOF > shar.c
X/*
X *  Shar puts readable text files together in a package
X *  from which they are easy to extract.
X *
X *	v 860716 M. Kersenbrock (tektronix!copper!michaelk) for Z80-CPM
X *	- enhanced usage message
X *
X *	v 860712 D. Wecker for ULTRIX and the AMIGA
X *	- stripped down.. does patterns but no directories
X *	- added a -u (unshar) switch
X *
X *	v 870128 R. Royar for ST
X *	- removed #ifdefs that left stray }s in file
X *	- reformatted text for consistant style
X *	- modified main to abort when no files are given
X *	- fixed call to fputc in tell macro, which omitted the stream
X *	  causing address errors on the ST.
X *	- fixed STUPID coding in getopt that tried to write to
X *	  address 0++.
X *	- known bugs:
X *		still no directory support.  Extracting
X *		files with directory names will get
X *		only the directory name on an open call.
X *
X *	v 880713 J. Altstadt for ST
X *	- removed extraneous #ifdefs for all other systems
X *	- reformatted text for consistant style
X *	- changed lots of things for unsharing:
X *	  1. added directory handling
X *	  2. added code to handle weird shar sed formats better,
X *	     will add more things when i get files that i can't unshar,
X *	     eg. when i get one of those icky sharchives that has split files
X *	     but won't add in the ability to handle shars that use awk to
X *	     extract files (yes, windsrch uses awk, blech!!!)
X *	  3. added in file overwrite protection, i didn't need it, but it was
X *	     easy (and i know i will need it some day when i don't have a
X *	     backup)
X *	  4. skip whitespace at begining of lines intended for sh, so it will
X *	     do stuff embedded in "if test" structures
X *
X *	v 890124 J. Altstadt for ST
X *	- unshar will handle files split across two (or more) sharchives
X *	- fixed bug that would allow creation of directories (files) with
X *	  the same name as an existing file (directory)
X */
X
X
X#include <stdio.h>
X#include <osbind.h>
X#include <ctype.h>
X#include <stat.h>
X
Xlong _stksize = 16 * 1024;	/* for 10k stack */
Xchar *Defenv = "ENOENV";	/* if no cc.ini file */
Xextern char *getenv(),*malloc(),*index();
Xvoid shar();
X
X
X#define BADCH	((int)'?')
X#define EMSG	""
X#define tell(s) {fputs(*nargv,stderr);fputs((s),stderr);fputc('\n',stderr);}
X#define rescanopts()	(optind = 1)
X 
Xint	optind = 1,	/* index into parent argv vector */
X	optopt;		/* character checked for validity */
Xlong	fsize;		/* length of file */
Xchar	*optarg;	/* argument associated with option */
Xchar	*sav[100];	/* saved file names */
Xint	savind;		/* save index */
X 
X/* OPTIONS */
Xint	Verbose = 0;		/* provide append/extract feedback */
Xint	Basename = 0;		/* extract into basenames */
Xint	Count = 0;		/* count characters to check transfer */
Xchar	*Delim = "SHAR_EOF";	/* put after each file */
Xchar	Filter[100] = "cat";	/* used to extract archived files */
Xchar	*Prefix = NULL;		/* line prefix to avoid funny chars */
Xint	OverWrite = 0;		/* do we overwrite an output file? */
X 
Xchar Usage1[] =
X"\nSHAR: Create/extract file archive for extraction by /bin/sh (normally).\n\
X\n\
Xusage: shar [[-uo] archive] [[-a] [-p prefix]\
X [-d delim] [-bcv] files > archive]\n\
X\n\
X	where:	-a	all the options (v,c,b,-pXX)\n";
X
Xchar Usage2[] =
X"		-b	extract absolute paths into current directory\n\
X		-c	check filesizes on extraction\n\
X		-d	use this EOF delimiter instead of SHAR_EOF\n";
X
Xchar Usage3[] =
X"		-o	unshar <archive>, overwriting old files\n\
X		-p	use this as prefix to each line in archived files\n\
X		-u	unshar <archive>, not overwriting old files\n\
X		-v	verbose on extraction, incl. echoing filesizes\n";
X
X
X#define SED "sed 's/^%s//'"	/* used to remove prefix from lines */
X#define OPTSTRING "u:o:ap:d:bcv"
X
X
Xvoid main(argc, argv)
Xint argc;
Xchar **argv;
X{
X	char *ppchFiles[256];
X	register int	C;
X	char **ppchList = ppchFiles;
X	register int errflg;
X	errflg = 0;
X 
X	while(EOF != (C = getopt(argc, argv, OPTSTRING))) {
X		switch(C) {
X		case 'v':
X			Verbose++;
X			break;
X		case 'c':
X			Count++;
X			break;
X		case 'b':
X			Basename++;
X			break;
X		case 'd':
X			Delim = optarg;
X			break;
X		case 'a': /* all the options */
X			optarg = "X";
X			Verbose++;
X			Count++;
X			Basename++;
X			/* fall through to set prefix */
X		case 'p':
X			sprintf(Filter, SED, Prefix = optarg);
X			break;
X		case 'o':
X			OverWrite++;
X		case 'u':
X			dounshar(optarg);
X			exit(0);
X			break;
X		default:
X			errflg++;
X		}
X	}
X
X	C = getarg(argc, argv);
X	if (errflg || EOF == C) {
X		if (EOF == C)
X			fprintf(stderr, "shar: No input files\n");
X		fprintf(stderr, "%s%s%s", Usage1, Usage2, Usage3);
X		exit(1);
X	}
X	savind = 0;
X
X	do {
X		if (getpat(optarg)) exit(2);
X	} while (EOF != (C = getarg(argc, argv)));
X
X	sav[savind] = 0;
X	header(sav);
X	for (ppchList = sav; *ppchList; ++ppchList)
X		shar(*ppchList);
X	puts("#\tEnd of shell archive");
X	puts("exit 0");
X	exit(0);
X}
X
X 
Xint header(ppchFiles)
Xchar *ppchFiles[];
X{
X	char clock[40];
X	register char **ppchList;
X	char *pchOrg;
X	char *pchName;
X	register int	problems;
X	problems = 0;
X	pchOrg = getenv("organization");
X	pchName = getenv("name");
X	puts("#\tThis is a shell archive.");
X	puts("#\tRemove everything above and including the cut line.");
X	puts("#\tThen run the rest of the file through sh.");
X	puts("#----cut here-----cut here-----cut here-----cut here----#");
X	puts("#!/bin/sh");
X	puts("# shar:	Shell Archiver");
X	puts("#\tRun the following text with /bin/sh to create:");
X	for (ppchList = ppchFiles; *ppchList; ++ppchList)
X		printf("#\t%s\n", *ppchList);
X	stime(clock);
X	printf("# This archive created: %s",clock);
X	if (pchName)
X		printf("# By:\t%s (%s)\n", pchName,pchOrg);
X	return(0);
X}
X
X
X/* create an ASCII time string and copy it into timeline.  Caller
X * should make sure timeline has ~35 characters space.
X */
Xstime(timeline)
Xchar timeline[];
X{
X	register int mytime;
X	register int date;
X	register int hours, minutes, seconds;
X	int day, month, year;
X	static char *months[] = {"January","February","March","April",
X				"May","June","July","August","September",
X				"October","November","December"};
X	char *tzone;
X	tzone = getenv("timezone");
X	mytime	= Tgettime();
X	date = Tgetdate();
X	seconds = (mytime&31)*2;
X	minutes = (mytime>>5)&63;
X	hours	= (mytime>>11)&31;
X	day = (date&31);
X	month = ((date>>5)&15)-1;
X	year = ((date>>9)&31)+1980;
X	sprintf(timeline,"%02d-%s-%d %d:%02d:%02d %s\n",
X	day,months[month],year,hours,minutes,seconds,tzone);
X	return(1);
X}
X
X
Xint
Xarchive(input, output)
Xchar *input, *output;
X{
X	char line[BUFSIZ];
X	register FILE *ioptr;
X 
X	if (ioptr = fopen(input, "r")) {
X		printf("%s << \\%s > %s\n", Filter, Delim, output);
X		while(fgets(line, BUFSIZ, ioptr)) {
X			if (Prefix)
X				fputs(Prefix, stdout);
X			fputs(line, stdout);
X			if (Count)
X				fsize += strlen(line);
X		}
X		puts(Delim);
X		fclose(ioptr);
X		return(0);
X	}
X	else {
X		fprintf(stderr, "shar: Can't open '%s'\n", input);
X		return(1);
X	}
X}
X
X
Xvoid shar(file)
Xchar *file;
X{
X	register char *basefile;
X	basefile = file;
X	if (!strcmp(file, "."))
X		return;
X	fsize = 0;
X	if (Basename) {
X		while(*basefile)
X			basefile++;		/* go to end of name */
X		while(basefile > file && *(basefile-1) != '/')
X			basefile--;
X	}
X	if (Verbose)
X		printf("echo shar: extracting %s\n", basefile);
X	if (archive(file, basefile))
X		exit(66);
X	if (Count) {
X		printf("if test %ld -ne \"`wc -c %s`\"\n",fsize,basefile);
X		printf("then\necho shar: error transmitting %s ",basefile);
X		printf("'(should have been %ld characters)'\nfi\n",fsize);
X	}
X}
X
X
Xgetpat(pattern)
Xchar *pattern;
X{
X	register char *ptr;
X 
X	ptr = pattern;
X	sav[savind] = malloc(strlen(ptr)+1);
X	strcpy(sav[savind++],ptr);
X	if (access(ptr,4)) {
X		printf("No read access for file: %s\n",ptr);
X		return(-1);
X	}
X	return(0);
X}
X
X 
X/*
X * get option letter from argument vector
X */
Xint
Xgetopt(nargc, nargv, ostr)
Xint nargc;
Xchar **nargv, *ostr;
X{
X	register char	*oli;		/* option letter list index */
X	static char	*place = EMSG;	/* option letter processing */
X 
X	if(!*place) {			/* update scanning pointer */
X		if(optind >= nargc || *(place = nargv[optind])
X				!= '-' || !*++place)
X			return(EOF);
X		if (*place == '-') {	/* found "--" */
X			++optind;
X			return(EOF);
X		}
X	}	/* option letter okay? */
X	if ((optopt = (int)*place++) == (int)':'
X			|| !(oli = index(ostr,optopt))) {
X		if(!*place)
X			++optind;
X		tell(": illegal option");
X	}
X	if (oli) {		/* %$^& what if oli = NULL!!! */
X		if (*++oli != ':') {		/* don't need argument */
X			optarg = NULL;
X			if (!*place)
X				++optind;
X		}
X		else {				/* need an argument */
X			if (*place)		/* no white space */
X				optarg = place;
X			else {
X				if (nargc <= ++optind) {	/* no arg */
X					place = EMSG;
X					tell(": option requires an argument");
X				}
X				else
X					optarg = nargv[optind]; /*white space*/
X			}
X			place = EMSG;
X			++optind;
X		}
X	}
X	return(optopt);		 /* dump back option letter */
X}
X
X
Xint
Xgetarg(nargc, nargv)
Xint nargc;
Xchar **nargv;
X{
X	if (nargc <= optind) {
X		optarg = (char *) 0;
X		return(EOF);
X	}
X	else {
X		optarg = nargv[optind++];
X		return 0;
X	}
X}
X
X
Xdounshar(ArcNam)
Xchar *ArcNam;
X{
X	register int i;
X	register FILE *inptr,*outptr;
X	register int hundreds;
X	char line[BUFSIZ];
X	int DirNum = -1;
X	int Prefix = 0;
X	char Dirs[10][40], FilNam[428], Delim[128], ScrStr[28];
X	char strip[40];
X	char *ptr, *lptr;
X	int wipeold, append, exists;
X	struct stat dirstat;
X 
X	if (!(inptr = fopen(ArcNam,"r"))) {
X		fprintf(stderr,"shar: Can't open archive '%s'\n", ArcNam);
X		return;
X	}
X	while (fgets(line, BUFSIZ, inptr)) {
X		for (lptr = line; (*lptr != (char) 0) && (isspace(*lptr));
X				lptr++)
X			;
X		if (strncmp(lptr, "sed ", 4) == 0) {
X			Prefix = 0;
X			strcpy(strip, "");
X			if (!(ptr = index(lptr,'/')))
X				goto getfil;
X			if (*++ptr == '^')
X				++ptr;
X			while (*ptr != '/') {
X				strncat(strip, ptr, 1);
X				Prefix++;
X				*(strip + Prefix) = (char) 0;
X				ptr++;
X			}
X			goto getfil;
X		}
X		else if (strncmp(lptr, "gres ", 5) == 0) {
X			Prefix = 0;
X			strcpy(strip, "");
X			if (!(ptr = index(lptr,'\'')))
X				goto getfil;
X			if (*++ptr == '^')
X				++ptr;
X			while (*ptr != '\'') {
X				if (*ptr == '0')
X					continue;
X				strncat(strip, ptr, 1);
X				Prefix++;
X				*(strip + Prefix) = (char) 0;
X				ptr++;
X			}
X			goto getfil;
X		}
X		else if (strncmp(lptr,"cat ",4) == 0) {
X			Prefix = 0;
Xgetfil:
X			FilNam[0] = 0;
X
X			for (i = 0; i <= DirNum; i++) {
X 				strcat(FilNam,Dirs[i]);
X				strcat(FilNam,"\\");
X			}
X
X			append = 0;
X			getshpar(lptr, ">", ScrStr, 1);
X			if (strcmp(">", ScrStr) == 0) {
X				getshpar(lptr, ">>", ScrStr, 1);
X				append = 1;
X			}
X			strcat(FilNam,ScrStr);
X			getshpar(lptr, "<<", Delim, 0);
X			wipeold = 1;
X			exists = 1;
X
X			i = stat(FilNam, &dirstat);
X			i = i < 0 ? i : (int) dirstat.st_mode;
X			if ((i >= 0) && (i <= 0X3F)) {
X				if (!OverWrite)
X					wipeold = 0;
X			}
X			else
X				exists = 0;
X
X			if (wipeold && !append) {
X				outptr = fopen(FilNam, "w");
X				fprintf(stderr, "Creating %s ", FilNam);
X			}
X			else if (append && exists) {
X				outptr = fopen(FilNam, "a");
X				fprintf(stderr, "Appending %s ", FilNam);
X			}
X			else if (append && !exists) {
X				fprintf(stderr, "Cannot append to %s,\n\
X\tyou need to unshar another sharchive first ", FilNam);
X			}
X			else
X				fprintf(stderr, "Will not overwrite %s ",
X						FilNam);
X			if (!outptr) {
X				fprintf(stderr, "Fatal:  could not open file \
X%s\n", FilNam);
X				exit(1);
X			}
X			fflush(stderr);
X			hundreds = 0;
X			while (fgets(line, BUFSIZ, inptr)) {
X				if (strncmp(line, Delim, strlen(Delim)) == 0)
X					break;
X				hundreds++;
X				if (!(hundreds % 100)) {
X					fprintf(stderr, ".");
X					fflush(stderr);
X				}
X				if ((wipeold && !append) || (append && exists))
X					if (strncmp(line, strip, Prefix))
X						fputs(line, outptr);
X					else
X						fputs(&line[Prefix],outptr);
X			}
X			if (outptr) {
X				fclose(outptr);
X				fprintf(stderr,"done\n");
X			}
X			else {
X				fprintf(stderr," error in creating file\n");
X				exit(1);
X			}
X		}
X		else if (strncmp(lptr, "mkdir ", 6) == 0) {
X			FilNam[0] = 0;
X
X			for (i = 0; i <= DirNum; i++) {
X 				strcat(FilNam,Dirs[i]);
X				strcat(FilNam,"\\");
X			}
X
X			getshpar(lptr, "mkdir", ScrStr, 1);
X			strcat(FilNam, ScrStr);
X
X			i = stat(FilNam, &dirstat);
X			i = i < 0 ? i : (int) dirstat.st_mode;
X			if ((i >= 0) && (i <= 0X3F))
X				if ((i & 0X10) == 0X10)
X					continue;	/* don't re-create */
X				else {
X					fprintf(stderr, "will not mkdir %s,\n\
X\tthere is already a file of that name\n", FilNam);
X					exit(1);
X				}
X			if (Dcreate(FilNam)) {
X				fprintf(stderr, "could not mkdir %s\n", FilNam);
X				exit(1);
X			}
X		}
X		else if (strncmp(lptr,"cd ",3) == 0) {
X			if (lptr[3] == '.' && lptr[4] == '.')
X				DirNum--;
X			else if (DirNum >= 9)
X				fprintf(stderr,"directories nested too deep\n");
X			else {
X				DirNum++;
X				getshpar(lptr, "cd", ScrStr, 1);
X				strcpy(Dirs[DirNum], ScrStr);
X			}
X			if (DirNum < -1)
X				DirNum = -1;
X		}
X		else if (strncmp(lptr, "chdir ", 6) == 0) {
X			if (lptr[6] == '.' && lptr[7] == '.')
X				DirNum--;
X			else if (DirNum >= 9)
X				fprintf(stderr,"directories nested too deep\n");
X			else {
X				DirNum++;
X				getshpar(lptr, "cd", ScrStr, 1);
X				strcpy(Dirs[DirNum], ScrStr);
X			}
X			if (DirNum < -1)
X				DirNum = -1;
X		}
X	}
X	fclose(inptr);
X}
X
X
Xgetshpar(line, sea, par, flipslash)
Xchar *line, *sea, *par;
Xint flipslash;
X{
X	register char *scr1, *scr2;
X 
X	while (*line) {
X		scr1 = line;
X		scr2 = sea;
X		while (*scr1 && *scr2 && *scr1 == *scr2) {
X			scr1++;
X			scr2++;
X		}
X		if (*scr2 == 0) {
X			if (*scr1 == 0) {
X				*par = 0;
X				return;
X			}
X			while (*scr1 == ' ' || *scr1 == '\t' ||
X					*scr1 == '\\' || *scr1 == '\'' ||
X					*scr1 == '"')
X				scr1++;
X			while (*scr1 != 0 && *scr1 != ' ' && *scr1 != '\t' &&
X					*scr1 != '\\' && *scr1 != '\'' &&
X					*scr1 != '"' && *scr1 != '\n' &&
X					*scr1 != '\r') {
X				if ((*scr1 == '/') && flipslash) {
X					*par++ = '\\';
X					scr1++;
X				}
X				else
X					*par++ = *scr1++;
X			}
X			if (*(par - 1) == '\\')	/* if \ at end, not ST path */
X				par--;
X			*par = 0;
X			return;
X		}
X		line++;
X	}
X	*par = 0;
X}
X
X
X/* since nothing ever initializes the environment table on the ST
X * I'll just use my home brew cc.ini environment file.
X */
Xchar *getenv(var)
Xregister char *var;
X{
X	FILE *fp;
X	register char *ptr;
X	static char line[81];
X	register char *safestore;
X 
X	if ((fp=fopen("cc.ini","r")) == NULL)
X		return(Defenv);
X	while (fgets(line,80,fp)) {
X		if ((ptr=index(line,' '))!=(char *)NULL)
X			*ptr = '\0';
X		if ((ptr=index(line,'\n'))!=(char *)NULL)
X			*ptr = '\0';
X		if ((ptr=index(line,'\t'))!=(char *)NULL)
X			*ptr = '\0';
X		if ((ptr=index(line,'='))!=(char *)NULL)
X			*ptr = '\0';
X		else
X			continue;
X		++ptr;
X		if (strcmp(line, var) == 0) {
X			safestore = malloc(strlen(ptr)+1);
X			strcpy(safestore,ptr);
X			return(safestore);
X		}
X		else if (feof(fp))
X			break;
X	}
X	return(Defenv);
X}
SHAR_EOF
if test 14606 -ne "`wc -c shar.c`"
then
echo shar: error transmitting shar.c '(should have been 14606 characters)'
fi
#	End of shell archive
exit 0