/* OS- and machine-dependent stuff for IBM-PC running MS-DOS and Turbo-C
 * Copyright 1991 Phil Karn, KA9Q
 *
 * XMS extensions Copyright 1993 Johan. K. Reinalda, WG7J
 */
#include <dir.h>
#include <dos.h>
#include <io.h>
#include <conio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <process.h>
#include <fcntl.h>
#include <alloc.h>
#include <stdarg.h>
#include <bios.h>
#include <time.h>
#include "global.h"
#include "mbuf.h"
#include "proc.h"
#include "iface.h"
#include "internet.h"
#include "session.h"
#include "tty.h"
#include "usock.h"
#include "socket.h"
#include "smtp.h"
#include "cmdparse.h"
#include "dirutil.h"
#include "files.h"
#include "pc.h"
#include "index.h"
#include "mailbox.h"
#ifdef EMS
#include "memlib.h"
#endif
#ifdef XMS
#include "xms.h"
#endif
  
#define CTLC    0x3
#define DEL     0x7f
  
static int kbchar __ARGS((void));
extern int Curdisp;
extern struct proc *Display;
FILE *Rawterm;
/* Highest i've ever seen in 'ps' is around 400, ie 800 bytes - WG7J */
unsigned _stklen = 2048;
volatile int Tick;
static int32 Starttime;
int32 Clock;
  
#ifdef MULTITASK
int Background;
int Nokeys;
extern unsigned Minheap;
#endif
extern int Tracesession;
  
#ifdef SPLITSCREEN
extern char MainColors;
extern char SplitColors;
#endif
#ifdef STATUSWIN
extern int StatusLines;
char MainStColors = WHITE+(MAGENTA<<4);
char SesStColors = WHITE+(BLUE<<4);
#else
int StatusLines = 0;
#endif
  
char *Screen;
int ScreenSize;
int SwapMode;
#ifdef XMS
unsigned int ScreenSizeK;
#endif
  
int Watchdog;                       /* 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;
  
int am_i_an_AT __ARGS((void));
  
  
/* Keyboard input buffer */
#define KBSIZE  256
static struct {
    char buf[KBSIZE];
    char *wp;
    char *rp;
    int cnt;
} Keyboard;
  
#if !defined CPU286 && !defined CPU386 && !defined CPU486 && !defined CPU586
int
am_i_an_AT()
{
    unsigned char *model_code = MK_FP(0xF000,0xFFFE);
  
    if(*model_code == 0xFC)
        return 1;
    else
        return 0;
}
#endif
  
/* Following code from Doug Crompton */
/* define the error messages for trapping disk problems
 */
static char *crit_err_msg[] = {
    "write protect",
    "unknown unit",
    "not ready",
    "unknown command",
    "data error (CRC)",
    "bad request",
    "seek error",
    "unknown media type",
    "sector not found",
    "printer out of paper",
    "write fault",
    "read fault",
    "general failure",
    "reserved",
    "reserved",
    "invalid disk change"
};
  
  
int
errhandler(int errval,int ax,int bp,int si)
{
    char msg[80];
    unsigned di;
    int drive;
    int errorno;
  
    di= _DI;
  
    if (ax < 0)
        hardretn(3);
    drive = ax & 0x00FF;
    errorno = di & 0x00FF;
    sprintf(msg, "\r\nError: %s on drive %c\r\n$",
    crit_err_msg[errorno], 'A' + drive);
    bdosptr(0x09,msg,0);
    hardretn(3);
    return 0;   /* to please the compiler */
}
  
int c_break(void) {     /* ctrl-brk or ctrl-c handler */
    return 1;
}
  
#ifdef __BORLANDC__
#undef fopen
FILE _FAR *_Cdecl fopen(const char _FAR *__path, const char _FAR *__mode);
#endif
  
/* 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);
    ctrlbrk(c_break);
  
#ifdef MSDOS
#if !defined CPU286 && !defined CPU386 && !defined CPU486 && !defined CPU586
    /* test to see if we're running on an AT class machine.
     * Set Isat flag accordingly - N1BEE
     */
    Isat = am_i_an_AT();
#else
    Isat = 1;
#endif
#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;
  
    /* Check for essential directories. */
    mkdir(LogsDir);
    mkdir(Fdir);
    mkdir(Spoolqdir);
    mkdir(Mailspool);
    mkdir(Mailqdir);
    mkdir(Routeqdir);
#ifdef MAILBOX
    mkdir(Helpdir);
    mkdir(Signature);
#endif
#if defined NNTPS || defined NNTP
    mkdir(Newsdir);
#endif
}
  
/* Called just before exiting to restore console state */
void
iostop()
{
    struct iface *ifp,*iftmp;
    void (**fp)(void);
  
    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)();
    }
}
#ifdef SHELL
/* Spawn subshell */
int
doshell(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    char *command;
    int ret;
    int OldWd;
  
#ifdef MULTITASK
    if(Background) {
        if(!start_back())
            return -1;
        Nokeys++;
        free(mallocw(Minheap)); /* Force heap/core break to reserve a heap */
    } else
#endif
    {
        OldWd = Watchdog;   /* Save old watchdog state, and turn it off */
        Watchdog = 0;       /* while shelled out */
    }
  
    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));
    }
  
#ifdef MULTITASK
    if(Background) {
        Nokeys--;
        stop_back();
    } else
#endif
    {
        Watchdog = OldWd;   /* Restore old watchdog state, */
        WDCurr = WDTick;    /* and start with a fresh count */
    }
  
    /* Update index files - WG7J */
    UpdateIndex(NULL,0);
  
    return ret;
}
#endif
  
#ifdef ALLCMD
/* Spawn mailer as subshell */
int
dobmail(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    char *command;
    int ret;
    int OldWd;
  
#ifdef MULTITASK
    if(Background) {
        if(!start_back())
            return -1;
        Nokeys++;
        free(mallocw(Minheap));
    } else
#endif
    {
        OldWd = Watchdog;   /* Save old watchdog state, and turn it off */
        Watchdog = 0;       /* while shelled out */
    }
  
    if((command = getenv("MAILER")) == NULLCHAR)
        command = "BM.EXE";
    ret = spawnvp(P_WAIT,command,argv);
  
#ifdef MULTITASK
    if(Background) {
        Nokeys--;
        stop_back();
    } else
#endif
    {
        Watchdog = OldWd;   /* Restore old watchdog state, */
        WDCurr = WDTick;    /* and start with a fresh count */
    }
  
    /* Update index files - WG7J */
    UpdateIndex(NULL,0);
  
    smtptick(NULL);         /* tickle smtp to send any mail */
    return ret;
}
#endif /*ALLCMD*/
  
#ifdef MULTITASK
/* if multitask mode is set - allow NOS and shell/mail to share system time */
int dobackg(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    return setbool(&Background,"Multitasking DOS Shell ",argc,argv);
}
#endif
  
/* if watch-dog mode is set - make NOS reboot the system if it stalls */
int 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;
  
#ifdef MULTITASK
    if(Background && Nokeys)
        return;
#endif
  
    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()
{
    int i_state;
    char c;
  
#ifdef MULTITASK
    if(Background && Nokeys)
        return -1;
#endif
  
    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
#ifdef ALLCMD
struct funcstr {
    int fkey;
    char alloced;
    char *name;
    char *fvalue;
};
  
static struct funcstr DFAR fkeys[] = {
    15,0,"Stab",NULLCHAR, /* tab + shift */
    59,0,"F1",NULLCHAR,   /* F1 */
    60,0,"F2",NULLCHAR,   /* F2 */
    61,0,"F3",NULLCHAR,   /* F3 */
    62,0,"F4",NULLCHAR,   /* F4 */
    63,0,"F5",NULLCHAR,   /* F5 */
    64,0,"F6",NULLCHAR,   /* F6 */
    65,0,"F7",NULLCHAR,   /* F7 */
    66,0,"F8",NULLCHAR,   /* F8 */
    67,0,"F9",NULLCHAR,   /* F9 */
    68,0,"F10",NULLCHAR,  /* F10 */
    71,0,"home",NULLCHAR, /* home*/
    72,0,"up",NULLCHAR,   /* up arrow*/
    73,0,"pgup",NULLCHAR, /* pgup */
    75,0,"left",NULLCHAR, /* left arrow */
    77,0,"right",NULLCHAR,/* right arrow */
    79,0,"end",NULLCHAR,  /* end */
    80,0,"down",NULLCHAR, /* down arrow */
    81,0,"pgdn",NULLCHAR, /* pgdn */
    82,0,"ins",NULLCHAR,  /* ins */
    83,0,"del",NULLCHAR,  /* del */
    84,0,"SF1",NULLCHAR,  /* F1 + shift*/
    85,0,"SF2",NULLCHAR,  /* F2 + shift*/
    86,0,"SF3",NULLCHAR,  /* F3 + shift*/
    87,0,"SF4",NULLCHAR,  /* F4 + shift*/
    88,0,"SF5",NULLCHAR,  /* F5 + shift*/
    89,0,"SF6",NULLCHAR,  /* F6 + shift*/
    90,0,"SF7",NULLCHAR,  /* F7 + shift*/
    91,0,"SF8",NULLCHAR,  /* F8 + shift*/
    92,0,"SF9",NULLCHAR,  /* F9 + shift*/
    93,0,"SF10",NULLCHAR,  /* F10 + shift*/
    94,0,"CF1",NULLCHAR,  /* F1 + control*/
    95,0,"CF2",NULLCHAR,  /* F2 + control*/
    96,0,"CF3",NULLCHAR,  /* F3 + control*/
    97,0,"CF4",NULLCHAR,  /* F4 + control*/
    98,0,"CF5",NULLCHAR,  /* F5 + control*/
    99,0,"CF6",NULLCHAR,  /* F6 + control*/
    100,0,"CF7",NULLCHAR, /* F7 + control*/
    101,0,"CF8",NULLCHAR, /* F8 + control*/
    102,0,"CF9",NULLCHAR, /* F9 + control*/
    103,0,"CF10",NULLCHAR,/* F10 + control*/
    104,0,"AF1",NULLCHAR, /* F1 + alt*/
    105,0,"AF2",NULLCHAR, /* F2 + alt*/
    106,0,"AF3",NULLCHAR, /* F3 + alt*/
    107,0,"AF4",NULLCHAR, /* F4 + alt*/
    108,0,"AF5",NULLCHAR, /* F5 + alt*/
    109,0,"AF6",NULLCHAR, /* F6 + alt*/
    110,0,"AF7",NULLCHAR, /* F7 + alt*/
    111,0,"AF8",NULLCHAR, /* F8 + alt*/
    112,0,"AF9",NULLCHAR, /* F9 + alt*/
    113,0,"AF10",NULLCHAR, /* F10 + alt*/
    114,0,"Cprnt",NULLCHAR, /* PrtSc + ctl*/
    117,0,"Cend",NULLCHAR, /* end  + ctl */
    118,0,"Cpgup",NULLCHAR, /* pgup + ctl */
    119,0,"Chome",NULLCHAR, /* home + ctl */
    132,0,"Cpgdn",NULLCHAR, /* pgdn + ctl */
    133,0,"F11",NULLCHAR, /* F11 */
    134,0,"F12",NULLCHAR, /* F12 */
    135,0,"SF11",NULLCHAR, /* F11 + shift */
    136,0,"SF12",NULLCHAR, /* F12 + shift */
    137,0,"CF11",NULLCHAR, /* F11 + ctrl */
    138,0,"CF12",NULLCHAR, /* F12 + ctrl */
    139,0,"AF11",NULLCHAR, /* F11 + alt */
    140,0,"AF12",NULLCHAR, /* F12 + alt */
    0,0,NULL,NULLCHAR
};
  
char Leftover = 0;
char *Nextkey;
#endif /*ALLCMD*/
#endif /*MSDOS*/
  
/* 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.
 * Modified for function key session switching, and command recall - WG7J
 */
#ifdef ALLCMD
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();
        if(Current == Command) {    /* Check for command recall */
            if(c == 72)     /* UP arrow */
                return UPARROW;
            if(c == 80)     /* DOWN arrow */
                return DNARROW;
        }
        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
                if(c > 58 && c < 68) {  /* F1 to F9 */
                    if(fkeys[c-58].fvalue == NULLCHAR) {
                        c = (c - 56) * -1; /* NO fkey defined - WG7J */
                        break;
                    }
                }
                for(i=0;(j = fkeys[i].fkey) != 0;i++)
                    if(j == c) {
                        Nextkey = fkeys[i].fvalue;
                        if(Nextkey == NULLCHAR) {
                            c = -1;
                            return c;
                        }
            /* If first char of fvalue is '~'
             * switch to command session.
             */
                        if((c = *Nextkey++) == '~') {
                            c = -2;
                            Leftover = *Nextkey++;
                        } else {
                            if(c != 0)
                                Leftover = *Nextkey++;
#ifdef notdef
                            if (c == '~') /* switch to command ses. on ~ char */
                                c = -2;  /* KN4L Change */
#endif
                            else
                                c = -1;
                        }
                        return c;
                    }
#endif
                c = -1;
        }
    }
    return c;
}
#else /*ALLCMD*/
  
int
kbread()
{
    int c;
  
    if((c = kbchar()) == 0){
        /* Lead-in to a special char */
        c = kbchar();
        if(Current == Command) {    /* Check for command recall */
            if(c == 72)     /* UP arrow */
                return UPARROW;
            if(c == 80)     /* DOWN arrow */
                return DNARROW;
        }
        switch(c){
            case 3:         /* NULL (bizzare!) */
                c = 0;
                break;
            case 68:        /* F-10 key (used as command-mode escape) */
                c = -2;
                break;
            case 83:        /* DEL key */
                c = 0x7f;
                break;
            default:        /* Dunno what it is */
                if(c > 58 && c < 68)    /* F1 to F9 */
                    c = (c - 56) * -1;
                else
                    c = -1;
        }
    }
    return c;
}
  
#endif /*ALLCMD*/
  
#ifdef  MSDOS
#ifdef ALLCMD
int
dofkey(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    int c,i,j;
    char *q, *r;
    char str[100];
  
    if(argc == 1) {
        tputs("\n  key num definition             key num definition\n");
        for(i=0;fkeys[i].fkey != 0;i++) {
            char *s;
            if((s=fkeys[i].fvalue) == NULL)
                s = "";
            else {
                q=s;
                r=str;
                s=str;
                while(*q)
                    if(*q < ' ') { /* This is ASCII dependent !! */
                        *r++ = '^';
                        *r++ = *q++ | 0x40;
                    } else if (*q == '^') {
                        *r++ = '^';
                        *r++ = *q++;
                    } else if (*q == '\177') {
                        *r++ = '^';
                        *r++ = '?';
                        q++;
                    } else {
                        *r++ = *q++;
                    }
                *r = '\0';
            }
            tprintf("%5.5s %3d %-20.20s ",fkeys[i].name,fkeys[i].fkey,s);
            if(i%2)
                tputc('\n');
        }
        tputs("\nusage: fkey <key number> [<value> | \"string\"]\n");
        return 0;
    }
  
    c = atoi(argv[1]);
    if(c == 0 || c > 255) {
        tputs("fkey number out of range.\n");
        return 1;
    }
  
    for(j = 0;(i = fkeys[j].fkey) != 0; j++)
        if(i == c)
            break;
  
    if(i == 0){
        tputs("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 if (*q == '^') {
                    *r++ = '^';
                    *r++ = *q++;
                } else if (*q == '\177') {
                    *r++ = '^';
                    *r++ = '?';
                    q++;
                } 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 if (*q == '?') {
                    *r++ = '\177';
                    q++;
                } else {
                    *r++ = *q++ & 0x1f;
                }
            } else
                *r++ = *q++;
        }
        *r = '\0';
        fkeys[j].fvalue = strdup(str);
    }
    return 0;
}
#endif /*ALLCMD*/
#endif /*MSDOS*/
  
/* 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 = (int32)((days * 0x1800b0L) + 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 int seg,offset;
  
    seg = (unsigned int)(l >> 16);
    offset = (unsigned int)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;
#ifdef XMS
    if(sp->screen->stype == EMS_SWAP)
        effree(sp->screen->sv.ems.token);
    else
#endif
#ifdef XMS
        if(sp->screen->stype == XMS_SWAP && sp->screen->sv.handle != 0)
            Free_XMS(sp->screen->sv.handle);
        else
#endif
            if(sp->screen->stype == FILE_SWAP && sp->screen->sv.fp != NULL)
                fclose(sp->screen->sv.fp);
            else if(sp->screen->stype == MEM_SWAP && sp->screen->sv.save != NULLCHAR)
                free(sp->screen->sv.save);
    free((char *)sp->screen);
}
  
#ifdef XMS
/* Free XMS memory used for screen swapping.
 * Called right before exit().
 */
void Free_Screen_XMS(void) {
    struct session *sp;
    int i;
  
    for(i=0,sp=Sessions;i < Nsessions;sp++,i++)
        if(sp->type != FREE && sp->screen->stype == XMS_SWAP && \
            sp->screen->sv.handle != 0)
            Free_XMS(sp->screen->sv.handle);
}
#endif
  
extern int Numrows,Numcols;
  
/* Save specified session screen and resume console screen */
void
swapscreen(old,new)
struct session *old,*new;
{
    struct text_info tr;
#ifdef XMS
    long handle;
    struct XMS_Move X;
#endif
  
    if(old == new)
        return; /* Nothing to do */
  
    fflush(Rawterm);
    gettextinfo(&tr);
  
    if(old != NULLSESSION){
#ifdef SPLITSCREEN
        /* Save old screen */
        if(old->split){
//            window(1,1,Numcols,Numrows);
        }
#endif
#ifdef EMS
        if(old->screen->stype == EMS_SWAP) {
            set1eptr(old->screen->sv.ems.token,(void far**)&old->screen->sv.ems.save);
            if(old->screen->sv.ems.save != NULLCHAR)
                memcpy(old->screen->sv.ems.save,Screen,ScreenSize);
        } else
#endif
#ifdef XMS
            if(old->screen->stype == XMS_SWAP) {
                if(old->screen->sv.handle == 0)
                    old->screen->sv.handle = Alloc_XMS(ScreenSizeK);
                if(old->screen->sv.handle != 0) {
                /* Now transfer to XMS */
                    X.Length = (long) ScreenSize;
                    X.SourceHandle = 0; /* Indicate conventional memory */
                    X.SourceOffset = (long) Screen;
                    X.DestHandle = old->screen->sv.handle;
                    X.DestOffset = 0L;
                    Move_XMS(&X);
                }
            } else
#endif
                if(old->screen->stype == FILE_SWAP) {
                    if(old->screen->sv.fp == NULL)
                        old->screen->sv.fp = tmpfile();
                    else
                        fseek(old->screen->sv.fp,0L,0);
                    if(old->screen->sv.fp != NULL){
                        fwrite(Screen,ScreenSize,1,old->screen->sv.fp);
                    }
                } else {
                    if(old->screen->sv.save == NULLCHAR)
                        old->screen->sv.save = malloc(ScreenSize);
                    if(old->screen->sv.save != NULLCHAR){
                        memcpy(old->screen->sv.save,Screen,ScreenSize);
                    }
                }
        old->screen->row = tr.cury;
        old->screen->col = tr.curx;
    }
    if(new != NULLSESSION){
        /* Load new screen */
        if(new->screen->stype == UNKNOWN_SWAP) {
            /* A brand new screen */
#ifdef EMS
            if(SwapMode == EMS_SWAP) {
                if(efmalloc(ScreenSize,&new->screen->sv.ems.token) != 0)
                    new->screen->sv.ems.token = -1;
                new->screen->stype = EMS_SWAP;
            } else
#endif
#ifdef XMS
                if(SwapMode == XMS_SWAP) {
                    new->screen->sv.handle = Alloc_XMS(ScreenSizeK);
                    new->screen->stype = XMS_SWAP;
                } else
#endif
                    if(SwapMode == FILE_SWAP)
                        new->screen->stype = FILE_SWAP;
                    else
                        new->screen->stype = MEM_SWAP;
  
#ifdef SPLITSCREEN
            if(new->split){
                new->tsavex = 1;
                new->tsavey = 1;
                new->bsavex = 1;
                new->bsavey = Numrows-1;
                /* The output window */
                window(1,1+StatusLines,Numcols,Numrows);
                textattr(SplitColors);
                clrscr();       /* Start with a fresh slate */
                cputs("_\b");
  
                /* The input window */
                window(1,1+StatusLines,Numcols,Numrows-2);
                textattr(MainColors);
                clrscr();       /* Start with a fresh slate */
                cputs("_\b");
            } else
#endif
            {
                /* leave room for the status line */
                window(1,1+StatusLines,Numcols,Numrows);
                clrscr();       /* Start with a fresh slate */
            }
        } else {
#ifdef SPLITSCREEN
            if(new->split){
                window(1,1+StatusLines,Numcols,Numrows-2);
                textattr(MainColors);
            } else
#endif
            {
                window(1,1+StatusLines,Numcols,Numrows);
            }
#ifdef EMS
            if(new->screen->stype == EMS_SWAP &&
            (int)new->screen->sv.ems.token != -1) {
                /* Get the text from EMS into screen buffer */
                set1eptr(new->screen->sv.ems.token,(void far**)&new->screen->sv.ems.save);
                memcpy(Screen,new->screen->sv.ems.save,ScreenSize);
            } else
#endif
#ifdef XMS
                if(new->screen->stype == XMS_SWAP &&
                new->screen->sv.handle) {
                /* Get the text from XMS into screen buffer */
                    X.Length = (long) ScreenSize;
                    X.SourceHandle = new->screen->sv.handle;
                    X.SourceOffset = 0L;
                    X.DestHandle = 0L;  /* Indicate conventional memory */
                    X.DestOffset = (long) Screen;
                    Move_XMS(&X);
                } else
#endif
                    if(new->screen->stype == FILE_SWAP &&
                    new->screen->sv.fp) {
                        fseek(new->screen->sv.fp,0L,0);
                        fread(Screen,ScreenSize,1,new->screen->sv.fp);
  
                /* Not closing is a little faster,
                 * but takes more resources
                 * while the session is active - WG7J
                 */
                        fclose(new->screen->sv.fp);
                        new->screen->sv.fp = NULLFILE;
  
                    } else if(new->screen->stype == MEM_SWAP &&
                    new->screen->sv.save) {
                        memcpy(Screen,new->screen->sv.save,ScreenSize);
                    /* Free the memory (saves 4K on a continuous basis) */
                        free(new->screen->sv.save);
                        new->screen->sv.save = NULLCHAR;
                    } else
                /* Somehow, we couldn't save the old screen */
                        clrscr();
            gotoxy(new->screen->col,new->screen->row);
        }
    }
    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 */
            if(StatusLines)
                cputs("\r        \r");
            else
                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(StatusLines || sp->split){
            if(c == 0x0a){
                cputs(Eol);
                clreol();
            } else if(c == 0x09)     /* TAB */
                do putch(' '); while(wherex() % 8 != 1);
            else
                putch(c);
        } else {
            putc(c,Rawterm);
        }
        /* Fix by Ron Murray, vk6zjm */
        if(sp->record != NULLFILE) {     /* Don't save CR if ascii mode */
            if(c == '\r' || c == '\n')
                fflush(sp->record);
            if(c != '\r' || sockmode(sp->output, -1) != SOCK_ASCII)
                putc(c,sp->record);
        }
#ifdef notdef
        if(sp->record != NULLFILE)
            putc(c,sp->record);
#endif
        if(sp->flowmode && c == '\n' && --sp->row <= 0){
            if(StatusLines)
                cputs("--More--");
            else
                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()
{
    if(Mtasker < 5) {   /* Read direct except under OS/2 or DPMI */
        int i_state;
        long rval;
  
        i_state = dirps();
        rval = * (long far *)MK_FP(0x40,0x6c);
        restore(i_state);
        return rval;
    } else {                /* Use BIOS call under OS/2 or DPMI */
        return(biostime(0,0L));
    }
}
  
/* 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;
}
  
#ifdef STATUSWIN
  
void StatusLine1(void);
  
/* First line. Global information */
void StatusLine1() {
  
    struct session *sp;
    int len=0,s,r,t;
    char *cp;
  
    extern int ConvUsers,ConvHosts,BbsUsers,FwdUsers,FtpUsers,SmtpUsers;
#ifdef MAILFOR    
    extern int Mail_Received;	/* see mailfor.c */


/* PE1DGZ: Show blinking 'MAIL' if unread mail is present */
    if(Mail_Received) {
        textattr(MainStColors | 0x80);
        len += cputs("MAIL ");
    }
#endif    
  
    /* Set the colors */
    textattr(MainStColors);
  
#ifdef MSDOS
#if __BORLANDC__ >= 0x0400
    {
        char buf[9];
  
        _strtime(buf);  /* This only exists in bc3.0 and up */
        buf[5] = ' ';
        buf[6] = '\0';
        len+=cputs(buf);
    }
#endif
#endif
  
    len+=cprintf("%5.5lu/%-6.6lu"
#ifdef CONVERS
    " CONV=%d LNKS=%d"
#endif
#ifdef MAILBOX
    " BBS=%d"
#ifdef MBFWD
    " FWD=%d"
#endif
#endif
#ifdef FTPSERVER
    " FTP=%d"
#endif
#ifdef SMTPSERVER
    " SMTP=%d"
#endif
    "  Ses:",
  
    Localheap(),coreleft()
#ifdef CONVERS
    ,ConvUsers, ConvHosts
#endif
#ifdef MAILBOX
    ,BbsUsers
#ifdef MBFWD
    ,FwdUsers
#endif
#endif
#ifdef FTPSERVER
    ,FtpUsers
#endif
#ifdef SMTPSERVER
    ,SmtpUsers
#endif
    );
  
    /* Print all active sessions . Modified from TNOS
     * Calculate how much room there is left on the line
     */
  
    for(sp=Sessions; sp < &Sessions[Nsessions];sp++) {
//        if(Numcols - len < 3)
//            break;
        if(sp->type == FREE || sp->type == COMMAND || sp->type == TRACESESSION)
            continue;
  
        /* if there is data waiting, blink the session number */
        r = socklen(sp->output,1);
        textattr ( (r) ? MainStColors | 0x80 : MainStColors);
        len+=cprintf (" %d", sp->num);
    }
    textattr(MainStColors); /* In case blinking was on! */
    clreol();
}
  
#ifdef MAILBOX
char *StBuf2;	/* allocated in main.c */
int StLen2;  

void StatusLine2(void);
  
void StatusLine2() {
    struct mbx *m;
    char *cp;
    int len;
  
    cp = StBuf2+StLen2;
    *cp = '\0';
    for(m=Mbox;m;m=m->next) {
        if((len = strlen(m->name)) != 0 && (len < Numcols-(cp-StBuf2)-4)) {
            *cp++ = ' ';
            if(m->sid & MBX_SID)
                *cp++ = '*';    /* Indicate a bbs */
            else switch(m->state) {
                case MBX_GATEWAY:
                    *cp++ = '!';
                    break;
                case MBX_READ:
                case MBX_SUBJ:
                case MBX_DATA:
                    *cp++ = '#';
                    break;
                case MBX_UPLOAD:
                case MBX_DOWNLOAD:
                case MBX_XMODEM_RX:
                case MBX_XMODEM_TX:
                    *cp++ = '=';
                    break;
                case MBX_SYSOPTRY:
                case MBX_SYSOP:
                    *cp++ = '@';
                    break;
                case MBX_CONVERS:
                case MBX_CHAT:
                    *cp++ = '^';
                case MBX_CMD:
                    *cp++ = ' ';    /* To keep things aligned nicely */
                    break;
                default:
                    *cp++ = '?';
                    break;
            }
            strcpy(cp,m->name);
            cp += len;
        }
    }
    cputs(StBuf2);
    clreol();
}
#endif /* MAILBOX */
  
char *StBuf3;	/* allocated in main.c */
int StLen3;
void StatusLine3(void);
  
/* The session dependent data */
void StatusLine3() {
    char *cp;
    int  s,t,SesType;
    struct usock *up;
  
    static struct session *MyCurrent;
    static int SesNameLen;
    static int SockStatus,SockName,SesData;
  
    /* Set the colors */
    textattr(SesStColors);
  
    /* Next line. Session specific information */
    if(MyCurrent != Current) {
        /* Keep track of the current session */
        MyCurrent = Current;
        SesType = MyCurrent->type;
        /* Remember to offset for "\r\n" at start of buffer ! */
        SesNameLen = 2;
        SesNameLen+=sprintf(StBuf3+SesNameLen,"%d %s:",
        MyCurrent->num,Sestypes[SesType]);
  
        /* We can't show network socket data until socket is valid ! */
        SesData = SockName = 0;
  
        SockStatus = 1; /* Show socket status by default */
        if(SesType == COMMAND || SesType == TRACESESSION) {
//            if(Command->curdirs)
//                sprintf(StBuf3+SesNameLen, "%-30.30s",Command->curdirs->dir);
            SockStatus = 0;     /* Don't show socket name and ses data */
  
        } else if(SesType == MORE ||
            SesType == REPEAT ||
            SesType == LOOK) {
                sprintf(StBuf3+SesNameLen," %-20.20s",MyCurrent->name);
                SockStatus = 0;     /* Don't show socket name and ses data */
          }
    }
    /* Only if this is a session with a network socket do we show the status */
    if(SockStatus) {
        if(!SockName && (s=MyCurrent->s) != -1) {
            int i;
            struct sockaddr fsocket;
  
            /* The session now has a valid network socket.
             * Go get the name, and pointer.
             */
            if(getpeername(s,(char *)&fsocket,&i) == -1)
                cp = "";
            else cp = psocket(&fsocket);
            SesNameLen+=sprintf(StBuf3+SesNameLen," %-18.18s TxQ ",cp);
            SockName = SesData = 1;
        }
        /* We have the socket name, now go print the socket session data */
        if(SockName) {
            /* Some sessions keep hanging on to their network socket
             * until the use hits return to close the session !
             * Others will delete it sooner...
             */
            if((s=MyCurrent->s) != -1 && ((cp=sockstate(s))!= NULL) ) {
                /* Network socket for session still valid */
                t = socklen(s,1);
                StLen3 = SesNameLen +
                sprintf(StBuf3+SesNameLen,"%4.4d St: %-12.12s",t,cp);
            } else {
                SesData = 0;    /* Don't print rest of line 3 ! */
                sprintf(StBuf3+SesNameLen-4,"  LIMBO !");
            }
            /* If the socket is still valid, print some data */
            if(SesData) {
                up = itop(s);
                switch(up->type) {
                    case(TYPE_TCP):
                    {
                        struct tcb *tcb = up->cb.tcb;
                        sprintf(StBuf3+StLen3,
                        " T: %5.5ld/%-5.5ld ms",
                        (long)read_timer(&tcb->timer),
                        (long)dur_timer(&tcb->timer));
                    }
                        break;
#ifdef AX25
                    case(TYPE_AX25I):
                    {
                        struct ax25_cb *axp = up->cb.ax25;
                        sprintf(StBuf3+StLen3,
                        " T1: %5.5ld/%5.5ld ms",
                        (long)read_timer(&axp->t1),
                        (long)dur_timer(&axp->t1));
                    }
                        break;
#endif
#ifdef NETROM
                    case(TYPE_NETROML4):
                    {
                        struct nr4cb *cb = up->cb.nr4;
                        sprintf(StBuf3+StLen3,
                        " T: %5.5ld/%5.5ld ms",
                        (long)read_timer(&cb->tcd),
                        (long)dur_timer(&cb->tcd));
                    }
                        break;
#endif
                }
            }
        }
    }
    cputs(StBuf3);
    clreol();
}
  
/* Build the status window on the screen - WG7J */
void UpdateStatus() {
  
    if(!StatusLines)
        return;
  
#ifdef MULTITASK
    /* if we are shelled out with multi-task on, do not update the status */
    if(Background && Nokeys)
        return;
#endif
  
  
    {
        struct text_info ti;
  
    /* get the current output context */
        gettextinfo(&ti);
  
    /* create the window */
        window(1,1,Numcols,StatusLines);
  
        StatusLine1();  /* Global system status */
        if(StatusLines > 1)
#ifdef MAILBOX
            StatusLine2();  /* Mailbox user status */
        if(StatusLines > 2)
#endif
            StatusLine3();    /* Session dependent status */
  
    /* restore the previous output context */
        window((int)ti.winleft,(int)ti.wintop,
        (int)ti.winright,(int)ti.winbottom);
        textattr(ti.attribute);
        gotoxy(ti.curx,ti.cury);
    }
}
  
#endif /* STATUSWIN */
  
