
/*
 * SUB.C
 *
 * (c)1986 Matthew Dillon     9 October 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"
#include "proto.h"

static void del_history( void );
static int dnext( struct DPTR *dp, char **pname, int *stat);
static char *svfile( char *s1, char *s2, FIB *fib);
static int exall( BPTR lock, char *path );
static void quicksort( char **av, int n );


#define HM_STR 0              /* various HISTORY retrieval modes */
#define HM_REL 1
#define HM_ABS 2

void
seterr( int err )
{
	static int LastErr;
	char buf[32], *val;
	int  stat=0;

	Lastresult=err;

	if( LastErr!=err ) {
		LastErr=err;
		sprintf(buf, "%d", err);
		set_var(LEVEL_SET, v_lasterr, buf);

		if( val=get_var(LEVEL_SET, v_stat))
			stat = atoi(val);
		if (stat < Lastresult) set_var(LEVEL_SET, v_stat, buf);
	}
}

char *
next_word( char *str )
{
	while (*str && ! ISSPACE(*str)) ++str;
	while (*str &&   ISSPACE(*str)) ++str;
	return str;
}

/*
 * FREE(ptr)   --frees without actually freeing, so the data is still good
 *               immediately after the free.
 */


void
Free( void *ptr )
{
	static char *old_ptr;

	if (old_ptr) free (old_ptr);
	old_ptr = ptr;
}

/*
 * Add new string to history (H_head, H_tail, H_len,
 *  S_histlen
 */

void
add_history( char *str )
{
	struct HIST *hist;
	char *get;

	for( get=str; *get; get++ )
		if( (*get&127)<' ')
			*get=' ';

	if (H_head != NULL && !strcmp(H_head->line, str))
		return;
	while (H_len > S_histlen)
		del_history();
	hist = (struct HIST *)salloc (sizeof(struct HIST));
	if (H_head == NULL) {
		H_head = H_tail = hist;
		hist->next = NULL;
	} else {
		hist->next = H_head;
		H_head->prev = hist;
		H_head = hist;
	}
	hist->prev = NULL;
	hist->line = salloc (strlen(str) + 1);
	strcpy (hist->line, str);
	++H_len;
}

static void
del_history()
{
	if (H_tail) {
		--H_len;
		++H_tail_base;
		free (H_tail->line);
		if (H_tail->prev) {
			H_tail = H_tail->prev;
			free (H_tail->next);
		H_tail->next = NULL;
		} else {
			free (H_tail);
			H_tail = H_head = NULL;
		}
	}
}

char *
get_history( char *ptr, int echo, int occur )
{
	struct HIST *hist;
	int len;
	int mode = HM_REL;
	int num  = 1, found=0;
	char *str=NULL;
	char *result = NULL;

	if (ptr[1] >= '0' && ptr[1] <= '9') {
		mode = HM_ABS;
		num  = atoi(&ptr[1]);
		goto skip;
	}
	switch (ptr[1]) {
	case '!':
		break;
	case '-':
		num += atoi(&ptr[2]);
		break;
	default:
		mode = HM_STR;
		str  = ptr + 1;
		break;
	}
skip:
	switch (mode) {
	case HM_STR:
		len = strlen(str);
		for (hist = H_head; hist; hist = hist->next) {
			if (Strncmp(hist->line, str, len) == 0 && *hist->line != '!') {
				result = hist->line;
				if( !occur || ++found==occur )
					break;
			}
		}
		break;
	case HM_REL:
		for (hist = H_head; hist && num--; hist = hist->next);
		if (hist)
		result = hist->line;
		break;
	case HM_ABS:
		len = H_tail_base;
		for (hist = H_tail; hist && len != num; hist = hist->prev, ++len);
		if (hist)
		result = hist->line;
		break;
	}
	if( echo==1 )
		fprintf(stderr, result ? "%s\n" : "History failed\n", result);
	if( !result ) 
		if( echo==2 )
			DisplayBeep(NULL);
		else 
			result="";
	return result;
}

void
replace_head( char *str )
{
	if (str && strlen(str) && H_head) {
		free (H_head->line);
		H_head->line = salloc (strlen(str)+1);
		strcpy (H_head->line, str);
	}
}


#if 0
#define CDLEN 20
static int   cd_len=CDLEN, cd_read, cd_write, cd_current;
static char *cd_hist[CDLEN];

add_cdhist( char *str )
{
	if( !str )
		return;
	if( cd_hist[cd_write] )
		free(cd_hist[cd_write]);
	cd_hist[cd_write++]=str;
	cd_write%=cd_len;
	cd_current=cd_write;
}

char *
back_cdhist( void )
{
	if( cd_current!=cd_write ) cd_current= --cd_current % cd_len;
	return cd_hist[cd_current];
}

char *
forw_cdhist( void )
{
	if( cd_current!=cd_read  ) cd_current= ++cd_current % cd_len;
	return cd_hist[cd_current];
}
#endif

void
pError(char *str )
{
	int ierr = (long)IoErr();
	ierror(str, ierr);
}

ierror( char *str, int err )
{
	struct PERROR *per = Perror;

	setioerror(err);

	if (err) {
		for (; per->errstr; ++per) {
			if (per->errnum == err) {
				fprintf (stderr, "%s%s%s\n",
				per->errstr,
				(str) ? ": " : "",
				(str) ? str : "");
				return err;
			}
		}
		fprintf (stderr, "Unknown DOS error %d: %s\n", err, (str) ? str : "");
	}
	return err;
}

void
setioerror( int err )
{
	static int LastIoError=-1;
	char buf[20];

	IoError=err;
	if( IoError<0 ) IoError=0;
	if( LastIoError!=IoError) {
		LastIoError=IoError;
		sprintf(buf, "%d", IoError);
		set_var(LEVEL_SET, v_ioerr, buf);
	}
}

char *
ioerror(int num)
{
	struct PERROR *per = Perror;

	for ( ; per->errstr; ++per)
		if (per->errnum == num)
			return per->errstr;
	return NULL;
}

/*
 * Disk directory routines
 *
 * dptr = dopen(name, stat)
 *    struct DPTR *dptr;
 *    char *name;
 *    int *stat;
 *
 * dnext(dptr, name, stat)
 *    struct DPTR *dptr;
 *    char **name;
 *    int  *stat;
 *
 * dclose(dptr)                  -may be called with NULL without harm
 *
 * dopen() returns a struct DPTR, or NULL if the given file does not
 * exist.  stat will be set to 1 if the file is a directory.  If the
 * name is "", then the current directory is openned.
 *
 * dnext() returns 1 until there are no more entries.  The **name and
 * *stat are set.  *stat != 0 if the file is a directory.
 *
 * dclose() closes a directory channel.
 *
 */

struct DPTR *
dopen( char *name, int *stat)
{
	struct DPTR *dp;

	IoError=0;
	*stat = 0;
	dp = (struct DPTR *)salloc(sizeof(struct DPTR));
	if (*name == '\0')
		dp->lock = DupLock(Myprocess->pr_CurrentDir);
	else
		dp->lock = Lock (name,ACCESS_READ);
	if (dp->lock == NULL) {
		IoError=IoErr();
		free (dp);
		return NULL;
	}
	dp->fib = (FIB *)SAllocMem((long)sizeof(FIB), MEMF_PUBLIC);
	if (!Examine (dp->lock, dp->fib)) {
		pError (name);
		dclose (dp);
		return NULL;
	}
	if (dp->fib->fib_DirEntryType >= 0) *stat = 1;
	return dp;
}

static int
dnext( struct DPTR *dp, char **pname, int *stat)
{
	if (dp == NULL) return (0);

	if (ExNext (dp->lock, dp->fib)) {
		*stat = 0;
		if( dp->fib->fib_DirEntryType >= 0)
			*stat= dp->fib->fib_DirEntryType!=ST_USERDIR ? 2 : 1;
		*pname = dp->fib->fib_FileName;
		return 1;
	}
	return 0;
}

int
dclose( struct DPTR *dp )
{
	if (dp == NULL)
		return 1;
	if (dp->fib)
		FreeMem (dp->fib,(long)sizeof(*dp->fib));
	if (dp->lock)
		UnLock (dp->lock);
	free (dp);
	return 1;
}


int
isdir( char *file )
{
	struct DPTR *dp;
	int stat;

	stat = 0;
	if (dp = dopen (file, &stat))
		dclose(dp);
	return (stat!=0);
}


void
free_expand( char **av )
{
	char **get = av;

	if (av) {
		while (*get)
		free (*get++-sizeof(struct file_info));
		free (av);
	}
}

/*
 * EXPAND(base,pac)
 *    base           - char * (example: "df0:*.c")
 *    pac            - int  *  will be set to # of arguments.
 *
 * 22-May-87 SJD.  Heavily modified to allow recursive wild carding and
 *                 simple directory/file lookups. Returns a pointer to
 *                 an array of pointers that contains the full file spec
 *                 eg. 'df0:c/sear*' would result in : 'df0:C/Search'
 *
 *                 Now no longer necessary to Examine the files a second time
 *                 in do_dir since expand will return the full file info
 *                 appended to the file name. Set by formatfile().
 *                 eg. fullfilename'\0'rwed  NNNNNN NNNN  DD-MMM-YY HH:MM:SS
 *
 *                 Caller must call free_expand when done with the array.
 *
 * base             bname =       ename =
 * ------           -------       -------
 *  "*"               ""            "*"
 *  "!*.info"         ""            "*.info" (wild_exclude set)
 *  "su*d/*"          ""            "*"      (tail set)
 *  "file.*"          ""            "file.*"
 *  "df0:c/*"         "df0:c"       "*"
 *  ""                ""            "*"
 *  "df0:.../*"       "df0:"        "*"      (recur set)
 *  "df0:sub/.../*"   "df0:sub"     "*"      (recur set)
 *
 * ---the above base would be provided by execom.c or do_dir().
 * ---the below base would only be called from do_dir().
 *
 *  "file.c"          "file.c"      ""       if (dp == 0) fail else get file.c
 *  "df0:"            "df0:"        "*"
 *  "file/file"       "file/file"   ""       (dp == 0) so fail
 *  "df0:.../"        "df0:"        "*"      (recur set)
 *
 */

char **
expand( char *base, int *pac )
{
	char *ptr;
	char **eav = (char **)salloc(sizeof(char *) * (2));
	short eleft, eac;
	char *name;
	char *bname, *ename, *tail;
	int stat, recur, scr, bl;
	struct DPTR *dp;

	IoError = *pac = recur = eleft = eac = 0;

	base = strcpy(malloc(strlen(base)+1), base);
	for (ptr = base; *ptr && *ptr != '?' && *ptr != '*'; ++ptr);

	if (!*ptr)   /* no wild cards */
		--ptr;
	else
		for (; ptr >= base && !(*ptr == '/' || *ptr == ':'); --ptr);

	if (ptr < base) {
		bname = strcpy (malloc(1), "");
	} else {
		scr = ptr[1];
		ptr[1] = '\0';
		if (!strcmp(ptr-3,".../")) {
			recur = 1;
			*(ptr-3) = '\0';
		}
		bname = strcpy (salloc(strlen(base)+2), base);
		ptr[1] = scr;
	}
	bl = strlen(bname);
	ename = ++ptr;
	for (; *ptr && *ptr != '/'; ++ptr);
	scr = *ptr;
	*ptr = '\0';
	if (scr) ++ptr;
	tail = ptr;

	if ((dp = dopen (bname, &stat)) == NULL || (stat == 0 && *ename)) {
		free (bname);
		free (base);
		free (eav);
		return (NULL);
	}

	if (!stat) {                /* eg. 'dir file' */
		char *p,*s;
		for(s = p = bname; *p; ++p) if (*p == '/' || *p == ':') s = p;
		if (s != bname) ++s;
		*s ='\0';
		eav[eac++] = svfile(bname,dp->fib->fib_FileName,dp->fib);
		goto done;
	}
	if (!*ename) ename = "*";    /* eg. dir df0: */
	if (*bname && bname[bl-1] != ':' && bname[bl-1] != '/') { /* dir df0:c */
		bname[bl] = '/';
		bname[++bl] = '\0';
	}
	while ((dnext (dp, &name, &stat)) && !breakcheck()) {
		int match = compare_ok(ename,name,0);
		if (match && (recur || !*tail)) {
			if (eleft < 2) {
				char **scrav = (char **)salloc(sizeof(char *) * (eac + 10));
				memmove (scrav, eav, (eac + 1) << 2);
				free (eav);
				eav = scrav;
				eleft = 10;
			}
			eav[eac++] = svfile(bname,name,dp->fib);
			--eleft;
		}
		if ((*tail && match) || recur) {
			int alt_ac;
			char *search, **alt_av, **scrav;
			BPTR lock;

			if (stat!=1)           /* expect more dirs, but this not a dir */
				continue;
			lock = CurrentDir (dp->lock);
			search = salloc(strlen(ename)+strlen(name)+strlen(tail)+6);
			strcpy (search, name);
			strcat (search, "/");
			if (recur) {
				strcat(search, ".../");
				strcat(search, ename);
			}
			strcat (search, tail);
			scrav = alt_av = expand (search, &alt_ac);
			free(search);
			CurrentDir (lock);
			if (scrav) {
				while (*scrav) {
					int l;
					if (eleft < 2) {
						char **scrav = (char **)salloc(sizeof(char *)*(eac+10));
						memmove ( scrav, eav, (eac + 1) << 2);
						free (eav);
						eav = scrav;
						eleft = 10;
					}

					l = strlen(*scrav);
					eav[eac] = salloc(bl+l+1+sizeof(struct file_info));
					memcpy( eav[eac], *scrav-sizeof(struct file_info),
					sizeof(struct file_info));
					eav[eac]+=sizeof(struct file_info);
					strcpy( eav[eac], bname);
					strcat( eav[eac], *scrav);

					free (*scrav-sizeof(struct file_info));
					++scrav;
					--eleft, ++eac;
				}
				free (alt_av);
			}
		}
	}
done:
	dclose (dp);
	*pac = eac;
	eav[eac] = NULL;
	free (bname);
	free (base);
	if (eac)
		return (eav);
	free (eav);
	return (NULL);
}

char *
strupr( char *s )
{
	char *old=s;
	while (*s) *s=toupper(*s), s++;
	return old;
}

char *
strlwr( char *s )
{
	char *old=s;
	while (*s) *s=tolower(*s), s++;
	return old;
}

/*
 * Compare a wild card name with a normal name
 */

int
compare_ok( char *wild, char *name, int casedep)
{
	int queryflag;
	char buf[260], wildbuf[260], *lowname;

	if (queryflag=(*wild=='&')) wild++;
	if (*wild=='!') *wild='~';

	if (! casedep) {
		strupr(wild);
		strcpy(buf,name);
		strupr(buf);
		lowname=buf;
	} else
		lowname=name;

	PreParse(wild, wildbuf);
	if ( ! PatternMatch(wildbuf,lowname)) return 0;

	if (queryflag) {
		printf("Select %s%-16s%s [y/n] ? ",o_hilite,name,o_lolite);
		gets(buf);
		return (toupper(*buf)=='Y');
	}
	return 1;
}

static char *
svfile( char *s1, char *s2, FIB *fib)
{
	int len=strlen(s1)+strlen(s2)+1;
	char *p = salloc (len+sizeof(struct file_info));
	struct file_info *info;

	info=(struct file_info *)p;
	p+=sizeof(struct file_info);
	strcpy(p, s1);
	strcat(p, s2);
	info->flags = fib->fib_Protection;
	if( fib->fib_DirEntryType<0 ) {
		info->size  = fib->fib_Size;
		info->blocks= fib->fib_NumBlocks;
	} else {
		info->size  = -1;
		info->blocks= 0;
	}
	if( fib->fib_Comment[0] )
		info->flags|= 1<<30;
	info->date=fib->fib_Date;
	info->class[0]=1;
	return p;
}



static FILE *out;
static int NumDirs;

void
expand_all( char *name, FILE *file )
{
	BPTR lock;
	char path[300];
	FIB  *fib;

	out=file;
	printf( " %s\n", name );
	NumDirs=0;

	if(fib=AllocMem(sizeof(struct FileInfoBlock),0)) {
		if( lock=Lock( name, ACCESS_READ )) {
			strcpy( path, name );
			exall( lock, path );
			printf( "\n", NumDirs );
		}
		FreeMem(fib,sizeof(struct FileInfoBlock));
	}
}

static int
exall( BPTR lock, char *path )
{
	BPTR old, sublock;
	int len;
	struct FileInfoBlock *fib;

	old=CurrentDir( lock );

	if( !(fib=AllocMem(sizeof(struct FileInfoBlock),0)) )
		return 1;

	len=strlen( path );
	Examine( lock, fib );
	while( ExNext( lock, fib ) ) {
		if( fib->fib_DirEntryType==ST_USERDIR )
			if( sublock=Lock( fib->fib_FileName, ACCESS_READ )) {
				if( !len || path[len-1]==':' )
					sprintf(path+len,"%s",  fib->fib_FileName);
				else 
					sprintf(path+len,"/%s", fib->fib_FileName);
				fprintf( out, "%s\n", path );
				fprintf( stdout, " Directories: %d\015", ++NumDirs );
				fflush ( stdout );
				if(exall( sublock, path ))
					break;
				path[len]=0;
			}
	}
	FreeMem( fib, sizeof(struct FileInfoBlock));
	CurrentDir( old );
	return dobreak();
}



/* Sort routines */

static int reverse, factor;

int
cmp( FILEINFO *s1, FILEINFO *s2)
{
	return Strcmp( (char *)(s1+1), (char *)(s2+1) );
}

int
sizecmp( FILEINFO *s1, FILEINFO *s2)
{
	return s2->size - s1->size;
}

int
datecmp( FILEINFO *s1, FILEINFO *s2 )
{
	int r;
	struct DateStamp *d1=&s1->date, *d2=&s2->date;
	if( !(r= d2->ds_Days - d1->ds_Days))
		if( !(r=d2->ds_Minute - d1->ds_Minute ) )
			r=d2->ds_Tick - d1->ds_Tick;
	return r;
}


int
numcmp( FILEINFO *s1, FILEINFO *s2 )
{
	return atoi((char *)(s1+1))-atoi((char *)(s2+1));
}

static void
enterclass( FILEINFO *info )
{
	char *class, *iclass=info->class, *t;

	if( *iclass==1 ) {
		if( class=getclass( (char *)(info+1))) {
			strncpy( iclass, class, 11 );
			iclass[11]=0;
			if( t=index(iclass,0xA0))
				*t=0;
		} else 
			iclass[0]=0;
	}
}

int
classcmp( FILEINFO *info1, FILEINFO *info2 )
{
	int r;

	enterclass( info1 );
	enterclass( info2 );

	r= Strcmp( info1->class, info2->class );
	if( !r ) r=Strcmp((char *)(info1+1),(char *)(info2+1));
	return r;
}


void
QuickSort( char *av[], int n)
{
	reverse=factor=0;
	DirQuickSort( av, n, cmp, 0, 0 );
}

static int (*compare)(FILEINFO *, FILEINFO *);

static int
docompare(char *s1,char *s2)
{
	FILEINFO *i1=(FILEINFO *)s1-1, *i2=(FILEINFO *)s2-1;
	int r=(*compare)( i1,i2 );

	if( reverse ) r =-r;
	if( factor )  r+= factor*((i2->size<0) - (i1->size<0));
	return r;
}

#define QSORT

void
DirQuickSort( char *av[], int n, int (*func)(FILEINFO *,FILEINFO *), int rev, int fac)
{
	reverse=rev; compare=func; factor=fac;

	quicksort( av, n-1 );
}

static void
quicksort( char **av, int n )
{
	char **i, **j, *x, *t;


	if( n>0 ) {
		i=av; j=av+n; x=av[ n>>1 ];
		do {
			while( docompare(*i,x)<0 ) i++;
			while( docompare(x,*j)<0 ) --j;
			if( i<=j )
				{ t=*i; *i=*j; *j=t; i++; j--; }
		} while( i<=j );

		if( j-av < av+n-i ) {
			quicksort( av, j-av  );
			quicksort( i , av+n-i);
		} else {
			quicksort( i , av+n-i);
			quicksort( av, j-av  );
		}
	}
}

int
filesize( char *name )
{
	BPTR lock;
	struct FileInfoBlock *fib;
	int  len=0;

	if( lock = Lock (name,ACCESS_READ)) {
		if( fib=(struct FileInfoBlock *)AllocMem(sizeof(*fib),MEMF_PUBLIC)) {
			if (Examine (lock, fib))
				len=fib->fib_Size;
			FreeMem( fib, sizeof(*fib));
		}
		UnLock(lock);
	}
	return len;
}


#ifndef MIN
#define MIN(x,y) ((x)<(y)?(x):(y))
#endif

char **
and( char **av1, int ac1, char **av2, int ac2, int *ac, int base )
{
	char **av=(char **)salloc(MIN(ac1,ac2)*sizeof(char *) ), *str;
	int i, j, k=0;

	for( i=0; i<ac1; i++ )
		for( j=0, str=base ? BaseName(av1[i]) : av1[i]; j<ac2; j++ )
			if( !Strcmp(str, base ? BaseName(av2[j]) : av2[j]))
				av[k++]=av1[i];
	*ac=k;
	return av;
}

char **
without( char **av1, int ac1, char **av2, int ac2, int *ac, int base )
{
	char **av=(char **)salloc(ac1*sizeof(char *) ), *str;
	int i, j, k=0;

	for( i=0; i<ac1; i++ ) {
		for( j=0, str=base ? BaseName(av1[i]) : av1[i]; j<ac2; j++ )
			if( !Strcmp(str, base ? BaseName(av2[j]) : av2[j] ) )
				break;
		if( j==ac2 )
			av[k++]=av1[i];
	}
	*ac=k;
	return av;
}

char **
or( char **av1, int ac1, char **av2, int ac2, int *ac, int base )
{
	char **av=(char **)salloc((ac1+ac2)*sizeof(char *) ), *str;
	int i, j, k=0;

	for( i=0; i<ac1; i++ )
		av[k++]=av1[i];

	for( i=0; i<ac2; i++ ) {
		for( j=0, str=base ? BaseName(av2[i]) : av2[i]; j<ac1; j++ )
			if( !Strcmp(str, base ? BaseName(av1[j]) : av1[j] ) )
				break;
		if( j==ac1 )
			av[k++]=av2[i];
	}

	*ac=k;
	return av;
}

void
clear_archive_bit( char *name )
{
	struct DPTR *dp;
	int stat;

	if(dp = dopen(name,&stat) ) {
		SetProtection( name, dp->fib->fib_Protection&~FIBF_ARCHIVE);
		dclose( dp );
	}
}

char *
itoa( int i )
{
	static char buf[20];
	char *pos=buf+19;
	int count=4, flag=0;

	if( i<0 )
		flag=1, i=-i;

	do {
		if( !--count )
			count=3, *--pos=',';
		*--pos= i%10+'0';
	} while( i/=10 );

	if( flag )
		*--pos='-';

	return pos;
}

char *
itok( int i )
{
	static char buf[16], which;
	char *exp=" KMG", *ptr= buf+(which=8-which);

	do
		i=(i+512)/1024, exp++;
	while( i>1024 );
	sprintf( ptr,"%d%c",i,*exp);

	return ptr;
}

char *
next_a0( char *str )
{
	while( *str && (UBYTE)*str!=0xA0 && *str!='=' && *str!=',') str++;
	if( *str )
		return str+1;
	return NULL;
}

static int
gethex( char *str, int l )
{
	int i, val=0, n, c;

	if( *str=='.' ) return l==2 ? 256 : 0;

	for( i=0; i<l || !l; i++ ) {
		c=*str++;
		if     ( c>='0' && c<='9' ) n=c-'0';
		else if( c>='a' && c<='f' ) n=c-'a'+10;
		else if( c>='A' && c<='F' ) n=c-'A'+10;
		else break;;
		val=16*val+n;
	}
	return (l && i!=l) ? -1 : val;
}

strwrdcmp( char *str, char *wrd )
{
	int ret;
	char *ind=index(wrd,0xA0);

	if( ind ) *ind=0;
	ret=compare_ok(wrd,str,0);
	if( ind ) *ind=0xA0;
	return !ret;
}

int
wrdlen( char *str )
{
	char *old=str;

	while( *str && (UBYTE)*str!=0xA0 ) str++;
	return str-old;
}

char *
getclass(char *file)
{
	CLASS *cl;
	char *class, *str, *arg, *get, *buf;
	int offs, byte, len, fail;
	BPTR fh;

	if( isdir(file) ) return "dir";

	if( !(buf=calloc(1024,1))) return NULL;
	if( !(fh=Open(file,MODE_OLDFILE))) return NULL;
	len=Read( fh,buf,1023);
	Close(fh);

	for( cl=CRoot; cl; cl=cl->next ) {
		class=cl->name;
		if(!(str=next_a0(cl->name))) continue;
		while( str ) {
			if(!(arg=next_a0( str ))) goto nextclass;
			switch( *str ) {
			case 's':
				if( (offs=strlen(file)-wrdlen(arg))<0 ) break;
				if( !strwrdcmp(file+offs,arg)) goto found;
				break;
			case 'n':
				if( !strwrdcmp(BaseName(file),arg) ) goto found;
				break;
			case 'd':
				goto found;
			case 'o':
				offs=gethex(arg,0);
				if( !(arg=index(arg,','))) goto nextclass;
				if( offs>len-10 ) break;
				for( get=buf+offs, ++arg; (byte=gethex(arg,2))>=0; arg+=2 )
					if( (UBYTE)*get++!=byte && byte!=256 )
						goto nexttry;
				goto found;
			case 'c':
				if( !len )
					goto nexttry;
				for( get=buf, fail=0; get<buf+len; get++ )
					if( *get<9 || *get>13 && *get<32 || *get>127  )
						fail++;
				if( fail*8>len )
					goto nexttry;
				goto found;
			case 'a':
				goto nextclass;
			default:
				goto nextclass;
			}
nexttry:	str=next_a0(arg);
		}
nextclass: ;
	}

	free(buf);
	return NULL;

found:
	free(buf);
	return (char *)class;
}

char *
getaction( char *class, char *action )
{
	CLASS *cl;
	char *cur, *ind;
	int len;

	for( len=0; class[len] && (UBYTE)class[len]!=0xA0; len++ ) ;
	for( cl=CRoot; cl; cl=cl->next ) {
		if( strncmp( cur=cl->name,class,len+1 ))
			continue;
		do
			cur=index( cur,0xA0 );
		while( cur && *++cur!='a');

		if( cur && (cur=index( ++cur,0xA0 ))) {
			do {
				if( !(ind=index( ++cur,'=' )))
					return NULL;
				len=ind-cur;
				if( len==strlen(action) && !strncmp(action,cur,len))
					return ++ind;
			} while( cur=index(cur,0xA0) );
		}
	}
	return NULL;
}

int
doaction( char *file, char *action, char *args )
{
	char *class, *com, *c, *copy, *spc=index(file,' ');

	if( !(class=getclass(file)))
		return 10;
	if( !(com=getaction(class,action)))
		return 11;
	if( c=index(com,0xA0) )
		*c=0;
	copy=salloc( strlen(com)+strlen(file)+strlen(args)+7 );
	sprintf(copy,spc?"%s \"%s\" %s":"%s %s %s", com, file, args);
	execute(copy);
	free(copy);
	if( c )
		*c=0xA0;
	return 0;
}

void *
salloc( int len )
{
	void *ret;

	if( !len ) len++;

	if( !(ret=malloc(len))) {
		fprintf(stderr,"Out of memory -- exiting\n");
		main_exit( 20 );
	}
	return ret;
}

void *
SAllocMem( long size, long req  )
{
	void *ret;

	if( !(ret=AllocMem(size,req))) {
		fprintf(stderr,"Out of memory -- exiting\n");
		main_exit( 20 );
	}
	return ret;
}
