/*
 * Timers and process delays work differently under POSIX.  The entire system
 * is driven on a single select() call, which uses the timeout to detect alarms
 * and the file descriptors to detect input.  An itimer is used to allow
 * keyboard input to continue during lengthy activities --- which I tried to
 * avoid for portability reasons, but it behaves *real* ugly otherwise.
 * Especially when LakeSW.ampr.org lets 350K of SMTP mail pile up...
 */

#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <signal.h>
#include "hardware.h"
#ifdef M_UNIX
#include <time.h>
#include <sys/select.h>
#else
#include <sys/time.h>
#endif
#include "timer.h"
#include "proc.h"
#include "unixtm.h"
#include "socket.h"
#include "session.h"

#ifdef NO_GETTOD
#include <sys/timeb.h>
#endif

#ifdef M_UNIX
#ifndef bzero
/* stupid bug in SCO 3.2.2 (is there any other kind for them?) */
#define bzero(s,n) memset((s),0,(n))
#endif
#endif

/* avoid collision with libc */
#define psignal j_psignal

/* and undo a collision avoidance, since we need the real one */
#undef malloc
#undef free
extern void *malloc __ARGS((size_t));
extern void free __ARGS((void *));

/* Don't use this yet... something's still calling malloc() directly */
#undef LMCHECK			/* TEST memory allocation checker */

struct io
{
    struct io *next;
    int fd;
    void *event;
};

char Hashtab[256];
int Tick;
int Keyboard;
int32 Clock;

static int __istate = 1;
static struct timeval Starttime;
static struct io *Events;
static int Shell;

/*****************************************************************************\
*		  Miscellanous functions not found in Unix		      *
\*****************************************************************************/

unsigned long
filelength(fd)
    int fd;
{
    static struct stat sb;

    if (fstat(fd, &sb) == -1)
	return 0;
    return sb.st_size;
}

#ifdef NEED_STRCASECMP

int
strcasecmp(s1, s2)
    register char *s1, *s2;
{
    while (*s1 && *s2 && tolower(*s1) == tolower(*s2))
	s1++, s2++;
    return tolower(*s1) - tolower(*s2);
}

#endif

#ifdef NEED_STRNCASECMP

int
strncasecmp(s1, s2, n)
    register char *s1, *s2;
    int n;
{
    while (n && *s1 && *s2 && tolower(*s1) == tolower(*s2))
	s1++, s2++, n--;
    return n? tolower(*s1) - tolower(*s2): 0;
}

#endif

char *
strupr(s)
    char *s;
{
    register char *p = s;

    while (*p)
	*p = toupper(*p), p++;
    return s;
}

char *
strlwr(s)
    char *s;
{
    register char *p = s;

    while (*p)
	*p = tolower(*p), p++;
    return s;
}

char *
stpcpy(d, s)
    register char *d;
    register const char *s;
{
    while (*s)
	*d++ = *s++;
    *d = '\0';
    return d;
}

char *
itoa(n, buf, base)
    int n, base;
    char *buf;
{
    if (base != 10)
	tprintf("WARNING: itoa() passed radix %d\n", base);
    sprintf(buf, "%d", n);
    return buf;
}

/* This was in assembler, I assume for speed. */

int16
hash_ip(ipaddr)
    long ipaddr;
{
    int h;

    h = ((ipaddr >> 16) & 0xFFFF) ^ (ipaddr & 0xFFFF);
    return Hashtab[((h >> 8) & 0xFF) ^ (h & 0xFF)];
}

/*****************************************************************************\
*				Memory interface			      *
\*****************************************************************************/

#ifdef LMCHECK

/*
 * We track memory allocation via hash buckets.  This is a fixed-size hash
 * table with variable-sized buckets:  we hash an address by taking some bits
 * from it, and store the result in a slot in the bucket.
 */

#define NMWHASH		4096	/* number of hash buckets - s/b power of 2 */
#define LOGNMWH		12	/* log2(NMWHASH) */
#define NMWHSHIFT	8	/* ptr shift for hashing */
#define NMWHSLOT	32	/* initial slots in bucket */

struct hbucket
{
    void **ptr;
    int nptr;
};

static struct hbucket MWhashtab[NMWHASH];

static void
mwhash(void *p)
{
    register struct hbucket *h;
    int i;

    h = MWhashtab + (((unsigned long) p >> NMWHSHIFT) & (NMWHASH - 1));
    if (!h->nptr)
    {
	h->ptr = malloc(NMWHSLOT * sizeof *h->ptr);
	memset(h->ptr, 0, NMWHSLOT * sizeof *h->ptr);
	i = 0;
    }
    else
    {
	for (i = 0; i < h->nptr; i++)
	{
	    if (!h->ptr[i])
		break;
	}
	if (i == h->nptr)
	{
	    h->ptr = realloc(h->ptr, (h->nptr + NMWHSLOT) * sizeof *h->ptr);
	    memset(h->ptr + h->nptr, 0, NMWHSLOT * sizeof *h->ptr);
	    h->nptr += NMWHSLOT;
	}
    }
    h->ptr[i] = p;
}

static int
mwunhash(void *p)
{
    register struct hbucket *h;
    int i;

    h = MWhashtab + (((unsigned long) p >> NMWHSHIFT) & (NMWHASH - 1));
    for (i = h->nptr; i--; )
    {
	if (h->ptr[i] == p)
	    break;
    }
    if (i == -1)
	return 0;
    h->ptr[i] = 0;
    return 1;
}

#endif

void *
mallocw(unsigned size)
{
    void *p;

    if (!(p = malloc(size)))
	where_outta_here(1);
#ifdef LMCHECK
    mwhash(p);
#endif
    return p;
}

void *
callocw(cnt, size)
    unsigned cnt, size;
{
    void *p;

    if (!(p = malloc(size * cnt)))
	where_outta_here(1);
#ifdef LMCHECK
    mwhash(p);
#endif
    memset(p, 0, size * cnt);
    return p;
}

void
j_free(void *p)
{
#ifdef LMCHECK
    if (!mwunhash(p))
    {
	printf("\r\7WARNING: free()ing unknown pointer %lx\r\n",
	       (unsigned long) p);
	return;
    }
#endif
    free(p);
}

#if 0

/*
 * these should by rights try to determine the available VM... oh, well
 */

unsigned long
availmem()
{
#ifdef linux
    return 0xC0000000 - (unsigned long) sbrk(0);
#else
    return 0x80000000 - (unsigned long) sbrk(0);
#endif
}

unsigned long
farcoreleft()
{
#ifdef linux
    return 0xC0000000 - (unsigned long) sbrk(0);
#else
    return 0x80000000 - (unsigned long) sbrk(0);
#endif
}

unsigned long
getss()
{
    return 0;
}

#endif

/*****************************************************************************\
*			Interrupt management - null			      *
\*****************************************************************************/

int
istate()
{
    return __istate;
}

int
dirps()
{
    sigset_t s;
    int ops;

    if (__istate)
    {
	sigemptyset(&s);
	sigaddset(&s, SIGALRM);
	sigprocmask(SIG_BLOCK, &s, (sigset_t *) 0);
    }
    ops = __istate;
    __istate = 0;
    return ops;
}

void
restore(ps)
    int ps;
{
    sigset_t s;

    if (__istate != ps)
    {
	sigemptyset(&s);
	sigaddset(&s, SIGALRM);
	sigprocmask((ps? SIG_UNBLOCK: SIG_BLOCK), &s, (sigset_t *) 0);
    }
    __istate = ps;
}

/*****************************************************************************\
*			      Date and time functions			      *
\*****************************************************************************/

long
secclock()
{
#ifdef NO_GETTOD
    static struct timeb t;

    ftime(&t);
    return t.time - Starttime.tv_sec - (Starttime.tv_usec > t.millitm * 1000);
#else
    static struct timezone tz;
    static struct timeval tv;

    gettimeofday(&tv, &tz);
    return tv.tv_sec - Starttime.tv_sec - (Starttime.tv_usec > tv.tv_usec);
#endif
}

long
msclock()
{
#ifdef NO_GETTOD
    static struct timeb t;

    ftime(&t);
    t.millitm *= 1000;
    if (t.millitm < Starttime.tv_usec)
    {
	t.millitm += 1000000;
	t.time--;
    }
    return (t.time - Starttime.tv_sec) * 1000 +
	(t.millitm - Starttime.tv_usec) / 1000;
#else
    static struct timezone tz;
    static struct timeval tv;

    gettimeofday(&tv, &tz);
    if (tv.tv_usec < Starttime.tv_usec)
    {
	tv.tv_usec += 1000000;
	tv.tv_sec--;
    }
    return (tv.tv_sec - Starttime.tv_sec) * 1000 +
	(tv.tv_usec - Starttime.tv_usec) / 1000;
#endif
}

static void
init_time(void)
{
#ifdef NO_GETTOD
    struct timeb t;

    ftime(&t);
    Starttime.tv_sec = t.time;
    Starttime.tv_usec = t.millitm * 1000;
#else
    struct timezone tz;

    gettimeofday(&Starttime, &tz);
#endif
}

void
gettime(tp)
    struct time *tp;
{
    struct tm *tm;
#ifdef NO_GETTOD
    static struct timeb tb;

    ftime(&tb);
    tm = localtime(&tb.time);
    tp->ti_hund = tb.millitm / 10;
#else
    static struct timeval tv;
    static struct timezone tz;

    gettimeofday(&tv, &tz);
    tm = localtime(&tv.tv_sec);
    tp->ti_hund = tv.tv_usec / 10000;
#endif
    tp->ti_hour = tm->tm_hour;
    tp->ti_min = tm->tm_min;
    tp->ti_sec = tm->tm_sec;
}

void
getdate(dp)
    struct date *dp;
{
    struct tm *tm;
#ifdef NO_GETTOD
    static struct timeb tb;

    ftime(&tb);
    tm = localtime(&tb.time);
#else
    static struct timeval tv;
    static struct timezone tz;

    gettimeofday(&tv, &tz);
    tm = localtime(&tv.tv_sec);
#endif
    dp->da_year = tm->tm_year + 1900;
    dp->da_mon = tm->tm_mon + 1;
    dp->da_day = tm->tm_mday;
}

long
dostounix(dp, tp)
    struct date *dp;
    struct time *tp;
{
    static struct tm tm;
    struct tm *tx;
    long now;

    tm.tm_year = dp->da_year - 1900;
    tm.tm_mon = dp->da_mon - 1;
    tm.tm_mday = dp->da_day;
    tm.tm_hour = tp->ti_hour;
    tm.tm_min = tp->ti_min;
    tm.tm_sec = tp->ti_sec;
    /* This desperately needs to be fixed.  How? */
    time(&now);
    tx = localtime(&now);
    tm.tm_isdst = tx->tm_isdst;
    return mktime(&tm);
}

/*****************************************************************************\
*			    Timers, I/O and scheduling			      *
\*****************************************************************************/

void
register_io(fd, event)
    int fd;
    void *event;
{
    struct io *evp;

    if (!(evp = mallocw(sizeof *evp)))
    {
	tprintf("register_io: no memory for event\n");
	where_outta_here(1);
    }
    evp->fd = fd;
    evp->event = event;
    evp->next = Events;
    Events = evp;
}

void
unregister_io(fd)
    int fd;
{
    struct io *evp, *evc;

    for (evp = 0, evc = Events; evc->fd != fd; evp = evc, evc = evc->next)
	;
    if (!evc)
    {
	tprintf("unregister_io: unknown fd\n");
	return;
    }
    if (evp)
	evp->next = evc->next;
    else
	Events = evc->next;
    j_free(evc);
}

static void
ouch(int sig)
{
    struct sigaction sa;

    sa.sa_handler = SIG_DFL;
    sa.sa_flags = 0;
    sigaction(SIGSEGV, &sa, (struct sigaction *) 0);
#ifdef SIGBUS
    sigaction(SIGBUS, &sa, (struct sigaction *) 0);
#endif
    if (fork() == 0)
    {
	sigaction(sig, &sa, (struct sigaction *) 0);
	raise(sig);
    }
    detach_all_asy();
    exit(1);
}

static void
ding(int i)
{
    extern struct timer *Timers;
    static struct timeval tv;
    struct timeval *tvp;
    static fd_set fds;
    struct io *evp;
    long oclock;

    /* do pending output */
    if (!i)
    {
	tflush();
	rflush();
    }
    /* collect input events to wait on */
    FD_ZERO(&fds);
    for (evp = Events; evp; evp = evp->next)
	FD_SET(evp->fd, &fds);
    /* get time until next timer; if zero, fake a very large one */
    /* if we have a nonzero argument, we're a timer tick; poll, don't block */
    if (i)
    {
	tv.tv_sec = tv.tv_usec = 0;
	tvp = &tv;
    }
    else if (!Timers)
	tvp = 0;
    else
    {
	tv.tv_sec = (Timers->expiration - Clock) * MSPTICK;
	if (tv.tv_sec <= 0)
	    tv.tv_sec = 0;
	tv.tv_usec = (tv.tv_sec % 1000) * 1000;
	tv.tv_sec /= 1000;
	tvp = &tv;
    }
    /* check for I/O */
    select(FD_SETSIZE, &fds, 0, 0, tvp);
    /* signal events for pending I/O */
    for (evp = Events; evp; evp = evp->next)
    {
	if (FD_ISSET(evp->fd, &fds))
	    psignal(evp->event, 1);
    }
    /* run any due timers */
    psignal(&Tick, 1);
    /* and update the system time */
    oclock = Clock;
    Clock = msclock() / MSPTICK;
    Tick = Clock - oclock;
}


static void
init_tick(void)
{
    struct sigaction sa;
    struct itimerval it;

    sa.sa_flags = 0;
    sa.sa_handler = ding;
    sigaction(SIGALRM, &sa, (struct sigaction *) 0);
    it.it_interval.tv_sec = 0;
    it.it_interval.tv_usec = MSPTICK * 1000;
    it.it_value = it.it_interval;
    setitimer(ITIMER_REAL, &it, (struct itimerval *) 0);
}

static void
deinit_tick(void)
{
    struct itimerval it;

    it.it_interval.tv_sec = it.it_interval.tv_usec = 0;
    it.it_value = it.it_interval;
    setitimer(ITIMER_REAL, &it, (struct itimerval *) 0);
}

void
init_sys(int no_itimer)
{
    struct sigaction sa;

    init_time();
    register_io(0, &Keyboard);
    sa.sa_handler = ouch;
    sa.sa_flags = 0;
    sigaction(SIGSEGV, &sa, (struct sigaction *) 0);
#ifdef SIGBUS
    sigaction(SIGBUS, &sa, (struct sigaction *) 0);
#endif
    if (!no_itimer)
	init_tick();
}

void
deinit_sys()
{
    deinit_tick();
    unregister_io(0);
}

void
giveup()
{
    /* suspend heartbeat */
    deinit_tick();
    /* block for I/O */
    ding(0);
    /* and reactivate the tick */
    init_tick();
}

#ifdef SHELL

int
doshell(int argc, char **argv, void *p)
{
    struct sigaction sa, old_int, old_quit;
    int ac, pid;
    int pi[2];
    char *av[4];

    /*
     * Under *ix, one would expect ! or shell to work like in other *ix
     * programs.  Since we don't really want to emulate DOS doshell()'s
     * special handling for argv[1] == "/c" anyway :-) we will handle
     * this properly.
     *
     * argc < 2: ${SHELL:-/bin/sh}
     * >= 2: concatenate and /bin/sh -c it (NOT $SHELL)!
     */
    if (!sm_usestdio())
    {
	tprintf("Not running on TNOS console\n");
	return 1;
    }
    if (pipe(pi) == -1)
    {
	tprintf("Can't create pipe for subprocess\n");
	return 1;
    }
    switch (pid = fork())
    {
    case -1:
	tprintf("Fork failed\n");
	return 1;
    case 0:
	close(pi[0]);
	ac = 1;
	if (argc > 1 || !(av[0] = getenv("SHELL")))
	    av[0] = "/bin/sh";
	if (argc > 1)
	{
	    av[ac++] = "-c";
	    av[ac++] = argv[1];
	}
	av[ac] = 0;
	execv(av[0], av);
	_exit(1);
    default:
	close(pi[1]);
	iosuspend();
	unregister_io(0);
	register_io(pi[0], &Shell);
	/* signal handling... */
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	sa.sa_handler = SIG_IGN;
	sigaction(SIGINT, &sa, &old_int);
	sigaction(SIGQUIT, &sa, &old_quit);
	pwait(&Shell);
	sigaction(SIGQUIT, &old_quit, 0);
	sigaction(SIGINT, &old_int, 0);
	unregister_io(pi[0]);
	close(pi[0]);
	register_io(0, &Keyboard);
	ioresume();
	ac = 0;
	/* this SHOULD take care of clearing vombie processes */
	waitpid (pid, &ac, WNOHANG);
	swapscreen(NULLSESSION, Command);
    }
    return 0;
}

#endif
