/*
 * COMM3.C
 *
 * Version 4.01A by Carlo Borreo & Cesare Dieni 17-Feb-90
 * Version 5.00L by Urban Mueller 17-Feb-91
 *
 */

#include "shell.h"

/* comm3.c */
static void doassign(char *log, char *phy);
static void assignlist(void);
static int strings_in_file(char *s);
static int htype_a_file(char *s);
static void install_menu(char **mav, int mac);
static int line_filter( char *(*line)(char *) );

static int
myfgets( char *buf, FILE *in )
{
	int l;
	char *ret;
	if( ret=fgets(buf,253,in) ) {
		l=strlen(buf);
		if( buf[l-1]!='\n' )
			buf[l]='\n', buf[l+1]=0;
	}
	return ret!=NULL && !dobreak();
}

do_tee( void )
{
	char buf[256];
	FILE *out;

	prepscroll( ac==1 );
	if( ac>2 ) { ierror( av[2], 500 ); return 20; }
	if( ac==1 ) out=stderr;
	else if( !(out=fopen( av[1], "w" ))) { pError( av[1] ); return 20; }
	while (myfgets(buf,stdin)) {
		fputs(buf, stdout);
		quickscroll();
		fprintf(out, "%s", buf);
	}
	if( ac!=1 ) fclose( out );
	return 0;
}

do_head( char *garbage, int com )
{
	int i, n;
	FILE *f;
	char buf[256];

	if (ac>2) {
		n=(int)(long)Atol(av[2]);
		if (IoErr()) {
			ierror(av[2],511);
			return 20;
		}
	} else n=10;

	f=fopen(av[1], "r");
	if (f==NULL) {
		pError(av[1]);
		return 20;
	}
	if (com) {	/* tail specific part */
		i=0;
		while (fgets(buf, 256, f) && ! dobreak()) i++;
		rewind(f);
		if (n>i) n=i;
		i=i-n;
		while (i-- && fgets(buf, 256, f) && ! dobreak()) ;
	}
	for (i=1; i<=n && fgets(buf, 256, f) && ! dobreak(); i++)
		printf("%s", buf);
	fclose(f);
	return 0;
}

static int
exword( char **src, char *buf )
{
	*buf=0;
	if( **src==0 ) return 0;
	while( **src && **src!=0xA0 )
		*buf++=*(*src)++;
	*buf=0;
	if( **src ) (*src)++;
	return 1;
}

static char helpfound=0;

void
man( FILE *f, char *s)
{
	char buf[140], entry[100];
	int len=sprintf(entry, "    %s", s);

	prepscroll(0);
	rewind(f);
	do                                  /* look for required argument */
		if (fgets(buf, 140, f) == NULL)
			return;
	while ( Strncmp(entry, buf, len) );
	helpfound=1;
	do {                                /* display help */
		quickscroll();
		printf("%s", buf);
		if (fgets(buf, 140, f) == NULL) return;
	} while ( ( !isalphanum(*buf) ) && strncmp(buf, "    ", 4) && !dobreak() );
}


do_man( void )
{
	FILE *f;
	int i;
	char buf[200], name[60], *src, *var, docfound=0;

	buf[0]=0;
	if( var=get_var(LEVEL_SET,"_man" ) )
		strcpy(buf,var);

	if (ac==1) ac=2, av[1]="MAN";
	for (i=1; i<ac; i++) {
		src=buf, helpfound=0;
		while( exword( &src, name) )
			if( f=fopen(name, "r") ) {
				docfound=1;
				man(f, av[i]);
				fclose(f);
				if( helpfound )
					break;
			}
		if( !docfound )
			fprintf(stderr,"%s not found\n",buf);
		else if( !helpfound )
			fprintf(stderr, "Help not found for %s\n", av[i]);
	}
	return 0;
}

do_assign( void )
{
	int i;

	if     (  ac==1  ) assignlist();
	else if(  ac==2  ) doassign(av[1], NULL);
	else if( !(ac&1) ) ierror(NULL, 500);
	else
		for( i=1; i<ac; i+=2 )
			doassign( av[i],av[i+1] );
	return 0;
}

static char *assign_errors[4]={
	"",
	"Name %s is not valid\n",
	"Weird error\n",
	"Can't cancel %s:\n"
	};


static void
doassign(char *log, char *phy)
{
	int last=strlen(log) - 1;

	if (log[last] != ':') fprintf(stderr, "Bad name %s\n", log);
	else {
		log[last] = 0;
		if( options && phy && o_kick20 ) {
			BPTR lock;
			int succ=0;
			if     ( options&1 ) succ=AssignLate( (UBYTE*)log,(UBYTE*)phy );
			else if( options&2 ) succ=AssignPath( (UBYTE*)log,(UBYTE*)phy );
			else if( options&4 )
				if( lock=Lock(phy,ACCESS_READ) )
					if( !(succ=AssignAdd((UBYTE *)log,lock)))
						UnLock(lock);
			if( !succ )
				pError( log );
		} else if( !(options&4) ) {
			int err=Assign(log, phy);
			fprintf(stderr,assign_errors[err],err==1?phy:log);
		}
	}
}

extern struct RootNode2x *RootNode;
#define ENTRIES 128

static void
assignlist()
{
	char *ptr, *log, **arr;
	int ctr, i;

	if(!(arr=expand_devs()))
		return;

	printf("Devices:\n");
	for (i=ctr=0; arr[i] && *arr[i]==1; i++) {
		printf("%-8s",arr[i]+1);
		if (ctr++ == 5) { ctr=0; printf("\n"); }
	}

	printf("\n\nVolumes:\n");
	for( ; arr[i] && *arr[i]==2; i++ )
		printf( "%-16s [Mounted]\n",arr[i]+1);

	printf("\nDirectories:\n");
	for( ; arr[i] && !dobreak(); i++ ) {
		log=arr[i]+1; ptr=log+strlen(log)+1;
		switch( *(log-1)) {
		case 3:
			printf("%-20s%s\n", log, ptr);
			for(;;) {
				ptr+=strlen(ptr)+1;
				if( !*ptr ) break;
					printf("%-19s+%s\n", "", ptr);
			}
			break;
		case 4: printf("%-20s<%s>\n", log, ptr); break;
		case 5: printf("%-20s[%s]\n", log, ptr); break;
		}
	}

	free_expand( arr );
}

char **
expand_devs(void)
{
	static char Type[]={1,3,2,4,5};
	struct DosInfo *info=(void*)((long)RootNode->rn_Info*4);
	struct DosList2x *list=(void*)((long) info->di_DevInfo*4);
	struct AssignList2x *path;
	char buf[256], **arr, *ptr;
	int n;

	if( !(arr=malloc(ENTRIES*sizeof(char *))))
		return NULL;

	Forbid();
	for( n=0; list && n<ENTRIES; list=(void*)((long)list->dol_Next*4)) {
		if( list->dol_Type<0 || list->dol_Type>DLST_NONBINDING )
			continue;
		ptr=buf;
		*ptr++=Type[list->dol_Type];
		ptr+=BtoCStr(ptr,list->dol_Name,200);
		*ptr++=':'; *ptr++=0;
		switch( list->dol_Type) {
		case DLST_DIRECTORY : 
			if(list->dol_Lock)
				PathName(list->dol_Lock, ptr, 200);
			else 
				strcpy(ptr,"Nonexisting lock");
			ptr+=strlen(ptr)+1;
			if( o_kick20 ) {
				path=(void*)list->dol_misc.dol_assign.dol_List;
				for( ; o_kick20 && path; path=(void*)path->al_Next ) {
					if(list->dol_Lock)
						PathName(path->al_Lock, ptr, 200);
					else 
						strcpy(ptr,"Nonexisting lock");
					ptr+=strlen(ptr)+1;
				}
			}
			*ptr++=0;
			break;
		case DLST_LATE      : 
		case DLST_NONBINDING:
			ptr+= sprintf(ptr,"%s",list->dol_misc.dol_assign.dol_AssignName);
			*ptr++=0;
			break;
		default:
			*ptr++=0;
		}
		arr[n]=salloc(ptr+1-buf);
		memcpy(arr[n++],buf,ptr+1-buf);
	}
	arr[n]=NULL;
	Permit();

	QuickSort( arr, n );

	return arr;
}


do_join( void )
{
	BPTR sou, dest;
	char *buffer;
	int i;
	long n;
	char *namedest=av[--ac];

	if (options==0 && exists(namedest)) { ierror(namedest,203); return 20; }
	if ( (buffer=malloc(8192)) == NULL ) { ierror(NULL,103); return 20; }
	if ( (dest=Open(namedest, MODE_NEWFILE)) == NULL )
		{ pError(namedest); goto fail1; }
	for (i=1; i<ac; i++) {
		if ( (sou=Open(av[i], MODE_OLDFILE)) == NULL ) pError(av[i]);
		else
			while( (n=Read(sou, buffer, 8192L)) > 0 )
				if (Write(dest, buffer, n) != n)
					{ pError(namedest); Close(sou); goto fail2; }
		Close(sou);
	}
fail2:
	Close(dest);
fail1:
	free(buffer);
	return 0;
}

#define BUFDIM 512L
#define MAXSTR 256

int minstr;

static int
strings_in_file(char *s)
{
	char c;
	char readbuf[BUFDIM+1], strbuf[MAXSTR+1];
	int i, strctr=0;
	BPTR fh;
	int out, n, inter=IsInteractive(Output());;

	prepscroll(0);
	if ( fh=Open(s, MODE_OLDFILE) ) {
		fprintf(stderr, "Strings in %s (len>=%d):\n",s,minstr);
		while ( (n=(int)Read(fh, readbuf, BUFDIM)) > 0 && !CHECKBREAK() )
			for (i=0; i<n; i++) {
				c=readbuf[i];
				if (c<0x20 || c>0x7f) {
					out=(strctr>=minstr);
					if (!out) strctr=0;
				} else {
					strbuf[strctr++]=c;
					out=(strctr>=BUFDIM);
				}
				if (out) {
					strbuf[strctr]='\0';
					puts(strbuf);
					if( inter ) fflush(stdout);
					quickscroll();
					strctr=0;
				}
			}
		Close(fh);
	} else
		pError(s);
	return 0;
}

do_strings( void )
{
	minstr=myatoi(av[--ac],1,255);
	all_args( strings_in_file, 0);
	return 0;
}

BPTR myfile[MAXMYFILES];

do_open( void )
{
	long mode;
	int n;

	switch (toupper(av[2][0])) {
		case 'R': mode=MODE_OLDFILE; break;
		case 'W': mode=MODE_NEWFILE; break;
		default : ierror(NULL,500); return 1;
	}
	n=myatoi(av[3],0,MAXMYFILES-1); if (atoierr) return 20;
	if (myfile[n]) myclose(n);
	myfile[n]=Open(av[1],mode);
	return myfile[n]==NULL;
}

do_close( void )
{
	int i, n;

	if (ac==1)
		for (i=1; i<MAXMYFILES; i++)
			myclose(i);
	for (i=1; i<ac; i++) {
		n=myatoi(av[i],0,MAXMYFILES-1); if (atoierr) return 20;
		myclose(n);
	}
	return 0;
}

void
myclose(int n)
{
	if (myfile[n]) { Close(myfile[n]); myfile[n]=NULL; }
}

do_fileslist( void )
{
	int i, flag=0;

	printf("Open files:");
	for (i=0; i<MAXMYFILES; i++)
		if (myfile[i]) { printf(" %d",i); flag=1; }
	if (!flag) printf(" None!");
	printf("\n");
	return 0;
}

BPTR
extOpen( char *name, long mode )
{
	if (name[0]=='.' && name[1]>='0' && name[1]<='9')
		return myfile[atoi(name+1)];
	return Open(name,mode);
}


void
extClose(BPTR fh)
{
	int i;

	for (i=0; i<MAXMYFILES; i++)
		if (myfile[i]==fh) return;
	Close(fh);
}

do_basename( void )
{
	char *res;
	int  i;

	for( i=2; i<ac; i++ )
		av[i]=BaseName(av[i]);
	set_var(LEVEL_SET, av[1], res=compile_av(av,2,ac,0xA0,0));
	free(res);
	return 0;
}

do_tackon( void )
{
	char buf[256];

	strcpy(buf, av[2]);
	TackOn(buf, av[3]);
	set_var(LEVEL_SET, av[1], buf);
	return 0;
}

extern char shellres[];

do_resident( void )
{
	int i=1;
	struct ResidentProgramNode *p;
	char buf[256];

	if (options==0 && ac>1) options=1;
	switch (options) {
	case 0:
		ObtainSemaphore (& (ArpBase->ResPrgProtection) );
		if (p=ArpBase->ResidentPrgList) {
			printf("Name             Users Access\n");
			for (; p; p=p->rpn_Next)
				printf("%-17s%5d%6d\n",
					p->rpn_Name, p->rpn_Usage, p->rpn_AccessCnt);
		} else
			printf("No resident program(s)\n");
		ReleaseSemaphore(& (ArpBase->ResPrgProtection) );
		break;
	case 1:
		for (; i<ac; i++)
			if (loadres(av[i]))
				printf("OK! %s is now resident\n", BaseName(av[i]));
			else
				pError(av[i]);
		break;
	case 2:
		for (; i<ac; i++)
			if (RemResidentPrg(av[i])) ierror(av[i],202);
			else printf("Removed %s\n",av[i]);
		break;
	case 4:
		for (; i<ac; i++) {
			if( !o_resident ) {
				Setenv(shellres,"1");
				o_resident=1;
			}
			sprintf(buf,"res_%s",BaseName(av[i]));
			Setenv(buf,av[i]);
		}
		break;
	default:
		ierror(NULL,500);
		break;
	}
	return 0;
}

int
loadres(char *s)
{
	BPTR seg;

	if (seg=(BPTR)LoadPrg(s)) AddResidentPrg(seg,BaseName(s));
	return (seg != NULL);
}

static struct ProcessControlBlock pcb={
	4000,		/* pcb_StackSize	*/
	0,			/* pcb_Pri			*/
	};
/* remaining fields are NULL */

do_truerun(char *avline, int backflag)
{
	char name[100], *args, buf[10];
	int cli;

	if (backflag) {
		pcb.pcb_Control=NULL;
		pcb.pcb_Input=pcb.pcb_Output=Open("NIL:",MODE_OLDFILE);
	} else {
		pcb.pcb_Control=NULL;
		pcb.pcb_Input=pcb.pcb_Output =NULL;
	}
	args=next_word(next_word(avline));

	if((cli=ASyncRun(av[1],args,&pcb))<0)
		if (dofind(av[1], "", name,v_path))
			cli=ASyncRun(name,args,&pcb);

	sprintf(buf,"%d",cli);
	set_var(LEVEL_SET,"_newproc",buf);

	if( cli<0 ) {
		ierror(av[1],205);
		return 20;
	}
	return 0;
}

int
exists( char *name )
{
	BPTR lock;

	if ( strlen(name)<=MAXFILENAME && (lock=Lock(name,ACCESS_READ))) {
		UnLock(lock);
		return 1;
	}
	return 0;
}

do_aset( void )
{
	Setenv(av[1],av[2]);
	return 0;
}

#define HTYPELINE 16L

static int
htype_a_file( char *s )
{
	BPTR fh;
	long n, filesize=0;
	UBYTE buf[HTYPELINE+1];
	char out[80], *put;
	int i, inter=IsInteractive(Output());

	if ( (fh=Open(s,MODE_OLDFILE))==NULL ) { pError(s); return 20; }
	prepscroll(0);
	while ( (n=Read(fh,(char *)buf,HTYPELINE))>0 && !dobreak()) {
		put=out;
		put+=sprintf(put,"%06lx: ",filesize);
		filesize+=n;
		for (i=0; i<n; i++) {
			put+=sprintf( put,(i&3) ? "%02x" : " %02x",buf[i]);
			if ((buf[i]&127)<0x20) buf[i]='.';
		}
		for ( ; i<HTYPELINE; i++) {
			put+=sprintf( put, (i&3) ? "  " : "   ");
			buf[i]=' ';
		}
		buf[i]=0;
		sprintf(put,"    %s",buf);
		puts(out);
		if( inter ) fflush(stdout);
		quickscroll();
	}
	Close(fh);
	return 0;
}

do_htype( void )
{
	all_args( htype_a_file, 0);
	return 0;
}

do_stack( void )
{
	long n;

	if (ac>1) {
		n=Atol(av[1]);
		if (!IoErr()) Mycli->cli_DefaultStack=(long)(n >> 2L);
	}
	else printf("current stack size is %ld bytes\n",
				(long)Mycli->cli_DefaultStack << 2L);
	return 0;
}

do_fault( void )
{
	struct PERROR *p;
	int i, n;

	for (i=1; i<ac; i++) {
		n=myatoi(av[i],0,32767);
		if (!atoierr) {
			for (p=Perror; p->errnum && p->errnum!=n; p++);
			if (p->errnum)
				printf("Fault %d: %s\n",n,p->errstr);
			else
				printf("Fault %d not recognized\n",n);
		}
	}
	return 0;
}

struct rpncommand {
	char *str;
	int parsin, parsout;
	};

static struct rpncommand rpn[]={
	"+",	2,	1,
	"-",	2,	1,
	"*",	2,	1,
	"/",	2,	1,
	"%",	2,	1,
	"&",	2,	1,
	"|",	2,	1,
	"~",	1,	1,
	">",	2,	1,
	"<",	2,	1,
	"==",	2,	1,
	"!",	1,	1,
	"MAX",	2,	1,
	"MIN",	2,	1,
	"DUP",	1,	2,
	"DROP",	1,	0,
	"SWAP",	2,	2,
	"HELP",	0,	0,
	NULL,	0,	1,	/* this looks for a number */
};

static long stack[50];
static int sp;


eval_rpn( char **av, int ac, int flag )
{
	char *zero="Division by zero\n";
	struct rpncommand *temp;
	long n0, n1, t;
	int j, i=0, oldsp=sp;

	for (; i<ac; i++) {
		for (j=0; rpn[j].str && Strcmp(rpn[j].str,av[i]); j++) ;
		n0=stack[sp-1];
		n1=stack[sp-2];
		sp -= (rpn[j].parsin);
		if (sp<0) { fprintf(stderr, "RPN: Empty stack\n"); goto error; }
		switch (j) {
		  case 0:	n0 += n1;			break;
		  case 1:	n0 = n1-n0;			break;
		  case 2:	n0 *= n1;			break;
		  case 3:	if(n0) n0=n1/n0; else fprintf(stderr,zero); break;
		  case 4:	if(n0) n0=n1%n0; else fprintf(stderr,zero); break;
		  case 5:	n0 &= n1;			break;
		  case 6:	n0 |= n1;			break;
		  case 7:	n0 =  ~n0	;		break;
		  case 8:	n0 = (n1 > n0);		break;
		  case 9:	n0 = (n1 < n0);		break;
		  case 10:	n0 = (n0 == n1);	break;
		  case 11:	n0 = !n0;			break;
		  case 12:	n0=n1>n0 ? n1 : n0;	break;
		  case 13:	n0=n1<n0 ? n1 : n0;	break;
		  case 14:	n1=n0;				break;
		  case 15:	t=n0; n0=n1; n1=t;	break;
		  case 16:						break;
		  case 17:	printf("In Commands Out\n");
			for (temp=rpn; temp->str; temp++)
			    printf(" %d %-10s%d\n",
				temp->parsin,temp->str,temp->parsout);
			break;
		  default:	n0=Atol(av[i]);
				if (IoErr()) {
					fprintf(stderr, "Bad RPN cmd: %s\n",av[i]);
					goto error;
				}
				break;
		  }
		stack[sp]=n0;
		stack[sp+1]=n1;
		sp += rpn[j].parsout;
	}
	if( flag && sp-1)
		fprintf( 
		  stderr,
		  "RPN: Stack not empty\n"
		);

	t=sp; sp=oldsp;
	if( flag )
		return stack[t-1]; /* return top value */
	else 
		return t-sp;

error:
	sp=oldsp;
	return 0;
}


do_rpn(char *garbage,int ifflag) /* ifflag!=0 if called from if */
{
	int i=1;
	long t;

	t=eval_rpn( av+i, ac-i, ifflag );
	if (ifflag) return t;              /* called from if: return top value */
	for (i=sp+t-1;i>=sp;i--) printf("%ld\n", stack[i]);/* else print stack */

	return t ? 0 : 20;
}

do_path( void )
{
	ULONG ll, ll1, *lp, new, *newp;
	char buf[256];
	BPTR lock;
	int i;

	if( options&1 ) {
		Forbid();
		for( ll= Mycli->cli_CommandDir; ll; ll= ll1 ) {
			lp=(ULONG *)(4*ll);
			ll1=*lp;
			DosFreeMem( lp );
		}
		Mycli->cli_CommandDir=0;
		Permit();
	} else if( ac==1 ) {     /* Should Forbid() here, but puts() Permit()s */
		puts("Current dir"); /* and failure here is not really harmful...  */
		for( ll= Mycli->cli_CommandDir; ll; ll= *lp ) {
			lp=(ULONG *)(4*ll);
			PathName(lp[1], buf, 256L);
			puts(buf);
		}
		puts("C:");
		return 0;
	}
	for( i=1; i<ac; i++ ) {
		if( !(lock=Lock(av[i],ACCESS_READ)) ) {
			ierror(av[i],205);
			continue;
		}
		if( !isdir(av[i])) {
			ierror(av[i],212);
			UnLock(lock);
			continue;
		}
		Forbid();
		for( ll= Mycli->cli_CommandDir, lp=NULL; ll; ll= lp[0] ) {
			lp=(ULONG *)(4*ll);
			if( CompareLock(lp[1],lock)==LCK_EQUAL) {
				UnLock(lock), lock=0;
				break;
			}
		}
		if( lock && (newp=DosAllocMem( 8 ))) {
			newp[1]=lock;
			new =(ULONG)newp/4;
			if( lp )
				*lp=new;
			else
				Mycli->cli_CommandDir=new;
		}
		Permit();
	}
	return 0;
}

do_pri( void )
{
	int t, pri;
	struct Process *proc;

	t=(int)(long)FindCLI(0L);
	t=myatoi(av[1],0,t); if (atoierr) return 20;
	pri=myatoi(av[2],-128,127); if (atoierr) return 20;
	Forbid();
	proc=(t==0 ? Myprocess : FindCLI((long)t));
	if (proc==NULL) fprintf(stderr, "process not found\n");
	else SetTaskPri((struct Task *)proc, (long)pri);
	Permit();
	return 0;
}

do_strleft( void )
{
	int n;

	n=posatoi(av[3]); if (atoierr) return 20;
	set_var_n(LEVEL_SET, av[1], av[2], n);
	return 0;
}

do_strright( void )
{
	int n, len=strlen(av[2]);

	n=posatoi(av[3]); if (atoierr) return 20;
	if( n>len ) n=len;
	set_var(LEVEL_SET, av[1], av[2]+len-n );
	return 0;
}

do_strmid( void )
{
	int n1, n2=999999, len=strlen(av[2]);

	n1=myatoi(av[3],1,999999)-1; if (atoierr) return 20;
	if (n1>len) n1=len;
	if (ac>4) {
		n2=posatoi(av[4]); if (atoierr) return 20;
	}
	set_var_n(LEVEL_SET, av[1], av[2]+n1, n2);
	return 0;
}

do_strlen( void )
{
	char buf[16];

	sprintf(buf,"%d",strlen(av[2]));
	set_var(LEVEL_SET, av[1], buf);
	return 0;
}

int atoierr;

myatoi(char *s,int mmin,int mmax)
{
	int n;

	n=Atol(s);
	if (atoierr=IoErr())
		ierror(s,511);
	else if (n<mmin || n>mmax) {
		atoierr=1; n=mmin;
		fprintf( stderr, "%s(%d) not in (%d,%d)\n",s,n,mmin,mmax );
	}
	return n;
}

unlatoi(char *s)
{
	int n=Atol(s);
	if (atoierr=IoErr())
		ierror(s,511), n=0;
	return n;
}

posatoi(char *s)
{
	int n=Atol(s);
	if (atoierr=IoErr())
		ierror(s,511);
	else if (n<0 )
		atoierr=1, n=0, fprintf( stderr, "%s must be positive\n",s );
	return n;
}


do_fltlower( void )
{
	return line_filter( strlwr );
}

do_fltupper( void )
{
	return line_filter( strupr );
}

#if 0
char *
stripcr( char *get )
{
	char *old=get, *put;

	for( put=get; *get; get++ )
		if( *get!=13 )
			*put++=*get;
	*put++=0;
	return old;
}

do_fltstripcr( void )
{
	return line_filter( stripcr );
}
#endif

int
line_filter( char *(*func)( char * ) )
{
	char buf[256];

	while (!CHECKBREAK() && gets(buf))
		puts((*func)(buf));
	return 0;
}

int
do_linecnt( void )
{
	int count=0;
	char buf[256];

	while (!CHECKBREAK() && gets(buf)) ++count;
	printf("%d lines\n",count);
	return 0;
}

int
do_uniq( void )
{
	int firstline=1;
	char buf[256], oldbuf[256];

	while (!CHECKBREAK() && gets(buf)) {
		if ( firstline || strcmp(buf, oldbuf)) {
			strcpy(oldbuf, buf);
			puts(buf);
		}
		firstline=0;
	}
	return 0;
}


#define RXFB_RESULT  17

static struct rexxmsg {
	struct Message cm_Node;
	LONG   RFU1;
	LONG   RFU2;
	LONG   rm_Action;
	LONG   rm_Result1;
	LONG   rm_Result2;
	char   *cm_Args[16];
	LONG   RFU7;
	LONG   RFU8;
	LONG   RFU9;
	LONG   RFU10;
	LONG   RFU11;
	LONG   RFU12;
} mymsg;

do_rxsend( char *avline )
{
	int i;
	long result;
	struct MsgPort *port, *reply;
	long len;
	char buf[20], *resptr;

	if (!(port = FindPort(av[1])))
		{ fprintf(stderr, "No port %s!\n", av[1]); return 20; }
	mymsg.cm_Node.mn_Node.ln_Type = NT_MESSAGE;
	mymsg.cm_Node.mn_Length = sizeof(struct rexxmsg);
	mymsg.rm_Action = (options&1 ? 1L << RXFB_RESULT : 0);
	if (!(reply = CreatePort(NULL, 0L))) {
		fprintf(stderr, "No reply port\n");
		return 20;
	}
	mymsg.cm_Node.mn_ReplyPort = reply;

	if( options&2 )
		av[2]=compile_av( av,2,ac,' ',0), ac=3;
	for ( i=2; i<ac; i++) {
		mymsg.cm_Args[0] = av[i];
		mymsg.rm_Result2 = 0;        /* clear out the last result. */
		PutMsg(port, &mymsg.cm_Node);

		Wait( 1<<reply->mp_SigBit | SIGBREAKF_CTRL_C );

		if( CHECKBREAK() )
			break;

		if (options&1) {
			if( (result=mymsg.rm_Result2)<1000000 ) { /* like AREXX */
				sprintf(buf,"%d",result);              
				set_var(LEVEL_SET,v_result,buf);
			} else {
				resptr=(char *)(result-4);
				len=*(long *)resptr;
				memmove(resptr,resptr+4,len);  /* Null terminate */
				resptr[len]=0;      
				set_var(LEVEL_SET,v_result,resptr);
				FreeMem(resptr, len+4 );
			}
		} else 
			unset_var( LEVEL_SET, v_result );
	}
	if( options&2 )
		free( av[2] );

	if (reply) DeletePort(reply);
	return 0;
}

static char *rxreturn;

do_rxrec( void )
{
	struct MsgPort *port;
	struct rexxmsg *msg;
	char *portname, *str;

	if (ac > 1)
		portname=av[1];
	else
		portname="rexx_csh";

	port=CreatePort(portname, 0L);
	if (port==NULL) {
		fprintf(stderr, "Can't have MsgPort %s\n", portname);
		return 20;
	}
	for (;;) {
		WaitPort(port);
		while (msg=(struct rexxmsg *)GetMsg(port)) {
			if ( ! Strcmp(msg->cm_Args[0], "bye")) {
				ReplyMsg((struct Message *)msg);
				DeletePort(port);
				return 0;
			}
			rxreturn=NULL;
			exec_command(msg->cm_Args[0]);
			if (msg->rm_Action & (1L << RXFB_RESULT)) {
				if( rxreturn ) {
					str= SAllocMem( strlen( rxreturn )+5 , 0 );
					*(long *)str=strlen( rxreturn );
					strcpy( str+4, rxreturn );
					msg->rm_Result2=(long)str;
				} else {
					str = get_var(LEVEL_SET, v_lasterr);
					msg->rm_Result2=(str) ? atoi(str) : 20;
				}
			}
			ReplyMsg((struct Message *)msg);
		}
	}
}

int
do_waitport( void )
{
	int count=4*10;
	struct MsgPort *port=NULL;

	if( ac==3 ) 
		{ count=2*myatoi(av[2],0, 32000); if( atoierr ) return 20; }

	while( --count>=0 && !(port=FindPort(av[1])) && !dobreak() )
		Delay(12);

	return port ? 0 : 20;
}

int
do_rxreturn( void )
{
	rxreturn=compile_av( av, 1, ac, ' ', 1 );
	return 0;
}

do_ascii( void )
{
	int x=1, y, c, c1, t;
	char *fmt1=" %3d %c%c |", *fmt2=" %4d";

	if( options&1 ) fmt1=" %3o %c%c |", fmt2="%4o";
	if( options&2 ) fmt1=" %3x %c%c |", fmt2="%4x";
	if( ac==x )
		for( y=0; y<32 && !dobreak(); y++ ) {
			printf("|");
			for( x=0; x<8; x++ ) {
				c1=c=y+32*x; t=' ';
				if( c<32 ) t='^', c1+=64;
				printf(fmt1,c, t, c1<128 || c1>=160?c1:'.');
			}
			printf("\n");
		}
	else 
		for( ; x<ac && !dobreak(); x++ ) {
			for( y=0; y<strlen(av[x]); y++ )
				printf(fmt2,av[x][y]);
			printf("\n");
		}
	return 0;
}

void
appendslash( char *path )
{
	int c;

	if( (c=path[strlen(path)-1]) !='/' && c!=':' )
		strcat(path,"/");
}

static void
whereis( char *path, char *file )
{
	char **eav, buf[100];
	int  eac, j;

	buf[0]=0;
	if( path ) {
		strcpy(buf,path);
		appendslash(buf);
	}
	strcat(buf,".../");
	strcat(buf,file);
	if( !index( file, '*' ) && !index( file, '?') )
		strcat(buf,"*");
	if(eav=expand(buf,&eac)) {
		for( j=0; j<eac && !dobreak(); j++ )
			printf("%s\n",eav[j]);
		free_expand(eav);
	}
}

do_whereis( void )
{
	char buf[200], *prev, *devs;
	int i;

	if( index( av[1],':') || index( av[1],'/' ) )
		{ fprintf(stderr,"No paths please\n"); return 20; };

	if( options&1 ) {
		Myprocess->pr_WindowPtr = (APTR)(-1);
		get_drives( devs=buf );
		do {
			prev=devs; devs=index(devs,0xA0);
			if( devs ) *devs++=0; 
			whereis( prev, av[1] );
		} while( devs );
		Myprocess->pr_WindowPtr = (APTR) o_noreq;
	} else if( ac==2 ) {
		whereis( NULL, av[1] );
	} else {
		for( i=2; i<ac; i++ ) {
			strcpy(buf,av[i]);
			appendslash( buf );
			whereis( buf, av[1] );
		}
	}
	return 0;
}

do_usage( void )
{
	int i;

	if( ac==1 ) {
		printf("Usage: usage [command...command]\n");
		printf("[ ]=option   [ | ]=choice   { }=repetition   name...name=1 or more names\n");
	} else 
		for( i=1; i<ac; i++ )
			show_usage( av[i] );
	return 0;
}

int NumMenus;

do_menu( void )
{
	if( o_nowindow )
		return 5;

	if( options&1 )
		remove_menu();

	if( ac==2 )
		show_usage( NULL );
	else if( NumMenus<MAXMENUS && ac!=1)
		install_menu( av+1, ac-1 );

	set_menu();
	return 0;
}

#define NUMITE 40
#define TITWID 90
#define ITEWID 148

static struct Menu DefaultMenu= {0, 0,0,TITWID,10, MENUENABLED,0,0};
static struct IntuiText DefaultIntuiText= {0,1,JAM2, 1,1,NULL,0,0};
static struct MenuItem DefaultMenuItem=
  {0, 0,0,ITEWID,0, HIGHCOMP|ITEMTEXT|ITEMENABLED,0,0,0,0,0,0};

struct Menu Menus[10];
char *MenuCommand[MAXMENUS][MAXITEMS];

static void
install_menu( char *mav[], int mac )
{
	struct TextAttr *ta;
	struct Menu *m;
	struct MenuItem *mi, **pmi;
	struct IntuiText *it;
	int y, i, fonthei;
	char *p, *com;

	if( o_nowindow || !Win )
		return;

	if( mac>=MAXITEMS )
		mac=MAXITEMS-1;

	ClearMenuStrip( Win );
	Delay(3);

	if( NumMenus )
		Menus[NumMenus-1].NextMenu=Menus+NumMenus;
	m  =&Menus[NumMenus];
	*m =DefaultMenu;
	m->LeftEdge  = NumMenus*TITWID;
	m->MenuName  = strcpy(salloc(strlen(mav[0])+1),mav[0]);
	if( strlen(m->MenuName)>TITWID/8 )
		m->MenuName[TITWID/8+1]=0;
	DefaultIntuiText.ITextFont=ta=Win->WScreen->Font;
	DefaultMenuItem.Height=2+(fonthei=ta->ta_YSize);

	y=0;
	pmi=&m->FirstItem;
	for( i=1; i<mac; i++) {
		it =(void *)salloc(sizeof(struct IntuiText));
		*it=DefaultIntuiText;
		mi =(void *)salloc(sizeof(struct MenuItem ));
		*mi=DefaultMenuItem;

		com=NULL;
		if( p=index(mav[i],',')) {
			*p=0; com=++p;
			if( p=index(com,',')) {
				*p=0;
				mi->Command=p[1];
				mi->Flags |=COMMSEQ;
			}
		}

		if( !com || !*com) {
			com=strcpy(salloc(strlen(mav[i])+2),mav[i]);
			MenuCommand[NumMenus][i-1]=com;
			com+=strlen(com);
			*com++=13;
			*com=0;
		} else {
			MenuCommand[NumMenus][i-1]=strcpy(salloc(strlen(com)+1),com);
		}

		it->IText=(UBYTE *)strcpy(salloc(strlen(mav[i])+2),mav[i]);

		*pmi= mi;
		pmi = &mi->NextItem;
		mi->TopEdge = y;
		mi->ItemFill= (APTR)it;

		y+=DefaultMenuItem.Height;
	}

	NumMenus++;
MError:
	return;
}


void
remove_menu()
{
	if( NumMenus>0 ) {
		struct MenuItem *mi, *nextmi;
		int i,j;

		for( i=0; i<NumMenus; i++ ) {
			for( mi=Menus[i].FirstItem,j=0 ; mi; mi=nextmi,j++ ) {
				free( ((struct IntuiText *)mi->ItemFill)->IText );
				free( ((struct IntuiText *)mi->ItemFill) );
				nextmi=mi->NextItem;
				free(mi);
				free(MenuCommand[i][j]);
			}
		}

		NumMenus=0;
		set_menu();
	}
}


void
set_menu()
{
	if( o_nowindow || !Win )
		return;

	if( NumMenus>0 )
		SetMenuStrip( Win, Menus );
	else 
		ClearMenuStrip( Win );

	Delay(3);
}

int
do_getenv( void )
{
	char buf[256], *val=buf;

	if( ac!=3 && ac!=2 ) {
		show_usage( NULL );
		return 20;
	}
	if( !Getenv(av[ac-1],buf,256))
		val="";

	if( ac==2 )
		printf( "%s\n", val );
	else 
		set_var( LEVEL_SET, av[1], val );
	return 0;
}

int
do_setenv( void )
{
	if( ac!=3 ) {
		show_usage( NULL );
		return 20;
	} else
		setenv( av[1], av[2] );
	return 0;
}

char **
read_name( char *name, int *ac )
{
	FILE *file;
	char **av=NULL;

	*ac=0;
	if( file=name ? fopen( name, "r") : stdin ) {
		av=read_file( file, ac );
		if( name ) fclose( file );
	} else 
		pError( name );
	return av;
}


char **
read_file( FILE *file, int *ac )
{
	int buflen=4096, lines=0, i, offs;
	char *buf, *tmp, *ptr, *got=NULL, **lineptr;

	if( !(buf=ptr=DosAllocMem( buflen )))
		goto error;
	do {
		while( ptr+400 < buf+buflen && (got=fgets( ptr, 400, file ) ) &&
		       !dobreak()) {
			ptr+=strlen(ptr)-1, lines++;
			*ptr++=0;
		}
		if( ptr+256 < buf+buflen ) {
			offs=ptr-buf;
			if( !(tmp=DosAllocMem( buflen*2 )))
				goto error;
			memcpy( tmp, buf, buflen );
			DosFreeMem( buf );
			buflen*=2, buf=tmp;
			ptr=buf+offs;
		}
	} while( got && !dobreak());
	if( !(lineptr=(char **)DosAllocMem( (lines+1)*sizeof( char * ))))
		goto error;
	*lineptr++=buf;
	for( ptr=buf, i=0; i<lines; i++ ) {
		lineptr[i]=ptr;
		ptr+=strlen(ptr)+1;
	}
	*ac=lines;
	return lineptr;

error:
	if( buf ) DosFreeMem( buf );
	fprintf( stderr, "Out of memory\n" );
	*ac=0;
	return NULL;
}

void
free_file( ptr )
	char **ptr;
{
	if( ptr-- ) {
		if( *ptr )
			DosFreeMem( *ptr );
		DosFreeMem(ptr);
	}
}


do_qsort( void )
{
	char **lineptr;
	int  lines, i;

	if( ac==1 ) {
		lineptr=read_file( stdin, &lines);
		DirQuickSort( lineptr, lines, cmp, options&1, 0 );
		prepscroll(0);
		for( i=0; i<lines && !dobreak(); i++ ) {
			quickscroll();
			puts( lineptr[i] );
		}
		free_file( lineptr );
	} else
		ierror( NULL,506 );
	return 0;
}

extern int w_width;

do_truncate( void )
{
	char buf[256];
	int  w=newwidth(), c;
	char *ptr;

	if( ac==2 )
		w=atoi( av[1] );

	prepscroll(0);
	while( gets(buf) && !dobreak() ) {
		for( c=0, ptr=buf; *ptr && c<w; ptr++ )
			if( *ptr=='\t' )
				c+=8-(c&7);
			else if( *ptr==27 ) {
				while( *ptr<'@' )
					ptr++;
			} else 
				c++;
		*ptr=0;
		quickscroll();
		puts(buf);
	}
	return 0;
}

int
do_readfile( void )
{
	char **rav, *str=NULL, *file=NULL;
	int rac;

	if( ac>2 ) file=av[2];
	if( rav=read_name( file, &rac ) ) { 
		if( str= compile_av( rav, 0, rac, 0xA0, 0 ) )
			set_var( LEVEL_SET, av[1], str );
		free_file( rav );
	}
	return str ? 0 : 20;
}

void
foreach( char **s, int (*func)(char *s) )
{
	char *str;

	for( ;; ) {
		str=*s;
		if( !(*s=index(*s,0xA0)))
			break;
		**s=0;
		(*func)(str);
		*(*s)++=0xA0;
		if( breakcheck())
			return;
	}
	(*func)(str);
}

int
do_writefile( void )
{
	char *ptr;

	if( !(ptr=get_var(LEVEL_SET,av[1])))
		{ fprintf(stderr,"Undefined variable %s\n",av[1]); return 20; }

	foreach( &ptr, puts );

	return 0;
}

int
do_split( void )
{
	int i;
	char *val, *gap, *oldval;

	if( !(val=get_var( LEVEL_SET, av[1] )))
		{ fprintf( stderr, "undefined variable %s\n", av[1] ); return 20; }
	oldval=val=strcpy(salloc(strlen(val)+1),val);
	for( i=2; i<ac-1; i++ ) {
		if( gap=index(val,0xA0 )) *gap=0;
		set_var( LEVEL_SET, av[i], val );
		val="";
		if( gap ) *gap=0xA0, val=gap+1;
	}
	set_var( LEVEL_SET, av[ac-1], val );
	free(oldval);
	return 0;
}

char *
copyof( char *str )
{
	return strcpy(salloc(strlen(str)+1),str);
}

int
do_class( char *avline )
{
	CLASS *new;

	if( options&1 ) {
		avline=next_word(avline);
		for( new=CRoot,CRoot=NULL; new; new=new->next )
			Free(new);
	}

	if( ac==1 ) {
		for( new=CRoot; new; new=new->next )
			printf("%s\n",new->name);
		return 0;
	}

	avline=next_word(avline);
	if(!(new=malloc( strlen(avline)+5)))
		ierror( NULL, 512 );
	else {
		new->next=NULL;
		strcpy( new->name,avline );
		if( CRoot )
			LastCRoot->next=new;
		else 
			CRoot=new;
		LastCRoot=new;
	}
	return 0;
}

do_getcl( void )
{
	char *s=getclass(av[1]);
	if( s ) printf("%s\n",s);
	return 0;
}

do_action( char *argline )
{
	char *args, err;
	int abort=options&1;

	args=compile_av( av,3,ac,' ',0 );
	err=doaction(av[2],av[1],args);
	if( !abort )
		if( err==10 )    fprintf(stderr,"Can't identify %s\n", av[2] );
		else if(err==11) fprintf(stderr,"Can't '%s' this file\n",av[1] );
	return abort ? !err : err;
}
