#ifdef UNIX
/*
 * 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...
 */

/* DO NOT allow this file to be optimized, since there is a bug in
   GCC 4.6.x that causes code in ding() to DEVOUR CPU time */


#ifndef _lint
#define J_FREE_HACK 1	/* this eliminates a compiler warning */
#endif

#include "global.h"
#include "ctype.h"
#ifdef linux
#undef IS_LITTLE_ENDIAN	/* to avoid a re-definition warning */
#endif
#include <sys/stat.h>
#include <sys/wait.h>
#include <signal.h>
#include "hardware.h"
#include "commands.h"
#ifndef _SYS_TIME_H
#include <sys/time.h>
#endif
#include "timer.h"
#include "proc.h"
#include "socket.h"
#include "session.h"
#ifdef ALPHATEST
#include "main.h"
#endif

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

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: unix.c,v 1.29 1997/09/14 14:37:46 root Exp $";
#endif

/* and undo a collision avoidance, since we need the real one */
#undef malloc
#undef free
#if 0	/* shouldn't need - should be in stdlib */
extern void *malloc (size_t);
extern void free (void *);
#endif

#ifdef SETPSINFO
int tnos_putenv (const char *envstr);
void fix_env (void);
#endif
static void deinit_tick(void);

/* some versions of GNU libc (at least some for Linux) have a bad bug
   that once it happens, a call to __libc_free() never returns. These
   two variables (along with code in ding() and j_free()) are here to
   keep this from disabling a system indefinitely. If a call to free
   takes longer than 60 seconds to return, then TNOS is exited.
 */

static int within_a_free_call = 0;
static int time_in_a_free_call = 0;
#define MAX_TIME_INA_FREE_CALL	60


/* 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];
volatile int Tick;
int Keyboard;
volatile int32 Clock;
extern struct timer *Timers;

static int __istate = 1;
static struct timeval Starttime;
static struct io *Events;
#ifdef SHELL
static int Shell;
#endif


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


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

	if (fstat (fd, &sb) == -1)
		return 0;
	return (unsigned long) 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 = (char) toupper (*p), p++;
	return s;
}


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

	while (*p)
		*p = (char) 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)
uint32 ipaddr;
{
int h;

	h = ((ipaddr >> 16) & 0xFFFF) ^ (ipaddr & 0xFFFF);
	return (int16) (Hashtab[((h >> 8) & 0xFF) ^ (h & 0xFF)]);	/*lint !e702 !e571 */
}

/*****************************************************************************\
*				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;
int waited = 0;

	while ((p = malloc(size)) == (void *)0)	{
		kpause (1000);
		if (++waited > 150)
			where_outta_here(1, "mallocw");
	}
#ifdef LMCHECK
	mwhash(p);
#endif
	return p;
}


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

	p = mallocw (size * cnt);
	memset(p, 0, size * cnt);
	return p;
}



static jmp_buf free_trap;


static void
j_free_trap (int sig OPTIONAL)
{
	++Ksig.kfreesegvs;
	longjmp (free_trap, 1);
}



void
j_free(IFLINT(const) void *p)
{
struct sigaction sa, saold;

	if (p)	{
#ifdef LMCHECK
		if (!mwunhash (p))	{
			printf("\r\7WARNING: free()ing unknown pointer %lx\r\n", (unsigned long) p);
			return;
		}
#endif
		time_in_a_free_call = 0;
		within_a_free_call = 1;

		/* protect free() from SEGV signals */
		sa.sa_handler = j_free_trap;
		sa.sa_flags = 0;
		(void) sigaction (SIGSEGV, &sa, &saold);

		if (setjmp (free_trap) == 0)
			free((void *)p);

		/* restore previous value for SIGV signals */
		(void) sigaction (SIGSEGV, &saold, (struct sigaction *) 0);
		within_a_free_call = 0;
	}
}



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

int
istate()
{
	return __istate;
}


int
disable ()
{
sigset_t s;
int ops;

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

int
enable (void)
{
	restore (1);
	return 0;
}

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

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


/*****************************************************************************\
*			      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;

	(void) gettimeofday (&tv, &tz);
	return tv.tv_sec - Starttime.tv_sec - (Starttime.tv_usec > tv.tv_usec);		/*lint !e514 */
#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;

	(void) 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;

	(void) 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;

	(void) gettimeofday (&tv, &tz);
	tm = localtime ((const time_t *)&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
tnos_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;

	(void) gettimeofday (&tv, &tz);
	tm = localtime ((const time_t *)&tv.tv_sec);
#endif
	dp->da_year = tm->tm_year + 1900;
	if (dp->da_year < 1970)
		dp->da_year += 100;
	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? */
	(void) 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)) == (struct io *)0)	{
		tputs ("register_io: no memory for event\n");
		where_outta_here (1, "register_io");
	}
	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)	{
		tputs ("unregister_io: unknown fd\n");
		return;
	}
	if (evp)
		evp->next = evc->next;
	else
		Events = evc->next;
	j_free (evc);
}


#ifndef DUMP_CORE
#ifdef ALPHATEST
extern void proc_launch (void);
#endif

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

	sa.sa_handler = SIG_DFL;
	sa.sa_flags = 0;
	(void) sigaction (SIGSEGV, &sa, (struct sigaction *) 0);
#ifdef SIGBUS
	(void) sigaction (SIGBUS, &sa, (struct sigaction *) 0);
#endif

#if 0
	if (fork() == 0)	{
		sigaction(sig, &sa, (struct sigaction *) 0);
		(void) kill(getpid(), sig);
	}
	detach_all_asy();
#endif

	if (shall_we_crash())
		crash_it_already ("SIGSEGV signal");
#ifdef ALPHATEST
	if (++Ksig.kresumes == RESTART_COUNT || Command->proc == Curproc)
		exit (1);

	/* Stop alarm clock in case it's running */
	stop_timer (&Curproc->alarm);

	/* attempt to simply kill off the process running, not TNOS */
	if (main_exit != TRUE)	{
		time_t now;
		
		log (-1, "signal SIGSEGV caught - killing process '%s' and resuming",
			Curproc->name);
		now = time ((time_t *)0);
		tcmdprintf ("*** signal SIGSEGV caught at %s*** killing process '%s' and resuming\n",
			ctime (&now), Curproc->name);
		tprintf ("\n!!! System error occurred !!!\n");
	}
	/* these were done without a signal handler, to allow dying if logging fails */

	/* reset the handler */
	sa.sa_handler = ouch;
	sa.sa_flags = 0;
	(void) sigaction (SIGSEGV, &sa, (struct sigaction *) 0);
#ifdef SIGBUS
	(void) sigaction (SIGBUS, &sa, (struct sigaction *) 0);
#endif
	switch (Curproc->ptype)	{
		case PTYPE_IO:
		case PTYPE_DAEMON:
		case PTYPE_SERVER:	Ksig.krestarts++;
					if (main_exit == TRUE)
						killself ();
					else
						proc_launch ();		/* restart the process ;-) */
					break;
		case PTYPE_NORMAL:
		default:		if (main_exit != TRUE)	{
						tflush ();
						kpause (3000);
					}
					killself ();		/* let the errant process be the martyr */
	}
#endif
}
#endif


static void
ding(int i)
{
static struct timeval tv;
long oclock;
struct timeval *tvp;		/*lint -esym(550,tvp) */
#ifndef _lint
static fd_set fds;
struct io *evp;
#endif

	/* first a check for a locked up call to free() */
	if (within_a_free_call)	{
		if (++time_in_a_free_call > MAX_TIME_INA_FREE_CALL)	{
			within_a_free_call = 0;
			where_outta_here(3, "a libc free() lockup");
		}
	}
	
	/* do pending output */
	if (!i)		{
		tflush();
		rflush();
	}
	/* collect input events to wait on */
#ifndef _lint
	FD_ZERO(&fds);
	for (evp = Events; evp; evp = evp->next)
		FD_SET(evp->fd, &fds);
#endif
	/* 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 {
		/* This section gets improperly optimized in GCC 4.6.x */
		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 */
#ifndef _lint
	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))
			ksignal (evp->event, 1);
	}
#endif
	/* run any due timers */
	ksignal ((volatile void *) &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;
	(void) 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;
	(void) 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;
	(void) setitimer (ITIMER_REAL, &it, (struct itimerval *) 0);
}


static void
cleanup (int sig OPTIONAL)
{
	where_outta_here (0, "cleanup");
}


void
init_sys(int no_itimer)
{
struct sigaction sa;

	init_time();
	register_io (0, &Keyboard);

#ifndef DUMP_CORE
	sa.sa_handler = ouch;
	sa.sa_flags = 0;
	(void) sigaction (SIGSEGV, &sa, (struct sigaction *) 0);
#ifdef SIGBUS
	(void) sigaction (SIGBUS, &sa, (struct sigaction *) 0);
#endif
#endif /* DUMP_CORE */

	sa.sa_handler = cleanup;
	sa.sa_flags = 0;
	(void) sigaction (SIGTERM, &sa, (struct sigaction *) 0);

	sa.sa_handler = SIG_IGN;
	sa.sa_flags = 0;
	(void) sigaction (SIGWINCH, &sa, (struct sigaction *) 0);
	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 SETPSINFO

static char **tnos_envp = NULLCHARP;
static int tnos_envp_size = 0;

static int
find_env (const char *lookfor)
{
char **env;
unsigned int len;
int theindex = 0;

	len = strlen (lookfor);
	for (env = tnos_envp; env && *env; env++, theindex++)	{
		if (!strncmp (lookfor, *env, len) && (*env)[len] == '=')
			return theindex;
	}
	return -1;
}


static int
_putenv (const char *envstr)
{
char **new_envp;

	new_envp = (char **) callocw ((unsigned int)(tnos_envp_size + 2), sizeof (char *));
	if (new_envp == NULLCHARP)
		return -1;

	memcpy (new_envp, tnos_envp, (size_t) ((unsigned int)tnos_envp_size * sizeof (char *)));
	new_envp[tnos_envp_size++] = strdup (envstr);
	free (tnos_envp);
	tnos_envp = new_envp;
	return 0;
}



int
tnos_putenv (const char *envstr)
{
char *cp;
unsigned int len;

	/* sanity checking */
	if (!envstr || !*envstr || (cp = strchr (envstr, '=')) == NULLCHAR)
		return -1;

	/* look to see if the entry already exists, if so, error */
	len = (unsigned int) (cp - envstr);
	cp = (char *) malloc (len + 1);
	if (cp == NULLCHAR)
		return -1;
	strncpy (cp, envstr, len);
	if (find_env (cp) != -1)
		return -1;
	free (cp);
	
	/* add new entry in the environment */
	return _putenv (envstr);
}


#if 0

char *
getenv (const char *lookfor)
{
char **env = tnos_envp;
unsigned int len;
int theindex;

	len = strlen (lookfor);
	theindex = find_env (lookfor);
	
	if (theindex != -1)
		return (env[theindex] + (len + 1));
#if 0
		return (*env + (len + 1));
#endif
	return NULLCHAR;
}



int
setenv (const char *name, const char *value, int overwrite)
{
int theindex;
char *buf;
int retval = 0;

	/* create the new entry */
	buf = (char *) callocw (1, strlen(name) + strlen(value) + 2);
	if (buf == NULLCHAR)
		return -1;
	sprintf (buf, "%s=%s", name, value);

	/* see if thie entry already exists */
	theindex = find_env (name);
	if (theindex == -1)	{	/* new entry */
		retval = _putenv (buf);
		free (buf);
	} else	{			/* entry exists */
		if (overwrite)	{
			free (tnos_envp[theindex]);
			tnos_envp[theindex] = buf;
		} else
			free (buf);
	}
	return retval;
}


void
unsetenv (const char *name)
{
int theindex;

	while ((theindex = find_env (name)) != -1)	{
		free (tnos_envp[theindex]);

		do	{
			tnos_envp[theindex] = tnos_envp[theindex + 1];
		} while (tnos_envp[++theindex]);
		tnos_envp_size--;
	}
}
#else
#if !defined (__ELF__)
#undef __environ
#ifndef _lint
#define __environ       environ
#endif
#endif

#ifndef _lint
extern char **__environ;
#endif


void
fix_env (void)
{

	__environ = tnos_envp;
}
#endif
#endif

#ifdef SHELL

int
doshell(int argc, char **argv, void *p OPTIONAL)
{
struct sigaction sa, old_int, old_quit;
int ac, pid;
int pi[2];
char const *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() || (Curproc->input != Command->input))    {
		tputs ("Not running on TNOS console\n");
		return 1;
	}
	if (pipe(pi) == -1)    {
		tputs ("Can't create pipe for subprocess\n");
		return 1;
	}
	switch (pid = fork())    {
		case -1:
			tputs ("Fork failed\n");
			return 1;
		case 0:
			close (pi[0]);
			ac = 1;
			if (argc > 1 || (av[0] = getenv ("SHELL")) == NULLCHAR)
				av[0] = "/bin/sh";
			if (argc > 1)	{
				av[ac++] = "-c";
				av[ac++] = argv[1];
			}
			av[ac] = 0;
			(void) execve (av[0], (char * const *)av, (char * const *)tnos_envp);	/*lint !e605 */
			_exit(1);
		default:
			close (pi[1]);
			iosuspend();
			unregister_io (0);
			register_io (pi[0], &Shell);
			/* signal handling... */
			(void) sigemptyset (&sa.sa_mask);
			sa.sa_flags = 0;
			sa.sa_handler = SIG_IGN;
			(void) sigaction (SIGINT, &sa, &old_int);
			(void) sigaction (SIGQUIT, &sa, &old_quit);
			kwait (&Shell);
			(void) sigaction (SIGQUIT, &old_quit, 0);
			(void) 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 */
			(void) waitpid (pid, &ac, WNOHANG);
			swapscreen(NULLSESSION, Command);
	}
	return 0;
}

#endif		/* SHELL */

#endif		/* UNIX */

