/*
 *			   ---   UNSHAR.C   ---
 *
 *		  ---   Copywrite 24 September, 1986   ---
 *		      ---    John Birchfield      ---
 *		      ---    411 Crane Ave.       ---
 *		      ---    Turlock, CA  95380   ---
 *		      ---      (209) 634-6243     ---
 *
 *	Program to decode files created by the shell archive { shar }
 *	utility on Un*x machines or pc's.  Current capabilities include
 *
 *	1.	Able to unshar into a user specified directory or subdirectory
 *		as specified by a command line option { -Ddirectory_name }
 *
 *	2.	Recognizes the following commands
 *			cat, sed, uudecode, mkdir, chdir, 
 *			{test -f, test -d, test <number -ne wc ... }
 *
 *	3.	Can handle shar scripts created with the
 *			Options { -a -v -p -b -c -d }
 *
 *	4.	Successfully traverses directories and sub-directories
 *		creating the necessary subdirectories as necessary.
 *
 *	CAVEATS:
 *		Word Counting between Un*x machines and pc's just isn't
 *		gonna work out too well.  The \r\n - \n thing is not
 *		an easy thing to work around - and to be quite frank
 *		I just ain't up to it.
 *
 *	Program written for the Desmet (C-Ware) C Compiler Version 2.61
 *	not all of the routines in DOS_C.C and DOS_A.A are used by this
 *	program.  The program consists of the following modules:
 *		UNSHAR.C
 *		SHAR_CMD.C
 *		DOS_C.C
 *		DOS_A.A
 *
 *	This program is hereby placed in the public domain for 
 *	non-commersial use.
 */

# include <stdio.h>
# include "unshar.h"

char	test_flag = '\0',
	root_dir [65] = "";

FILE *input;




/*
 *	MAIN ()	-	get the options and input filename and
 *		if possible go to it.
 */
 
main (argc, argv)
int argc;
char **argv;
{
	char dts [22];

	dates (dts);
	strcat (&dts [8], " at ");
	times (&dts [12]);
	get_options (argc, argv);
	p_error (0, "\t\t--------------------------------------\n",
		"\t\t      UNSHAR  Version 24 Sep '86\n",
		"\t\t    Extracting to '", (root_dir [0])?root_dir:"Current",
		"' Directory\n",
		"\t\t       On", dts, "\n",
		"\t\t--------------------------------------\n", "");
	do_cmds (input);
}



/*
 *	DO_CMDS ()	-	process the input file as a token stream
 *		and when a command is recognized, perform it.  The 
 *		cmd_flag array is used to determine the current state
 *		of { if then else } constructs within a shar script.
 *		as you can see, it allow four levels of nesting of 'em.
 *		the i/o to the token mechanism is explained somewhere
 *		around get_tok ().
 */
 
int do_cmds (fp)
FILE *fp;
{
	static int  cmd_level = 0;
	static char cmd_flag [5] = {'\1', '\0', '\0', '\0', '\0' },	
	            cmd_tok [132] = "";

	while (get_tok (cmd_tok, fp)) {
		switch (token_type (cmd_tok)) {
			case IF_CMD:
				++cmd_level;
				break;				
			case TEST_CMD:
				cmd_flag [cmd_level] = ((char) (do_test ())
					&& (cmd_flag [cmd_level-1]));
				break;
			case CAT_CMD:
				if (cmd_flag [cmd_level])
					do_cat (fp);
				else
					do_skip (fp);
				cmd_init ();
				break;
			case SED_CMD:
				if (cmd_flag [cmd_level])
					do_sed (fp);
				else
					do_skip (fp);
				cmd_init ();
				break;
			case ECHO_CMD:
				if (cmd_flag [cmd_level])
					do_echo ();
				else
					cmd_init ();
				break;
			case EXIT_CMD:
				if (cmd_flag [cmd_level])
					do_exit (fp);
				else
					cmd_init ();
			case FI_CMD:
				cmd_flag [cmd_level--] = ((char) FALSE);
				break;
			case ELSE_CMD:
				cmd_flag [cmd_level] = (!(cmd_flag [cmd_level])
						&& (cmd_flag [cmd_level-1]));
				break;
			case THEN_CMD:
				break;
			case CD_CMD:
				if (cmd_flag [cmd_level])
					do_cd (fp);
				else
					cmd_init ();
				break;
			case MKDIR_CMD:
				if (cmd_flag [cmd_level])
					do_mkdir (fp);
				else
					cmd_init ();
				break;
			case UUDCD_CMD:
				if (cmd_flag [cmd_level])
					do_uudecode (fp);
				else
					cmd_init ();
				break;
			default:
				if (cmd_flag [cmd_level])
					p_error (0,
					   "   Skipping command '",
					   cmd_tok, "' ...\n", "");
				cmd_init ();
				break;
		}
	}
}




/*
 *	Data and Defines for token passing routines
 */
 
# define is_white(c)	((c==' ') || (c=='\t') || (c=='\n') ||\
			 (c=='\r') || (c=='\0'))
# define not_single(c)	((c!='\0') && (c!='\'') && (c!='\n') && (c!='\r'))
# define not_double(c)	((c!='\0') && (c!='\"') && (c!='\n') && (c!='\r'))
char tok_buf [255] = "",
     *tbp=&tok_buf[0];





/*
 *	GET_BUF ()	-	get_buf () is driven by get_tok ().
 *		Basically, get_buf () is called when tok_buf has been
 *		exhausted.
 */
 
int get_buf (fp)
FILE *fp;
{
	int rval;
	char *tp;
	
	tbp = &tok_buf [0];
	do {
		tp = &tok_buf [0];
		if ((rval = fgets (tok_buf, 255, fp))==0)
			return 0;
		while (*tp) {
			if (*tp=='#') {
				*tp = '\0';
				break;
			}
			else
				tp++;
		}
	} while (tok_buf [0]==0);
	return rval;
}




/*
 *	GET_TOK ()	-	get_tok () retrieves the next token
 *		available in the i/o stream being processed. The array
 *		tok_buf is the token passing buffer used. Tokens are
 *		of the form
 *			[a-z0-9special]
 *			'[a-z0-9special]' - '\'' are stripped
 *			"[a-z0-9special]" - '\"' are stripped
 *		get_tok () returns 0 on EOF
 */
 
int get_tok (tok, fp)
char *tok;
FILE *fp;
{
	int rval = 1;
	char *tp;

	tp = tok;
	*tp = '\0';
	while (*tok=='\0') {
		if (*tbp=='\0')
			if ((rval = get_buf (fp))==0)
				return rval;
		while (is_white(*tbp))
			if (*tbp=='\0')
				break;
			else
				tbp++;
		if (*tbp=='\'') {
			tbp++;
			while (not_single(*tbp))
				*tp++=*tbp++;
			*tbp++='\0';
		}
		else if (*tbp=='\"') {
			tbp++;
			while (not_double(*tbp))
				*tp++=*tbp++;
			*tbp++='\0';
		}
		else
			while (!(is_white(*tbp)))
				*tp++=*tbp++;
		*tp='\0';
	}
	return rval;
}



/*
 *	TK_LIST	-	A list of the available commands to UNSHAR.
 *		their relative displacement in the list relates
 *		to a corresponding set of Defines in UNSHAR.H
 */
 
char *tk_list [] = {
	"if", "echo", "test", "cat", "sed" , "exit", "else", "fi", "then",
	"cd", "mkdir", "uudecode"
};





/*
 *	TOKEN_TYPE ()	-	returns the relative index into the
 *		tk_list array for processing by do_cmds ().
 */
 
int token_type (tok)
char *tok;
{
	int i;
	for (i=0; i<TK_MAX; i++)
		if (strcmp (tok, tk_list [i])==0)
			break;
	return i;
}



/*
 *	CMD_INIT ()	-	cmd_init () is used to flush the
 *		buffer tok_buf.  This is necessary when a command is
 *		not processed for some reason, or upon the successful
 *		completion of a shar command.  This is necessary
 *		because the same input file is being processed thru
 *		multiple buffers.  After a cmd_init () call the next
 *		call to get_tok () will cause the immediate refilling
 *		of tok_buf by get_buf ().
 */
 
void cmd_init ()
{
	tok_buf [0]= '\0';
	tbp = &tok_buf [0];
}



/*
 *	DO_ECHO ()	-	do_echo () is the only shar command
 *		contained in this module.  It would be nicer to have
 *		it with the other routines but it's so much easier here
 *		since it directly reads from the tok_buf...
 */
 
void do_echo ()
{
	fputs (tbp, stdout);
	cmd_init ();
}



/*
 *	USAGE ()	-	Tell 'em what it's all about and
 *		go home to mamma.
 */
 
usage ()
{
  fputs ("Usage: UNSHAR [-T -D<directory name>] <shar file name>\n",
  	stdout);
  fputs ("\tWhere <directory name> specifies the root directory\n",
  	stdout);
  fputs ("\t   for UNSHAR to place extracted files\n\n", 
	stdout);
  fputs ("\tand -T says to return TRUE from unknown 'test' commands\n\n",
  	stdout);
  fputs ("\t   Either Upper or lower case will work for the option flags\n\n",
  	stdout);
  exit (2);
}



/*
 *	GET_OPTIONS ()	-	get_options () checks for proper options
 *		and if they're valid sets things up for do_cmds.
 *		the options available are...
 *
 *	-t			tell unshar to return true on an unknown
 *				test command
 *	-D<directoryname>	tell unshar to build the extracted files
 *				in the directory specified { if it doesn't
 *				exist make it } BeWare ... It will make
 *				ALL the sub-directories you specify along the
 *				way.
 */

get_options (argc, argv)
int argc;
char **argv;
{
	char got_dir = FALSE,
	     got_name = FALSE,
	     file_name [65],
	     *cp;
	
	if (argc < 2) {
		usage ();
	}
	file_name [0] = '\0';
	while (--argc) {
		if ((*(cp=(*++argv)))=='-')
			switch (toupper (*++cp)) {
				case 'D':
					if (got_dir)
						usage ();
					if (*++cp)
						strcpy (root_dir, cp);
					got_dir = TRUE;
					break;
				case 'T':
					test_flag = TRUE;
					break;
				default:
					p_error (0, "Invalid Option ",
						 cp, "\n", "");
					usage ();
					break;
			}
		else {
			strcpy (file_name, *argv);
			break;
		}
	}
	if ((input = open (file_name, 0))==-1) {
		p_error (0, "UNSHAR: Error ... Can't Open '",
			(file_name [0])?file_name:" ","'\n", "");
		usage ();
	}
	if (root_dir [0] != '\0')
		mk_dirs (root_dir);
}




/*
 *	MK_DIRS ()	-	mk_dirs () is passed a path of directories.
 *		It will traverse this list and make all the directories
 *		along the way to the end.  Like I said above, BeWare...
 */
 
void mk_dirs (s)
char *s;
{
	char *p1, buf [65];
	p1 = & buf [0];

	if ((*s=='\\' || *s=='/') && (*(s+1)=='\0'))
		return;

	*p1++ = *s++;
	do {
		while (*s!='/' && *s!='\\' && *s!='\0')
			*p1++=*s++;
		*p1='\0';
		mkdir (buf);
		*p1++ = '/';
		if (*s=='\\' || *s=='/')
			s++;
	} while (*s);
	*p1='\0';
	if (*(s-1) != '\\' && *(s-1) != '//') {
		*s++='/';
		*s++='\0';
	}
}
