/* OS- and machine-dependent stuff for IBM-PC running MS-DOS and Turbo-C
 * Copyright 1991 Phil Karn, KA9Q
 */
#include <stdio.h>
#include <conio.h>
#include <dir.h>
#include <dos.h>
#include <io.h>
#include <sys/stat.h>
#include <string.h>
#include <process.h>
#include <fcntl.h>
#include <alloc.h>
#include <stdarg.h>
#include <bios.h>
#include "global.h"
#include "mbuf.h"
#include "proc.h"
#include "iface.h"
#include "internet.h"
#include "session.h"
#include "socket.h"
#include "smtp.h"
#include "cmdparse.h"
#include "dirutil.h"
#include "pc.h"

#define	CTLC	0x3
#define	DEL	0x7f

static int kbchar __ARGS((void));
extern int Curdisp;
extern struct proc *Display;
FILE *Rawterm;
unsigned _stklen = 8192;
int Tick;
static int32 Starttime;
int32 Clock;
int Background = 1;

int Watchdog = 0;			/* Watch Dog off by default */
int WDTick = 300*(1000 / MSPTICK);	/* 5 minutes watchdog timer */
int WDCurr = 300*(1000 / MSPTICK);	/* Initial count down timer */

/* This flag is set by setirq() if IRQ 8-15 is used, indicating
 * that the machine is a PC/AT with a second 8259 interrupt controller.
 * If this flag is set, the interrupt return code in pcgen.asm will
 * send an End of Interrupt command to the second 8259 as well as the
 * first.
 */
int Isat;

static char Ttbuf[BUFSIZ];
static char Tsbuf[BUFSIZ];
static int saved_break;

/* Keyboard input buffer */
#define	KBSIZE	256
static struct {
	char buf[KBSIZE];
	char *wp;
	char *rp;
	int cnt;
} Keyboard;
int Nokeys = 0;

int
am_i_an_AT()
{
	unsigned char *model_code = MK_FP(0xF000,0xFFFE);

	if(*model_code == 0xFC)
		return 1;
	else
		return 0;
}
int
errhandler(errval,ax,bp,si)
int errval,ax,bp,si;
{
	return 3;	/* Fail the system call */
}

/* Called at startup time to set up console I/O, memory heap */
void
ioinit()
{

	/* Fail all I/O errors */
	harderr(errhandler);

	/* Save these two file table entries for something more useful */
	fclose(stdaux);
	fclose(stdprn);
	setbuf(stdout,Tsbuf);

	Rawterm = fopen("con","wb");
	setbuf(Rawterm,Ttbuf);
	/* this breaks tab expansion so you must use ANSI or NANSI */
	ioctl(fileno(Rawterm), 1, (ioctl(fileno(Rawterm),0) & 0xff) | 0x20);
	saved_break = getcbrk();
	setcbrk(0);

#ifdef MSDOS
	/* test to see if we're running on an AT class machine.
	 * Set Isat flag accordingly - N1BEE
         */
	Isat = am_i_an_AT();
#endif
	Starttime = bioscnt();
	/* Link timer handler into timer interrupt chain */
	chtimer(btick);

	/* Find out what multitasker we're running under, if any */
	chktasker();

	/* Initialize keyboard queue */
	Keyboard.rp = Keyboard.wp = Keyboard.buf;

}
/* Called just before exiting to restore console state */
void
iostop()
{
	struct iface *ifp,*iftmp;
	void (**fp)();

	setbuf(Rawterm,NULLCHAR);
	ioctl(fileno(Rawterm), 1, ioctl(fileno(Rawterm), 0) & 0xff & ~0x20);
	setcbrk(saved_break);

	for(ifp = Ifaces;ifp != NULLIF;ifp = iftmp){
		iftmp = ifp->next;
		if_detach(ifp);
	}
	/* Call list of shutdown functions */
	for(fp = Shutdown;*fp != NULLVFP;fp++){
		(**fp)();
	}	
}
/* Spawn subshell */
int
doshell(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	char *command;
	int ret;

	if(Background) {
		if(!start_back())
			return -1;
		Nokeys++;
	}

	if(argc == 1 || !stricmp(argv[1], "/c")) {
		if((command = getenv("COMSPEC")) == NULLCHAR)
			command = "COMMAND.COM";
		ret = spawnvp(P_WAIT,command,argv);
	} else {
		ret = spawnvp(P_WAIT,argv[1],(argv + 1));
	}

	if(Background) {
		Nokeys--;
		stop_back();
	}

	return ret;
}

/* Spawn mailer as subshell */
int
dobmail(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	char *command;
	int ret;

	if(Background) {
		if(!start_back())
			return -1;
		Nokeys++;
	}

	if((command = getenv("MAILER")) == NULLCHAR)
		command = "BM.EXE";
	ret = spawnvp(P_WAIT,command,argv);

	if(Background) {
		Nokeys--;
		stop_back();
	}

	smtptick(NULL);		/* tickle smtp to send any mail */
	return ret;
}

/* if multitask mode is set - allow NOS and shell/mail to share system time */
dobackg(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setbool(&Background,"Multitasking DOS Shell ",argc,argv);
}

/* if watch-dog mode is set - make NOS reboot the system if it stalls */
dowatchdog(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setbool(&Watchdog,"NOS Watch Dog",argc,argv);
}

/* Keyboard interrupt handler */
void
kbint()
{
	int sig = 0;
	int c;

	if(Background && Nokeys)
		return;
	
	while((c = kbraw()) != -1 && Keyboard.cnt < KBSIZE){
		sig = 1;
		*Keyboard.wp++ = c;
		if(Keyboard.wp == &Keyboard.buf[KBSIZE])
			Keyboard.wp = Keyboard.buf;
		Keyboard.cnt++;
	}
	if(sig){
		psignal(&Keyboard,0);
	}
}
static int
kbchar()
{
	char i_state;
	char c;

	if(Background && Nokeys)
		return -1;

	i_state = dirps();
	while(Keyboard.cnt == 0)
		pwait(&Keyboard);
	Keyboard.cnt--;
	restore(i_state);
	c = *Keyboard.rp++;
	if(Keyboard.rp == &Keyboard.buf[KBSIZE])
		Keyboard.rp = Keyboard.buf;
	return uchar(c);
}
/* Flush the raw terminal output */
void
rflush()
{
	fflush(Rawterm);
}

#ifdef MSDOS
struct funcstr {
	char fkey;
	char alloced;
	char *fvalue;
};

struct funcstr fkeys[] = {
	15,0,NULLCHAR,	/* tab + shift */
	59,1,"\033OP",	/* F1 */
	60,1,"\033OQ",	/* F2 */
	61,1,"\033OR",	/* F3 */
	62,1,"\033OS",	/* F4 */
	63,0,NULLCHAR,	/* F5 */
	64,0,NULLCHAR,	/* F6 */
	65,0,NULLCHAR,	/* F7 */
	66,0,NULLCHAR,	/* F8 */
	67,0,NULLCHAR,	/* F9 */
	68,0,NULLCHAR,	/* F10 */
	71,1,"\010",		/* home*/
	72,1,"\033[A",	/* up arrow*/
	73,1,"\025",		/* pgup */
	75,1,"\033[D",	/* left arrow */
	77,1,"\033[C",	/* right arrow */
	79,1,"\005",		/* end */
	80,1,"\033[B",	/* down arrow */
	81,1,"\012",		/* pgdn */
	82,1,"\001",		/* ins */
	83,1,"\0177",		/* del */
	84,0,NULLCHAR,	/* F1 + shift*/
	85,0,NULLCHAR,	/* F2 + shift*/
	86,0,NULLCHAR,	/* F3 + shift*/
	87,0,NULLCHAR,	/* F4 + shift*/
	88,0,NULLCHAR,	/* F5 + shift*/
	89,0,NULLCHAR,	/* F6 + shift*/
	90,0,NULLCHAR,	/* F7 + shift*/
	91,0,NULLCHAR,	/* F8 + shift*/
	92,0,NULLCHAR,	/* F9 + shift*/
	93,0,NULLCHAR,	/* F10 + shift*/
	94,0,NULLCHAR,	/* F1 + control*/
	95,0,NULLCHAR,	/* F2 + control*/
	96,0,NULLCHAR,	/* F3 + control*/
	97,0,NULLCHAR,	/* F4 + control*/
	98,0,NULLCHAR,	/* F5 + control*/
	99,0,NULLCHAR,	/* F6 + control*/
	100,0,NULLCHAR,	/* F7 + control*/
	101,0,NULLCHAR,	/* F8 + control*/
	102,0,NULLCHAR,	/* F9 + control*/
	103,0,NULLCHAR,	/* F10 + control*/
	104,0,NULLCHAR,	/* F1 + alt*/
	105,0,NULLCHAR,	/* F2 + alt*/
	106,0,NULLCHAR,	/* F3 + alt*/
	107,0,NULLCHAR,	/* F4 + alt*/
	108,0,NULLCHAR,	/* F5 + alt*/
	109,0,NULLCHAR,	/* F6 + alt*/
	110,0,NULLCHAR,	/* F7 + alt*/
	111,0,NULLCHAR,	/* F8 + alt*/
	112,0,NULLCHAR,	/* F9 + alt*/
	113,0,NULLCHAR,	/* F10 + alt*/
	114,0,NULLCHAR,	/* PrtSc + ctl*/
	117,0,NULLCHAR,	/* end  + ctl */
	118,0,NULLCHAR,	/* pgup + ctl */
	119,0,NULLCHAR,	/* home + ctl */
	132,0,NULLCHAR,	/* pgdn + ctl */
	  0,0,NULLCHAR
};

char Leftover = 0;
char *Nextkey;
#endif

/* Read characters from the keyboard, translating them to "real" ASCII.
 * If none are ready, block. The F-10 key is special; translate it to -2.
 */
int
kbread()
{
#ifndef	MSDOS
	int c;
#else
	int c,i,j;

	if((c = Leftover) != 0)  {
		Leftover = *Nextkey++;
		return c;
	}
#endif	MSDOS
	if((c = kbchar()) == 0){
		/* Lead-in to a special char */
		c = kbchar();
		switch(c){
		case 3:		/* NULL (bizzare!) */
			c = 0;
			break;
		case 68:	/* F-10 key (used as command-mode escape) */
			if(fkeys[10].fvalue == NULLCHAR){
				c = -2;
				break;
			}
		default:	/* Dunno what it is */
#ifdef	MSDOS
			for(i=0;(j = fkeys[i].fkey) != 0;i++)
				if(j == c) {
					Nextkey = fkeys[i].fvalue;
					if(Nextkey == NULLCHAR) {
						c = -1;
						return c;
					}
					if((c = *Nextkey++) != 0)
						Leftover = *Nextkey++;
					else
						c = -1;
					return c;
				}
#endif
			c = -1;
		}
	}
	return c;
}
#ifdef	MSDOS
int
dofkey(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	int c,i,j;
	char *q, *r;
	char str[100];

	if(argc == 1) {
	   tprintf("key num    key  num    key  num    key  num   key   num\n");
	   tprintf("f1   59    sf1   84    cf1   94    af1  104   pgup   73\n");
	   tprintf("f2   60    sf2   85    cf2   95    af2  105   pgdn   81\n");
	   tprintf("f3   61    sf3   86    cf3   96    af3  106   home   71\n");
	   tprintf("f4   62    sf4   87    cf4   97    af4  107   end    79\n");
	   tprintf("f5   63    sf5   88    cf5   98    af5  108   arup   72\n");
	   tprintf("f6   64    sf6   89    cf6   99    af6  109   ardn   80\n");
	   tprintf("f7   65    sf7   90    cf7  100    af7  110   ar l   75\n");
	   tprintf("f8   66    sf8   91    cf8  101    af8  111   ar r   77\n");
	   tprintf("f9   67    sf9   92    cf9  102    af9  112   ins    82\n");
	   tprintf("f10  68    sf10  93    cf10 103    af10 113   del    83\n");
	   tprintf("usage: fkey <key number> [<value> | \"string\"]\n");
	   return 0;
	}

	c = atoi(argv[1]);
	if(c == 0 || c > 255) {
		tprintf("fkey number out of range.\n");
		return 1;
	}

	for(j = 0;(i = fkeys[j].fkey) != 0; j++)
		if(i == c) 
			break;

	if(i == 0){
		tprintf("fkey number not found\n");
		return 1;
	}

	if(argc == 2) {
		q = fkeys[j].fvalue;
		r = str;
		if(q == NULLCHAR)
			tprintf("fkey %d has no assigned value.\n",c);
		else {
			while(*q)
				if(*q < ' ') { /* This is ASCII dependent !! */
					*r++ = '^';
					*r++ = *q++ + 0x40;
				} else
					*r++ = *q++;
			*r = '\0';
			tprintf("fkey = %s\n",str);
		}
		return 0;
	}

	if(argc == 3) {
		if(fkeys[j].alloced)
			fkeys[j].alloced = 0;
		else
			if(fkeys[j].fvalue != NULLCHAR)
				free(fkeys[j].fvalue);

		r = str;
		q = argv[2];
		while(*q){
			if(*q == '^'){	/* ^ gives control char next */
				q++;
				if(*q == '^') {
					*r++ = *q++; /* No, he wants a ^ */
				} else {
					*r++ = *q++ & 0x1f;
				}
			} else
				*r++ = *q++;
		}
		*r = '\0';
		fkeys[j].fvalue = strdup(str);
	}
	return 0;
}
#endif

/* Install hardware interrupt handler.
 * Takes IRQ numbers from 0-7 (0-15 on AT) and maps to actual 8086/286 vectors
 * Note that bus line IRQ2 maps to IRQ9 on the AT
 */
int
setirq(irq,handler)
unsigned irq;
INTERRUPT (*handler)();
{
	/* Set interrupt vector */
	if(irq < 8){
		setvect(8+irq,handler);
	} else if(irq < 16){
		Isat = 1;
		setvect(0x70 + irq - 8,handler);
	} else {
		return -1;
	}
	return 0;
}
/* Return pointer to hardware interrupt handler.
 * Takes IRQ numbers from 0-7 (0-15 on AT) and maps to actual 8086/286 vectors
 */
INTERRUPT
(*getirq(irq))()
unsigned int irq;
{
	/* Set interrupt vector */
	if(irq < 8){
		return getvect(8+irq);
	} else if(irq < 16){
		return getvect(0x70 + irq - 8);
	} else {
		return NULLVIFP;
	}
}
/* Disable hardware interrupt */
int
maskoff(irq)
unsigned irq;
{
	if(irq < 8){
		setbit(0x21,(char)(1<<irq));
	} else if(irq < 16){
		irq -= 8;
		setbit(0xa1,(char)(1<<irq));
	} else {
		return -1;
	}
	return 0;
}
/* Enable hardware interrupt */
int
maskon(irq)
unsigned irq;
 {
	if(irq < 8){
		clrbit(0x21,(char)(1<<irq));
	} else if(irq < 16){
		irq -= 8;
		clrbit(0xa1,(char)(1<<irq));
	} else {
		return -1;
	}
	return 0;
}
/* Return 1 if specified interrupt is enabled, 0 if not, -1 if invalid */
int
getmask(irq)
unsigned irq;
{
	if(irq < 8)
		return (inportb(0x21) & (1 << irq)) ? 0 : 1;
	else if(irq < 16){
		irq -= 8;
		return (inportb(0xa1) & (1 << irq)) ? 0 : 1;
	} else
		return -1;
}
/* Called from assembler stub linked to BIOS interrupt 1C, called on each
 * hardware clock tick. Signal a clock tick to the timer process.
 */
void
ctick()
{
	if(Watchdog)
		if(WDCurr-- == 0)
			sysreset();	
	Tick++;
#ifdef notdef
	Clock++;	/* Keep system time */
#endif
	psignal(&Tick,1);
}
/* Called from the timer process on every tick. NOTE! This function
 * can NOT be called at interrupt time because it calls the BIOS
 */
void
pctick()
{
	long t;
	static long oldt;	/* Value of bioscnt() on last call */
	static long days;	/* # of times bioscnt() has rolled over */

	/* Update the time-since-boot */
	t = bioscnt();

	if(t < oldt)
		days++;	/* bioscnt has rolled past midnight */
	oldt = t;
	Clock = (days * 0x1800b0) + t - Starttime;
}

/* Set bit(s) in I/O port */
void
setbit(port,bits)
unsigned port;
char bits;
{
	outportb(port,(char)inportb(port)|bits);
}
/* Clear bit(s) in I/O port */
void
clrbit(port,bits)
unsigned port;
char bits;
{
	outportb(port,(char)(inportb(port) & ~bits));
}
/* Set or clear selected bit(s) in I/O port */
void
writebit(port,mask,val)
unsigned port;
char mask;
int val;
{
	register char x;

	x = inportb(port);
	if(val)
		x |= mask;
	else
		x &= ~mask;
	outportb(port,x);
}
/* Convert a pointer to a long integer */
long
ptol(p)
void *p;
{
	long x;

	x = FP_OFF(p);
#ifdef	LARGEDATA
	x |= (long)FP_SEG(p) << 16;
#endif
	return x;
}
void *
ltop(l)
long l;
{
	register unsigned seg,offset;

	seg = l >> 16;
	offset = l;
	return MK_FP(seg,offset);
}
#ifdef notdef	/* Assembler versions in pcgen.asm */
/* Multiply a 16-bit multiplier by an arbitrary length multiplicand.
 * Product is left in place of the multiplicand, and the carry is
 * returned
 */
int16
longmul(multiplier,n,multiplicand)
int16 multiplier;
int n;				/* Number of words in multiplicand[] */
register int16 *multiplicand;	/* High word is in multiplicand[0] */
{
	register int i;
	unsigned long pc;
	int16 carry;

	carry = 0;
	multiplicand += n;
	for(i=n;i != 0;i--){
		multiplicand--;
		pc = carry + (unsigned long)multiplier * *multiplicand;
		*multiplicand = pc;
		carry = pc >> 16;
		}
	}
	return carry;
}
/* Divide a 16-bit divisor into an arbitrary length dividend using
 * long division. The quotient is returned in place of the dividend,
 * and the function returns the remainder.
 */
int16
longdiv(divisor,n,dividend)
int16 divisor;
int n;				/* Number of words in dividend[] */
register int16 *dividend;	/* High word is in dividend[0] */
{
	/* Before each division, remquot contains the 32-bit dividend for this
	 * step, consisting of the 16-bit remainder from the previous division
	 * in the high word plus the current 16-bit dividend word in the low
	 * word.
	 *
	 * Immediately after the division, remquot contains the quotient
	 * in the low word and the remainder in the high word (which is
	 * exactly where we need it for the next division).
	 */
	unsigned long remquot;
	register int i;

	if(divisor == 0)
		return 0;	/* Avoid divide-by-zero crash */
	remquot = 0;
	for(i=0;i<n;i++,dividend++){
		remquot |= *dividend;
		if(remquot == 0)
			continue;	/* Avoid unnecessary division */
#ifdef	__TURBOC__
		/* Use assembly lang routine that returns both quotient
		 * and remainder, avoiding a second costly division
		 */
		remquot = divrem(remquot,divisor);
		*dividend = remquot;	/* Extract quotient in low word */
		remquot &= ~0xffffL;	/* ... and mask it off */
#else
		*dividend = remquot / divisor;
		remquot = (remquot % divisor) << 16;
#endif
	}
	return remquot >> 16;
}
#endif
void
sysreset()
{
	void (*foo) __ARGS((void));

	foo = MK_FP(0xffff,0);	/* FFFF:0000 is hardware reset vector */
	(*foo)();
}
void
newscreen(sp)
struct session *sp;
{
	if(sp != NULLSESSION)
		sp->screen = callocw(1,sizeof(struct screen));
}
void
freescreen(sp)
struct session *sp;
{
	if(sp == NULLSESSION || sp->screen == NULLSCREEN)
		return;
	if(sp->screen->save != NULLCHAR)
		free(sp->screen->save);
	free((char *)sp->screen);
}

/* Save specified session screen and resume console screen */
void
swapscreen(old,new)
struct session *old,*new;
{
	struct text_info tr;

	if(old == new)
		return;	/* Nothing to do */

	fflush(Rawterm);
	gettextinfo(&tr);
	if(old != NULLSESSION){
		/* Save old screen */
		if(old->screen->save == NULLCHAR)
			old->screen->save
			 = malloc(2*tr.screenheight*tr.screenwidth);
		if(old->screen->save != NULLCHAR){
			if(old->split){
				window(1,1,80,25);
				tr.winbottom = 25;
			}
			gettext(tr.winleft,tr.wintop,tr.winright,
			 tr.winbottom,old->screen->save);
		}
		old->screen->row = tr.cury;
		old->screen->col = tr.curx;
	}
	if(new != NULLSESSION){
		/* Load new screen */
		if(new->screen->save != NULLCHAR){
			if(new->split){
				window(1,1,80,23);
				clrscr();
			} else {
				window(1,1,80,25);
				clrscr();
			}
			puttext(tr.winleft,tr.wintop,tr.winright,
			 tr.winbottom,new->screen->save);
			gotoxy(new->screen->col,new->screen->row);
			/* Free the memory (saves 4K on a continuous basis) */
			free(new->screen->save);
			new->screen->save = NULLCHAR;
		} else {
			clrscr();	/* Start with a fresh slate */
			if(new->split){
				new->tsavex = 1;
				new->tsavey = 1;
				new->bsavex = 1;
				new->bsavey = 24;
				window(1,1,80,25);
				cputs("_\b");
				window(1,1,80,23);
			}
		}
	}
	alert(Display,1);	/* Wake him up */
}
void
display(i,v1,v2)
int i;
void *v1;
void *v2;
{
	int c;
	struct session *sp;

	/* This is very tricky code. Because the value of "Current" can
	 * change any time we do a pwait, we have to be careful to detect
	 * any change and go back and start again.
	 */
	for(;;){
		sp = Current;

		if(sp->morewait){
			pwait(&sp->row);
			if(sp != Current || sp->row <= 0){
				/* Current changed value, or the user
				 * hasn't really hit a key
				 */
				continue;
			}
			/* Erase the prompt */
			fprintf(Rawterm,"\r        \r");
		}
		sp->morewait = 0;
		if((c = rrecvchar(sp->output)) == -1){
			/* the alert() in swapscreen will cause this to
			 * return -1 when current changes
			 */
			pwait(NULL);	/* Prevent a nasty loop */
			continue;
		}
		if(sp->split){
			if(c == 0x0a){
				cputs(Eol);
				clreol();
			} else
				putch(c);
		} else {
			putc(c,Rawterm);
		}
		if(sp->record != NULLFILE)
			putc(c,sp->record);
		if(sp->flowmode && c == '\n' && --sp->row <= 0){
			fprintf(Rawterm,"--More--");
			sp->morewait = 1;
		}
	}
}
/* Return time since startup in milliseconds. If the system has an
 * 8254 clock chip (standard on ATs and up) then resolution is improved
 * below 55 ms (the clock tick interval) by reading back the instantaneous
 * value of the counter and combining it with the global clock tick counter.
 * Otherwise 55 ms resolution is provided.
 *
 * Reading the 8254 is a bit tricky since a tick could occur asynchronously
 * between the two reads. The tick counter is examined before and after the
 * hardware counter is read. If the tick counter changes, try again.
 * Note: the hardware counter counts down from 65536.
 */
int32
msclock()
{
	int32 hi;
	int16 lo;
	int16 count[4]; /* extended (48-bit) counter of timer clocks */

	if(!Isat)
		return Clock * MSPTICK;

	do {
		hi = Clock + Tick;
		lo = clockbits();
	} while(hi != Clock + Tick); /* Make sure a tick didn't just occur */

	count[0] = 0;
	count[1] = hi >> 16;
	count[2] = hi;
	count[3] = -lo;
	longmul(11,4,count);	/* The ratio 11/13125 is exact */
	longdiv(13125,4,count);
	return ((long)count[2] << 16) + count[3];
}
/* Return clock in seconds */
int32
secclock()
{
	int32 hi;
	int16 lo;
	int16 count[4];	/* extended (48-bit) counter of timer clocks */

	if(!Isat)
		return Clock * MSPTICK / 1000L;

	do {
		hi = Clock + Tick;
		lo = clockbits();
	} while(hi != Clock + Tick); /* Make sure a tick didn't just occur */

	count[0] = 0;
	count[1] = hi >> 16;
	count[2] = hi;
	count[3] = -lo;
	longmul(11,4,count);	/* The ratio 11/13125 is exact */
	longdiv(13125,4,count);
	longdiv(1000,4,count);
	return ((long)count[2] << 16) + count[3];
}

int
doisat(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setbool(&Isat,"AT/386 mode",argc,argv);
}

/* Directly read BIOS count of time ticks. This is used instead of
 * calling biostime(0,0L). The latter calls BIOS INT 1A, AH=0,
 * wich resets the midnight overflow flag, losing days on the clock.
 */
long
bioscnt()
{
	int i_state;
	long rval;

	i_state = dirps();
	rval = * (long far *)MK_FP(0x40,0x6c);
	restore(i_state);
	return rval;
}

/* same as getenv(), but return "" instead of NULL when it does not exist */
char *getnenv (name)
char *name;
{
	char *rv;

	if ((rv = getenv(name)) == NULL)
		 rv = "";			/* NULL replaced by "" */

	return rv;
}
