shell for MS-DOS (part 2)

Douglas Orr doug at umich.UUCP
Mon Aug 5 04:14:26 AEST 1985


Here is part 2 of the Unix-like shell for DOS.  This part contains
the actual Microsoft C sources for the program.

	-Doug
	{ihnp4,mb2c,pur-ee}!umich!textset!doug
	doug%textset at umich.csnet

#	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:
#	sh.c
#	cmds.c
#	builtin.c
#	wildcard.c
#	gen.h
#	sh.h
# This archive created: Sun Aug  4 14:03:53 1985
cat << \SHAR_EOF > sh.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>

#include "gen.h"
#include "sh.h"

Public char * * dissect();
Public char * smalloc();

Local bool escflg = True;	/*  interpret \ as "escape" character */

/*
 *	sh:  a Unix(tm) like command interpreter for MS-DOS.
 *
 *  by  Douglas Orr
 *      Textset Inc.
 *      Ann Arbor, Michigan
 *
 *  Copyright (c) 1985 Textset.   All rights reserved.
 *
 *  This program may be freely distributed, but not sold for profit.
 *
 */

main(argc,argv,envp)
int	argc;
char	* argv[];
char	* envp[];
	{

	/*  arguments processing */
	while( --argc )
	if( (*++argv)[0] == '-' )
		{
		switch( (*argv)[1] )
			{
			case 'e':
				escflg = !escflg;
				break;
			default:
				fprintf( stderr, "sh: invalid argument %s\n", 
					*argv );
				break;
			}
		}

	/*  real stuff  */
	init();
	sh();

	}


/*  Environment Stuff  */

/*
 *   for now, just use the uSoft environment stuff.  This could probably
 *  be improved
 */
char *
l_getenv( name )
char * name;
	{
	return( getenv(name) );
	}

l_setenv( name, val )
char * name;
char * val;
	{

	char * str;

	str = smalloc( strlen(name) + strlen(val) + 2 );

	strcpy( str, name );
	strcat( str, "=" );
	strcat( str, val );

	return( putenv( str ) );

	}

l_printenv()
	{

	char * * ep;

	putchar( '\n' );
	for( ep=environ; *ep; ep++ )
		printf( "%s\n", *ep );
	putchar( '\n' );

	return( 0 );

	}


init()
	{

	char * path;

	/*  ignore interrupts */
	signal( SIGINT, SIG_IGN );

	/*   default values  */
	if( (path = l_getenv( "PATH" )) == NULL )
		{
		l_setenv( "PATH", "\;\bin" );
		path = l_getenv( "PATH" );
		}

	setbuf( stdin, NULL );
	setbuf( stdout, NULL );
	setbuf( stderr, NULL );

	rehash( path );

	/*   read in the profile  */
	if( access( "/profile", F_ROK ) == 0 )
		source( "/profile" );

	/*  set important variables  */
	if( l_getenv( "PROMPT" ) == NULL )
		l_setenv( "PROMPT", "% " );
	if( l_getenv( "PS1" ) == NULL )
		l_setenv( "PS1", "> " );

	}


/*
 *	iiiiit's SHOWTIME
 */
sh()
	{
	char buf[256];
	bool intr;

	while( !feof(stdin) )
		{

		/*  prompt  */
		if( Interactive() )
			fprintf( stderr, "%s", l_getenv("PROMPT") );

		/*  input  */
		if( getbuf( buf, sizeof buf, stdin ) )
			{
			/*  execute  */
			(void)sys( buf, sizeof buf );
			}

		}

	}

/*
 *  Do initial input processing.  Turn the high bit on to
 * prevent escaped characters and special characters within single
 * quotes from being interpreted
 */

#define	ChNorm (0)
#define	ChDquote (1)
#define	ChSquote (2)

#define	Escape(x) (x|0x80)

getbuf( buf, bufsize, fd )
char * buf;
int bufsize;
FILE * fd;
	{
	char * ptr = buf;
	int ch_state;
	Reg int ch;

	ch_state = ChNorm;
	while( (ch = getc(fd)) != EOF )
		{
		if( (ch == '\\') && escflg )
			ch = Escape(getc(fd));
		else
		if( ch == '\'' )
			{
			if( ch_state == ChSquote )
				ch_state = ChNorm;
			else
			if( ch_state == ChNorm )
				ch_state = ChSquote;
			else
			/*  state == dquote */
				ch = Escape(ch);
			}
		else
		if( ch == '"' )
			{
			if( ch_state == ChDquote )
				ch_state = ChNorm;
			else
			if( ch_state == ChNorm )
				ch_state = ChDquote;
			else
			/*  state == squote */
				ch = Escape(ch);
			}

		/* ?!  include other wildcards here, as added  */
		if( ((ch_state == ChSquote) &&  ((ch == '$') || (ch == '*')))
		||  ((ch_state == ChDquote) &&  (ch == '*')) )
			ch = Escape(ch);

		*ptr++ = ch;
		if( ptr-buf >= bufsize-1 ) 
			break;
		/*
		 *  !! This isn't exactly how Unix does quote processing ...
		 *   my current opinion is that it's not worth the trouble
		 */
		if( ch == '\n' )
			{
			if( ch_state == ChNorm )
				break;
			else
			if( Interactive() )
				fprintf( stderr, "%s ", l_getenv( "PS1" ) );
			}

		}

	*ptr = '\0';

	/*  eof */
	if( ptr == buf )
		return( False );

	/*  screw up  */
	if( ch_state != ChNorm )
		{
		fprintf( stderr, "error: mismatched quotes\n" );
		return( False );
		}

	return( True );

	}


/*
 *	Keep a stack of fds.  Push our current I/O environment before
 *  executing commands so that we have someplace to go back to after
 *  I/O redirections
 */
#define FMax (20)
Local int fdstk[FMax];
Local int ftop = 0;

push_fd(fd)
int fd;
	{
	if( ftop >= FMax )
		return( -1 );
	else
		fdstk[ftop++] = fd;
	}

pop_fd()
	{
	if( ftop == 0 )
		return( -1 );
	else
		return( fdstk[--ftop] );
	}


save_fds()
	{
	push_fd( dup(0) );
	push_fd( dup(1) );
	push_fd( dup(2) );
	}

restore_fds()
	{
	int fd;

	fflush(stderr);
	fflush(stdout);

	dup2( (fd = pop_fd()), 2 );
	close(fd);
	dup2( (fd = pop_fd()), 1 );
	close(fd);
	dup2( (fd = pop_fd()), 0 );
	close(fd);

	setbuf( stdin, NULL );
	clearerr(stdin);
	clearerr(stdout);
	clearerr(stderr);

	}


/*
 *   Use as source input the indicated file
 */
source( file )
char * file;
	{
	int fd;


	if( (fd = open( file, 0 )) >= 0 )
		{

		/*   save current input  */
		if( push_fd( dup(0) ) == -1 )
			{
			fprintf( stderr, "source files nested too deeply\n" );
			return;
			}
		dup2( fd, 0 );
		close(fd);

		/*  loop on new input  */
		sh();

		/*  restore old input  */
		dup2( (fd = pop_fd()), 0 );
		clearerr(stdin);
		close(fd);

		}
	else
		perror( file );

	}


/*
 *	execute the given command
 */
sys( pgm, pgmlen )
char * pgm;
int pgmlen;
	{

	int rc;
	char * * argv;

	/*  set up I/O environment  */
	save_fds();

	/*  slice into an argv  */
	if( (argv = dissect( pgm, pgmlen )) )
		{
		/*  doit  */
		rc = cmd( argv, environ );

		/*  !! Assertion:  all args are malloc'd */
		while( *argv )
			free( *argv++ );
		}

	/*  restore  */
	restore_fds();

	return( rc );

	}
SHAR_EOF
cat << \SHAR_EOF > cmds.c
/*  yow  */
#include <dos.h>
#include <stdio.h>
#include <direct.h>
#include <string.h>
#include <process.h>
#include <ctype.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <setjmp.h>

#include "gen.h"
#include "sh.h"


/*
 *	sh:  a Unix(tm) like command interpreter for MS-DOS.
 *
 *  by  Douglas Orr
 *      Textset Inc.
 *      Ann Arbor, Michigan
 *
 *  Copyright (c) 1985 Textset.   All rights reserved.
 *
 *  This program may be freely distributed, but not sold for profit.
 *
 */

/*   declare built-in functions  */
Public b_ls(), b_echo(), b_pushd(), b_popd();
Public b_pwd(), b_cd(), b_mkdir(), b_rmdir(), b_rm();
Public b_history(), b_exit(), b_dirs(), b_rehash();
Public b_set(), b_fgrep(), b_source();

Public int fcmp();

/*  disk transfer area  */
typedef	union dta
	{
	struct {
		char dt_dos[21];
#define DosSub (0x10)
#define	DosHid (0x02)
		byte dt_attr;
		short dt_time;
		short dt_date;
		unsigned short dt_size_low;
		unsigned short dt_size_high;
		char  dt_name[13];
		} d;
	short buf[128];
	} Dta;
Local Dta dta;

/*
 *	give us an easily addressable disk transfer area 
 */
set_dta()
	{

	union REGS regs;
	union SREGS segregs;
	Dta far * dta_addr = &dta;

	regs.h.ah = 0x1a;	/* set dta */
	regs.x.dx = FP_OFF(dta_addr);
	segregs.ds = FP_SEG(dta_addr);
	intdosx( &regs, &regs, &segregs );

	}


/*
 *	set our disk transfer area and match the first file in the given
 *  path.  
 *  !! Warning:  uSoft routines that do disk accesses reset the
 *  disk transfer area.  Don't call in between directory reads
 *  examples: open, stat, etc.
 *
 *  ?! add flags to allow optionally getting hidden/volume file names
 */

char *
open_dir( ptr )
char	* ptr;
	{

	char far * fptr;
	union REGS regs;
	union SREGS segregs;

	set_dta();

	fptr = ptr;
	regs.h.ah = 0x4e;	/* find first entry */
	regs.x.dx = FP_OFF(fptr);
	segregs.ds = FP_SEG(fptr);
	regs.x.cx = DosSub;	/* look for normal files & subdirectories*/

	intdosx( &regs, &regs, &segregs );

	if( regs.x.cflag )
		return( NULL );
	else
		/*  name of the current file in the dta  */
		return( dta.d.dt_name );

	}

char *
nxt_entry( ptr )
char	* ptr;
	{

	union REGS regs;

	regs.h.ah = 0x4f;	/* find next entry */
	regs.x.cx = DosSub;	/* look for normal files & subdirs */

	intdos( &regs, &regs );

	if( regs.x.cflag )
		return( NULL );
	else
		return( dta.d.dt_name );

	}


/*		extract fields from dta		*/
dta_mode()
	{

	char * ptr;
	int	mode = 0;

	if( (ptr = strrchr( dta.d.dt_name, '.' )) )
		{
		if( (strcmpi( ptr, ".bat" ) == 0)
		||  (strcmpi( ptr, ".exe" ) == 0) 
		||  (strcmpi( ptr, ".com" ) == 0) )
			mode |= S_IEXEC;
		}

	if( dta.d.dt_attr & DosSub )
		mode |= S_IFDIR;

	return( mode );

	}

/*
 *	return most of the time info provided in struct tm
 */
struct tm *
dta_time()
	{

	Local struct tm dta_time;

	dta_time.tm_hour = (dta.d.dt_time >> 11) & 0x1f;
	dta_time.tm_min = (dta.d.dt_time >> 5) & 0x3f;
	dta_time.tm_sec = (dta.d.dt_time & 0x1f) * 2;
	dta_time.tm_year = ((dta.d.dt_date >> 9) & 0x7f) + 80;
	dta_time.tm_mon = ((dta.d.dt_date >> 5) & 0xf);
	dta_time.tm_mday = (dta.d.dt_date & 0x1f);

	if( dta_time.tm_mon > 12 || dta_time.tm_mon < 1 )
		dta_time.tm_mon = 0;

	return( &dta_time );

	}

long
dta_size()
	{
	return( (((long)dta.d.dt_size_high) << 16) + dta.d.dt_size_low );
	}


/*	"disk" operations	*/

/* 
 *   change the current disk 
 */
chdsk( disk )
int	disk;
	{

	union REGS regs;

	if( isupper(disk) )
		disk -= 'A';
	else
		disk -= 'a';

	if( disk < 0 || disk > 25 )
		{
		fprintf( stderr, "invalid drive\n" );
		return( -1 );
		}

	regs.h.ah = 0x0e;	/* change disk */
	regs.h.dl = disk;	/* to this value */

	intdos( &regs, &regs );
	return( 0 );

	}

bool
isdev( disk )
char * disk;
	{
	return( (strlen(disk) == 2) && (disk[1] == ':') );
	}

getcdsk()
	{

	union REGS regs;

	regs.h.ah = 0x19;	/* get current disk */
	intdos( &regs, &regs );

	return( regs.h.al + 'a' );

	}


#define	MaxCmdLen (15)
#define Cm_Builtin (0x01)
#define Cm_Batch (0x02)

typedef	struct cmds
	{
	char cm_name[MaxCmdLen];
	char * cm_path;
	short cm_flags;
	int (* cm_rtne)();
	} Cmds;

#define	MaxCmds (256)
Local Cmds cmds[MaxCmds];


/*   built-in commands  */
Local struct bi {
	char * bi_name;
	int (* bi_rtne)();
	} 
builtins[] =
	{
	"ls", b_ls,
	"echo", b_echo,
	"cd", b_cd,
	"pwd", b_pwd,
	"pushd", b_pushd,
	"pd", b_pushd,
	"popd", b_popd,
	"rm", b_rm,
	"mkdir", b_mkdir,
	"rmdir", b_rmdir,
	"history", b_history,
	"exit", b_exit,
	"dirs", b_dirs,
	"rehash", b_rehash,
	"set", b_set,
	"fgrep", b_fgrep,		/* msdos sucks */
	"source", b_source,		/* just like on MTS */
	NULL, NULL,
	};
Local int cmdcnt = 0;



/*	Command Parsing Stuff	*/

add_cmd( cmd, path, flags, rtne )
char * cmd;
char * path;
short flags;
int (* rtne)();
	{

	if( cmdcnt >= MaxCmds )
		{
		fprintf( stderr, "error: hash table overflow\n" );
		return;
		}

	/*	?! make this into a real hash table, eventually  */
	strcpy( cmds[cmdcnt].cm_name, cmd );
	if( path )
		cmds[cmdcnt].cm_path = strdup(path);
	else
		cmds[cmdcnt].cm_path = NULL;
	cmds[cmdcnt].cm_flags = flags;
	cmds[cmdcnt].cm_rtne = rtne;
	cmdcnt++;

	}

/*
 * ... ok ... so we're not really hashing anything
 */
rehash( path )
char * path;
	{

	char mypath[128];
	char buf[128];
	char cbuf[128];
	Reg char * pptr;
	char * ptr;
	char * extptr;
	char * cur_path;
	int flags;
	int i;


	/*	?! make this into a real hash table, eventually  */
	for( i=0; i<cmdcnt; i++ )
		{
		if( cmds[i].cm_path )
			free( cmds[i].cm_path );
		}
	cmdcnt = 0;


	/*   add "built in" commands  */
	for( i=0; builtins[i].bi_name; i++ )
		add_cmd( builtins[i].bi_name, NULL, Cm_Builtin,
			builtins[i].bi_rtne );

	/*  add the stuff in the path  */
	for( pptr=path; *pptr; )
		{

		if( *pptr == ';' )
			++pptr;

		cur_path = pptr++;
		while( (*pptr != ';') && (*pptr != '\0') )
			++pptr;

		strncpy( mypath, cur_path, pptr-cur_path );
		mypath[pptr-cur_path] = '\0';

		/*	open the directory	*/
		strcat( mypath, "\\*.*" );
		ptr = open_dir( mypath );
		mypath[ strlen(mypath)-4 ] = '\0';

		/*  find everything that looks executeable  */
		for( ; ptr; ptr = nxt_entry() )
			{

			strlwr( ptr );

			if( !(extptr = strrchr(ptr, '.'))
			||  (strcmp(extptr, ".exe") != 0  
			  && strcmp(extptr, ".bat") != 0
			  && strcmp(extptr, ".com") != 0) )
				continue;

			flags = 0;

			strncpy( cbuf, ptr, extptr-ptr );
			cbuf[extptr-ptr] = '\0';
			sprintf( buf, "%s\\%s", mypath, ptr );

			if( strcmp( extptr, ".bat" ) == 0 )
				flags |= Cm_Batch;

			add_cmd( cbuf, buf, flags, NULL );

			}

		}

	}


#define MaxArgs (128)
Local char * argv[MaxArgs];

/*
 *	duplicate arg string, turning off all high bits
 */
char *
astrdup(str)
char * str;
	{
	Reg char * ptr;
	char * val;

	val = ptr = strdup(str);
	while( *ptr )
		*ptr++ &= 0x7f;

	return( val );

	}

/*
 *	dissect breaks the command into arguments
 *
 * !! ASSUME getbuf "escape"s all quotes within quotes, so quote
 *    processing is trivial at this point
 */

char * * 
dissect( buf, bufsize )
char * buf;
int bufsize;
	{

	int	done = False;
	int argc = 0;
	int i;
	char * bufend;
	char * wordend;

	/* ?! it might be better to store history as argv lists, and
	 *        do substitution after initial arg breakup
	 */

	i = strlen(buf);
	if( i && buf[i-1] == '\n' )
		buf[i-1] = '\0';

	if( !sub_hist( buf, bufsize ) )
		return( NULL );

	/*  plant this stuff, for batch and mystery commands  */
	argv[argc++] = "\\command";
	argv[argc++] = "/c";

	bufend = buf+bufsize;

	while( !done )
		{

		while( isspace(*buf) )
			buf++;

		if( *buf ) {

		if( argc == MaxArgs )
			{
			fprintf(stderr, "error: too many arguments\n");
			return( NULL );
			}


		if( *buf == '\'' || *buf == '"' )
			{
			/*  word starts on next character */
			i = *buf++;
			wordend = buf;
			while( (*wordend != i) && *wordend )
				wordend++;
			}
		else
			{
			/*  normal space delimited word */
			wordend = buf;
			while( (!isspace(*wordend)) && *wordend )
				wordend++;
			}


		if( !*wordend )
			done = True;

		/*  !! assuming at least one space between args */
		*wordend = '\0';

		/*  can we do a wildcard substitution on this? */
		if( iswild( buf ) )
			{
			if( (i = do_wildcard( buf, argv, argc )) == 0)
				{
				fprintf( stderr, "couldn't match %s\n",
					buf );
				return( NULL );
				}
			else
				argc += i;
			}
		else
			argv[argc++] = astrdup(buf);

		buf = wordend+1;

		}
		else
			done = True;

		}

	argv[argc] = NULL;

	/*  make with the I/O redirections  */
	if( (argc = do_redirect( argc, argv )) < 0 )
		{
		fprintf( stderr, "redirection error\n" );
		return( NULL );
		}

	if( argc == 2 )
		return( NULL );

	return( argv+2 );

	}


/* ?!  Add []{} eventually  */
iswild( buf )
char * buf;
	{

	while( *buf )
		{
		if( *buf++ == '*' )
			return( True );
		}

	return( False );
	}


/* ?!  Add []{} eventually  */
do_wildcard( pat, argv, argc )
char *  pat;
char *  * argv;
int argc;
	{
	char * * av;
	char * ptr;
	char * cwd;
	char tmp[128];
	char * endpat;
	int tmpch;
	int wargc;
	int dev = -1;

	av = argv + argc;

	cwd = NULL;

	/*
	 * ?! currently only handle one level of wildcard  - FIXIT
	 *    UGLY ALERT
	 */
	if( (ptr = strrchr( pat, '/' )) 
	 || (ptr = strrchr( pat, '\\' )) )
		{

		/*
		 * save the directory spec, with which to rebuild file 
		 * names later 
		 */
		strncpy( tmp, pat, ptr-pat+1 );
		endpat = tmp + (ptr-pat) + 1;
		tmpch = *ptr;
		*ptr = '\0';

		if( pat[1] == ':' )
			{
			dev = getcdsk();
			if( chdsk( pat[0] ) == -1 )
				goto bad_end;
			pat += 2;
			}

		/*  go to the directory you wish to examine  */
		if( (cwd = getcwd( NULL, 128 )) == NULL )
			goto bad_end;

		if( ptr != pat )
			{
			if( chdir( pat ) == -1 )
				{
				fprintf( "%s: no such directory\n", pat );
				goto bad_end;
				}
			}
		else
			chdir( "/" );

		*ptr = tmpch;	/* back the way they were */
		pat = ptr+1;

		}
	else
		endpat = tmp;


	/*  save the pattern, and delete it from the buffer  */

	for( ptr=open_dir( "*.*" ); ptr; ptr = nxt_entry() )
		{
		strlwr(ptr);
		if( ismatch( pat, ptr ) )
			{
			if( av-argv >= MaxArgs )
				{
				fprintf( stderr, "%d: too many arguments\n", 
					av-argv );
				goto bad_end;
				}
			if( ptr[0] == '.' && pat[0] != '.' )
				continue;
			strcpy( endpat, ptr );
			*av++ = strdup(tmp);
			}
		}
	*av = NULL;

	if( cwd )
		{
		chdir( cwd );
		free( cwd );
		}

	/*  sort the wildcarded arguments arguments  */
	/*  !!  this can overflow your stack if you are unlucky  */
	wargc = av - (argv+argc);
	if( wargc > 1 )
		qsort( (char *)(argv+argc), wargc, sizeof(char *), fcmp );

	if( dev != -1 )
		chdsk(dev);
	return( wargc );

	/*  he was a bad boy, and he came to a bad end */
bad_end:
	if( dev != -1 )
		chdsk(dev);
	return( 0 );

	}

/*	perform I/O redirections  */
do_redirect( argc, argv )
int argc;
char * * argv;
	{

	int i, j;
	int skip;
	char * file;
	char * mode;
	FILE * fd;
	int direct;
	int mod;

	/*
	 *  moderate UGLY ALERT
	 */
	for( i=2; i<argc; )
		{
		if( ((direct = argv[i][0]) == '>') || (direct == '<') )
			{
			skip=1;
			file = argv[i]+1;
			mod = '\0';

			if( (*file == '>') || (*file == '&') )
				mod = *file++;

			/*  is file name in the second argument? */
			if( *file == '\0' )
				{
				if( i == argc-1 )
					return( -1 );
				skip=2;
				file = argv[i+1];
				}

			if( direct == '<' )
				{
				mode = "r";
				fd = stdin;
				}
			else
				{
				mode = "w";
				fd = stdout;
				if( mod == '>' )
					mode = "a";
				}

			if( freopen( file, mode, fd ) == NULL )
				return( -1 );

			/*  >& gets stderr & stdout  */
			if( mod == '&' )
				dup2( 1, 2 );

			/*  UGLY part  */
			for( j=i; j+skip <= argc ; j++ )
				argv[j] = argv[j+skip];
			argc -= skip;

			}
		else
			i++;
		}

	return( argc );

	}


/*
 *	perform history substitutions
 */

/*  ?!  add  :modifiers, and ^^^ substitutions  */
#define HistMax (50)
short	hindex[HistMax];
char	* history[HistMax] = { NULL, };
short	hind = 0;
short	hmax = HistMax;
short	hcount = 0;

sub_hist( buf, bufsize )
char * buf;
int bufsize;
	{

	char * hptr;
	char * hend;
	char * sub = NULL;
	int ind;
	int hval;
	char tmpchr;
	int i;
	char * to, * from;
	int sublen;
	int cmdlen;

	for( hptr=buf; (hptr = strchr( hptr, '!' )); )
		{

		sub = NULL;
		ind = hind-1;
		if( ind < 0 )
			ind = hmax-1;

		if( *(hptr+1) == '!' )
			hend = hptr+2;
		else
			{
			for( hend=hptr+1; 
			     *hend && !(isspace(*hend) || ispunct(*hend)); 
			     hend++)
				/* find end of history spec */;
			}

		/*   case !!  */
		if( *(hptr+1) == '!' )
			{
			/* !! -> insert previous command  */
			if( (sub = history[ind]) == NULL )
				goto noevent;
			}
		else
		/*   case !num  */
		if( isdigit(*(hptr+1)) )
			{
			/* !## -> insert command ## */
			tmpchr = *hend;
			*hend = '\0';
			hval = atoi( hptr+1 );
			*hend = tmpchr;

			for( i=0; i<hmax; i++ )
				{
				if( ind < 0 )
					ind = hmax-1;

				if( history[ind] == NULL ) break;

				if( hindex[ind] == hval )
					{
					sub = history[ind];
					break;
					}
				ind--;
				}
			}
		else
		/* !str -> insert history string starting with "str" */
			{
			for( i=0; i<hmax; i++ )
				{
				if( ind < 0 )
					ind = hmax-1;

				if( history[ind] == NULL ) break;

				if( strncmp( hptr+1, history[ind], (hend-hptr)-1 ) == 0 )
					{
					sub = history[ind];
					break;
					}
				ind--;
				}
			}

		if( sub == NULL )
			{
			noevent:
				fprintf(stderr,"%.*s: event not found\n", 
					hend-hptr-1, hptr+1);
				return( False );
			}
		else
			{

			/*  perform substitution  */
			sublen = strlen(sub);
			cmdlen = strlen(buf);
			if( (cmdlen - (hend-hptr) + sublen) > bufsize )
				{
				fprintf( stderr, "event too large\n" );
				return( False );
				}

			/*  make a null terminated string out of this  */
			tmpchr = *hend;
			*hend = '\0';

			if( cmdlen - (hend-hptr) + sublen > bufsize )
				{
				fprintf( stderr, "substitution too large\n" );
				return( False );
				}

			/*	do the substitution  */
			insert(hptr,hend-hptr,(buf+cmdlen)-hptr,sub,sublen);

			/*  replace the original character  */
			*(hptr+sublen) = tmpchr;

			}

		}


	/*  retire old buffer if we have wrapped around  */
	if( history[hind] )
		free( history[hind] );

	/*  record this buffer for future histories */
	hindex[hind] = ++hcount;
	history[hind++] = astrdup( buf );
	if( hind >= hmax )
		hind = 0;

	/*  echo substituted string */
	if( sub )
		fprintf( stderr, "%s\n", buf );

	return( True );

	}

/*   UGLY ALERT  */
insert( old, oldlen, totlen, new, newlen )
char * old;	/*  history string  */
int oldlen;	/*  size of history string */
int totlen;	/*  length from start of history to end of command */
char * new;	/*  replacement string  */
int newlen;	/*  size of replacement string */
	{

	Reg char * from, * to;
	int i;

	/*  do the substitution  */

	if( newlen < oldlen )
		{
		/*  shift left  */
		to = old+newlen;
		from = old+oldlen;
		while( (*to++ = *from++) )
			/* shift, shift, shift, shift */;
		}
	else
	if( newlen > oldlen )
		{
		/*  shift right  */
		from = old+totlen;  /* start with trailing null */
		to = from + (newlen - oldlen);
		for( i = (newlen-oldlen); i-- >= 0; )
			*to-- = *from--;
		}

	/*  stick in the new command  */
	from = new;
	to = old;
	for( i=0; i<newlen; i++ )
		*to++ = *from++;

	}


jmp_buf sbuf;
sig_handle()
	{
	/*  reset the interrupt signal */
	signal( SIGINT, SIG_IGN );
	/*  bounce back  */
	longjmp( sbuf, True );
	}

/*   locate the appropriate executable command and doit */
cmd( argv, envp )
char	* * argv;
char	* * envp;
	{

	Reg int len;
	Reg int i;
	char * cmdptr;
	char * ptr;
	int rc;
	int batch;
	struct stat statb;

	cmdptr = NULL;
	batch = False;

	/*   is this a specific path reference  */
	if( strchr( argv[0], '/' )  == NULL 
	&&  strchr( argv[0], '\\' ) == NULL )
		{

		len = strlen( argv[0] );

		/*  is this really a drive selection? */
		if( (len == 2) && (argv[0][1] == ':') )
			return( chdsk( argv[0][0] ) );

		/*
		 * check . first  ... UGLY ALERT  (a real hashing scheme
		 * could take care of this a little more nicely)
		 */
		if( stat( argv[0], &statb ) == -1
		||  (statb.st_mode & S_IEXEC) == 0 )
		{

		/*  not in .  is it in the "hash" table?  */
		for( i=0; i<cmdcnt; i++ )
			{
			if( len == strlen( cmds[i].cm_name )
			&&  (strncmp( cmds[i].cm_name, argv[0], len ) == 0 ))
				break;
			}

		if( i < cmdcnt )
			{
			cmdptr = cmds[i].cm_path;

			/*   a built in command? */
			if( cmds[i].cm_flags & Cm_Builtin )
				{

				if( setjmp(sbuf) )
					{
					fprintf( stderr, "SH: interrupt\n" );
					return( -1 );
					}
				signal( SIGINT, sig_handle );

				rc = (*cmds[i].cm_rtne)(argv,envp);

				signal( SIGINT, SIG_IGN );
				return( rc );
				}
			else
				batch = (cmds[i].cm_flags & Cm_Batch);

			}
		}

		}
	else
		{

		/*  some sort of a specific path invocation  */
		cmdptr = argv[0];

		/*  reality check */
		if( (ptr = strrchr( cmdptr, '.' )) )
			{
			if( strcmp( ptr, ".bat" ) == 0 )
				batch = True;
			}

		}

	if( cmdptr && !batch )
		{
		/*  invoke the specific command  */
		if( (rc = spawnve( P_WAIT, cmdptr, argv, envp )) < 0)
			perror( argv[0] );
		return( rc );
		}
	else
		{
		/* 
		 * let DOS take a crack at it - this is not in the true Unix
		 * tradition, but pragmatically, it works out well for
		 * stuff like "mode," etc.
		 * 
		 * !! IMPORTANT - argv always has arguments "\command" "/c" 
		 *    as argv[-2,-1]
		 */
		if( (rc = spawnve( P_WAIT, "\command", argv-2, envp )) < 0)
			perror( argv[0] );
		return( rc );
		}

	}
SHAR_EOF
cat << \SHAR_EOF > builtin.c
#include <dos.h>
#include <stdio.h>
#include <string.h>
#include <process.h>
#include <time.h>

#include <ctype.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <malloc.h>
#include <direct.h>

#include "gen.h"
#include "sh.h"


/*
 *	sh:  a Unix(tm) like command interpreter for MS-DOS.
 *
 *  by  Douglas Orr
 *      Textset Inc.
 *      Ann Arbor, Michigan
 *
 *  Copyright (c) 1985 Textset.   All rights reserved.
 *
 *  This program may be freely distributed, but not sold for profit.
 *
 */


Public char * history[];
Public short  hindex[];
Public short hind, hmax;

Local char * mons[] =
	{
	"Inv", 
	"Jan", "Feb", "Mar",
	"Apr", "May", "Jun",
	"Jul", "Aug", "Sep",
	"Oct", "Nov", "Dec",
	};

char *
smalloc( size )
unsigned size;
	{
	char	* addr;

	if( !(addr = malloc(size)) )
		{
		/*  death with honor */
		fprintf( stderr, "out of space\n" );
		exit( 1 );
		}
	return( addr );

	}

char *
strdup( str )
Reg char * str;
	{
	return( strcpy( smalloc( strlen(str)+1 ), str ) );
	}




fcmp( file1, file2 )
char * * file1;
char * * file2;
	{
	return( strcmp( *file1, *file2 ) );
	}


/*		Built-in  commands		*/

/*	toy ls  */

struct  fs {
	char * fs_name;
	struct tm fs_time;
	unsigned short fs_mode;
	off_t fs_size;
	};


#define	Set(flag) (flags |= (1 << flag))
#define	Reset(flag) (flags &= ~(1 << flag))
#define Test(flag) (flags & (1 << flag))
#define	Recurse (0)
#define	Lflag (1)
#define	Sflag (2)
#define	Rflag (3)
#define	Aflag (4)
#define	Cflag (5)


Local
fscmp( file1, file2 )
struct fs * file1;
struct fs * file2;
	{
	return( strcmp( file1->fs_name, file2->fs_name ) );
	}

Local long ls_size;
Local int ls_cnt;

b_ls( argv, envp )
char * * argv;
char * * envp;
	{
	char * ptr;
	short flags = 0;
	int rc;

#ifdef	correct
	/*
	 * !! as near as I can tell, ls is the only built-in that allocates
	 *    storage.  If you interrupt, that storage doesn't get freed,
	 *    currently.  One way around this is the following, which isn't
	 *    very satisfactory.  Another is to just ignore it and hope you
	 *    don't run out of storage.  Yet another would be to set another
	 *    interrupt which frees the storage before leaving which is more
	 *    of a hassle than I have time for today.  Caveat user.
	 */
	signal( SIGINT, SIG_IGN );
#endif	correct


	ls_size = 0;
	ls_cnt = 0;

	Set(Recurse);		/* list subdirectories */

	if( !Interactive() )
		Set(Cflag);	/* print out one column */

	while( (*++argv)[0] == '-' )
		{
		for( ptr=(*argv)+1; *ptr; ptr++ )
		switch( *ptr )
			{
			case 'l':
				Set(Lflag);
				Reset(Cflag);
				break;
			case 's':
				Set(Sflag);
				break;
			case 'R':
				Set(Rflag);
				break;
			case 'a':
				Set(Aflag);
				break;
			default:
				fprintf( stderr, "ls: illegal option %s\n", 
					*argv );
				return( -1 );

			}
		}

	rc = ls( argv, flags );

	if( Test(Sflag) )
		fprintf( stdout, "%ld total.  %d file%s.\n", ls_size, ls_cnt,
		ls_cnt == 1 ? "" : "s" );

	return( rc );
	}



/*
 *   UGLY ALERT  ... since I couldn't find any good way to do what I
 *   would do on Unix, were I to be building a program to recursively
 *   search directories - that being open the damn things like files,
 *   I debased myself (a frequent occurance in dosville) and issue "cd"s
 *   to move around.  My apologies.  I do know better, in the global
 *   sense.
 */

Local
lstatus( file, flags, maxlen, nl )
struct	fs * file;
short flags;
int maxlen;
bool nl;
	{

	/*   UGLY ALERT   recurse on directories if appropriate  */
	/*  (Jaayynnnee ... how do you stop this crazy thing?) */
	if( (file->fs_mode & S_IFDIR) && Test(Recurse) )
		{
		int dev = -1;
		char * auxlist[1];
		char * cwd = NULL;
		char * newd;
		char * dir = file->fs_name;

		/*   cheat   */
		if( !Test(Rflag) )
			Reset(Recurse);	/* only recurse one level, by default  */

		auxlist[0] = NULL;

		if( dir[1] == ':' )
			{
			dev = getcdsk();
			if( chdsk( dir[0] ) == -1)
				{
				fprintf( stderr, "couldn't access %c:\n",  dir[0] );
				return( -1 );
				}
			dir += 2;
			if( dir[0] == '\0' )
				dir = "/";
			}

		cwd = getcwd(NULL,128);
		if( (cwd == NULL) || chdir(dir) == -1)
			{
			fprintf( stderr, "! couldn't cd to %s\n",  file->fs_name );
			return( -1 );
			}

		if( (newd = getcwd(NULL,128)) != NULL )
			{
			fprintf( stdout, "%s\n%s:\n", (nl) ? "" : "\n", strlwr(newd) );
			free(newd);
			}

		/*  do a list of the place to which we just attached  */
		ls( auxlist, flags );
		putc( '\n', stdout );

		if( dev != -1 )
			chdsk( dev );
		if( cwd )
			{
			chdir( cwd ); free( cwd );
			}

		nl = True;

		}
	else
		{
		if( Test(Lflag) )
			{
			/* ?! doesn't always line up right  */
			fprintf( stdout, "%-8ld  %2d:%02d   %3s-%02d-%02d    %-13s\n", 
				file->fs_size,  file->fs_time.tm_hour,
			 	file->fs_time.tm_min, 
				mons[file->fs_time.tm_mon], 
				file->fs_time.tm_mday,
				file->fs_time.tm_year, file->fs_name );
			nl = True;
			}
		else
			{
			fprintf( stdout, "%s", file->fs_name );

			if( Test(Aflag) )
				{
				if( file->fs_mode & S_IFDIR )
					fprintf( stdout, "\\" );
				else
				if( file->fs_mode & S_IEXEC )
					fprintf( stdout, "*" );
				else
					printf( " " );
				}

			fprintf( stdout, "%*s", maxlen-strlen(file->fs_name), 
				"" );
			nl = False;
			}
		}

	return( nl );

	}

Local
ls( list, flags )
char * * list;
short flags;
	{

	int argc;
	struct  fs * files;
	char * * filelist;

	int cnt;
	int maxlen;
	Reg int i, j;
	Reg int r;
	int cols;
	char * ptr;

	Local struct stat statb;
	int nl;

	/*  initialize  */

	for( argc=0, filelist=list ; *filelist; filelist++ )
		argc++;

	maxlen = 0;
	files = NULL;
	cnt = 0;

	/*   implicitly ls of current directory  */
	if( argc == 0 )
		{
		int ac;
		files = (struct fs *)smalloc( (ac=20) * sizeof(struct fs) );

		if( !Test(Rflag) )
			Reset(Recurse);

		for( ptr=open_dir("*.*"); ptr; ptr=nxt_entry() )
			{

			if( cnt == ac )
				{
				ac += 20;
				files = (struct fs *)realloc( files, ac*sizeof(struct fs) );
				if( files == NULL )
					{
					/*  cut your losses */
					fprintf( stderr, "ls: out of space\n" );
					return(-1);
					}
				}

			/* degenerate */
			if( strcmp( ptr, "." ) == 0 || strcmp( ptr, ".." ) == 0 )
				continue;

			files[cnt].fs_mode = dta_mode();
			files[cnt].fs_time = *dta_time();
			files[cnt].fs_size = dta_size();
			files[cnt++].fs_name = strlwr(strdup(ptr));

			if( strlen(ptr) > maxlen )
					maxlen = strlen( ptr );

			}

		}
	else
		{

		files = (struct fs *)smalloc( (argc+1) * sizeof(struct fs) );
		filelist = list;

		while( *filelist )
			{

			if( (stat( *filelist, &statb ) == -1) )
				{
				if( isdev(*filelist)
				|| (strcmp(*filelist, "/")==0 
				||  strcmp(*filelist,"\\")==0))
					{
					statb.st_mode = S_IFDIR;
					statb.st_size = 0;
					statb.st_mtime = 0;
					}
				else
					{
					fprintf( stderr, "%s: no such file\n", 
						*filelist );
					return( -1 );
					}
				}

			files[cnt].fs_name = strlwr(strdup(*filelist));
			files[cnt].fs_mode = statb.st_mode;
			files[cnt].fs_time = *localtime( &statb.st_mtime );
			files[cnt].fs_time.tm_mon += 1;    /* relative to 1 */
			files[cnt++].fs_size = statb.st_size;

			if( strlen(*filelist) > maxlen )
					maxlen = strlen( *filelist );

			filelist++;	  /* very important  */

			}
		}

	/*  !!  this can overflow stack if the list is too large  */
	if( cnt )
		qsort( (char *)files, cnt, sizeof(struct fs), fscmp );


	/*  formatting considerations  */
	maxlen++;
	if( Test(Aflag) )
		cols = 79 / (maxlen+1);
	else
		cols = 79 / maxlen;

	if( cols < 0 )
		cols = 1;

	if( Test(Lflag) )
		{
		for( i=0; i<cnt; i++ )
			(void)lstatus( &files[i], flags, maxlen, True );
		}
	else
	if( Test(Cflag) )
		{
		for( i=0; i<cnt; i++ )
			fprintf( stdout, "%s\n", files[i].fs_name );
		}
	else
		{
		int ind;

		/* print out by rows */
		nl = True;
		for( r=0; r < ((cnt+cols-1)/cols); r++ )
			{

			for( ind=r, i=0; i<cols; i++ )
				{

				/*  have we seen everything? */
				if( ((r*cols) + i) >= cnt )
					break;

				nl = lstatus( &files[ind], flags, maxlen, nl );

				/* add the size of a column */
				ind += cnt/cols;  
				if( i < (cnt % cols) ) 
					ind++;

				}

			/*  nl flag indicates if a new line is desired */
			if( !nl )
				{
				putc( '\n', stdout );
				nl = True;
				}

			}
		}

	/*  free up the storage you have used, run some totals */
	ls_cnt += cnt;

	for( i=0; i<cnt; i++ )
		{
		ls_size += files[i].fs_size;
		free( files[i].fs_name );
		}

	if( files )
		free( files );

	return( 0 );

	}


b_echo( argv, envp )
char * * argv;
char * * envp;
	{

	int	cnt = 0;

	argv++;
	while( *argv )
		{
		if( cnt++ != 0 )
			fprintf( stdout, " " );
		fprintf( stdout, "%s", *argv );
		argv++;
		}

	putc( '\n', stdout );
	return( 0 );

	}

b_cd( argv, envp )
char * * argv;
char * * envp;
	{

	argv++;

	if( strlen(*argv) == 2
	&&  (*argv)[1] == ':' )
		return( chdsk( *argv ) );

	if( chdir( *argv ) == -1 )
		perror( "chdir" );
	return( 0 );

	}

b_pwd( argv, envp )
char * * argv;
char * * envp;
	{

	char * dirp;

	if( (dirp = getcwd(NULL,128)) == NULL )
		{
		perror( "getcwd" );
		return( -1 );
		}
	else
		{
		fprintf( stdout, "%s\n", strlwr(dirp) );
		free( dirp );
		return( 0 );
		}

	}


#define	MaxDirs (10)
Local char * dirs[MaxDirs];
Local int dircnt = 0;

b_dirs( argv, envp )
char * * argv;
char * * envp;
	{

	Reg int i;
	char * cwd;

	if( (cwd = getcwd( NULL, 128 )) )
		{
		fprintf( stdout, "%s ", strlwr(cwd) );
		free( cwd );
		}

	for( i=dircnt-1; i >= 0; i-- )
		fprintf( stdout, "%s ", dirs[i] );
	putc( '\n', stdout );

	return( 0 );

	}

b_pushd( argv, envp )
char * * argv;
char * * envp;
	{

	char * dirp;
	char * cwd;

	if( *++argv == NULL )
		{
		if( dircnt == 0 )
			{
			fprintf( stderr, "no directory specified\n" );
			return( -1 );
			}
		else
			dirp = dirs[--dircnt];
		}
	else
		dirp = *argv;

	if( dircnt < MaxDirs )
		{

		if( (cwd = getcwd( NULL, 128 )) == NULL )
			{
			perror( "chdir" );
			return( -1 );
			}

		if( dirp[1] == ':' )
			{
			if( chdsk(dirp[0]) == -1 )
				perror( "chdisk" );
			dirp += 2;
			if( dirp[0] == '\0' )
				dirp = "/";
			}

		if ( chdir( dirp ) == -1 )
			{
			perror( "chdir" );
			free(cwd);
			}
		else
			dirs[dircnt++] = strlwr(cwd);

		b_dirs( NULL, NULL );
		return( 0 );

		}
	else
		fprintf( stderr, "stack overflow\n" );

	return( -1 );

	}


b_popd( argv, envp )
char * * argv;
char * * envp;
	{

	char * cwd;
	char * dir;

	if( dircnt == 0 )
		{
		fprintf( stderr, "stack empty\n" );
		return( -1 );
		}
	else
		{
		dir = cwd = dirs[--dircnt];
		if( dir[1] == ':' )
			{
			if( chdsk(dir[0]) == -1 )
				perror( "chdisk" );
			dir += 2;
			}

		if( chdir( dir )  == -1)
			perror( "chdir:" );
		else
			b_dirs( NULL, NULL );

		free( cwd );
		}
	return( 0 );

	}

b_mkdir( argv, envp )
char * * argv;
char * * envp;
	{

	int error = 0;
	int rc;

	while( *++argv )
		{
		if( (rc = mkdir( *argv )) != 0 )
			perror( "mkdir" );
		error |= rc;
		}
	return( error );

	}


b_rmdir( argv, envp )
char * * argv;
char * * envp;
	{

	int error = 0;
	int rc;

	while( *++argv )
		{
		if( (rc = rmdir( *argv )) != 0 )
			perror( "rmdir" );
		error |= rc;
		}
	return( error );

	}


b_rm( argv, envp )
char * * argv;
char * * envp;
	{

	int	error = 0;
	int rc;

	while( *++argv )
		{
		if( (rc = unlink( *argv )) != 0 )
			perror( "rm" );
		error |= rc;
		}
	return( error );

	}

b_history( argv, envp )
char * * argv;
char * * envp;
	{

	int count;
	int i;
	int ind;

	if( *++argv == NULL )
		count = hmax;
	else
		{
		count = atoi( *argv );
		if( count > hmax ) count = hmax;
		}

	ind = hind-1;
	for( i=0; i<count; i++ )
		{
		if( ind < 0 )
			ind = hmax-1;

		if( history[ind] == NULL )
			break;

		fprintf( stdout, "%d: %s\n", hindex[ind], history[ind] );
		ind--;
		}

	return( 0 );

	}

/* ahhbedee ahhbedee ahhba  That's All Folks  */
b_exit(argv, envp)
char * * argv;
char * * envp;
	{
	if( *++argv )
		exit( atoi(*argv) );
	else
		exit(0);
	}

b_rehash( argv, envp )
char * * argv;
char * * envp;
	{

	rehash( l_getenv( "PATH" ) );
	return( 0 );

	}

b_set( argv, envp )
char * * argv;
char * * envp;
	{
	char * vptr;

	if( *++argv == NULL )
		l_printenv( envp );
	else
		{
		while( *argv )
			{
			if( (vptr = strchr( *argv, '=' )) == NULL )
				{
				fprintf( stderr, "set: syntax error\n" );
				return( -1 );
				}

			*vptr++ = '\0';
			if( l_setenv( *argv, vptr ) == -1 )
				{
				perror( "set" );
				return( -1 );
				}
			argv++;
			}
		return( 0 );
		}

	}


b_source( argv, envp )
char * * argv;
char * * envp;
	{
	while( *++argv )
		source( *argv );
	}


/*
 *   If you're wondering what fgrep is doing here, it's because the
 *  goddamn msdos arg list is so small.  Anything that I do frequently
 *  is a builtin command, since you can't adequately wildcard expand
 *  on large file lists.  Every day, in every way, msdos sucks.
 */

Local
fgrep( file, pattern, fd )
char	* file;
char	* pattern;
FILE *	fd;
	{

	char line[BUFSIZ];
	int rc;

	rc = 0;
	while( fgets( line, sizeof(line), fd ) != NULL )
		if( rc = match( pattern, line ) )
			fprintf( stdout, "%s: %s", 
				(file) ? file : "stdin", line );

	return( rc );

	}

Local
match( pattern, line )
char	* pattern;
char	* line;
	{

	/*  !? not a great algorithm  */
	char	* ptr;
	char	* end;
	int	plen = strlen(pattern);
	int llen = strlen(line);

	if( plen > llen )
		return( 0 );

	end = line+(llen-plen);

	for( ptr=line; ptr < end; ptr++ )
		{
		/*   ... actually, pretty mediocre  */
		if( strncmp( pattern, ptr, plen ) == 0 )
			return( 1 );
		}

	return( 0 );

	}

b_fgrep( argv, envp )
char * argv[];
char * * envp;
	{

	FILE *  fd;
	int rc;
	char * pattern;

	pattern = *++argv;
	argv++;

	rc = 0;
	if( *argv == NULL )
		{
		rc = fgrep( NULL, pattern, stdin );
		return( 0 );
		}
	else
	while( *argv )
		{
		if( (fd = fopen( *argv, "r" )) == NULL )
			fprintf( stderr, "couldn't open %s\n", *argv );
		else
			{
			rc |= fgrep( *argv, pattern, fd );
			fclose( fd );
			}
		argv++;
		}

	return( !rc );

	}
SHAR_EOF
cat << \SHAR_EOF > wildcard.c
#include "gen.h"



/*	toy pattern matching ... recognize '*' only, for now */

/*
 *	sh:  a Unix(tm) like command interpreter for MS-DOS.
 *
 *  by  Douglas Orr
 *      Textset Inc.
 *      Ann Arbor, Michigan
 *
 *  Copyright (c) 1985 Textset.   All rights reserved.
 *
 *  This program may be freely distributed, but not sold for profit.
 *
 */


ismatch( pattern, filename )
char * pattern;
char * filename;
	{

	char	* pat;
	char	* fn;

	pat = pattern;
	fn = filename;

	while( *pat ) 
		{

		if( *pat == '*' )
			{

			++pat;
			while( *fn )
				{
				if( ismatch( pat, fn ) )
					return( True );
				else
					++fn;
				}
			break;

			}
		else
		if( *pat == *fn )
			{
			pat++;
			fn++;
			}
		else
			return( False );

		}

	return( *pat == *fn );

	}


SHAR_EOF
cat << \SHAR_EOF > gen.h
#define	Reg register
#define	Local static
#define	Public extern

#define	True (1)
#define	False (!True)

/*  access types  */
#define	F_OK (0)
#define	F_ROK (4)


typedef	unsigned char byte;
typedef byte bool;


Public char * l_getenv();
Public l_setenv();

SHAR_EOF
cat << \SHAR_EOF > sh.h

/*	sh definitions	*/

#define	Interactive() (isatty(fileno(stdin)) && isatty(fileno(stdout)))


Public char * open_dir();
Public char * nxt_entry();
Public char * l_getenv();

Public struct tm * dta_time();
Public long dta_size();

SHAR_EOF
#	End of shell archive
exit 0



More information about the Comp.sources.unix mailing list