/*
 * COMM1.C
 *
 * Matthew Dillon, August 1986
 *
 * Version 2.07M by Steve Drew 10-Sep-87
 * Version 4.01A by Carlo Borreo & Cesare Dieni 17-Feb-90
 * Version 5.00L by Urban Mueller 17-Feb-91
 *
 */

#include "shell.h"

/* comm1.c */
static void display_file(char *filestr);
static char *myfgets(char *buf, int buflen, struct __stdio *file);
static int search_file(char *s);
static int quicksearch(char *name, int nocasedep, char *pattern);
static int rm_file(char *file);
static void setsystemtime(struct DateStamp *ds);
static int found( char *lstart, int lnum, int loffs, char *name, char left );
static void recurse(char *name, int (*action)(char *));
static void recurse2( char *name, void (*action)(FIB *));

extern int has_wild;

int
do_sleep( void )
{
	int i;

	if (ac == 2) for (i=atoi(av[1]); i>0 && !CHECKBREAK(); i--) Delay(50);
	return 0;
}

int
do_protect( void )
{
	static char flags[]="DEWRAPSH";
	char *s, *p;
	long setmask=0, clrmask=0xFF, mask;
	int  i, mode=0, stat;
	struct DPTR *dp;

	for (s=strupr(av[--ac]); *s; s++) {
		if (*s=='=') { mode=0; continue; }
		if (*s=='+') { mode=1; clrmask=FIBF_ARCHIVE; continue; }
		if (*s=='-') { mode=2; clrmask=FIBF_ARCHIVE; continue; }

		if (p=index(flags, *s)) {
			if( mode==0 ) setmask|= 1<<(p-flags), clrmask=0xFF;
			if( mode==1 ) setmask|= 1<<(p-flags);
			if( mode==2 ) clrmask|= 1<<(p-flags);
		} else
			ierror(av[ac],500);
	}

	for (i=1; i<ac; i++) {
		if( (dp=dopen(av[i],&stat))) {
			mask = dp->fib->fib_Protection ^ 0x0F;
			mask&=~clrmask;
			mask|= setmask;
			dclose(dp);
			if( !SetProtection( av[i], mask ^ 0x0F))
				pError(av[i]);
		} else
			pError(av[i]);
	}
	return 0;
}

int
do_filenote( void )
{
	struct DPTR *dp;
	char *note;
	int i, stat;

	if( options&1 ) {
		for( i=1; i<ac && !dobreak(); i++ )
			if( dp=dopen( av[i], &stat )) {
				printf( "%-12s %s\n", av[i],dp->fib->fib_Comment );
				dclose( dp );
			}
	} else {
		note=av[--ac];
		for (i=1; i<ac; i++) if (!SetComment(av[i], note)) pError(av[i]);
	}
	return 0;
}

int
do_cat( void )
{
	FILE *fi;
	int lctr, i;
	char buf[256];

	prepscroll(0);
	if (ac<=1) {
		if (has_wild) { printf("No files matching\n"); return 20; }
		lctr=0;
		while (fgets(buf,256,stdin) &&
		       !dobreak()) {
			if (options) printf("%4d ",++lctr);
			quickscroll();
			fputs(buf,stdout);
		}
	} else {
		for (i=1; i<ac; i++)
			if (fi = fopen (av[i], "r")) {
				lctr=0;
				while (fgets(buf,256,fi) && !dobreak()) {
					if (options&1) printf("%4d ",++lctr);
					quickscroll();
					printf("%s",buf);
				}
				fclose (fi);
			} else
				pError(av[i]);
	}
	return 0;
}


void
get_drives(char *buf)
{
	struct DirectoryEntry *de_head=NULL, *de;

	buf[0]=0;
	AddDADevs(&de_head, DLF_DEVICES | DLF_DISKONLY );
	for(de=de_head; de; de=de->de_Next) {
		if( buf[0] )
			strcat( buf, "\240" );
		strcat( buf, de->de_Name );
	}
	FreeDAList(de_head);
}

static char infobuf[100];
static char namebuf[12];

char *
drive_name( char *name )
{
	struct DirectoryEntry *de_head=NULL, *de;
	struct MsgPort *proc=DeviceProc( name );

	strcpy( namebuf, name );
	AddDADevs(&de_head, DLF_DEVICES | DLF_DISKONLY );
	for(de=de_head; de; de=de->de_Next)
		if( DeviceProc( de->de_Name) == proc ) 
			strcpy( namebuf, de->de_Name );
	FreeDAList(de_head);

	return namebuf;
}

int
do_info( void )
{
	struct DirectoryEntry *de_head=NULL, *de;
	int i;

	puts("Unit    Size  Bytes  Used Blk/Byt-Free Full Errs  Status    Name");
	Myprocess->pr_WindowPtr = (APTR)(-1);

	if( ac==1 ) {
		AddDADevs(&de_head, DLF_DEVICES | DLF_DISKONLY );
		for(de=de_head; de; de=de->de_Next)
			oneinfo( de->de_Name, 0);
		FreeDAList(de_head);
	} else {
		for( i=1; i<ac; i++ )
			oneinfo( drive_name( av[i] ), 0 );
	}

	Myprocess->pr_WindowPtr = (APTR) o_noreq;
	return 0;
}

char *
oneinfo( char *name, int mode )
{
	BPTR lock;
	struct InfoData *info;
	long size, free, freebl, blocks;
	char *p, buf[130], *state="          ";
	char *fmt="%s\240%s\240%d\240%d\240%d\240%s\240%d%%\240%d\240%s\240%s";

	infobuf[0]=0;
	if( !name ) name="";
	strcpy(infobuf,"0");
	info=(struct InfoData *)AllocMem((long)sizeof(struct InfoData),MEMF_PUBLIC);
	if (lock=Lock(name,ACCESS_READ)) {
		if (Info(lock, info)) {
			PathName(lock, buf, 128L);
			if (p=index(buf,':')) *p = '\0';
			size  = ((info->id_NumBlocks + 2)* info->id_BytesPerBlock);
			freebl= (info->id_NumBlocks-info->id_NumBlocksUsed);
			free  = freebl * info->id_BytesPerBlock;
			switch(info->id_DiskState) {
				case ID_WRITE_PROTECTED: state="Read Only "; break;
				case ID_VALIDATED:       state="Read/Write"; break;
				case ID_VALIDATING:      state="Validating"; break;
			}
			blocks=info->id_NumBlocks;
			if( mode==0 ) fmt="%-7s%5s%6d%7d%7d %5s%4d%%%4d  %s %s\n";
			sprintf(infobuf,fmt,
				name,
				itok( size ),
				info->id_BytesPerBlock,
				info->id_NumBlocksUsed,
				freebl,
				itok( free ),
				(blocks) ? (info->id_NumBlocksUsed * 100)/blocks : 0,
				info->id_NumSoftErrors,
				state,
				buf);
			if( mode==2 ) sprintf( infobuf, "%d", free );
			if( mode==3 ) sprintf( infobuf, "%d", freebl );
			if( mode==4 ) sprintf( infobuf, "%s", itok( free ));
			if( mode==5 ) sprintf( infobuf, "%s:", buf );
		} else
			pError (name);
		UnLock(lock);
	} else {
		if     ( mode==1 ) sprintf( infobuf, "%s\240No disk present", name );
		else if( mode==0 ) sprintf( infobuf, "%-7s No disk present\n",name);
		else if( mode==5 ) sprintf( infobuf, ""  );
		else               sprintf( infobuf, "0" );
	}
	if( mode==0 ) printf( "%s",infobuf);
	FreeMem(info,sizeof(struct InfoData));
	return infobuf;
}


/* things shared with display_file */

#define DIR_SHORT 0x1
#define DIR_FILES 0x2
#define DIR_DIRS  0x4
#define DIR_NOCOL 0x8
#define DIR_NAMES 0x10
#define DIR_HIDE  0x20
#define DIR_LEN   0x40
#define DIR_TIME  0x80
#define DIR_BACK  0x100
#define DIR_UNIQ  0x200
#define DIR_IDENT 0x400
#define DIR_CLASS 0x800
#define DIR_QUIET 0x1000
#define DIR_AGE   0x2000
#define DIR_VIEW  0x4000
#define DIR_NOTE  0x8000

static BPTR lastlock;
static int filecount, col, wwidth;
static long bytes, blocks;

/* the args passed to do_dir will never be expanded */

extern expand_err;
extern int w_width;


static struct DateStamp Stamp;

int
do_dir( void )
{
	int i=1, c, eac, reverse, nump=ac, retcode=0;
	char **eav, **av1, **av2, inter=interactive();
	int (*func)(), ac1, ac2;

	if( options&DIR_CLASS ) options|=DIR_IDENT;
	if( ac == i) ++nump, av[i]="";
	if( !(options & (DIR_FILES | DIR_DIRS))) options|=(DIR_FILES | DIR_DIRS);
	if( options&DIR_UNIQ) {
		if( ac-i!=2 )  { show_usage(NULL); return 20; }
		i=0, nump=3;
	}

	if( options&DIR_AGE )
		DateStamp( &Stamp );

	col = filecount = bytes = blocks = 0L;
	lastlock=NULL;

	wwidth=77;
	if( inter ) printf( "\033[?7l" ), wwidth=w_width; /* end of line wrap off */

	prepscroll(0);
	for( ; i<nump && !CHECKBREAK(); ++i ) {
		if( options&DIR_UNIQ ) {
			switch( i ) {
				case 0: av1=expand( av[ac-2], &ac1 );
				        av2=expand( av[ac-1], &ac2 );
				        eav=without( av1, ac1, av2, ac2, &eac, 1 );
				        break;
				case 1: printf("\nCommon files\n");
				        eav=and( av1, ac1, av2, ac2, &eac, 1 );
				        break;
				case 2: printf("\n");
				        eav=without( av2, ac2, av1, ac1, &eac, 1 );
				        break;
			}
			col = filecount = bytes = blocks = 0L;
			lastlock=NULL;
		} else if (!(eav = expand(av[i], &eac)) && expand_err) {
			pError(av[i]);
			retcode=5;
			continue;
		}

		reverse= ( options&DIR_BACK ) ? 1 : 0;
		func=cmp;
		if( options & DIR_TIME) func=datecmp;
		if( options & DIR_LEN ) func=sizecmp;
		if( options & DIR_CLASS)func=classcmp;
		DirQuickSort(eav, eac, func, reverse);
		for(c=0; c<eac && !CHECKBREAK(); ++c) {
			if( options & DIR_HIDE ) {
				char *b=BaseName(eav[c]);
				int  l=strlen(b)-5;
				struct file_info *info =
				    (struct file_info *)(eav[c]-sizeof(struct file_info));
				if(*b=='.'|| (l>=0 && !strcmp(b+l,".info"))||(info->flags&128))
					continue;
			}
			if (options & DIR_NAMES)
				puts(eav[c]);
			else
				display_file(eav[c]);
		}

		if (col) { quickscroll(); printf("\n"); col=0; }
		if (options&DIR_UNIQ || (filecount>1 && i==nump-1)) {
			blocks += filecount; /* account for dir blocks */
			quickscroll();
			printf(" %ld Blocks, %s Bytes used in %d files\n",
			    blocks, itoa(bytes), filecount);
		}
		if( options&DIR_UNIQ )
			free(eav);
		else 
			free_expand (eav);
		if (lastlock) UnLock(lastlock), lastlock=0;
	}
	if (lastlock) UnLock(lastlock), lastlock=0;

	if( options&DIR_UNIQ )
		free_expand( av1 ), free_expand( av2 );

	if( inter ) printf( "\033[?7h" );                /* end of line wrap off */

	return retcode;
}


static void
display_file( char *filestr )
{
	int isadir,slen;
	char sc, *base, buf[130];
	struct file_info *info;
	BPTR thislock;

	base=BaseName(filestr);
	sc = *base;
	*base = '\0';
	thislock=Lock(filestr,SHARED_LOCK);
	/* if (thislock==NULL) return; */
	if (lastlock==NULL || CompareLock(thislock,lastlock)) {
		/* struct InfoData *id=AllocMem( sizeof(struct InfoData), 0); */
		if (col) { quickscroll(); printf("\n"); col=0; }
		quickscroll();
		PathName(thislock, buf, 128L);
		/* Info( thislock, id ); */
		printf("Directory of %s\n", buf );
		/*itok((id->id_NumBlocks-id->id_NumBlocksUsed)*id->id_BytesPerBlock));*/
		/* FreeMem( id, sizeof(struct InfoData)); */
		if (lastlock) UnLock(lastlock);
		lastlock=thislock;
	} else
		UnLock(thislock);

	*base    = sc;
	if((slen = strlen(base)+1) & 1 ) ++slen;
	info     = (struct file_info *)(filestr - sizeof(struct file_info));
	isadir   = info->size<0;

	if (!(((options & DIR_FILES) && !isadir) ||
	                 ((options & DIR_DIRS) &&  isadir)))
		return;
	if (isadir && !(options & DIR_NOCOL)) printf (o_hilite);
	if (options & DIR_SHORT) {
		slen= (slen>18) ? 37 : 18;
		if (col && col+slen>=wwidth )
			{ quickscroll(); printf("\n"); col = 0; }
		printf(" %-*s",slen,base);
		col+= ++slen;
	} else {
		quickscroll();
		printf("   %-24s %s \n",base,formatfile(info));
	}
	if (isadir && !(options & DIR_NOCOL))
		printf(o_lolite);
	if(info->size>0)
		bytes  += info->size;
	blocks += info->blocks;
	filecount++;
}

/* will have either of these formats:
 *
 *    fullfilename'\0'hsparwed   <Dir>       DD-MMM-YY HH:MM:SS\n'\0'
 *    fullfilename'\0'hsparwed NNNNNNN NNNN  DD-MMM-YY HH:MM:SS\n'\0'
 *                              1111111111222222222233333333334 4  4
 *                    01234567890123456789012345678901234567890 1  2
 */

static char linebuf[140];
static long dlen, dblocks;

static void
count( FIB *fib )
{
	dlen+=fib->fib_Size;
	dblocks+=fib->fib_NumBlocks+1;
}

char *
formatfile( struct file_info *info)
{
	char *str=linebuf, *class, *t;
	int i, stat;

	if( options & DIR_NOTE ) {
		struct DPTR *dp;
		if( dp=dopen( (char *)(info+1), &stat )) {
			strcpy( str, dp->fib->fib_Comment );
			dclose( dp );
		}
		return linebuf;
	}

	*str++= info->flags & 1<<30 ? 'c' : '-';
	for (i=7; i>=0; i--)
		*str++ = ((info->flags & (1L<<i)) ? "hspa----" : "----rwed")[7-i];
	if (info->size >= 0)
		sprintf(str,options&DIR_QUIET?" %7ld ":" %7ld %4ld  ",
		            info->size, info->blocks);
	else
		strcpy(str,options&DIR_QUIET?"   <Dir> ":"   <Dir>       ");
	str+=strlen(str);
	if( options&DIR_VIEW && info->size<0 ) {
		dlen=dblocks=0;
		recurse2( (char *)(info+1),count);
		sprintf( str, "%10s",itoa(dlen));
		info->size=dlen; info->blocks=dblocks;
	} else if( options&DIR_IDENT ) {
		if( *info->class!=1 )
			strcpy(str,info->class);
		else if( class=getclass((char *)(info+1)))
			if( t=index(strncpy(str,class,40),0xA0) )
				*t=0;
	} else if( options&DIR_AGE) {
		long mins=0;
		if( Stamp.ds_Days!=0 ) {
			mins =Stamp.ds_Days*1440 + Stamp.ds_Minute;
			mins-=info->date.ds_Days*1440 + info->date.ds_Minute;
		}
		if( mins>=0 )
			sprintf(str,"%4d %02d:%02d",mins/1440,mins/60%60,mins%40);
		else 
			sprintf(str,"Future");
	} else if( !(options&DIR_VIEW) )
		strcat(str,dates(&info->date));
	return linebuf;
}


int
do_quit( void )
{
	if (Src_stack) {
		Quit = 1;
		return(do_return());
	}
	main_exit(0);
	return 0;
}

int
do_echo( void )
{
	char *args=compile_av(av,1,ac,' ',0);
	fprintf( (options&2)?stderr:stdout, (options&1)?"%s":"%s\n",args );
	free(args);
	return 0;
}


static int
breakcheckd(void)
{
	int ret=!o_nobreak && SetSignal(0L,0L) & SIGBREAKF_CTRL_D;
	SetSignal(0L, SIGBREAKF_CTRL_D);
	if( ret )
		fprintf(stderr,"^D\n");
	return ret;
}

/* gets a line from file, joining two lines if the first ends in '\' */

static char *
myfgets(char *buf, int buflen, FILE *file)
{
	char *bufptr=buf, *limit=buf+buflen;

	do {
		if (fgets(bufptr, limit-bufptr, file)==NULL) {
			if (bufptr != buf)
				fprintf(stderr,"Source: file ends in '\\'\n");
			return NULL;
		}
		bufptr = bufptr+strlen(bufptr)-2;
	} while (*bufptr=='\\');
	return buf;
}

int
do_source( char *str )
{
	FILE *fi;
	char *buf;
	int len;

	if (Src_stack == MAXSRC) {
		ierror(NULL,217);
		return -1;
	}
	if ((fi = fopen (av[1], "r")) == 0)
		{ pError(av[1]); return -1;	}
	buf=malloc(512);
	set_var(LEVEL_SET, v_passed, next_word(next_word(str)));
	++H_stack;
	Src_pos[Src_stack] = 0;
	Src_base[Src_stack] = fi;
	Src_if[Src_stack]=If_stack;
	++Src_stack;
	while (myfgets (buf, 512, fi) && !dobreak() && !breakcheckd()) {
		len = strlen(buf);
		if(buf[len-1]=='\n')
			buf[len-1] = '\0';
		Src_pos[Src_stack - 1] += len;
		if (Verbose && !forward_goto) fprintf(stderr,"%s\n",buf);
		exec_command (buf);
	}
	--H_stack;
	--Src_stack;
	if( If_stack>Src_if[Src_stack] )
		If_stack=Src_if[Src_stack], disable=If_stack && If_base[If_stack-1];

	if (forward_goto) ierror(NULL,501);
	forward_goto = 0;
	unset_level(LEVEL_SOURCE + Src_stack);
	unset_var(LEVEL_SET, v_gotofwd);
	unset_var(LEVEL_SET, v_passed);
	free(buf);
	fclose (fi);
	return 0;
}

/* set process cwd name and $_cwd, if str != NULL also print it. */

int
do_pwd( char *str )
{
	char pwd[130];

	PathName(Myprocess->pr_CurrentDir, pwd, 128L);
	if (str) puts(pwd);
	set_var(LEVEL_SET, v_cwd, pwd);
	/* put the current dir name in our CLI task structure */
	CtoBStr(pwd, Mycli->cli_SetName, 128L);
	return 0;
}

/*
 * CD
 *
 * CD(str, 0)      -do CD operation.
 *
 */

extern int qcd_flag;

static char lastqcd[80];
static lastoffs;

int
do_cd( char *str )
{
	BPTR oldlock, filelock;
	FILE *file;
	char buf[100], *old;
	int  i=1, repeat;

	if( options & 1 ) {
		if( !(file=fopen( o_csh_qcd, "w" )))
			{ fprintf(stderr,"Can't open output\n"); return 20; }
		for( ; i<ac && !breakcheck(); i++ ) {
			fprintf(file,"%s\n",av[i]);
			strcpy(buf,av[i]);
			appendslash( buf );
			expand_all( buf, file );
		}
		fclose(file);
		return 0;
	}

	str= ( has_wild && ac>=2 ) ? av[1] : next_word(str);
	if (!strcmp("..",str)) str="/";

	if( !*str ) {
		printf("%s\n", get_var( LEVEL_SET, v_cwd ));
		return 0;
	}

	if (filelock=Lock(str,ACCESS_READ)) {
		lastqcd[0]=0;
		if (!isdir(str)) { UnLock(filelock); ierror(str,212); return 20; }
	} else {
		repeat= !strncmp( lastqcd, str, 79 );
		strncpy( lastqcd, str, 79);

		if( !quick_cd( buf, av[i], repeat) )
			{ fprintf(stderr,"Object not found %s\n",str); return 20; }
		if (!(filelock=Lock(buf,ACCESS_READ))) 
			{ pError(buf); return 20; }
	}
	if (oldlock=CurrentDir(filelock)) UnLock(oldlock);
	if( !(old=get_var(LEVEL_SET, v_cwd)) )
		old="";
	set_var(LEVEL_SET, v_lcd, old);
	do_pwd(NULL);

	return 0;
}

char *
quick_cd( char *buf, char *name, int repeat )
{
	qcd_flag=repeat ? 2 : 1;
	strcpy(buf,name);
	if( quicksearch( o_csh_qcd, 1, buf)!=2 )
		return NULL;
	return buf;
}


int
do_mkdir( void )
{
	int i;
	BPTR lock;
	
	for (i=1; i<ac; ++i) {
		if (exists(av[i]))
			ierror(av[i],203);
		else if (lock=CreateDir(av[i]))
			UnLock (lock);
		else
			pError(av[i]);
	}
	return 0;
}

int
do_mv( void )
{
	char *dest, buf[256];
	int dirflag, i;

	dirflag=isdir(dest=av[--ac]);
	if (ac>3 && !dirflag) { ierror(dest, 507); return (-1); }
	for (i=1; i<ac; ++i) {
		strcpy(buf, dest);
		if (dirflag) TackOn(buf, BaseName(av[i]));
		if (Rename(av[i], buf)==0)
			{ pError(av[i]); return -1; }
		else 
			clear_archive_bit( buf );
	}
	return 0;
}

static int dirstoo;

int
all_args( int (*action)(char *str), int dirsflag )
{
	int i;

	dirstoo=dirsflag;
	for ( i=1; i<ac && !dobreak(); ++i)
		if (isdir(av[i])) {
			if (options & 1) recurse(av[i], action);
			else if (dirstoo) (*action)(av[i]);
		} else
			(*action)(av[i]);
	return 0;
}

char *searchstring;
char docr;

#define SEARCH_REC   1
#define SEARCH_CASE  2
#define SEARCH_WILD  4
#define SEARCH_NUM   8
#define SEARCH_EXCL  16
#define SEARCH_QUIET 32
#define SEARCH_VERB  64
#define SEARCH_BIN   128
#define SEARCH_FILE  256
#define SEARCH_ABORT 512
#define SEARCH_LEFT  1024
#define SEARCH_ONLY  2048

static int abort_search;
static char lowbuf[256], file_name, file_cr;

static int
search_file( char *s )
{
	FILE *fopen(), *fi;
	char *p, *q;
	int nocasedep, lctr, len, excl=((options & 16) !=0 ), yesno;
	char buf[256], searchit[120], first, left;

	if( abort_search )
		return 0;

	nocasedep=!(options & SEARCH_CASE);
	lctr= docr= file_name= file_cr= 0;
	if (!(options & (SEARCH_QUIET|SEARCH_FILE)))
		if( options & SEARCH_VERB )
			printf("Examining %s...\n",s);
		else 
			printf("\015Examining %s...                  ",s), docr=1;

	strcpy(searchit,searchstring);
	if (options & SEARCH_WILD) strcat(searchit,"\n");
	len=strlen(searchit);
	if (nocasedep) strupr(searchit);
	first=*searchit;

	if( strcmp("STDIN",s) && !(options&SEARCH_WILD) && !excl ||
	             options&SEARCH_BIN )
		if( quicksearch(s,nocasedep,searchit) )
			return 0;

	if( options&SEARCH_BIN )
		{ fprintf(stderr,"Out of memory\n"); return 20; }

	fi = strcmp("STDIN",s) ?  fopen(s,"r") : stdin;
	if (fi==NULL) { pError(s); return 20; }

	prepscroll(0);

	while (fgets(buf,256,fi) && !dobreak()) {
		lctr++; left=1;
		if (options & SEARCH_WILD)
			yesno=compare_ok(searchit, buf, options&SEARCH_CASE);
		else {
			if (nocasedep) {
				strcpy(lowbuf,buf);
				strupr(lowbuf);
				p=lowbuf;
			} else
				p=buf;
			q=p;
			while ((p=index(p,first)) && strncmp(p++,searchit,len)) ;
			yesno= (p!=NULL);
			left = --p - q;
		}
		if( yesno ^ excl )
			if(!(options&SEARCH_ONLY)|| !isalphanum(p[-1])&&!isalphanum(p[len]))
				if( found(buf, lctr, 0, s, left ) )
					break;
	}
	if (fi!=stdin) fclose (fi);
	if( file_cr ) printf("\n");
	return 0;
}

int qcd_flag, qcd_offs;

static int
quicksearch( char *name, int nocasedep, char *pattern )
{
	int i, ptrn=strlen(pattern);
	char ut[256], *buffer, *lend;
	char *uptab=ut, *get, c, *lpos, *lstart;
	int len, lnum, qcd=qcd_flag, repeat=(qcd==2 && qcd_offs!=0);
	int sofar, got;
	BPTR fh;

#ifdef AZTEC_C
	while(0) while(0) c=c=0, uptab=uptab=ut, get=get=NULL;
#endif

	qcd_flag=0;
	if( !(fh=Open(name,MODE_OLDFILE))) { 
		i=(long)IoErr(), docr=0;
		printf("\n");
		ierror(name,i);
		return 1;
	}
	len=filesize( name );
	if( !(buffer=(void *)malloc(len+2))) { Close(fh); return 0; }
	sofar=0;
	do {
		got=Read( fh, (char *)buffer+sofar, 60000);
		sofar+=got;
	} while( got==60000 );
	Close( fh);
	if( sofar != len ) { pError(pattern); return 1; }

	if( nocasedep )
		strupr( pattern );

	if( !qcd )
		prepscroll(0);

	for( i=0; i<256; i++ ) uptab[i]=i;
	if( nocasedep ) for( i='a'; i<='z'; i++ ) uptab[i]=i-'a'+'A';
retry:
	c=*pattern, buffer[len]=c, buffer[len+1]=c;
	get= (qcd==2) ? buffer+qcd_offs : buffer;
	if( qcd==1 ) qcd_offs=0;

	lpos=lstart=buffer, lnum=1;
	for( ;; ) {
		do ; while( uptab[*get++]!=c );
		if( --get>=buffer + len )
			break;
		for( i=1; i<ptrn; i++ )
			if( uptab[get[i]]!=pattern[i] )
				break;
		if( i==ptrn ) {
			for( ;lpos<get; lpos++ )
				if( *lpos=='\n' )
					lstart=lpos+1, lnum++;
			for( lend=lstart+1; *lend!='\n'; lend++ ) ;
			if( qcd ) {
				if( get[-1]==':' || get[-1]=='/' ||
				      lpos==lstart && lend[-1]==':' ) {
					char *tmp;
					for( tmp=get+ptrn; *tmp&& *tmp!='\n'&& *tmp!='/'; tmp++ );
					if( *tmp!='/' ) {
						*lend=0;
						strncpy(pattern,lstart,79);
						qcd_offs=lend-buffer;
						free( buffer );
						return 2;
					}
				} else 
					lend=lpos+1;
			} else {
				*lend=0;
				if(!(options&SEARCH_ONLY) ||
				     !isalphanum(lpos[-1])&&!isalphanum(lpos[ptrn]))
					if(found(lstart, lnum, get-buffer, name, lpos==lstart ))
						break;
				*lend='\n';
			}
			get=lend+1;
		} else
			get++;
	}
	if( repeat )  { repeat=0; qcd_offs=0; goto retry; }
	if( file_cr ) { printf("\n"); quickscroll(); }
	free( buffer );
	return 1;
}

static int
found( char *lstart, int lnum, int loffs, char *name, char left )
{
	int fileabort=0;

	if ( docr )
		{ quickscroll(); printf("\n"); docr=0; }

	if( (options&SEARCH_LEFT) && !left)
		return 0;

	if( options&SEARCH_FILE ) {
		file_cr=1;
		if( !file_name )
			printf("%s",name), file_name=1;
		if( options&SEARCH_NUM )
			fileabort=1;
		else 
			printf(" %d",lnum);
	} else if( options & SEARCH_BIN ) {
		if (!(options & SEARCH_NUM))
			printf("Byte offset %d\n",loffs);
		else 
			printf("%d\n",loffs);
		quickscroll();
	} else {
		if (!(options & SEARCH_NUM))
			printf("%4d ",lnum);
		printf((lstart[strlen(lstart)-1]=='\n')?"%s":"%s\n",lstart);
		quickscroll();
	}
	abort_search= options&SEARCH_ABORT;
	return dobreak() || fileabort || abort_search;
}


int
do_search( void )
{
	if( Cout_name ) options |= SEARCH_VERB;
	abort_search=0;
	searchstring=av[--ac];
	all_args(search_file, 0);
	if(docr) printf("\n"),docr=0;
	return 0;
}

static int
rm_file(char *file)
{
	if ( file[strlen(file)-1]=='/' ) file[strlen(file)-1]=0;
	if (has_wild) printf(" %s...",file);
	if (options & 2) SetProtection(file,0L);
	if (!DeleteFile(file)) 
		{ pError (file); return 20; }
	else if (has_wild)
		printf("Deleted\n");
	return 0;
}

int
do_rm( void )
{
	all_args( rm_file, 1);
	return 0;
}

static void
recurse(char *name, int (*action)(char *))
{
	BPTR lock, cwd;
	FIB *fib=(FIB *)AllocMem((long)sizeof(FIB),MEMF_PUBLIC);
	char *namecopy=malloc(256);

	if (name[0] =='\0') return;
	namecopy[0]=0;
	if (lock=Lock(name,ACCESS_READ)) {
		cwd =CurrentDir(lock);
		if (Examine(lock, fib))
			while (ExNext(lock, fib) && !CHECKBREAK()) {
				if (*namecopy)
					{ (*action)(namecopy); namecopy[0]=0; }
				if (fib->fib_DirEntryType>=0) recurse(fib->fib_FileName,action);
				else strcpy(namecopy,fib->fib_FileName);
			}
		if (*namecopy) (*action)(namecopy);
		UnLock(CurrentDir(cwd));
		if (dirstoo) (*action)(name);
	} else
		pError(name);
	free(namecopy);
	FreeMem(fib, (long)sizeof(FIB));
}

static void
recurse2( char *name, void (*action)(FIB *))
{
	BPTR lock, cwd;
	FIB  *fib=(FIB *)AllocMem(sizeof(FIB),MEMF_PUBLIC);

	if (lock=Lock(name,ACCESS_READ)) {
		cwd =CurrentDir(lock);
		if (Examine(lock, fib))
			while (ExNext(lock, fib) && !CHECKBREAK()) {
				(*action)(fib);
				if (fib->fib_DirEntryType>=0)
					recurse2(fib->fib_FileName,action);
			}
		UnLock(CurrentDir(cwd));
	}

	FreeMem(fib, sizeof(FIB));
}


int
do_history( void )
{
	struct HIST *hist;
	int i = H_tail_base;
	int len = (av[1]) ? strlen(av[1]) : 0;

	for (hist = H_tail; hist && !dobreak(); hist = hist->prev, i++)
		if (len == 0 || !strncmp(av[1], hist->line, len))
			printf("%3d %s\n", i, hist->line);
	return 0;
}

int
do_mem( void )
{
	static long clast, flast;
	long cfree, ffree;
	char *desc="Free";

	Forbid();
	cfree = AvailMem (MEMF_CHIP);
	ffree = AvailMem (MEMF_FAST);
	Permit();
	if( options&8 ) {
		clast=cfree, flast=ffree;
		return 0;
	}
	if( options&16 )
		cfree=clast-cfree, ffree=flast-ffree, desc="Used";
	if( options&4 ) {
		if     ( options & 1 ) printf("%ld\n",cfree);
		else if( options & 2 ) printf("%ld\n",ffree);
		else                   printf("%ld\n",cfree+ffree);
	} else {
		if     ( options & 1 ) printf("Free CHIP memory %s\n",itoa(cfree));
		else if( options & 2 ) printf("Free FAST memory %s\n",itoa(ffree));
		else {
			if(ffree) {
				printf("FAST memory: %s\n",itoa(ffree));
				printf("CHIP memory: %s\n",itoa(cfree));
			}
			printf("Total  %s: %s\n",desc,itoa(cfree+ffree));
		}
	}
	return 0;
}

int
do_forline( void )
{
	char vname[33], buf[256], *cstr;
	int lctr;
	FILE *f;

	strcpy(vname,av[1]);
	f=fopen(av[2],"r");
	if (f==NULL) pError(av[2]);
	lctr=0;
	++H_stack;
	cstr = compile_av (av, 3, ac, ' ', 0);
	while (fgets(buf,256,f) && !dobreak()) {
		buf[strlen(buf)-1]='\0';	/* remove CR */
		lctr++;
		set_var(LEVEL_SET, vname, buf);
		sprintf(buf,"%d",lctr);
		set_var(LEVEL_SET, v_linenum, buf);
		exec_command(cstr);
	}
	fclose(f);
	--H_stack;
	free (cstr);
	unset_var (LEVEL_SET, vname);
	unset_var (LEVEL_SET, v_linenum);
	return 0;
}

int
do_fornum( void )
{
	char vname[33], buf[16];
	int n1, n2, step, i=1, verbose;
	char *cstr;

	verbose=(options & 1);
	strcpy(vname,av[i++]);
	n1=myatoi(av[i++],-32767,32767); if (atoierr) return 20;
	n2=myatoi(av[i++],-32767,32767); if (atoierr) return 20;
	if (options & 2) {
		step=myatoi(av[i++],-32767,32767); if (atoierr) return 20;
	} else
		step=1;
	++H_stack;
	cstr = compile_av (av, i, ac, ' ', 0);
	for (i=n1; (step>=0 ? i<=n2 : i>=n2) && !CHECKBREAK(); i+=step) {
		if (verbose) fprintf(stderr, "fornum: %d\n", i);
		sprintf(buf,"%d",i);
		set_var (LEVEL_SET, vname, buf);
		exec_command(cstr);
	}
	--H_stack;
	free (cstr);
	unset_var (LEVEL_SET, vname);
	return 0;
}

/*
 * foreach var_name  ( str str str str... str ) commands
 * spacing is important (unfortunately)
 *
 * ac=0    1 2 3 4 5 6 7
 * foreach i ( a b c ) echo $i
 * foreach i ( *.c ) "echo -n "file ->";echo $i"
 */

int
do_foreach( void )
{
	int cstart, cend;
	char *cstr, **fav, vname[33];
	int i=1, verbose;

	verbose=(options & 1);
	strcpy(vname, av[i++]);
	if (*av[i] == '(') i++;
	cstart = i;
	while (i<ac && *av[i] != ')') i++;
	if (i > ac) { fprintf(stderr,"')' expected\n"); return 20; }
	++H_stack;
	cend = i;

	fav = (char **)malloc(sizeof(char *) * (ac));
	cstr = compile_av (av, cend + 1, ac, ' ', 0);

	for (i = cstart; i < cend; ++i) fav[i] = av[i];

	for (i = cstart; i<cend && !CHECKBREAK(); ++i) {
		set_var (LEVEL_SET, vname, fav[i]);
		if (verbose) fprintf(stderr, "foreach: %s\n", fav[i]);
		exec_command(cstr);
	}
	--H_stack;
	free (fav);
	free (cstr);
	unset_var (LEVEL_SET, vname);
	return 0;
}

int
do_forever( char *str )
{
	int rcode = 0;
	char *ptr = next_word( str );

	++H_stack;
	for (;;) {
		if (CHECKBREAK()) { rcode = 20; break; }
		if (exec_command (ptr) < 0) {
			str = get_var(LEVEL_SET, v_lasterr);
			rcode = (str) ? atoi(str) : 20;
			break;
		}
	}
	--H_stack;
	return rcode;
}

extern struct IntuitionBase *IntuitionBase;

int
do_window( void )
{
	long x=-1, y=-1, w=-1, h=-1, maxwidth, maxheight, arg[5];
	int i;

	if(options & 32) {
		struct Screen *scrn;
		struct Window *window;
		char buf[80];
		buf[40]=0;
		for (scrn=IntuitionBase->FirstScreen; scrn; scrn=scrn->NextScreen) {
			buf[0]=0;
			if( scrn->Title )
				strncpy(buf,scrn->Title,40);
			printf("\nScreen \"%s\" (%d,%d,%dx%d):\n",
				buf,
				scrn->LeftEdge,
				scrn->TopEdge,
				scrn->Width,
				scrn->Height
			);
			for (window=scrn->FirstWindow; window; window=window->NextWindow) {
				buf[0]=0;
				if( window->Title )
					strncpy(buf,window->Title,40);
				printf("\tWindow\t\"%s\" (%d,%d,%dx%d)\n",
					buf,
					window->LeftEdge,
					window->TopEdge,
					window->Width,
					window->Height
				);
			}
		}
		return 0;
	}

	if( o_nowindow || !Win )
		return 20;

	maxwidth = Win->WScreen->Width;
	maxheight= Win->WScreen->Height;
	if( options&1 )
		x=Win->LeftEdge,y=Win->TopEdge,w=Win->MinWidth,h=Win->MinHeight;
	if( options&2 ) x=y=0, w=maxwidth, h=maxheight;
	if( options&4 ) WindowToFront(Win);
	if( options&8 ) WindowToBack(Win);
	if( options&16) ActivateWindow(Win);
	if( ac >= 5) {
		for(i=1; i<5; i++) {
			arg[i] = myatoi(av[i],0,1023); if (atoierr) return 20;
		}
		x=arg[1]; y=arg[2]; w=arg[3]; h=arg[4];
	}
	if( w!=-1 ) {
		int i;
		if ( x+w>maxwidth || y+h>maxheight ) {
			ierror(NULL, 500);
			return 20;
		}
		if( w<Win->MinWidth  ) w=Win->MinWidth;
		if( h<Win->MinHeight ) h=Win->MinHeight;
		if( Win->LeftEdge!=0 || Win->TopEdge!=0 )
			MoveWindow(Win, -Win->LeftEdge, -Win->TopEdge );
		if( Win->Width!=w || Win->Height!=h )
			SizeWindow(Win, w-Win->Width   , h-Win->Height  );
		if( x || y )
			MoveWindow(Win, x, y );
		for( i=0; i<7; i++ ) {
			if(  Win->LeftEdge==x && Win->TopEdge==y && 
			     Win->Width   ==w && Win->Height ==h )
				break;
			Delay(5);
		}
	} else 
		Delay(30); /* pause 1/2 sec. before trying to print */

	printf("\014");
	return 0;
}


static void
setsystemtime(struct DateStamp *ds)
{
	struct timerequest tr;
	long secs= ds->ds_Days*86400+ds->ds_Minute*60+ds->ds_Tick/TICKS_PER_SECOND;

	if (OpenDevice(TIMERNAME, UNIT_VBLANK,(struct IORequest *)&tr, 0L)) {
		fprintf(stderr,"Clock error: can't open timer device\n");
		return;
	}

	tr.tr_node.io_Message.mn_Node.ln_Type = NT_MESSAGE;
	tr.tr_node.io_Message.mn_Node.ln_Pri = 0L;
	tr.tr_node.io_Message.mn_Node.ln_Name = NULL;
	tr.tr_node.io_Message.mn_ReplyPort = NULL;
	tr.tr_node.io_Command = TR_SETSYSTIME;
	tr.tr_time.tv_secs = secs;
	tr.tr_time.tv_micro = 0L;
	if (DoIO ((struct IORequest *)&tr))
		fprintf(stderr,"Clock error: can't talk to timer device\n");
	CloseDevice ((struct IORequest *)&tr);
}

char tday[10];

char *
dates( struct DateStamp *dss )
{
	static char timestr[40];
	char tdate[10], ttime[10];
	struct DateTime dt;
	struct DateStamp *myds=&(dt.dat_Stamp);

	dt.dat_Format=FORMAT_DOS;
	dt.dat_StrDay=tday;
	dt.dat_StrDate=tdate;
	dt.dat_StrTime=ttime;
	dt.dat_Flags=NULL;
	myds->ds_Days=dss->ds_Days;
	myds->ds_Minute=dss->ds_Minute;
	myds->ds_Tick=dss->ds_Tick;
	StamptoStr(&dt);
	sprintf(timestr,"%s %s\n",tdate,ttime);
	timestr[18]=0;	/* protection against bad timestamped files */
	return timestr;
}

int
do_date( void )
{
	static long stopwatch;
	struct DateStamp dss;
	struct DateTime dt;
	long time;
	int i=1;

	dt.dat_Format=FORMAT_DOS;
	if (ac==1) {
		DateStamp(&dss);
		time=dss.ds_Minute*6000+2*dss.ds_Tick;   /* 2 = 100/TickPerSec   */
		if( options & 1 )
			stopwatch=time;
		else if( options&2 )
			printf( "%d.%02d\n",(time-stopwatch)/100,(time-stopwatch)%100);
		else 
			printf("%s %s\n",tday,dates(&dss));
	} else {
		DateStamp(&dt.dat_Stamp);
		for ( ; i<ac; i++) {
			dt.dat_StrDate=NULL;
			dt.dat_StrTime=NULL;
			dt.dat_Flags=DTF_FUTURE;
			if (index(av[i],':')) dt.dat_StrTime=av[i];
				else dt.dat_StrDate=av[i];
			if (StrtoStamp(&dt)) ierror(av[i],500);
		}
		setsystemtime( & (dt.dat_Stamp) );
	}
	return 0;
}
