/******************************************************************************
 *
 * 	METAL/Z-Msg library functions
 *
 *	FILE: MELIB.C
 *
 *
 *	Z-Msg, Metal and Metal Message System are Trademarked and
 *		   Copyright (c) 1984,1985,1986 Tim Gary
 *			    All rights reserved.
 *
 *
 *	Common utility finctions used in METAL/Z-MSG
 *
 ******************************************************************************
 *
 * 1.50xx 06/12/86 Added ^K enable/disable in TYPE funciton..
 * 1.50xx ??/??/86 Added ! options for typing files.. user type only displays
 * 1.50xx 04/12/86 Put SQ stuff in.. works ok now..
 * 1.50xx 04/06/86 Generalized library stuff..
 * 1.50xx 03/10/86 Echo toggle fixed for middle of lines, users stuff
 *		   cut down in size..
 * 1.40xx 02/13/86 Time on system at command prompt fixed..
 *        02/11/86 Time on system today based on current+total today...
 *        02/10/86 Added library type function (cheapie!)
 *        02/04/86 Fix time stuff (again), add user file routines..
 *        01/26/86 timefix moved here, same with new dateasc/timeasc functions.
 *        01/25/86 Changed and added user-type, and time/date stuff..
 *------
 * 1.31a  10/13/85 Release version.  Includes the following changes:
 *		   link_prog(name), go_os(f), and hangup() routines.
 *		   New method of reading the clock (use asm insert routines)
 *		   Real tabs for typed text (8 char stops).
 *		   ^E echo surpress.  ^O abort in typing files.
 *		   Run com file on exit from bbs (bye or cpm).
 *		   General code cleanup.
 * 1.30xx 7/07/85  QX10 routine fixed...
 *        7/01/85  Echo toggle with ^E implemented..
 *   T    6/23/85  Sysop ^P thing for list output added, new qx10 clock stuff.
 *   E    6/19/85  Findbye put back..
 *   S    6/12/85  Modified new date routine to check if ;y was entered earlier
 *   T    6/09/85  Enf of line bell every other char after linelen-8
 *   I    5/02/85  Modified Date question to only accept y/n answers.
 *   N    3/07/85  LinkBye added for use in cpm 8/16 systems for hanging up.
 *   G    3/04/85  LF's converted to spaces in getl..
 *        2/26/85  ZCPR3 Support added, and old RESETDRIVE stuff deleted.
 *------
 * 1.20b 01/13/85  MM58167A Kenmore Computer Tech. clock board support added.
 *                 Time compare added.  Set getl to ignore linefeeds...
 *                 Fix type to strip bit 8 (for WS files), getchar also strips.
 *                 New clock routines added.. CCS and QX10.
 *
 * 1.20a 11/04/84  Counters routines made less dependant upon right info.
 *------
 * 1.10e 11/02/84  Added to pcounters routine, now wil write to specified file
 *		  if arg!=0.  Put writestat routine here, for easy access.
 * 	 	  Parsing routine fixed to zap trailing blanks in ask func.
 *		  Future note: CAN'T change in getline!!!!!!!!
 *
 * 1.10c 10/0x/84 Hayes clock date order changed to reasonable mm/dd/yy form.
 *
 * 1.10b <none>   (testing only version)
 * 1.10a  8/31/84 Overlay stuff started.
 *------
 * 1.01a  6/27/84 Multi user stuff inserted.  Setup for Aztec C 1.06.
 *		  CCS clock stuff added.
 *
 * 1.0c   5/17/84 Added Hayes clock support.
 *
 * 1.0b   4/20/84 Added usindex() (index ignoring case), distext()
 * 	 	  Findbye routine setup..
 * 	          Added Upper/lower case routines.  Added Novice user help.
 *		  Putchar returns char typed at [More] prompt for checking.
 *		  Removed several MENTER only functions (putcaller, writestat)
 *		  External height variable used for screen height now.
 *
 * 1.0a	  1/25/84  Added terminal height code.  Now pauses with [more] prompt
 *
 * 1.0	  1/17/84  Version 3.0a new format of files.
 *
 *****************************************************************************/

#include "xpm.h"	/* CPMIO header file for i/o operations.*/
#include "megen.h"	/* general defines		*/
#include "meglob.h"	/* global variable definitions	*/
#include "meovfn.h"	/* overlay function numbers	*/
#include "mefiles.h"	/* file names			*/

#include "ctype.h"

static char line=1;	/* current line (relative to last input)	*/
static int col_pos=0;	/* position on line 				*/
static int *squeezed=NULL;  /* sqeezed flag..  if != 0, then ptr to alloc */
static int break_disable; /* flag to check for break or not for file */

/* return pointer to combined location+filename, based on passed offset */

char *makefn(n)
 int n;
{
static char filename[18];

rst_sq();		/* Reset this most of the time */
sprintf(filename,"%s%s",O.file_locs[n],files[n]);
return filename;	/* return pointer to it */
}


/********************************
 * Return pointer to user type  *
 ********************************/

u_types *get_tp(t_char)
 char t_char;
{
register int i;

for (i=0; O.user_type[i].type!='\0'; i++) 
	if (O.user_type[i].type==t_char) break;

if (O.user_type[i].type=='\0') --i;	/* return last real type */

return &(O.user_type[i]);	/* return pointer */
}


getw(fil)
 FILE *fil;
{
register int n;

n=getrawc(fil);
n|=(getrawc(fil)<<8);

return n;
}


getrawc(fil)
 FILE *fil;
{
register int i;

i=getc(fil);

if (i!=EOF) return i;
if (eobuf(fil)) return EOF;
return 0x1a;	/* if EOF flag, but not physicl eof, return logical eof */
}



/********************************
 * type a file, stop on a break *
 ********************************/

type(filename)
 char *filename;
{
FILE *fil;

if ((fil=open(filename,F_RD | F_UNLOCK))==NULL) return ERROR;  /* open text file for read */

type_from(fil);		/* type file from current location	*/
close(fil);

return NULL;
}


type_from(fil)
 FILE *fil;
{
register int cfast;
char u_stats[15];

*u_stats='\0';		/* clear this */

/* first initialize buffer, and check for squeezed file	*/

if (read(fil,1)!=128) return ERROR;
if (sq_init(fil)==ERROR) return ERROR;

novhelp();	/* print abort help */

while ( ( cfast=tgetc(fil) )!=EOF )
	{
	cfast&=0x7f;	/* strip high bit so doc mode ws files work */

	if (!col_pos && cfast=='!')	/* if column 0 and '!' */
		{
		register int i,n;
		for (i=0;
		    ((n=tgetc(fil))!=EOF) && index("+snxXabcdefKk",(char)n);
		    i++)
			{
			if (tolower((char) n) == 'k') break_disable ^= TRUE;
			 else u_stats[i]=n;
			}
		u_stats[i]='\0';		/* terminate string */
		if (n==EOF) break;		/* abort if end of file here */
		do cfast=tgetc(fil);
		  while (!isspace(cfast));	/* until we hit white space */
		}

	if (cfast!='\r')
	   {
	   if ( !(*u_stats) || index(u_stats,user.status))
	     if ( bcputchar(cfast)==ERROR || (globalchar&0x1f)==0x0f)
		{
		if (!break_disable) {
			globalchar='\0';
			break;
			}
		}
	   }

	}

rst_sq();		/* Reset squeezed flags if any */
break_disable=FALSE;
return NULL;

}


tgetc(fil)
 FILE *fil;
{
if (squeezed) return sq_getc(fil);
 else return getc(fil);
}

/*  Library version of type routines.. */

typedef struct {
	char	file_stat;	/* 00=file here.. ff=empty	*/
	char	file_name[11];	/* filename (like fcb setup)	*/
	int	seek;		/* seek location in file	*/
	int	length;		/* number of records in file	*/
	char	pad[16];	/* crc/pad..			*/
	} lib_fcb;

static FILE *l_fd=0;		/* init to point to nothing..	*/
static char l_name[18];		/* name of last library file	*/

FILE *lopen(libname,mname)
 char *libname;		/* library name */
 char *mname;		/* member name  */
{
char name[12];		/* member name after fix */
lib_fcb *l_fcb;
int ndrecs;		/* number of directory records to scan	*/
register int i,n,flag;

if (!(*l_name) || strcmp(libname,l_name))  /* no match.. close old, open new */
	{
	if (l_fd) close(l_fd);
	if ( !(l_fd=open(libname,0)) )	/* error on open */
		{
		*l_name='\0';		/* clear for next */
		return NULL;
		}
	strcpy(l_name,libname);	/* make new name */
	}

/* library open..  scan for member.. and display (like type) */

if (index(mname,'.') || strlen(mname)!=11)
	{
	fcbinit(mname,buffer);
	strncpy(name,buffer+1,11);
	}
   else strncpy(name,mname,11);

name[11]='\0';

setarec(l_fd,0);	/* make sure we are at begining	*/
l_fcb=bufloc(l_fd);
read(l_fd,0);
ndrecs=l_fcb->length;

flag=TRUE;
for (i=0; i<ndrecs && flag; i++)
	{
	read(l_fd,1);
	for (n=0; n<4; n++)
		{
		l_fcb=bufloc(l_fd)+(n*32);
/*
printf("%11s\n",l_fcb->file_name);
*/
		if ( !(l_fcb->file_stat) &&
		     !strncmp(l_fcb->file_name,name,11))
			{
			flag=FALSE;
			break;
			}
		}
	}

if (flag) return NULL;		/* member not found */

setarec(l_fd,l_fcb->seek);	/* seek to start of member text   */

return l_fd;
}


lclose(f)
 FILE *f;
{
if (*l_name) close(l_fd);
rst_sq();
*l_name='\0';
}


ltype(libname,mname)
 char *libname;		/* library name */
 char *mname;		/* member name  */
{

if (!lopen(libname,mname)) return ERROR;	/* not found */
type_from(l_fd);		/* type from here on... (til EOF) */

/* we leave library files open until next time */

}


/*******************************************************
 * Stuff for typing squeezed files..
 *******************************************************/

static int rept_count,l_sqchar,c_sqchar,bit_pos;

sq_init(fil)		/* check for sq, setup tables if so */
 FILE *fil;
{
register unsigned u,t;

u=getw(fil);

if (u!=0xff76)		/* magic squeezed file number? */
	{
	setbuf(fil,0);	/* position back to start of getc buffer */
	return;		/* and return */
	}

setbuf(fil,4);		/* skip to filename	*/
while (getc(fil)!=0);	/* skip filename	*/

u=getw(fil);	/* get number of node entries */

if (!( squeezed=malloc(u*sizeof(int)*2) ) ) return ERROR; /* can't alloc tbl */


/* printf("\nNumber of table entries: %d\n",u);	*/


for (t=0; t<u; t++)	/* fill in table.. */
	{
	squeezed[t<<1]=getw(fil);
	squeezed[(t<<1)+1]=getw(fil);
	}


/* printf("Done reading table.\n"); */

rept_count=l_sqchar=c_sqchar=0;		/* clear these */
bit_pos=8;				/* set this    */

return 0;	/* return all clear */
}


/* get a squeezed character, and decode */

sq_getc(fil)
 FILE *fil;
{
register int c,ret_ch;			/* returned character value */

if (rept_count>0)
	{
	rept_count--;
	ret_ch=l_sqchar;
	}
   else {
	c=sq_func(fil);
	if (c==0x90)
		{
		if (rept_count=sq_func(fil))
			{
			rept_count-=2;
			ret_ch=l_sqchar;
			}
		  else ret_ch=0x90;
		}
	  else ret_ch=l_sqchar=c;
	}

return ret_ch==0x1a ? EOF : ret_ch;
}


sq_func(fil)		/* decode current or next char */
 FILE *fil;
{
register int i=0;

do {

   if (++bit_pos>7)
	{
	c_sqchar=getrawc(fil);
	bit_pos=0;
	}
     else c_sqchar>>=1;

   i=squeezed[(i<<1)+(c_sqchar & 1)];

   } while (i>=0);

i=0-(i+1);
return i ? i : 0x1a;

}


rst_sq()	/* clear squeezed flag, and dealloc ptr to alloc area */
{
if (squeezed>(char *)1) free(squeezed);		/* 1 is correct number! */
squeezed=NULL;
}


/*******************************************************
 * display text from pointer to array of char pointers *
 *   (eg   static char *it_hlp[] = { "1","2","3",0 }; )*
 *******************************************************/

dis_text(sp)
 register char **sp;
{
while (*sp && (send(*sp++)!=ERROR));
}


/* A one liner to help novices out on the system */

novhelp()
{
if (!(user.flags&EXPERT))	/* if not expert, print msg */
	send("\n  ** Control-K to abort, Control-S to pause **\n");
}


/* Regular old putchar, but checks for break (^K, etc..) chars. 
   Returns ERROR if one hit, else it returns the char		*/

bcputchar(ch)
 int ch;
{
register int a;

if (!breakkey())
	{
	if ( ( (a=toupper(putchar(ch))) & 0x1f)==0xb ) a=ERROR;
	}
  else a=ERROR;

return a;
}


send(s)		/* send string while checking for break */
register char *s;
{
while (*s) if (bcputchar(*s++)==ERROR) return ERROR;;
return 0;	/* else ok */
}

/****************************************
 * Print string to printer (LST:)	*
 ****************************************/

print(str)
 register char *str;
{
while (*str) bdos(5,*str++);
}


/********************************
 * check for ^k ^x, ^s, etc..	*
 ********************************/

breakkey()		/* check for break */
{
register int key;

if (key=bdos(6,0xff))
	{
	int tc;
	tc=key&0x1f;
	if ( tc==0x0b || tc==0x18)
		{
		putchar('\n');
		return ERROR;
		}
	if (tc==0x13)	/* pause if ^S */
		getd();
	globalchar=key;		/* save the char for later reference */
	}
return 0;	/* none pressed */
}


/******************************************************************************
 * ask: get string after prompting user.
 *
 *	question= prompt string (will NOT be printed if ';' used
 *		to get mult. commands/options..
 *	str	= place to put string
 *	maxchars= max # of chars to be accepted from terminal
 *	options	= following BITs that may be combined:
 *		UPLOW	: accept upper/lower case input
 *		UP	: upper case conversion is made
 *		NOECHO	: turn off display of chars as they are entered
 *		NUMBER	: a 'position' number is output instead of chars typed
 *		MSGLINE	: ONLY FOR getl(*).. for it to accept MAXMSGLINE chars
 *		NOTOS   : don't print time on system at prompt
 *
 *****************************************************************************/

ask(question,str,maxchars,options)
 char *question,*str;
 int maxchars;
 register char options;
{
int mins;
static char sepchars[3]={ ';', '\0', '\0' };	/* string of seperator chars */
       char *cp;
       char *sepcp=0;

sepchars[1]=sepstr;	/* make string of 1 or 2 seperator chars */

if (!(options&NOTOS)) mins=check_tos();	/* get time on sys today */

if ((strloc==0) || (glbstr[strloc]=='\0'))
	{
	if (O.RTC && !(options&NOTOS))
		{
		if (*question=='\n')
			printf("\n[%d mins] %s",mins+user.minutes,question+1);
		  else printf("[%d mins] %s",mins+user.minutes,question);
		}
	  else send(question);
	getl(glbstr,options);			/* get the line */
	strloc=0;				/* make sure of this */
	}

/* see if seperator in string */

for (cp=&glbstr[strloc]; *cp && !(sepcp=index(sepchars,*cp)); cp++);

if (sepcp)
	{	/* change sepcp into ptr to actual text line */
	sepcp=index(&glbstr[strloc],*sepcp);
	*sepcp='\0';	/* terminate at seperator character */
	}

if (strlen(&glbstr[strloc])>=maxchars)
	glbstr[strloc+maxchars]='\0';

if (options & UP) upcase(&glbstr[strloc]);	/* all upper case if spec'd */

strcpy(str,&glbstr[strloc]);		/* copy it to where it's wanted */

if (sepcp) strloc=(sepcp-glbstr)+1;	/* updates strloc if ';' */
	else strloc=0;			/* else start over */

}	/* ask */


/* read a line of input from console */

getl(s,options)
 char *s,options;
{
register int c;
register int llen;
int loop_flag;
char *temp;
register int count;

if (options&MSGLINE) llen=O.MAXMSGLINE;
	else llen=MAXLINE;

temp=s; count=0;

if (user.flags&BELL) putchar(7);	/* ring bell */

loop_flag=TRUE;
while ((c=getd()) != '\r' )
	{
	if (!echo_flag) options|=NOECHO;  /* force no echo if this set */
	if (c=='\n') c=' ';		  /* convert a linefeed to a space.. */
	if (c=='\b' || c==0x7f)
		{			  /* backspace or DEL */
		if (!count) continue;
		--count;
		--s;
		if (!(options & NOECHO) || (options & NUMBER)) send("\b \b");
		continue;
		}
	if (c>=' ')			  /* tab goes in directly */
		{
		*s++=c;
		if (count<llen)
			++count;
		  else  loop_flag=FALSE;  /* line to long, find last space or punc */
		/* test end of line for wrap.. */
		if (count>=llen-9 && (isspace(c) || (ispunct(c) && c!='\'')) ) loop_flag=FALSE;
	/*	if (count>=llen-8 && (count&1)) */  /* bell every other char */
	/*	putchar(7);  */
		}
	if (c==9)
		{
		if (count+16<llen)
			{
			if ( !(col_pos % 8) ) c=8;
			  else c=(8-col_pos)&7;
			while (c--) { ++count; *s++=' '; }   /* fill spaces */
			c=9;
			}
		  else c=' ';
		}
	if ((c==24) || (c==21))
		{			/* ^X or ^U cancels line */
		send("#\n");
		count=0; s=temp;	/* clear buffer (effectively) */
		}
	else if (!(options & NOECHO))
		{
		if (c==9 || c>=' ') putchar(c);
		}
	     else if ((options & NUMBER) != 0) putchar( (count % 10)+'0');

	if (!loop_flag) break;
	} /* while */
*s='\0';	/* end of string */
putchar('\n');
return temp;
}


/**************
 * New putchar that counts columns
 *************/

putchar(ch)
 register int ch;
{
register int i,c;

c=NULL;	/* returned character for MORE prompt */

if (ch < ' ')
	{
	switch (ch) {
	   case	'\t':
		     for (i = 0;  i <= (col_pos+8)-((col_pos+8) & 0xf8);  i++)
		     putchar(' ');
		     return c;		/* quicky return */
	   case	'\n':
		     col_pos=0;
		     line++;
		     outc('\r');
		     break;
	   case	'\r':
		     col_pos=0;
		     break;
	   case	'\b':
		     --col_pos;
		     break;
		}  /* switch */
	}  /* control char check */
  else col_pos++;

  outc(ch);	/* output character.. */

if (height && line>=height)
	{
	new_page();	/* MUST BE HERE OR RECURSIVE PUTCHAR WILL CROKE */
	if (user.flags&EXPERT)
		{			/* don't print much if expert */
		printf("  ");
		c=getd();
		putchar('\r');
		}
	  else	{
		printf("[Press RETURN to continue]");
		c=getd();		/* get char which clears lines */
		putchar('\n');
		}
	}

return c;
} /* new putchar */


outc(ch)
 register int ch;
{
bdos(6,ch);
if (print_flag==ON) bdos(5,ch);		/* if printer flag on, write to it */
}


/* new getchar to clear line variable, etc..	*/

getchar()
{
register int cfast;

cfast=getd();
if (echo_flag) putchar(cfast);

return cfast;
}

getd()
{
register int cfast;
new_page();		/* reset line counter */
cfast=c_wait();

if (user.status==SYSOP)
    if (cfast==0x10)	/* ^P */
	{
	if (print_flag) print_flag=OFF;
		else print_flag=ON;
	return c_wait();
	}

if (cfast==5)		/* ^E */
	{
	if (echo_flag) echo_flag=OFF;
		else echo_flag=ON;
	return c_wait();
	}
return cfast;
}

c_wait()
{
register int cfast;
while(!(cfast = (bdos(6,0xff) & 0x7f) ) );
return cfast;
}


new_page()
{
line=1;
}


/* New readclock routine... */

/* this one requires the config program to select a clock routine
   out of several supplied files..  the user may create their own
   clock code, etc..							*/

readclock()
{
int (*clock_fn)()=0x0212;	/* location of actual routine */
usr **u_p=0x0207;		/* pointer to pointer of user structure */
char *rtcbuf=0x0209;		/* buffer returning hr,mi,se,yr1,yr2,mo,da */

*u_p=&user;			/* pass pointer to user variable */
if (*(rtcbuf-5)) (*clock_fn)();	/* call it if installed */
 else O.RTC=NOCLOCK;		/* otherwise set this for later */

movmem(rtcbuf,time,2);
*date=rtcbuf[5]; *(date+1)=rtcbuf[6]; *(date+2)=rtcbuf[4];
}


char *asctime(t)	/* convert internal format time ascii */
 char *t;
{
static char timestr[9];	/* watch out!!! don't call routine twice in one func */
int c;

c=*t;
if (c>=0x12)
	{
	c=( (*t & 0xf0) >> 4 ) * 10 + (*t & 0xf);
	if (c!=12) c-=12;
	sprintf(timestr,"%2d:%02x pm",c,(int)t[1]);
	}
 else {
      if (c==0) c=0x12;
      sprintf(timestr,"%2x:%02x am",c,(int)t[1]);
      }

return timestr;
}

char *ascdate(d)
 char *d;
{
static char datestr[9];
sprintf(datestr,"%02x/%02x/%02x",(int)d[0],(int)d[1],(int)d[2]);
return datestr;
}

timeasc(at,t)
 char *at,*t;
{
*t=xtou(at);			/* get first two hex digits */
if (*t==0x12) *t=0;
if (index(at,'p')) *t+=0x12;	/* if it's pm, at 12 to hour */
t[1]=xtou(at+3);		/* get minutes */
}

dateasc(ad,d)
 char *ad,*d;
{
*d=xtou(ad);
d[1]=xtou(ad+3);
d[2]=xtou(ad+6);
}

/* convert ascii hex into unsigned */

unsigned xtou(s)
 char *s;
{
unsigned u=0;

while(*s && isspace(*s)) ++s;	/* ignore leading blanks */

while (isxdigit(*s))
	{
	u<<=4;	/* shift previous result by one hex digit (*16) */
	if (tolower(*s)>='a' && tolower(*s)<='f') u+=(tolower(*s++)-'a')+10;
	 else u+=(*s++)-'0';
	}

return u;
}


getdate()
{
if (O.RTC!=NOCLOCK) readclock();
return date;
}


/******************************************************************
 * compare times, return minutes difference.  Works for 47:59 hours.
 * passed: current time, compare time, current date, compare date
 ******************************************************************/

timecomp(t1,t2,d1,d2)
 char *t1,*t2,*d1,*d2;
{
register int mins1,mins2;

mins1=bcdtodec(t1[1])+(bcdtodec(*t1)*60);
mins2=bcdtodec(t2[1])+(bcdtodec(*t2)*60);
/* if dates have changed. Add a day */
if (strncmp(d1,d2,3)) mins1+=1440; /* this works since no 0 vals in date */

return mins1-mins2;
}	


bcdtodec(c)
 char c;
{
return (int)((c&0x0f)+((c&0xf0)>>4)*10);
}


check_tos()
{
int mins=0;
int tm;

if (O.RTC)
	{
	readclock();	/* get time */
	mins=timecomp(time,user.time,date,user.date)+user.minutes;
	tm=user.type_ptr->minutes;
	if (tm)
	   {
	   if (mins>tm)
		{
		send("\n[Time Limit Expired]\n");
		hangup(YES);	/* hangup after printing copyright/etc */
		}
	     else if (mins>(tm-3))
		     send("\n[Your time on the system is almost up]\n");
	   } /* if tm */
	}

return mins-user.minutes;
}


/************************************************************************
 * String functions...							*/


/* convert string to all upper case */

upcase(str)
register char *str;
{
for (;*str; str++) *str=toupper(*str);
}


/* check for a space or control char in a string
   returns TRUE if space or control, else returns FALSE */

isspc(str)
register char *str;
{
while(*str) if (*str++<=' ') return TRUE; /* return TRUE if space/cntrl */
return FALSE;	/* else FALSE returned */
}


/* capitalize a string (all words in the string */

capstr(str)
 char *str;
{
register char *tp;

*str=toupper(*str);	/* first char is easy */

for ( tp=str; tp<str+strlen(str); ++tp )
	{
	if (*tp==' ') if (*(tp+1)!='\0') *(tp+1)=toupper(*(tp+1));
			else break;
	}
}


/* case independent string compare function */

ustrcmp(s1,s2)
 register char *s1,*s2;
{
for ( ; toupper(*s1)==toupper(*s2); s1++, s2++)
	if (*s1=='\0') return (toupper(*s1)-toupper(*s2));
			/* if at end of first string, test for=length/return */

return (toupper(*s1)-toupper(*s2));
}

/* case independent string compare function */

ustrncmp(s1,s2,n)
 register char *s1,*s2;
 register int n;
{
for ( ; (toupper(*s1)==toupper(*s2)) && (n>0); s1++, s2++, n--)
	if (*s1=='\0') return (toupper(*s1)-toupper(*s2));
			/* if at end of first string, test for=length/return */

return (n ? (toupper(*s1)-toupper(*s2)) : 0);
}


/*****************************
 * Same as index(s,c), but with two strings
 *****************************/

sindex(s1,s2)
 register char *s1,*s2;
{
 register char *sp;

for (sp=s2; strlen(s1)<=strlen(sp); sp++)
	if (!strncmp(s1,sp,strlen(s1))) return sp;

return 0;	/* no match */
}


/*****************************
 * Same as sindex(s1,s2), but IGNORE CASE in comparison
 *****************************/

usindex(s1,s2)
 char *s1,*s2;
{
 register char *sp;

for (sp=s2; strlen(s1)<=strlen(sp); sp++)
	if (!ustrncmp(s1,sp,strlen(s1))) return sp;

return 0;	/* no match */
}


/* Hangup the phone (exit system..) if value passed!=0 print signoff msg */

hangup(flag)
 int flag;
{
char *zero=0;

if (flag)
     printf("\n%s (TM)\nCopyright (c) 1984,1985,1986 Tim Gary\nAll rights reserved.\n\n",O.BBSNAME);

if (strcmp(BYE,BYECOM))	link_prog(BYE);		/* run prog if name changed */
if (bye!=nothing) *zero=0xcd;

exit(0);

}


/*  link_prog...   This routine loads and runs a com file..
 */

link_prog(name)
char *name;
{
setmem(0x5c,0x24,0);
setusr(fcbinit(name,0x5c));

#asm
;
;	This is taken from mentr.asm routine..
;
; The routine moves itself into the buffer at 80h, and
; procedes to load the program that is called for...
;
	ext	ostack		; place where old stack is kept
;
	lxi	h,ostack
	sphl
	pop	h
	lxi	h,0
	push	h		; make sure top of stack is zero

	mvi	c,0fh		; open code..
	lxi	d,005ch		; fcb
	call	0005		; bdos
	cpi	0ffh		; error?
	jz	0		; yes boot...
;
	lxi	d,loader
	lxi	h,0080h		; dma..
	mvi	c,07fh
movloop:
	ldax	d
	mov	m,a
	inx	d
	inx	h
	dcr	c
	jnz	movloop
	jmp	080h		; jump to loader...
;
loader:
	lxi	d,100h		; tpa
load1:
	push	d
	mvi	c,1ah		; set dma..
	call	0005		; bdos
	lxi	d,005ch		; fcb
	mvi	c,14h		; read seq.
	call	0005		; bdos
	pop	d
	ora	a		; error?
	jnz	80h+(done-loader)	; if read error, finish up..
; bump the dma counts by 80h
	lxi	h,0080h
	dad	d
;	
	xchg
	jmp	80h+(load1-loader)	; jr loader
;
; the file has presumably been read.. so run it..
;
done:	mvi	c,1ah		; set dma
	lxi	d,0080h		; reset default dma address..
	call	0005		; bdos
;
	mvi	c,10h		; close
	lxi	d,05ch
	call	0005
;
	jmp	100h		; tpa.. (program just loaded)
;
lend:	db	0
;
#endasm

}


/******************************************************************
 * initialize the bye variable to point to location if BYE active */

findbye()
{
register char *p;

bye=(*(char *)2)*256+*(char *)1-2;
bye=(*(char *)(bye+1))*256+*(char *)bye+6;

/* bye keeps mucking everyone up by changing the locations of strings */
for (p=bye; p<=bye+35; p++)
	if (!ustrncmp("bye",p,3)) break;

if (p>=bye+25) bye=nothing;

/* now for the values that we use the bye variable for.. */
maxuser=bye;
maxdrive=bye+1;
tout=bye+2;
nulls=bye+3;
}


/*******************************************************
 * Users file utility routines... With hash file lookup
 *
 * Returns <0 value if error, else users seek loc
 *******************************************************/

find_user(name)
 char *name;
{
int record,del_rec=0;
usr *up;

record=read_hash(name);		/* get record pointed to by hash file */

if (record==ERROR) return HASHEMPTY;	/* nobody in hash table */

up=bufloc(users);		/* point to user structure being read */

do {
   setarec(users,record);
   read(users,0);
   record=up->number;		/* point to NEXT record in hash list.. */
   if ( (up->file_info)&DEADUSER ) del_rec=getrec(users);
   } while ( ustrcmp(up->name,name) && (up->file_info&MOREHASH));

/* We are now ALWAYS at the end of the hashed name list, or deleted user... */

if ( ustrcmp(up->name,name) )
	{
	if (del_rec) setarec(users,del_rec);
	return ERROR;				/* not found in hash list */
	}

return getrec(users);	/* return seek location if found */
}


read_hash(name)
 char *name;
{
FILE *hashfile;
unsigned hashval;
int *ip;

if ( !(hashfile=open_hash()) ) return ERROR;

hashval=(hash_fn(name)%HFSIZE);
setarec(hashfile,(hashval/64));	/* seek to record determined by hash_fn() */
read(hashfile,0);		/* read the record */

ip=bufloc(hashfile);
hashval=ip[hashval%64];		/* reuse hashval for record returned */
close(hashfile);

return hashval;
}


/* Return a value computed from the string passed.. */

unsigned hash_fn(s)
 char *s;
{
register unsigned val=0;
register unsigned i;

for (i=0; *s; i++,s++) val=val*(i*toupper(*s))+i; 	/* new */

return val;
}


/* Open hash file..  if it doesn't exist, create one.. (slow) */

FILE *open_hash()
{
FILE *fp;
int *ibuf,lrec;
register unsigned i;
usr *up;

#if FALSE
/* testing only.. */
int goodcount=0;
int badcount=0;
#endif

if (fp=open(HASHFILE,F_RD)) return fp;		/* exists already..  */
if ( !(fp=open(HASHFILE,F_RW)) ) return NULL;	/* couldn't open at all */

send("\n[Please wait, creating hash file]\n");

ibuf=malloc((HFSIZE<<1)+16);	/* Allocate table space to build hash file */

for (i=0; i<HFSIZE; i++) ibuf[i]=ERROR;		/* fill hash table with -1 */

up=bufloc(users);
for (lrec=0; ((setarec(users,lrec)),read(users,0))==128; lrec++)
	{
	i=hash_fn(up->name)%HFSIZE;
	if (ibuf[i]==ERROR)
#if FALSE
		{
		++goodcount;
		putchar('+');	/* indicate nice one */
#endif
		ibuf[i]=lrec;   /* not in table.. put it there.. */
#if FALSE
		}
#endif
	 else {			/* find end of hash trail */
	      int record;
	      record=ibuf[i];
#if FALSE
	      ++badcount;
	      putchar('#');	/* indicate bad guy */
#endif
	     do {
		setarec(users,record);
		read(users,0);
   		record=up->number;
		} while (up->file_info&MOREHASH);
	     up->file_info|=MOREHASH;		/* set this */
	     up->number=lrec;			/* and pointer to next */
	     write(users,0);			/* resave */
	     setarec(users,lrec);
	     read(users,0);
	     }  /* ERROR else */

	up->number=0;			/* clear next hash ptr */
	up->file_info&=(char)(~MOREHASH);
	write(users,0);			/* write it back */

	}  /* users file for loop */

/* we are now finished building tables, and updating users file */

for (i=0; i<(HFSIZE>>6); i++)
	{
	movmem(&ibuf[i<<6],bufloc(fp),128);
	write(fp,1);	/* write hash record */
	}

setarec(fp,0);		/* reset this to first */
free(ibuf);

#if FALSE
printf("\nOptimum hash values=%d, collisions=%d.\n",goodcount,badcount);
#endif

return fp;

}


/* EOF MELIB.C */
