/* OS- and machine-dependent stuff for IBM-PC running MS-DOS */

#include <stdio.h>
#include <ctype.h>

#if (defined(MSC) || defined(__TURBOC__))
# include <process.h>
# include <signal.h>
# ifdef MSC
#  include <malloc.h>
   void (*getvect())();
# endif
#else
# ifdef RAWCON
#  include <sgtty.h>
# endif
#endif

#include "global.h"
#include "config.h"
#include "mbuf.h"
#include "internet.h"
#include "iface.h"
#include "cmdparse.h"
#include "timer.h"
#include "environ.h"

/* Interface list header */
struct interface *ifaces;

#if (!defined(MSC) && !defined(__TURBOC__))
/* Aztec memory allocation control */
int _STKLOW = 0;	/* Stack above heap */
int _STKSIZ = 4096/16;	/* 4K stack -- overridden in grabcore */
int _HEAPSIZ = 4096/16; /* Isn't really used */
int _STKRED = 4096;	/* Stack red zone in bytes -- this really matters */
#endif

char ttbuf[BUFSIZ];

#ifdef SERVERS
extern FILE *logfp;			/* log file pointer */
extern char logname[];			/* log file name */
#endif

#ifdef TRACE
extern FILE *trfp;			/* trace file pointer */
extern char trname[];			/* trace file name */
#endif

static unsigned watchhw;		/* watchdog hardware reset value */
static unsigned watchport;		/* watchdog output port number */

#if (defined(MSC) || defined(__TURBOC__))
/* critical error handler stuff - will fail system calls all the time */

void (*orgcritvect)(void);
extern void crit_hdl(void);

void crit_remove (void)

{
	setvect(0x24,orgcritvect);
}
#endif

/* Called at startup time to set up console I/O, memory heap */
ioinit()
{
#if (!defined(MSC) && !defined(__TURBOC__))
# ifdef RAWCON
	struct sgttyb ttybuf;
# endif
#endif

	/* Save these file table entries for something more useful */
	/* PE1CHL: this seems to cause problems so let's skip it for awhile */
#ifdef CLOSEDEVS
# ifdef stdaux
	fclose(stdaux);
# endif
# ifdef stdprn
	fclose(stdprn);
# endif
# ifdef stdprt
	fclose(stdprt);
# endif
#endif

#if (!defined(MSC))
	/* Interrupts use a special stack deep in data space.
	 * Calls to sbrk() (invoked by malloc when it needs more memory
	 * from the system) at interrupt time will fail because sbrk()
	 * will think that the stack has overwritten the heap. So
	 * grab all the memory we can now for the heap so that malloc
	 * won't have to call sbrk and alloc_mbuf() won't fail unnecessarily
	 * at interrupt time.
	 */
	grabcore();
#endif

	setbuf(stdout,ttbuf);

#if (defined(MSC) || defined(__TURBOC__))
	signal(SIGINT,SIG_IGN);		/* don't like killing NET */
#else
# ifdef RAWCON
	/* Put display in raw mode. Note that this breaks tab expansion,
	 * so you need to run NANSI.SYS or equivalent.
	 */
	ioctl(1,TIOCGETP,&ttybuf);
	ttybuf.sg_flags = RAW;
	ioctl(1,TIOCSETP,&ttybuf);
# endif
#endif

#if (defined(MSC) || defined(__TURBOC__))
	orgcritvect = getvect(0x24);
	setvect(0x24,crit_hdl);
	atexit(crit_remove);
#endif
}
/* Called just before exiting to restore console state */
iostop()
{
#if (!defined(MSC) && !defined(__TURBOC__))
# ifdef RAWCON
	struct sgttyb ttybuf;
# endif
#endif

#if (!defined(MSC) && !defined(__TURBOC__))
# ifdef RAWCON
	ioctl(1,TIOCGETP,&ttybuf);
	ttybuf.sg_flags &= ~RAW;
	ioctl(1,TIOCSETP,&ttybuf);
# endif
#endif
	while(ifaces != NULLIF){
		/* first flush the interface's buffers */
		while(ifaces->recv != NULLFP && (*ifaces->recv)(ifaces))
			;
		if(ifaces->stop != NULLFP)
			(*ifaces->stop)(ifaces);
		ifaces = ifaces->next;
	}
}

/* Print or Set the date and time */

dodate(argc,argv)
	int argc;
	char *argv[];
{
	long tloc;
	register char *p;
#ifdef	MSC
	struct dosdate_t dosdate;
	struct dostime_t dostime;
#endif
#ifdef	__TURBOC__
	struct date dosdate;
	struct time dostime;
#endif

	if (argc < 2) {
	    time(&tloc);
	    printf("%s",ctime(&tloc));
	    return 0;
	}

#if (defined(MSC) || defined(__TURBOC__))
	p = argv[1];

	if (strlen(p) != 10 && strlen(p) != 12)
	    return -1;
	while (*p)
	    if (!isdigit(*p++))
		return -1;

# ifdef	MSC
	_dos_getdate(&dosdate);			/* read the year */
	p = argv[1];
	dosdate.month = 10 * p[0] + p[1] - 11 * '0';
	dosdate.day = 10 * p[2] + p[3] - 11 * '0';
	dostime.hour = 10 * p[4] + p[5] - 11 * '0';
	dostime.minute = 10 * p[6] + p[7] - 11 * '0';
	dostime.second = 10 * p[8] + p[9] - 11 * '0';
	if (p[10]) {
	    if ((dosdate.year = 10 * p[10] + p[11] - 11 * '0' + 1900) < 1980)
		dosdate.year += 100;
	}
	dostime.hsecond = 0;

	if (_dos_settime(&dostime) || _dos_setdate(&dosdate))
	    return -1;
# endif
# ifdef	__TURBOC__
	getdate(&dosdate);			/* read the year */
	p = argv[1];
	dosdate.da_mon = 10 * p[0] + p[1] - 11 * '0';
	dosdate.da_day = 10 * p[2] + p[3] - 11 * '0';
	dostime.ti_hour = 10 * p[4] + p[5] - 11 * '0';
	dostime.ti_min = 10 * p[6] + p[7] - 11 * '0';
	dostime.ti_sec = 10 * p[8] + p[9] - 11 * '0';
	if (p[10]) {
	    if ((dosdate.da_year = 10 * p[10] + p[11] - 11 * '0' + 1900) < 1980)
		dosdate.da_year += 100;
	}
	dostime.ti_hund = 0;

	settime(&dostime);
	setdate(&dosdate);
# endif
#else
	printf("Date/Time setting not supported in this version!\n");
#endif

	return 0;
}

/* Spawn subshell */
doshell(argc,argv)
int argc;
char *argv[];

{
	char *command,**envp;
	int ret;
#ifdef MSDOS
	unsigned short envseg;
#endif
#if (!defined(MSC) && !defined(__TURBOC__))
# ifdef RAWCON
	struct sgttyb ttybuf,ttysav;

	ioctl(1,TIOCGETP,&ttysav);
	ioctl(1,TIOCGETP,&ttybuf);
	ttybuf.sg_flags &= ~RAW;
	ioctl(1,TIOCSETP,&ttybuf);
# endif
#endif

	if((command = getenv("COMSPEC")) == NULLCHAR)
	    command = "/COMMAND.COM";

#ifdef SERVERS
	if (logfp != NULLFILE)		/* logging to file? */
	    fclose(logfp);
#endif

#ifdef TRACE
	if (trfp != stdout)		/* trace to file? */
	    fclose(trfp);		/* close it during shell exec */
#endif

	envp = make_env();		/* create env, starts on PARA bound */

#if (defined(MSC) || defined(__TURBOC__))
	ret = spawnve(P_WAIT,command,&argv[0],envp);
#else
	if (envp != NULLCHRP)		/* could the env be allocated? */
	    envseg = getds() + (((unsigned) envp[0]) >> 4); /* get segment addr of env */
	else
	    envseg = 0;			/* no space, pass our own env */

	ret = fexecve(command,&argv[0],envseg);
# ifdef RAWCON
	ioctl(1,TIOCSETP,&ttysav);
# endif
#endif

	free_env();			/* free the envp and temp env */

#ifdef SERVERS
	if (logfp != NULLFILE)		/* re-open logfile if it was open */
	    logfp = fopen(logname,"a+");
#endif

#ifdef TRACE
	if (trfp != stdout)		/* re-open tracefile if not stdout */
	    if ((trfp = fopen(trname,"a+")) == NULLFILE)
		trfp = stdout;
#endif

	if (ret != 0)
	    return -1;

#if (defined(MSC) || defined(__TURBOC__))
	return 0;
#else
	return wait();
#endif
}

#if (!defined(MSC) && !defined(__TURBOC__))
/* the following piece of code was taken from the Aztec 4.10 runtime */
/* source for fexecv() and slightly adapted to pass the environment */
/* to the executed program. */
/* Copyright (C) 1983, 1984 by Manx Software Systems */

fexecve(path, argv, env)
char *path, **argv;
unsigned short env;
{
	register char *cp, *xp;
	int i;
	char buffer[130];
	char fcb1[16], fcb2[16];

	cp = buffer+1;
	i = 1;
	fcbinit(".", fcb1);		/* initialize fcb's, in case no args */
	fcbinit(".", fcb2);
	if (*argv) {
		++argv;			/* skip arg0, used for unix (tm) compatibility */
		while ((xp = *argv++) != '\0') {
			if (i == 1)
				fcbinit(xp, fcb1);
			else if (i == 2)
				fcbinit(xp, fcb2);
			*cp++ = ' ';
			while (*xp) {
				if (cp >= buffer+127)
					goto done;
				*cp++ = *xp++;
			}
			++i;
		}
	}
done:
	buffer[0] = cp - (buffer+1);
	*cp = '\r';		/* add CR to make some dos commands happy */
	return fexec(path, env, buffer, fcb1, fcb2);
}
#endif

/* checks the time then ticks and updates ISS */
static short clockstart = 0;
static int32 clkval = 0;
void
check_time()
{
	int32 iss();
	int32 ntime;
	int maxticks = SEC2TICK(15);	/* upper limit on delayed ticks */
	char i_state;
#if (defined(MSC) || defined(__TURBOC__))
# define peekw(off,seg) (*((unsigned far*) (((long) (seg) << 16) | (off))))
#else
	extern int16 peekw();
#endif

	if(!clockstart){
		/* Executed only once */
		i_state = disable();
		clkval = ((int32) peekw(0x6e,0x40) << 16) | peekw(0x6c,0x40);
		restore(i_state);
		clockstart = 1;
		return;
	}
	/* set the "watchhw" bit of "watchport" (when it is defined) */
	if (watchhw)
		outportb(watchport,inportb(watchport) | watchhw);

	/* Read the low order word of the BIOS tick counter directly. The
	 * INT 1Ah call isn't used because it stupidly clears the
	 * "midnight passed" flag and we'd have to update the date ourselves.
	 * (See Norton, p222).
	 *
	 * The PC's time-of-day handling is a real crock of shit. Why not a
	 * nice simple long binary count from a fixed UTC epoch, as in UNIX??
	 */
	i_state = disable();
	ntime = ((int32) peekw(0x6e,0x40) << 16) | peekw(0x6c,0x40);
	restore(i_state);

	/* separately check for time-of-day rollover to prevent strange
	 * behaviour of the timers at midnight...
	 */
	if (ntime < clkval){
		clkval -= 1573040L;		/* counts per day */
	}

	while(ntime != clkval){ /* Handle possibility of several missed ticks */
#ifdef IP
		icmpclk();	/* Call this one before tick */
#endif
		tick();
#ifdef IP
		(void)iss();
#endif
		if(maxticks--)	/* check limit on ticks to handle */
			clkval++;
		else
			clkval = ntime; /* ignore any remaining ticks */
	}
	/* clear the "watchhw" bit of "watchport" (when it is defined) */
	if (watchhw)
		outportb(watchport,inportb(watchport) & ~watchhw);
}
/* Read characters from the keyboard, translating them to "real" ASCII
 * If none are ready, return the -1 from kbraw()
 */
int
kbread()
{
	int kbraw(),c;

	if((c = kbraw()) == 0){
		/* Lead-in to a special char */
		c = kbread();
		switch(c){
		case 3:		/* NULL (bizzare!) */
			c = 0;
			break;
		case 59:	/* F-1 key */
			c = -11;
			break;
		case 60:	/* F-2 key */
			c = -10;
			break;
		case 61:	/* F-3 key */
			c = -9;
			break;
		case 62:	/* F-4 key */
			c = -8;
			break;
		case 63:	/* F-5 key */
			c = -7;
			break;
		case 64:	/* F-6 key */
			c = -6;
			break;
		case 65:	/* F-7 key */
			c = -5;
			break;
		case 66:	/* F-8 key */
			c = -4;
			break;
		case 67:	/* F-9 key */
			c = -3;
			break;
		case 68:	/* F-10 key (used as command-mode escape) */
			c = -2;
			break;
		case 71:	/* HOME key */
			c = -20;
			break;
		case 72:	/* UP arrow */
			c = -19;
			break;
		case 73:	/* PAGE UP */
			c = -18;
			break;
		case 75:	/* LEFT arrow */
			c = -17;
			break;
		case 77:	/* RIGHT arrow */
			c = -16;
			break;
		case 79:	/* END key */
			c = -15;
			break;
		case 80:	/* DOWN arrow */
			c = -14;
			break;
		case 81:	/* PAGE DOWN */
			c = -13;
			break;
		case 82:	/* INS key */
			c = -12;
			break;
		case 83:	/* DEL key */
			c = 0x7f;
			break;
		default:	/* Dunno what it is */
			c = -1;
		}
	}
	return c;
}

#if (!defined(MSC) && !defined(__TURBOC__))
#define CTLZ	26
/* Special version of aputc() (used by putchar and printf) that filters
 * out nasty characters that screw up the DDOS and ANSI terminal drivers
 * Also, it facilitates re-routing of standard output to an alternative
 * handler, e.g. to send it via TCP.
 */
int (*put_stdout)() = (int (*)()) 0;
int
aputc(c,file)
int c;
FILE *file;
{
	if(file == stdout){
		/* Nulls get displayed as spaces by ansi.sys (wrong)
		 * ^Z's seem to hang the DoubleDos and DesqView screen drivers
		 */
		if((c == '\0' || c == CTLZ))
			return c;

		/* Check for presence of alternative putc function
		 */
		if(put_stdout != (int (*)()) 0)
			return (*put_stdout)(c,file);
	}
	/* Do end-of-line translations */
	if(c == '\n')
		putc('\r',file);
	return putc(c,file);
}
#endif

/* Reset the CPU, reboot DOS */
sysreset()
{
	void (*reset)();		/* just a pointer to function */

	reset = (void (*)()) 0xffff0000; /* point to reset location */
	(*reset)();			/* and call it */
}

#if (!defined(LARGEDATA))
/* Convert a pointer to a long integer, so that it can be printed in a
   compiler- and system-independent way.
   This function currently assumes that pointers are 16-bit (int) size.
 */
long ptr2long (p)
char *p;
{
	unsigned int rv = (unsigned int) p;

	return (long) rv;
}
#endif

#ifdef __TURBOC__
/* the strrchr() function is broken in Turbo C 2.0 */
/* this implementation is probably more efficient, too... */

char *strrchr (const char *s, int c)

{
    register char *rv = NULL;			/* return value */

    do
    {
	if (*s == (char) c)			/* a match here? */
	    rv = (char *) s;			/* remember it */
    }
    while (*s++);

    return rv;					/* return last match or NULL */
}
#endif

#ifdef MSC
int memstat (argc,argv)
int argc;
char *argv[];

{
#ifdef SHOWHEAP
	unsigned long used;
	struct heapinfo {
	    int far *pentry;
	    size_t size;
	    int useflag;
	} hinfo;
#endif
	int _heapset();
#ifndef LARGEDATA
	unsigned int _memavl(),_memmax();
#endif

#ifdef SHOWHEAP
	if (argc > 1){
	    used = 0;
	    hinfo.pentry = NULL;
	    while (_heapwalk(&hinfo) == _HEAPOK) {
		switch (hinfo.useflag)
		{
		case _FREEENTRY:
		    printf("%8u",hinfo.size);
		    break;
		case _USEDENTRY:
		    used += hinfo.size;
		    break;
		}
	    }
	    printf("\n%lu bytes used, ",used);
	}
#endif

#ifdef LARGEDATA
	printf("heap %s\n",(_heapset(0) == _HEAPOK)? "okay":"BAD");
#else
	printf("%u bytes available, largest block %u bytes, heap %s\n",
	       _memavl(),_memmax(),(_heapset(0) == _HEAPOK)? "okay":"BAD");
#endif

	return 0;
}
#endif

/* control the watchdog function */

int
dowatchdog (argc,argv)
	int argc;
	char *argv[];
{
	switch (argc)
	{
	case 3:
		watchhw = htoi(argv[2]) & 0xff;
	case 2:
		if ((watchport = htoi(argv[1])) == 0) {
			printf("Usage: %s [<port> <value>]\n",argv[0]);
			watchhw = 0;
		}
		break;
	case 1:
		if (watchhw)
			printf("PORT=%04x VALUE=%02x\n",watchport,watchhw);
		else
			printf("OFF\n");
		break;
	}
	return 0;
}
