shithub: purgatorio

Download patch

ref: 147d51bf7c6cd8798ddb26e33d857be13a66c548
parent: ef6ce918c04fc82bea759fd843cc09e273ec9f6c
author: henesy <devnull@localhost>
date: Sun Nov 4 12:33:14 EST 2018

add utils and such

ape/diff: b/emu/DragonFly/null: No such file or directory ape/diff: b/emu/FreeBSD/null: No such file or directory ape/diff: b/emu/Irix/null: No such file or directory ape/diff: b/emu/Linux/null: No such file or directory ape/diff: b/emu/MacOSX/null: No such file or directory ape/diff: b/emu/NetBSD/null: No such file or directory ape/diff: b/emu/Nt/null: No such file or directory ape/diff: b/emu/OpenBSD/null: No such file or directory ape/diff: b/emu/Plan9/null: No such file or directory ape/diff: b/emu/Solaris/null: No such file or directory ape/diff: b/emu/Unixware/null: No such file or directory ape/diff: b/emu/port/null: No such file or directory ape/diff: b/emu/null: No such file or directory ape/diff: b/limbo/null: No such file or directory ape/diff: b/scripts/null: No such file or directory ape/diff: b/services/httpd/null: No such file or directory ape/diff: b/services/webget/null: No such file or directory ape/diff: b/services/null: No such file or directory ape/diff: b/tools/db/null: No such file or directory ape/diff: b/tools/libstyx/null: No such file or directory ape/diff: b/tools/odbc/null: No such file or directory ape/diff: b/tools/styxtest/null: No such file or directory ape/diff: b/tools/null: No such file or directory ape/diff: b/utils/0a/null: No such file or directory ape/diff: b/utils/0c/null: No such file or directory ape/diff: b/utils/0l/null: No such file or directory ape/diff: b/utils/5a/null: No such file or directory ape/diff: b/utils/5c/null: No such file or directory ape/diff: b/utils/5coff/null: No such file or directory ape/diff: b/utils/5cv/null: No such file or directory ape/diff: b/utils/5l/null: No such file or directory ape/diff: b/utils/6a/null: No such file or directory ape/diff: b/utils/6c/null: No such file or directory ape/diff: b/utils/6l/null: No such file or directory ape/diff: b/utils/8a/null: No such file or directory ape/diff: b/utils/8c/null: No such file or directory ape/diff: b/utils/8l/null: No such file or directory ape/diff: b/utils/9c/null: No such file or directory ape/diff: b/utils/acid/null: No such file or directory ape/diff: b/utils/awk/null: No such file or directory ape/diff: b/utils/c2l/null: No such file or directory ape/diff: b/utils/cat/null: No such file or directory ape/diff: b/utils/cc/null: No such file or directory ape/diff: b/utils/cp/null: No such file or directory ape/diff: b/utils/cvbit/null: No such file or directory ape/diff: b/utils/data2c/null: No such file or directory ape/diff: b/utils/data2s/null: No such file or directory ape/diff: b/utils/echo/null: No such file or directory ape/diff: b/utils/format/null: No such file or directory ape/diff: b/utils/ftl/null: No such file or directory ape/diff: b/utils/iar/null: No such file or directory ape/diff: b/utils/idea/null: No such file or directory ape/diff: b/utils/include/null: No such file or directory ape/diff: b/utils/iyacc/null: No such file or directory ape/diff: b/utils/ka/null: No such file or directory ape/diff: b/utils/kc/null: No such file or directory ape/diff: b/utils/kl/null: No such file or directory ape/diff: b/utils/kprof/null: No such file or directory ape/diff: b/utils/ksize/null: No such file or directory ape/diff: b/utils/kstrip/null: No such file or directory ape/diff: b/utils/ld/null: No such file or directory ape/diff: b/utils/lib/null: No such file or directory ape/diff: b/utils/libmach/null: No such file or directory ape/diff: b/utils/libregexp/null: No such file or directory ape/diff: b/utils/md5sum/null: No such file or directory ape/diff: b/utils/mk/null: No such file or directory ape/diff: b/utils/mkdir/null: No such file or directory ape/diff: b/utils/mkext/null: No such file or directory ape/diff: b/utils/mkppcimage/null: No such file or directory ape/diff: b/utils/ms2/null: No such file or directory ape/diff: b/utils/mv/null: No such file or directory ape/diff: b/utils/na/null: No such file or directory ape/diff: b/utils/ndate/null: No such file or directory ape/diff: b/utils/nm/null: No such file or directory ape/diff: b/utils/ntsrv/null: No such file or directory ape/diff: b/utils/qa/null: No such file or directory ape/diff: b/utils/qc/null: No such file or directory ape/diff: b/utils/ql/null: No such file or directory ape/diff: b/utils/rcsh/null: No such file or directory ape/diff: b/utils/rm/null: No such file or directory ape/diff: b/utils/sed/null: No such file or directory ape/diff: b/utils/sqz/null: No such file or directory ape/diff: b/utils/srclist/null: No such file or directory ape/diff: b/utils/tc/null: No such file or directory ape/diff: b/utils/test/null: No such file or directory ape/diff: b/utils/tl/null: No such file or directory ape/diff: b/utils/tr/null: No such file or directory ape/diff: b/utils/va/null: No such file or directory ape/diff: b/utils/vc/null: No such file or directory ape/diff: b/utils/vl/null: No such file or directory ape/diff: b/utils/null: No such file or directory
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/DragonFly/README	Sun Nov  4 12:33:14 2018
@@ -0,0 +1 @@
+DragonFly BSD changes contributed by fgudin and extrudedaluminiu (see issue 181 at inferno-os.googlecode.com)
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/DragonFly/asm-386.S	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,107 @@
+	.file	"asm-DragonFly-386.S"
+#include <sys/syscall.h>
+
+/*
+ * executeonnewstack(void *tos, void (*tramp)(void *arg), void *arg)
+ */
+
+	.type	 ournewstack,@function
+	.global	executeonnewstack
+executeonnewstack:
+	pushl	%ebp
+	movl	%esp, %ebp
+	pushl	%esi
+
+	movl	8(%ebp), %esi	/* get tos */
+	subl	$4, %esi
+	movl	16(%ebp), %eax
+	movl	%eax, (%esi)	/* stash arg on new stack */
+	subl	$4, %esi
+	movl	12(%ebp), %eax
+	movl	%eax, (%esi)	/* stash tramp on new stack */
+	mov	%esi, %esp	/* swap stacks pronto */
+	popl	%eax		/* recover the tramp address */
+	call	*%eax		/* and jump to it (ho ho) */
+
+	/* if we return here, tramp didn't do it's job */
+
+	addl	$8, %esp	/* clean up for pose value */
+
+	leal	SYS_exit, %eax
+	int	$0x80
+
+/*
+ * unlockandexit(int *key)
+ *
+ * NB: the return status may be rubbish if the stack is reused
+ *	between the unlock and the system call, but this should
+ *	not matter since no task is waiting for the result
+ */
+
+	.type	unlockandexit,@function
+	.global	unlockandexit
+unlockandexit:
+	pushl	%ebp
+	movl	%esp, %ebp
+
+	movl	8(%ebp), %esi		/* get the key address */
+	pushl	$0			/* exit status 0 */
+	movl	$0, %eax		/* unlock the stack allocator */
+	movl	%eax, (%esi)
+	leal	SYS_exit, %eax		/* call exit */
+	int	$0x80
+
+/*
+ * umult(ulong m1, ulong m2, ulong *hi)
+ */
+
+	.type	umult,@function
+	.global	umult
+umult:
+	pushl	%ebp
+	movl	%esp, %ebp
+	pushl	%ebx
+
+	movl	8(%ebp), %eax
+	movl	12(%ebp), %ebx
+	mull	%ebx
+	movl	16(%ebp), %ebx
+	movl	%edx, (%ebx)
+
+	popl	%ebx
+	popl	%ebp
+	ret
+
+	.type	FPsave,@function
+	.global	FPsave
+FPsave:
+	pushl	%ebp
+	movl	%esp, %ebp
+	movl	8(%ebp), %eax
+	fstenv	(%eax)
+	popl	%ebp
+	ret
+
+	.type	FPrestore,@function
+	.global	FPrestore
+FPrestore:
+	pushl	%ebp
+	movl	%esp, %ebp
+	movl	8(%ebp), %eax
+	fldenv	(%eax)
+	popl	%ebp
+	ret
+
+	.type	getcallerpc,@function
+	.global	getcallerpc
+getcallerpc:
+	movl	4(%ebp), %eax
+	ret
+
+	.type	_tas,@function
+	.globl	_tas
+_tas:
+	movl	$1, %eax
+	movl	4(%esp), %ecx
+	xchgl	%eax, 0(%ecx)
+	ret
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/DragonFly/audio.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,547 @@
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/filio.h>
+#include "audio.h"
+#include <sys/soundcard.h>
+
+#define 	Audio_Mic_Val		SOUND_MIXER_MIC
+#define 	Audio_Linein_Val	SOUND_MIXER_LINE
+
+#define	Audio_Speaker_Val	SOUND_MIXER_SPEAKER
+#define	Audio_Headphone_Val	SOUND_MIXER_PHONEOUT
+#define	Audio_Lineout_Val	SOUND_MIXER_VOLUME
+
+#define 	Audio_Pcm_Val		AFMT_S16_LE
+#define 	Audio_Ulaw_Val		AFMT_MU_LAW
+#define 	Audio_Alaw_Val		AFMT_A_LAW
+
+#include "audio-tbls.c"
+
+#define	min(a,b)	((a) < (b) ? (a) : (b))
+static int debug;
+
+#define AUDIO_FILE_STRING	"/dev/dsp"
+
+enum {
+	A_Pause,
+	A_UnPause
+};
+
+enum {
+	A_In,
+	A_Out
+};
+
+static QLock inlock;
+static QLock outlock;
+
+static	int	audio_file  = -1;	/* file in/out */
+static	int	audio_file_in  = -1;	/* copy of above when opened O_READ/O_RDWR */
+static	int	audio_file_out  = -1;	/* copy of above when opened O_WRITE/O_RDWR */
+
+static	int	audio_swap_flag = 0;	/* endian swap */
+
+static	int	audio_in_pause = A_UnPause;
+
+static Audio_t av;
+static int mixerleftvol[32];
+static int mixerrightvol[32];
+
+static int audio_enforce(Audio_t*);
+static int audio_open(void);
+static int audio_pause_in(int, int);
+static int audio_flush(int, int);
+static int audio_pause_out(int);
+static int audio_set_blocking(int);
+static int audio_set_info(int, Audio_d*, int);
+static void audio_swap_endian(char*, int);
+
+void
+audio_file_init(void)
+{
+	int i;
+	static ushort flag = 1;
+
+	audio_swap_flag = *((uchar*)&flag) == 0;	/* big-endian? */
+	audio_info_init(&av);
+	for (i = 0; i < 32; i++)
+		mixerleftvol[i] = mixerrightvol[i] = 100;
+}
+
+void
+audio_ctl_init(void)
+{
+}
+
+void
+audio_file_open(Chan *c, int omode)
+{
+	char ebuf[ERRMAX];
+
+	if (debug)
+		print("audio_file_open(0x%.8lux, %d)\n", c, omode);
+	switch(omode){
+	case OREAD:
+		qlock(&inlock);
+		if(waserror()){
+			qunlock(&inlock);
+			nexterror();
+		}
+
+		if(audio_file_in >= 0)
+			error(Einuse);
+		if (audio_file < 0)
+			audio_file = audio_open();
+		audio_file_in = audio_file;
+		poperror();
+		qunlock(&inlock);
+		break;
+	case OWRITE:
+		qlock(&outlock);
+		if(waserror()){
+			qunlock(&outlock);
+			nexterror();
+		}
+		if(audio_file_out >= 0)
+			error(Einuse);
+		if (audio_file < 0)
+			audio_file = audio_open();
+		audio_file_out = audio_file;
+		poperror();
+		qunlock(&outlock);
+		break;
+	case ORDWR:
+		qlock(&inlock);
+		qlock(&outlock);
+		if(waserror()){
+			qunlock(&outlock);
+			qunlock(&inlock);
+			nexterror();
+		}
+		if(audio_file_in >= 0 || audio_file_out >= 0)
+			error(Einuse);
+		if (audio_file < 0)
+			audio_file = audio_open();
+		audio_file_in = audio_file_out = audio_file;
+		poperror();
+		qunlock(&outlock);
+		qunlock(&inlock);
+		break;
+	}
+	if (debug)
+		print("audio_file_open: success\nin %d out %d both %d\n",
+			audio_file_out, audio_file_in, audio_file);
+}
+
+void
+audio_ctl_open(Chan *c, int omode)
+{
+	USED(c);
+	USED(omode);
+}
+
+void
+audio_file_close(Chan *c)
+{
+	switch(c->mode){
+	case OREAD:
+		qlock(&inlock);
+		qlock(&outlock);
+		if (audio_file_out < 0) {
+			close(audio_file);
+			audio_file = -1;
+		}
+		qunlock(&outlock);
+		audio_file_in = -1;
+		qunlock(&inlock);
+		break;
+	case OWRITE:
+		qlock(&inlock);
+		qlock(&outlock);
+		if (audio_file_in < 0) {
+			close(audio_file);
+			audio_file = -1;
+		}
+		audio_file_out = -1;
+		qunlock(&outlock);
+		qunlock(&inlock);
+		break;
+	case ORDWR:
+		qlock(&inlock);
+		qlock(&outlock);
+		close(audio_file);
+		audio_file_in = audio_file_out = audio_file = -1;
+		qunlock(&outlock);
+		qunlock(&inlock);
+		break;
+	}
+}
+
+void
+audio_ctl_close(Chan *c)
+{
+}
+
+long
+audio_file_read(Chan *c, void *va, long count, vlong offset)
+{
+	struct  timespec time;
+	long ba, status, chunk, total;
+	char *pva = (char *) va;
+
+	qlock(&inlock);
+	if(waserror()){
+		qunlock(&inlock);
+		nexterror();
+	}
+
+	if(audio_file_in < 0)
+		error(Eperm);
+
+	/* check block alignment */
+	ba = av.in.bits * av.in.chan / Bits_Per_Byte;
+
+	if(count % ba)
+		error(Ebadarg);
+
+	if(!audio_pause_in(audio_file_in, A_UnPause))
+		error(Eio);
+	
+	total = 0;
+	while(total < count) {
+		chunk = count - total;
+		osenter();
+		status = read(audio_file_in, pva + total, chunk);
+		osleave(); 
+		if(status < 0)
+			error(Eio);
+		total += status;
+	}
+
+	if(total != count)
+		error(Eio);
+
+	if(audio_swap_flag && av.out.bits == 16)
+		audio_swap_endian(pva, count); 
+
+	poperror();
+	qunlock(&inlock);
+
+	return count;
+}
+
+long
+audio_file_write(Chan *c, void *va, long count, vlong offset)
+{
+	struct  timespec time;
+	long status = -1;
+	long ba, total, chunk, bufsz;
+
+	if (debug > 1)
+		print("audio_file_write(0x%.8lux, 0x%.8lux, %ld, %uld)\n",
+			c, va, count, offset);
+
+	qlock(&outlock);
+	if(waserror()){
+		qunlock(&outlock);
+		nexterror();
+	}
+
+	if(audio_file_out < 0)
+		error(Eperm);
+
+	/* check block alignment */
+	ba = av.out.bits * av.out.chan / Bits_Per_Byte;
+
+	if(count % ba)
+		error(Ebadarg);
+
+	if(audio_swap_flag && av.out.bits == 16)
+		audio_swap_endian(va, count); 
+
+	total = 0;
+	bufsz = av.out.buf * Audio_Max_Buf / Audio_Max_Val;
+
+	if(bufsz == 0)
+		error(Ebadarg);
+
+	while(total < count) {
+		chunk = min(bufsz, count - total);
+		osenter();
+		status = write(audio_file_out, va, chunk);
+		osleave();
+		if(status <= 0)
+			error(Eio);
+		total += status;
+	}
+
+	poperror();
+	qunlock(&outlock);
+
+	return count;
+}
+
+static int
+audio_open(void)
+{
+	int fd;
+
+	/* open non-blocking in case someone already has it open */
+	/* otherwise we would block until they close! */
+	fd = open(AUDIO_FILE_STRING, O_RDWR|O_NONBLOCK);
+	if(fd < 0)
+		oserror();
+
+	/* change device to be blocking */
+	if(!audio_set_blocking(fd)) {
+		if (debug)
+			print("audio_open: failed to set blocking\n");
+		close(fd);
+		error("cannot set blocking mode");
+	}
+
+	if (debug)
+		print("audio_open: blocking set\n");
+
+	/* set audio info */
+	av.in.flags = ~0;
+	av.out.flags = 0;
+
+	if(! audio_set_info(fd, &av.in, A_In)) {
+		close(fd);
+		error(Ebadarg);
+	}
+
+	av.in.flags = 0;
+
+	/* tada, we're open, blocking, paused and flushed */
+	return fd;
+}
+
+long
+audio_ctl_write(Chan *c, void *va, long count, vlong offset)
+{
+	int	fd;
+	int	ff;
+	Audio_t tmpav = av;
+
+	tmpav.in.flags = 0;
+	tmpav.out.flags = 0;
+
+	if (!audioparse(va, count, &tmpav))
+		error(Ebadarg);
+
+	if (!audio_enforce(&tmpav))
+		error(Ebadarg);
+
+	qlock(&inlock);
+	if (waserror()) {
+		qunlock(&inlock);
+		nexterror();
+	}
+
+	if (audio_file_in >= 0 && (tmpav.in.flags & AUDIO_MOD_FLAG)) {
+		if (!audio_pause_in(audio_file_in, A_Pause))
+			error(Ebadarg);
+		if (!audio_flush(audio_file_in, A_In))
+			error(Ebadarg);
+		if (!audio_set_info(audio_file_in, &tmpav.in, A_In))
+			error(Ebadarg);
+	}
+	poperror();
+	qunlock(&inlock);
+
+	qlock(&outlock);
+	if (waserror()) {
+		qunlock(&outlock);
+		nexterror();
+	}
+	if (audio_file_out >= 0 && (tmpav.out.flags & AUDIO_MOD_FLAG)){
+		if (!audio_pause_out(audio_file_out))
+			error(Ebadarg);
+		if (!audio_set_info(audio_file_out, &tmpav.out, A_Out))
+			error(Ebadarg);
+	}
+	poperror();
+	qunlock(&outlock);
+
+	tmpav.in.flags = 0;
+	tmpav.out.flags = 0;
+
+	av = tmpav;
+
+	return count;
+}
+
+
+
+static int
+audio_set_blocking(int fd)
+{
+	int val;
+
+	if((val = fcntl(fd, F_GETFL, 0)) == -1)
+		return 0;
+	
+	val &= ~O_NONBLOCK;
+
+	if(fcntl(fd, F_SETFL, val) < 0)
+		return 0;
+
+	return 1;
+}
+
+static int
+doioctl(int fd, int ctl, int *info)
+{
+	int status;
+	osenter();
+	status = ioctl(fd, ctl, info);  /* qlock and load general stuff */
+	osleave();
+	if (status < 0)
+		print("doioctl(0x%.8lux, 0x%.8lux) failed %d\n", ctl, *info, errno);
+	return status;
+}
+
+static int
+choosefmt(Audio_d *i)
+{
+	int newbits, newenc;
+	
+	newbits = i->bits;
+	newenc = i->enc;
+	switch (newenc) {
+	case Audio_Alaw_Val:
+		if (newbits == 8)
+			return AFMT_A_LAW;
+		break;
+	case Audio_Ulaw_Val:
+		if (newbits == 8)
+			return AFMT_MU_LAW;
+		break;
+	case Audio_Pcm_Val:
+		if (newbits == 8)
+			return AFMT_U8;
+		else if (newbits == 16)
+			return AFMT_S16_LE;
+		break;
+	}
+	return -1;
+}
+
+static int
+audio_set_info(int fd, Audio_d *i, int d)
+{
+	int status;
+	int unequal_stereo = 0;
+
+	if(fd < 0)
+		return 0;
+
+	/* fmt */
+	if(i->flags & (AUDIO_BITS_FLAG || AUDIO_ENC_FLAG)) {
+		int oldfmt, newfmt;
+		oldfmt = AFMT_QUERY;
+		if (doioctl(fd, SNDCTL_DSP_SETFMT, &oldfmt) < 0)
+			return 0;
+		if (debug)
+			print("audio_set_info: current format 0x%.8lux\n", oldfmt);
+		newfmt = choosefmt(i);
+		if (debug)
+			print("audio_set_info: new format 0x%.8lux\n", newfmt);
+		if (newfmt == -1 || newfmt != oldfmt && doioctl(fd, SNDCTL_DSP_SETFMT, &newfmt) < 0)
+			return 0;
+	}
+
+	/* channels */
+	if(i->flags & AUDIO_CHAN_FLAG) {
+		int channels = i->chan;
+		if (debug)
+			print("audio_set_info: new channels %d\n", channels);
+		if (doioctl(fd, SNDCTL_DSP_CHANNELS, &channels) < 0
+			|| channels != i->chan)
+			return 0;
+	}
+
+	/* sample rate */
+	if(i->flags & AUDIO_RATE_FLAG) {
+		int speed = i->rate;
+		if (debug)
+			print("audio_set_info: new speed %d\n", speed);
+		if (doioctl(fd, SNDCTL_DSP_SPEED, &speed) < 0 || speed != i->rate)
+			return 0;
+	}
+
+	/* dev volume */
+	if(i->flags & (AUDIO_LEFT_FLAG | AUDIO_VOL_FLAG | AUDIO_RIGHT_FLAG)) {
+		int val;
+		if (i->flags & (AUDIO_LEFT_FLAG | AUDIO_VOL_FLAG))
+			mixerleftvol[i->dev] = (i->left * 100) / Audio_Max_Val;
+		if (i->flags & (AUDIO_RIGHT_FLAG | AUDIO_VOL_FLAG))
+			mixerrightvol[i->dev] = (i->right * 100) / Audio_Max_Val;
+		val = mixerleftvol[i->dev] | (mixerrightvol[i->dev] << 8);
+		doioctl(fd, MIXER_WRITE(i->dev), &val);
+	}
+
+	if (i->flags & AUDIO_DEV_FLAG) {
+	}
+	
+	return 1;
+}
+
+void 
+audio_swap_endian(char *p, int n)
+{
+	int b;
+
+	while (n > 1) {
+		b = p[0];
+		p[0] = p[1];
+		p[1] = b;
+		p += 2;
+		n -= 2;
+	}
+}
+
+static int
+audio_pause_out(int fd)
+{
+	USED(fd);
+	return 1;
+}
+
+static int
+audio_pause_in(int fd, int f)
+{
+	USED(fd);
+	USED(f);
+	return 1;
+}
+
+static int
+audio_flush(int fd, int d)
+{
+	int x;
+	return doioctl(fd, SNDCTL_DSP_SYNC, &x) >= 0;
+}
+
+static int
+audio_enforce(Audio_t *t)
+{
+	if((t->in.enc == Audio_Ulaw_Val || t->in.enc == Audio_Alaw_Val) && 
+		(t->in.rate != 8000 || t->in.chan != 1))
+		 return 0;
+	if((t->out.enc == Audio_Ulaw_Val || t->out.enc == Audio_Alaw_Val) && 
+		(t->out.rate != 8000 || t->out.chan != 1))
+		 return 0;
+	return 1;
+}
+
+Audio_t*
+getaudiodev(void)
+{
+	return &av;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/DragonFly/cmd.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,213 @@
+#include	<sys/types.h>
+#include	<signal.h>
+#include 	<pwd.h>
+#include	<sys/resource.h>
+#include	<sys/wait.h>
+#include	<fcntl.h>
+
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+enum
+{
+	Debug = 0
+};
+
+/*
+ * os-specific devcmd support.
+ * this version should be reasonably portable across Unix systems.
+ */
+typedef struct Targ Targ;
+struct Targ
+{
+	int	fd[3];	/* fd[0] is standard input, fd[1] is standard output, fd[2] is standard error */
+	char**	args;
+	char*	dir;
+	int	pid;
+	int	wfd;	/* child writes errors that occur after the fork or on exec */
+	int	uid;
+	int	gid;
+};
+
+extern int gidnobody;
+extern int uidnobody;
+
+static int
+childproc(Targ *t)
+{
+	int i, nfd;
+
+	if(Debug)
+		print("devcmd: '%s'", t->args[0]);
+
+	nfd = getdtablesize();
+	for(i = 0; i < nfd; i++)
+		if(i != t->fd[0] && i != t->fd[1] && i != t->fd[2] && i != t->wfd)
+			close(i);
+
+	dup2(t->fd[0], 0);
+	dup2(t->fd[1], 1);
+	dup2(t->fd[2], 2);
+	close(t->fd[0]);
+	close(t->fd[1]);
+	close(t->fd[2]);
+
+	/* should have an auth file to do host-specific authorisation? */
+	if(t->gid != -1){
+		if(setgid(t->gid) < 0 && getegid() == 0){
+			fprint(t->wfd, "can't set gid %d: %s", t->gid, strerror(errno));
+			_exit(1);
+		}
+	}
+
+	if(t->uid != -1){
+		if(setuid(t->uid) < 0 && geteuid() == 0){
+			fprint(t->wfd, "can't set uid %d: %s", t->uid, strerror(errno));
+			_exit(1);
+		}
+	}
+
+	if(t->dir != nil && chdir(t->dir) < 0){
+		fprint(t->wfd, "can't chdir to %s: %s", t->dir, strerror(errno));
+		_exit(1);
+	}
+
+	signal(SIGPIPE, SIG_DFL);
+
+	execvp(t->args[0], t->args);
+	if(Debug)
+		print("execvp: %s\n",strerror(errno));
+	fprint(t->wfd, "exec failed: %s", strerror(errno));
+
+	_exit(1);
+}
+
+void*
+oscmd(char **args, int nice, char *dir, int *fd)
+{
+	Targ *t;
+	int r, fd0[2], fd1[2], fd2[2], wfd[2], n, pid;
+
+	t = mallocz(sizeof(*t), 1);
+	if(t == nil)
+		return nil;
+
+	fd0[0] = fd0[1] = -1;
+	fd1[0] = fd1[1] = -1;
+	fd2[0] = fd2[1] = -1;
+	wfd[0] = wfd[1] = -1;
+	if(pipe(fd0) < 0 || pipe(fd1) < 0 || pipe(fd2) < 0 || pipe(wfd) < 0)
+		goto Error;
+	if(fcntl(wfd[1], F_SETFD, FD_CLOEXEC) < 0)	/* close on exec to give end of file on success */
+		goto Error;
+
+	t->fd[0] = fd0[0];
+	t->fd[1] = fd1[1];
+	t->fd[2] = fd2[1];
+	t->wfd = wfd[1];
+	t->args = args;
+	t->dir = dir;
+	t->gid = up->env->gid;
+	if(t->gid == -1)
+		t->gid = gidnobody;
+	t->uid = up->env->uid;
+	if(t->uid == -1)
+		t->uid = uidnobody;
+
+	signal(SIGCHLD, SIG_DFL);
+	switch(pid = fork()) {
+	case -1:
+		goto Error;
+	case 0:
+		setpgid(0, getpid());
+		if(nice)
+			oslopri();
+		childproc(t);
+		_exit(1);
+	default:
+		t->pid = pid;
+		if(Debug)
+			print("cmd pid %d\n", t->pid);
+		break;
+	}
+
+	close(fd0[0]);
+	close(fd1[1]);
+	close(fd2[1]);
+	close(wfd[1]);
+
+	n = read(wfd[0], up->genbuf, sizeof(up->genbuf)-1);
+	close(wfd[0]);
+	if(n > 0){
+		close(fd0[1]);
+		close(fd1[0]);
+		close(fd2[0]);
+		free(t);
+		up->genbuf[n] = 0;
+		if(Debug)
+			print("oscmd: bad exec: %q\n", up->genbuf);
+		error(up->genbuf);
+		return nil;
+	}
+
+	fd[0] = fd0[1];
+	fd[1] = fd1[0];
+	fd[2] = fd2[0];
+	return t;
+
+Error:
+	r = errno;
+	if(Debug)
+		print("oscmd: %q\n",strerror(r));
+	close(fd0[0]);
+	close(fd0[1]);
+	close(fd1[0]);
+	close(fd1[1]);
+	close(fd2[0]);
+	close(fd2[1]);
+	close(wfd[0]);
+	close(wfd[1]);
+	error(strerror(r));
+	return nil;
+}
+
+int
+oscmdkill(void *a)
+{
+	Targ *t = a;
+
+	if(Debug)
+		print("kill: %d\n", t->pid);
+	return kill(-t->pid, SIGTERM);
+}
+
+int
+oscmdwait(void *a, char *buf, int n)
+{
+	Targ *t = a;
+	int s;
+
+	if(waitpid(t->pid, &s, 0) == -1){
+		if(Debug)
+			print("wait error: %d [in %d] %q\n", t->pid, getpid(), strerror(errno));
+		return -1;
+	}
+	if(WIFEXITED(s)){
+		if(WEXITSTATUS(s) == 0)
+			return snprint(buf, n, "%d 0 0 0 ''", t->pid);
+		return snprint(buf, n, "%d 0 0 0 'exit: %d'", t->pid, WEXITSTATUS(s));
+	}
+	if(WIFSIGNALED(s)){
+		if(WTERMSIG(s) == SIGTERM || WTERMSIG(s) == SIGKILL)
+			return snprint(buf, n, "%d 0 0 0 killed", t->pid);
+		return snprint(buf, n, "%d 0 0 0 'signal: %d'", t->pid, WTERMSIG(s));
+	}
+	return snprint(buf, n, "%d 0 0 0 'odd status: 0x%x'", t->pid, s);
+}
+
+void
+oscmdfree(void *a)
+{
+	free(a);
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/DragonFly/deveia.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,39 @@
+/*
+ * FreeBSD serial port definitions
+ */
+
+static char *sysdev[] = {
+        "/dev/cuaa0",
+        "/dev/cuaa1",
+        "/dev/cuaa2",
+        "/dev/cuaa3",
+};
+
+#include <sys/ioctl.h>
+#include "deveia-posix.c"
+#include "deveia-bsd.c"
+
+
+static struct tcdef_t bps[] = {
+	{0,		B0},
+	{50,		B50},
+	{75,		B75},
+	{110,		B110},
+	{134,		B134},
+	{150,		B150},
+	{200,		B200},
+	{300,		B300},
+	{600,		B600},
+	{1200,	B1200},
+	{1800,	B1800},
+	{2400,	B2400},
+	{4800,	B4800},
+	{9600,	B9600},
+	{19200,	B19200},
+	{38400,	B38400},
+	{57600,	B57600},
+	{115200,	B115200},
+	{230400,	B230400},
+	{-1,		-1}
+};
+
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/DragonFly/devfs.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,7 @@
+#include "devfs-posix.c"
+
+static vlong
+osdisksize(int fd)
+{
+	return 0;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/DragonFly/emu	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,111 @@
+dev
+	root
+	cons
+	env
+	mnt
+	pipe
+	prog
+	prof
+	srv
+	dup
+	ssl
+	cap
+	fs
+	cmd	cmd
+	indir
+
+	draw	win-x11a
+	pointer
+	snarf
+
+	ip	ipif-posix ipaux
+	eia
+	audio	audio
+	mem
+
+lib
+	interp
+	tk
+	freetype
+	math
+	draw
+
+	memlayer
+	memdraw
+	keyring
+	sec
+	mp
+
+	9
+
+link
+
+mod
+	sys
+	draw
+
+	tk
+	math
+	srv	srv
+	keyring
+	crypt
+	ipints
+	loader
+	freetype
+
+port
+	alloc
+	cache
+	chan
+	dev
+	devtab
+
+	dial
+	dis
+	discall
+	env
+	error
+	errstr
+	exception
+	exportfs
+	inferno
+	latin1
+	main
+	parse
+	pgrp
+	print
+	proc
+	qio
+	random
+	sysfile
+	uqid
+
+code
+
+init
+	emuinit
+
+root
+	/dev	/
+	/fd	/
+	/prog	/
+	/prof	/
+	/net	/
+	/net.alt	/
+	/chan	/
+	/nvfs	/
+	/env	/
+#	/chan
+#	/dev
+#	/dis
+#	/env
+#	/n
+#	/net
+#	/nvfs /
+#	/prog
+#	/icons
+#	/osinit.dis
+#	/dis/emuinit.dis
+#	/dis/lib/auth.dis
+#	/dis/lib/ssl.dis
+#	/n/local /
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/DragonFly/emu-g	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,97 @@
+dev
+	root
+	cons
+	env
+	mnt
+	pipe
+	prog
+	srv
+	dup
+	ssl
+	cap
+	fs
+	cmd	cmd
+	indir
+
+	ip	ipif-posix ipaux
+	eia
+	mem
+
+lib
+	interp
+	math
+	keyring
+	sec
+	mp
+
+	9
+
+link
+
+mod
+	sys
+	math
+	srv	srv
+	keyring
+	crypt
+	ipints
+	loader
+
+port
+	alloc
+	cache
+	chan
+	dev
+	devtab
+
+	dial
+	dis
+	discall
+	env
+	error
+	errstr
+	exception
+	exportfs
+	inferno
+	latin1
+	main
+	parse
+	pgrp
+	print
+	proc
+	qio
+	random
+	sysfile
+	uqid
+
+code
+	void setpointer(int x, int y) { USED(x); USED(y); }
+	ulong strtochan(char *s) { USED(s); return ~0; }
+
+init
+	emuinit
+
+root
+	/dev	/
+	/fd	/
+	/prog	/
+	/prof	/
+	/net	/
+	/net.alt	/
+	/chan	/
+	/nvfs	/
+	/env	/
+#	/chan
+#	/dev
+#	/dis
+#	/env
+#	/n
+#	/net
+#	/nvfs /
+#	/prog
+#	/icons
+#	/osinit.dis
+#	/dis/emuinit.dis
+#	/dis/lib/auth.dis
+#	/dis/lib/ssl.dis
+#	/n/local /
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/DragonFly/mkfile	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,46 @@
+SYSTARG=DragonFly
+OBJTYPE=386
+<../../mkconfig
+SYSTARG=DragonFly
+OBJTYPE=386
+
+#Configurable parameters
+
+CONF=emu			#default configuration
+CONFLIST=emu
+CLEANCONFLIST=
+
+INSTALLDIR=$ROOT/$SYSTARG/$OBJTYPE/bin	#path of directory where kernel is installed
+
+#end configurable parameters
+
+<$ROOT/mkfiles/mkfile-$SYSTARG-$OBJTYPE	#set vars based on target system
+
+<| $SHELLNAME ../port/mkdevlist $CONF	#sets $IP, $DEVS, $PORT, $LIBS
+
+OBJ=\
+	asm-$OBJTYPE.$O\
+	os.$O\
+	$CONF.root.$O\
+	lock.$O\
+	$DEVS\
+	$PORT\
+
+HFILES=\
+
+CFLAGS='-DROOT="'$ROOT'"' -DEMU -I. -I../port -I$ROOT/$SYSTARG/$OBJTYPE/include -I$ROOT/include -I$ROOT/libinterp $CTHREADFLAGS $CFLAGS $EMUOPTIONS
+SYSLIBS= -lm 
+KERNDATE=`{$NDATE}
+
+default:V:	$O.$CONF
+
+<../port/portmkfile
+
+$O.$CONF:	$OBJ $CONF.c $CONF.root.h $LIBFILES
+	$CC $CFLAGS '-DKERNDATE='$KERNDATE $CONF.c
+	$LD $LDFLAGS -o $target $OBJ $CONF.$O $LIBFILES $SYSLIBS
+
+install:V: $O.$CONF
+	cp $O.$CONF $INSTALLDIR/$CONF
+
+devfs.$O:	../port/devfs-posix.c
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/DragonFly/os.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,530 @@
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+#undef getwd
+#include	<signal.h>
+#include 	<sys/socket.h>
+#include	<time.h>
+#include	<sys/time.h>
+#include	<termios.h>
+#include	<sched.h>
+#include	<pwd.h>
+#include	<errno.h>
+#include	<unistd.h>
+#include	<sys/resource.h>
+
+enum
+{
+	DELETE  = 0x7F,
+	NSTACKSPERALLOC = 16,
+	X11STACK=	256*1024
+};
+char *hosttype = "DragonFly";
+
+extern void unlockandexit(int*);
+extern void executeonnewstack(void*, void (*f)(void*), void*);
+static void *stackalloc(Proc *p, void **tos);
+static void stackfreeandexit(void *stack);
+
+extern int dflag;
+
+void
+pexit(char *msg, int t)
+{
+	Osenv *e;
+	Proc *p;
+	void *kstack;
+
+	lock(&procs.l);
+	p = up;
+	if(p->prev)
+		p->prev->next = p->next;
+	else
+		procs.head = p->next;
+
+	if(up->next)
+		p->next->prev = p->prev;
+	else
+		procs.tail = p->prev;
+	unlock(&procs.l);
+
+	if(0)
+		print("pexit: %s: %s\n", up->text, msg);
+
+	e = up->env;
+	if(e != nil) {
+		closefgrp(e->fgrp);
+		closepgrp(e->pgrp);
+		closeegrp(e->egrp);
+		closesigs(e->sigs);
+	}
+	kstack = p->kstack;
+	free(p->prog);
+	free(p);
+	if(kstack != nil)
+		stackfreeandexit(kstack);
+}
+
+void
+trapBUS(int signo, siginfo_t *info, void *context)
+{
+	if(info)
+		print("trapBUS: signo: %d code: %d addr: %lx\n",
+		info->si_signo, info->si_code, info->si_addr);
+	else
+		print("trapBUS: no info\n"); 
+	disfault(nil, "Bus error");
+}
+
+static void
+trapUSR1(int signo)
+{
+	int intwait;
+
+	USED(signo);
+
+	intwait = up->intwait;
+	up->intwait = 0;	/* clear it to let proc continue in osleave */
+
+	if(up->type != Interp)		/* Used to unblock pending I/O */
+		return;
+	if(intwait == 0)		/* Not posted so its a sync error */
+		disfault(nil, Eintr);	/* Should never happen */
+}
+
+static void
+trapUSR2(int signo)
+{
+	USED(signo);
+	/* we've done our work of interrupting sigsuspend */
+}
+
+static void
+trapILL(int signo)
+{
+	disfault(nil, "Illegal instruction");
+}
+
+static void
+trapSEGV(int signo)
+{
+	disfault(nil, "Segmentation violation");
+}
+
+static void
+trapFPE(int signo)
+{
+	char buf[64];
+	USED(signo);
+	snprint(buf, sizeof(buf), "sys: fp: exception status=%.4lux", getfsr());
+	disfault(nil, buf);
+}
+
+static sigset_t initmask;
+
+static void
+setsigs(void)
+{
+	struct sigaction act;
+	sigset_t mask;
+
+	memset(&act, 0 , sizeof(act));
+	sigemptyset(&initmask);
+	
+	signal(SIGPIPE, SIG_IGN);	/* prevent signal when devcmd child exits */
+	if(signal(SIGTERM, SIG_IGN) != SIG_IGN)
+		signal(SIGTERM, cleanexit);
+
+	act.sa_handler = trapUSR1;
+	act.sa_mask = initmask;
+	sigaction(SIGUSR1, &act, nil);
+
+	act.sa_handler = trapUSR2;
+	sigaction(SIGUSR2, &act, nil);
+	sigemptyset(&mask);
+	sigaddset(&mask, SIGUSR2);
+	sigaddset(&initmask, SIGUSR2);
+	sigprocmask(SIG_BLOCK, &mask, NULL);
+
+	/*
+ 	 * prevent Zombies forming when any process terminates
+	 */
+	act.sa_sigaction = 0;
+	act.sa_flags |= SA_NOCLDWAIT;
+	if(sigaction(SIGCHLD, &act, nil))
+		panic("sigaction SIGCHLD");
+
+	if(sflag == 0) {
+		act.sa_sigaction = trapBUS;
+		act.sa_flags |= SA_SIGINFO;
+		if(sigaction(SIGBUS, &act, nil))
+			panic("sigaction SIGBUS");
+		act.sa_handler = trapILL;
+		if(sigaction(SIGILL, &act, nil))
+			panic("sigaction SIGBUS");
+		act.sa_handler = trapSEGV;
+		if(sigaction(SIGSEGV, &act, nil))
+			panic("sigaction SIGSEGV");
+		act.sa_handler = trapFPE;
+		if(sigaction(SIGFPE, &act, nil))
+			panic("sigaction SIGFPE");
+		if(sigaddset(&initmask, SIGINT) == -1)
+			panic("sigaddset");
+	}
+	if(sigprocmask(SIG_BLOCK, &initmask, nil)!= 0)
+		panic("sigprocmask");
+}
+
+static int
+tramp(void *arg)
+{
+	Proc *p;
+
+	p = arg;
+	p->pid = p->sigid = getpid();
+	sigprocmask(SIG_BLOCK, &initmask, nil);	/* in 5.3, rfork_thread doesn't copy from parent, contrary to docs? */
+	(*p->func)(p->arg);
+	pexit("{Tramp}", 0);
+	_exit(0);
+}
+
+void
+kproc(char *name, void (*func)(void*), void *arg, int flags)
+{
+	Proc *p;
+	Pgrp *pg;
+	Fgrp *fg;
+	Egrp *eg;
+	int pid;
+	void *tos;
+
+	p = newproc();
+
+	if(flags & KPDUPPG) {
+		pg = up->env->pgrp;
+		incref(&pg->r);
+		p->env->pgrp = pg;
+	}
+	if(flags & KPDUPFDG) {
+		fg = up->env->fgrp;
+		incref(&fg->r);
+		p->env->fgrp = fg;
+	}
+	if(flags & KPDUPENVG) {
+		eg = up->env->egrp;
+		incref(&eg->r);
+		p->env->egrp = eg;
+	}
+
+	p->env->uid = up->env->uid;
+	p->env->gid = up->env->gid;
+	kstrdup(&p->env->user, up->env->user);
+
+	strcpy(p->text, name);
+
+	p->func = func;
+	p->arg = arg;
+
+	lock(&procs.l);
+	if(procs.tail != nil) {
+		p->prev = procs.tail;
+		procs.tail->next = p;
+	}
+	else {
+		procs.head = p;
+		p->prev = nil;
+	}
+	procs.tail = p;
+	unlock(&procs.l);
+
+	if(flags & KPX11){
+		p->kstack = nil;	/* never freed; also up not defined */
+		tos = (char*)mallocz(X11STACK, 0) + X11STACK - sizeof(void*);
+	}else
+		p->kstack = stackalloc(p, &tos);
+	pid = rfork_thread(RFPROC|RFMEM|RFNOWAIT, tos, tramp, p);
+	if(pid < 0)
+		panic("rfork");
+}
+
+void
+oshostintr(Proc *p)
+{
+	kill(p->sigid, SIGUSR1);
+}
+
+void
+osblock(void)
+{
+	sigset_t mask;
+
+	sigprocmask(SIG_SETMASK, NULL, &mask);
+	sigdelset(&mask, SIGUSR2);
+	sigsuspend(&mask);
+}
+
+void
+osready(Proc *p)
+{
+	if(kill(p->sigid, SIGUSR2) < 0)
+		fprint(2, "emu: osready failed: pid %d: %s\n", p->sigid, strerror(errno));
+}
+
+void
+oslongjmp(void *regs, osjmpbuf env, int val)
+{
+	USED(regs);
+	siglongjmp(env, val);
+}
+
+struct termios tinit;
+
+static void
+termset(void)
+{
+	struct termios t;
+
+	tcgetattr(0, &t);
+	tinit = t;
+	t.c_lflag &= ~(ICANON|ECHO|ISIG);
+	t.c_cc[VMIN] = 1;
+	t.c_cc[VTIME] = 0;
+	tcsetattr(0, TCSANOW, &t);
+}
+
+static void
+termrestore(void)
+{
+	tcsetattr(0, TCSANOW, &tinit);
+}
+
+void
+cleanexit(int x)
+{
+	USED(x);
+
+	if(up->intwait) {
+		up->intwait = 0;
+		return;
+	}
+
+	if(dflag == 0)
+		termrestore();
+
+	kill(0, SIGKILL);
+	exit(0);
+}
+
+void
+osreboot(char *file, char **argv)
+{
+	if(dflag == 0)
+		termrestore();
+	execvp(file, argv);
+	panic("reboot failure");
+}
+
+int gidnobody= -1, uidnobody= -1;
+
+void
+getnobody()
+{
+	struct passwd *pwd;
+	
+	if(pwd = getpwnam("nobody")) {
+		uidnobody = pwd->pw_uid;
+		gidnobody = pwd->pw_gid;
+	}
+}
+
+void
+libinit(char *imod)
+{
+	struct passwd *pw;
+	Proc *p;
+	void *tos;
+	char sys[64];
+
+	setsid();
+
+	gethostname(sys, sizeof(sys));
+	kstrdup(&ossysname, sys);
+	getnobody();
+
+	if(dflag == 0)
+		termset();
+
+	setsigs();
+
+	p = newproc();
+	p->kstack = stackalloc(p, &tos);
+
+	pw = getpwuid(getuid());
+	if(pw != nil)
+		kstrdup(&eve, pw->pw_name);
+	else
+		print("cannot getpwuid\n");
+ 
+	p->env->uid = getuid();
+	p->env->gid = getgid();
+
+	executeonnewstack(tos, emuinit, imod);
+}
+
+int
+readkbd(void)
+{
+	int n;
+	char buf[1];
+
+	n = read(0, buf, sizeof(buf));
+	if(n < 0)
+		print("keyboard close (n=%d, %s)\n", n, strerror(errno));
+	if(n <= 0)
+		pexit("keyboard thread", 0);
+
+	switch(buf[0]) {
+	case '\r':
+		buf[0] = '\n';
+		break;
+	case DELETE:
+		cleanexit(0);
+		break;
+	}
+	return buf[0];
+}
+
+/*
+ * Return an abitrary millisecond clock time
+ */
+long
+osmillisec(void)
+{
+	static long sec0 = 0, usec0;
+	struct timeval t;
+
+	if(gettimeofday(&t,(struct timezone*)0)<0)
+		return 0;
+	if(sec0==0) {
+		sec0 = t.tv_sec;
+		usec0 = t.tv_usec;
+	}
+	return (t.tv_sec-sec0)*1000+(t.tv_usec-usec0+500)/1000;
+}
+
+int
+limbosleep(ulong milsec)
+{
+	return osmillisleep(milsec);
+}
+
+/*
+ * Return the time since the epoch in nanoseconds and microseconds
+ * The epoch is defined at 1 Jan 1970
+ */
+vlong
+osnsec(void)
+{
+	struct timeval t;
+
+	gettimeofday(&t, nil);
+	return (vlong)t.tv_sec*1000000000L + t.tv_usec*1000;
+}
+
+vlong
+osusectime(void)
+{
+	struct timeval t;
+ 
+	gettimeofday(&t, nil);
+	return (vlong)t.tv_sec * 1000000 + t.tv_usec;
+}
+
+int
+osmillisleep(ulong milsec)
+{
+	struct timespec time;
+
+	time.tv_sec = milsec / 1000;
+	time.tv_nsec = (milsec % 1000) * 1000000;
+	nanosleep(&time, 0);
+	return 0;
+}
+
+void
+osyield(void)
+{
+	sched_yield();
+}
+
+void
+ospause(void)
+{
+	for(;;)
+		pause();
+}
+
+void
+oslopri(void)
+{
+	setpriority(PRIO_PROCESS, 0, getpriority(PRIO_PROCESS,0)+4);
+}
+
+static struct {
+	Lock l;
+	void *free;
+} stacklist;
+
+static void
+_stackfree(void *stack)
+{
+	*((void **)stack) = stacklist.free;
+	stacklist.free = stack;
+}
+
+static void
+stackfreeandexit(void *stack)
+{
+	lock(&stacklist.l);
+	_stackfree(stack);
+	unlockandexit(&stacklist.l.val);
+}
+
+static void *
+stackalloc(Proc *p, void **tos)
+{
+	void *rv;
+	lock(&stacklist.l);
+	if (stacklist.free == 0) {
+		int x;
+		/*
+		 * obtain some more by using sbrk()
+		 */
+		void *more = sbrk(KSTACK * (NSTACKSPERALLOC + 1));
+		if (more == 0)
+			panic("stackalloc: no more stacks");
+		/*
+		 * align to KSTACK
+		 */
+		more = (void *)((((unsigned long)more) + (KSTACK - 1)) & ~(KSTACK - 1));
+		/*
+		 * free all the new stacks onto the freelist
+		 */
+		for (x = 0; x < NSTACKSPERALLOC; x++)
+			_stackfree((char *)more + KSTACK * x);
+	}
+	rv = stacklist.free;
+	stacklist.free = *(void **)rv;
+	unlock(&stacklist.l);
+	*tos = rv + KSTACK - sizeof(void*);
+	*(Proc **)rv = p;
+	return rv;
+}
+
+int
+segflush(void *a, ulong n)
+{
+	USED(a);
+	USED(n);
+	return 0;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/FreeBSD/asm-386.S	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,107 @@
+	.file	"asm-FreeBSD-386.S"
+#include <sys/syscall.h>
+
+/*
+ * executeonnewstack(void *tos, void (*tramp)(void *arg), void *arg)
+ */
+
+	.type	 ournewstack,@function
+	.global	executeonnewstack
+executeonnewstack:
+	pushl	%ebp
+	movl	%esp, %ebp
+	pushl	%esi
+
+	movl	8(%ebp), %esi	/* get tos */
+	subl	$4, %esi
+	movl	16(%ebp), %eax
+	movl	%eax, (%esi)	/* stash arg on new stack */
+	subl	$4, %esi
+	movl	12(%ebp), %eax
+	movl	%eax, (%esi)	/* stash tramp on new stack */
+	mov	%esi, %esp	/* swap stacks pronto */
+	popl	%eax		/* recover the tramp address */
+	call	*%eax		/* and jump to it (ho ho) */
+
+	/* if we return here, tramp didn't do it's job */
+
+	addl	$8, %esp	/* clean up for pose value */
+
+	leal	SYS_exit, %eax
+	int	$0x80
+
+/*
+ * unlockandexit(int *key)
+ *
+ * NB: the return status may be rubbish if the stack is reused
+ *	between the unlock and the system call, but this should
+ *	not matter since no task is waiting for the result
+ */
+
+	.type	unlockandexit,@function
+	.global	unlockandexit
+unlockandexit:
+	pushl	%ebp
+	movl	%esp, %ebp
+
+	movl	8(%ebp), %esi		/* get the key address */
+	pushl	$0			/* exit status 0 */
+	movl	$0, %eax		/* unlock the stack allocator */
+	movl	%eax, (%esi)
+	leal	SYS_exit, %eax		/* call exit */
+	int	$0x80
+
+/*
+ * umult(ulong m1, ulong m2, ulong *hi)
+ */
+
+	.type	umult,@function
+	.global	umult
+umult:
+	pushl	%ebp
+	movl	%esp, %ebp
+	pushl	%ebx
+
+	movl	8(%ebp), %eax
+	movl	12(%ebp), %ebx
+	mull	%ebx
+	movl	16(%ebp), %ebx
+	movl	%edx, (%ebx)
+
+	popl	%ebx
+	popl	%ebp
+	ret
+
+	.type	FPsave,@function
+	.global	FPsave
+FPsave:
+	pushl	%ebp
+	movl	%esp, %ebp
+	movl	8(%ebp), %eax
+	fstenv	(%eax)
+	popl	%ebp
+	ret
+
+	.type	FPrestore,@function
+	.global	FPrestore
+FPrestore:
+	pushl	%ebp
+	movl	%esp, %ebp
+	movl	8(%ebp), %eax
+	fldenv	(%eax)
+	popl	%ebp
+	ret
+
+	.type	getcallerpc,@function
+	.global	getcallerpc
+getcallerpc:
+	movl	4(%ebp), %eax
+	ret
+
+	.type	_tas,@function
+	.globl	_tas
+_tas:
+	movl	$1, %eax
+	movl	4(%esp), %ecx
+	xchgl	%eax, 0(%ecx)
+	ret
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/FreeBSD/audio.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,547 @@
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/filio.h>
+#include "audio.h"
+#include <sys/soundcard.h>
+
+#define 	Audio_Mic_Val		SOUND_MIXER_MIC
+#define 	Audio_Linein_Val	SOUND_MIXER_LINE
+
+#define	Audio_Speaker_Val	SOUND_MIXER_SPEAKER
+#define	Audio_Headphone_Val	SOUND_MIXER_PHONEOUT
+#define	Audio_Lineout_Val	SOUND_MIXER_VOLUME
+
+#define 	Audio_Pcm_Val		AFMT_S16_LE
+#define 	Audio_Ulaw_Val		AFMT_MU_LAW
+#define 	Audio_Alaw_Val		AFMT_A_LAW
+
+#include "audio-tbls.c"
+
+#define	min(a,b)	((a) < (b) ? (a) : (b))
+static int debug;
+
+#define AUDIO_FILE_STRING	"/dev/dsp"
+
+enum {
+	A_Pause,
+	A_UnPause
+};
+
+enum {
+	A_In,
+	A_Out
+};
+
+static QLock inlock;
+static QLock outlock;
+
+static	int	audio_file  = -1;	/* file in/out */
+static	int	audio_file_in  = -1;	/* copy of above when opened O_READ/O_RDWR */
+static	int	audio_file_out  = -1;	/* copy of above when opened O_WRITE/O_RDWR */
+
+static	int	audio_swap_flag = 0;	/* endian swap */
+
+static	int	audio_in_pause = A_UnPause;
+
+static Audio_t av;
+static int mixerleftvol[32];
+static int mixerrightvol[32];
+
+static int audio_enforce(Audio_t*);
+static int audio_open(void);
+static int audio_pause_in(int, int);
+static int audio_flush(int, int);
+static int audio_pause_out(int);
+static int audio_set_blocking(int);
+static int audio_set_info(int, Audio_d*, int);
+static void audio_swap_endian(char*, int);
+
+void
+audio_file_init(void)
+{
+	int i;
+	static ushort flag = 1;
+
+	audio_swap_flag = *((uchar*)&flag) == 0;	/* big-endian? */
+	audio_info_init(&av);
+	for (i = 0; i < 32; i++)
+		mixerleftvol[i] = mixerrightvol[i] = 100;
+}
+
+void
+audio_ctl_init(void)
+{
+}
+
+void
+audio_file_open(Chan *c, int omode)
+{
+	char ebuf[ERRMAX];
+
+	if (debug)
+		print("audio_file_open(0x%.8lux, %d)\n", c, omode);
+	switch(omode){
+	case OREAD:
+		qlock(&inlock);
+		if(waserror()){
+			qunlock(&inlock);
+			nexterror();
+		}
+
+		if(audio_file_in >= 0)
+			error(Einuse);
+		if (audio_file < 0)
+			audio_file = audio_open();
+		audio_file_in = audio_file;
+		poperror();
+		qunlock(&inlock);
+		break;
+	case OWRITE:
+		qlock(&outlock);
+		if(waserror()){
+			qunlock(&outlock);
+			nexterror();
+		}
+		if(audio_file_out >= 0)
+			error(Einuse);
+		if (audio_file < 0)
+			audio_file = audio_open();
+		audio_file_out = audio_file;
+		poperror();
+		qunlock(&outlock);
+		break;
+	case ORDWR:
+		qlock(&inlock);
+		qlock(&outlock);
+		if(waserror()){
+			qunlock(&outlock);
+			qunlock(&inlock);
+			nexterror();
+		}
+		if(audio_file_in >= 0 || audio_file_out >= 0)
+			error(Einuse);
+		if (audio_file < 0)
+			audio_file = audio_open();
+		audio_file_in = audio_file_out = audio_file;
+		poperror();
+		qunlock(&outlock);
+		qunlock(&inlock);
+		break;
+	}
+	if (debug)
+		print("audio_file_open: success\nin %d out %d both %d\n",
+			audio_file_out, audio_file_in, audio_file);
+}
+
+void
+audio_ctl_open(Chan *c, int omode)
+{
+	USED(c);
+	USED(omode);
+}
+
+void
+audio_file_close(Chan *c)
+{
+	switch(c->mode){
+	case OREAD:
+		qlock(&inlock);
+		qlock(&outlock);
+		if (audio_file_out < 0) {
+			close(audio_file);
+			audio_file = -1;
+		}
+		qunlock(&outlock);
+		audio_file_in = -1;
+		qunlock(&inlock);
+		break;
+	case OWRITE:
+		qlock(&inlock);
+		qlock(&outlock);
+		if (audio_file_in < 0) {
+			close(audio_file);
+			audio_file = -1;
+		}
+		audio_file_out = -1;
+		qunlock(&outlock);
+		qunlock(&inlock);
+		break;
+	case ORDWR:
+		qlock(&inlock);
+		qlock(&outlock);
+		close(audio_file);
+		audio_file_in = audio_file_out = audio_file = -1;
+		qunlock(&outlock);
+		qunlock(&inlock);
+		break;
+	}
+}
+
+void
+audio_ctl_close(Chan *c)
+{
+}
+
+long
+audio_file_read(Chan *c, void *va, long count, vlong offset)
+{
+	struct  timespec time;
+	long ba, status, chunk, total;
+	char *pva = (char *) va;
+
+	qlock(&inlock);
+	if(waserror()){
+		qunlock(&inlock);
+		nexterror();
+	}
+
+	if(audio_file_in < 0)
+		error(Eperm);
+
+	/* check block alignment */
+	ba = av.in.bits * av.in.chan / Bits_Per_Byte;
+
+	if(count % ba)
+		error(Ebadarg);
+
+	if(! audio_pause_in(audio_file_in, A_UnPause))
+		error(Eio);
+	
+	total = 0;
+	while(total < count) {
+		chunk = count - total;
+		osenter();
+		status = read(audio_file_in, pva + total, chunk);
+		osleave(); 
+		if(status < 0)
+			error(Eio);
+		total += status;
+	}
+
+	if(total != count)
+		error(Eio);
+
+	if(audio_swap_flag && av.out.bits == 16)
+		audio_swap_endian(pva, count); 
+
+	poperror();
+	qunlock(&inlock);
+
+	return count;
+}
+
+long
+audio_file_write(Chan *c, void *va, long count, vlong offset)
+{
+	struct  timespec time;
+	long status = -1;
+	long ba, total, chunk, bufsz;
+
+	if (debug > 1)
+		print("audio_file_write(0x%.8lux, 0x%.8lux, %ld, %uld)\n",
+			c, va, count, offset);
+
+	qlock(&outlock);
+	if(waserror()){
+		qunlock(&outlock);
+		nexterror();
+	}
+
+	if(audio_file_out < 0)
+		error(Eperm);
+
+	/* check block alignment */
+	ba = av.out.bits * av.out.chan / Bits_Per_Byte;
+
+	if(count % ba)
+		error(Ebadarg);
+
+	if(audio_swap_flag && av.out.bits == 16)
+		audio_swap_endian(va, count); 
+
+	total = 0;
+	bufsz = av.out.buf * Audio_Max_Buf / Audio_Max_Val;
+
+	if(bufsz == 0)
+		error(Ebadarg);
+
+	while(total < count) {
+		chunk = min(bufsz, count - total);
+		osenter();
+		status = write(audio_file_out, va, chunk);
+		osleave();
+		if(status <= 0)
+			error(Eio);
+		total += status;
+	}
+
+	poperror();
+	qunlock(&outlock);
+
+	return count;
+}
+
+static int
+audio_open(void)
+{
+	int fd;
+
+	/* open non-blocking in case someone already has it open */
+	/* otherwise we would block until they close! */
+	fd = open(AUDIO_FILE_STRING, O_RDWR|O_NONBLOCK);
+	if(fd < 0)
+		oserror();
+
+	/* change device to be blocking */
+	if(!audio_set_blocking(fd)) {
+		if (debug)
+			print("audio_open: failed to set blocking\n");
+		close(fd);
+		error("cannot set blocking mode");
+	}
+
+	if (debug)
+		print("audio_open: blocking set\n");
+
+	/* set audio info */
+	av.in.flags = ~0;
+	av.out.flags = 0;
+
+	if(! audio_set_info(fd, &av.in, A_In)) {
+		close(fd);
+		error(Ebadarg);
+	}
+
+	av.in.flags = 0;
+
+	/* tada, we're open, blocking, paused and flushed */
+	return fd;
+}
+
+long
+audio_ctl_write(Chan *c, void *va, long count, vlong offset)
+{
+	int	fd;
+	int	ff;
+	Audio_t tmpav = av;
+
+	tmpav.in.flags = 0;
+	tmpav.out.flags = 0;
+
+	if (!audioparse(va, count, &tmpav))
+		error(Ebadarg);
+
+	if (!audio_enforce(&tmpav))
+		error(Ebadarg);
+
+	qlock(&inlock);
+	if (waserror()) {
+		qunlock(&inlock);
+		nexterror();
+	}
+
+	if (audio_file_in >= 0 && (tmpav.in.flags & AUDIO_MOD_FLAG)) {
+		if (!audio_pause_in(audio_file_in, A_Pause))
+			error(Ebadarg);
+		if (!audio_flush(audio_file_in, A_In))
+			error(Ebadarg);
+		if (!audio_set_info(audio_file_in, &tmpav.in, A_In))
+			error(Ebadarg);
+	}
+	poperror();
+	qunlock(&inlock);
+
+	qlock(&outlock);
+	if (waserror()) {
+		qunlock(&outlock);
+		nexterror();
+	}
+	if (audio_file_out >= 0 && (tmpav.out.flags & AUDIO_MOD_FLAG)){
+		if (!audio_pause_out(audio_file_out))
+			error(Ebadarg);
+		if (!audio_set_info(audio_file_out, &tmpav.out, A_Out))
+			error(Ebadarg);
+	}
+	poperror();
+	qunlock(&outlock);
+
+	tmpav.in.flags = 0;
+	tmpav.out.flags = 0;
+
+	av = tmpav;
+
+	return count;
+}
+
+
+
+static int
+audio_set_blocking(int fd)
+{
+	int val;
+
+	if((val = fcntl(fd, F_GETFL, 0)) == -1)
+		return 0;
+	
+	val &= ~O_NONBLOCK;
+
+	if(fcntl(fd, F_SETFL, val) < 0)
+		return 0;
+
+	return 1;
+}
+
+static int
+doioctl(int fd, int ctl, int *info)
+{
+	int status;
+	osenter();
+	status = ioctl(fd, ctl, info);  /* qlock and load general stuff */
+	osleave();
+	if (status < 0)
+		print("doioctl(0x%.8lux, 0x%.8lux) failed %d\n", ctl, *info, errno);
+	return status;
+}
+
+static int
+choosefmt(Audio_d *i)
+{
+	int newbits, newenc;
+	
+	newbits = i->bits;
+	newenc = i->enc;
+	switch (newenc) {
+	case Audio_Alaw_Val:
+		if (newbits == 8)
+			return AFMT_A_LAW;
+		break;
+	case Audio_Ulaw_Val:
+		if (newbits == 8)
+			return AFMT_MU_LAW;
+		break;
+	case Audio_Pcm_Val:
+		if (newbits == 8)
+			return AFMT_U8;
+		else if (newbits == 16)
+			return AFMT_S16_LE;
+		break;
+	}
+	return -1;
+}
+
+static int
+audio_set_info(int fd, Audio_d *i, int d)
+{
+	int status;
+	int unequal_stereo = 0;
+
+	if(fd < 0)
+		return 0;
+
+	/* fmt */
+	if(i->flags & (AUDIO_BITS_FLAG || AUDIO_ENC_FLAG)) {
+		int oldfmt, newfmt;
+		oldfmt = AFMT_QUERY;
+		if (doioctl(fd, SNDCTL_DSP_SETFMT, &oldfmt) < 0)
+			return 0;
+		if (debug)
+			print("audio_set_info: current format 0x%.8lux\n", oldfmt);
+		newfmt = choosefmt(i);
+		if (debug)
+			print("audio_set_info: new format 0x%.8lux\n", newfmt);
+		if (newfmt == -1 || newfmt != oldfmt && doioctl(fd, SNDCTL_DSP_SETFMT, &newfmt) < 0)
+			return 0;
+	}
+
+	/* channels */
+	if(i->flags & AUDIO_CHAN_FLAG) {
+		int channels = i->chan;
+		if (debug)
+			print("audio_set_info: new channels %d\n", channels);
+		if (doioctl(fd, SNDCTL_DSP_CHANNELS, &channels) < 0
+			|| channels != i->chan)
+			return 0;
+	}
+
+	/* sample rate */
+	if(i->flags & AUDIO_RATE_FLAG) {
+		int speed = i->rate;
+		if (debug)
+			print("audio_set_info: new speed %d\n", speed);
+		if (doioctl(fd, SNDCTL_DSP_SPEED, &speed) < 0 || speed != i->rate)
+			return 0;
+	}
+
+	/* dev volume */
+	if(i->flags & (AUDIO_LEFT_FLAG | AUDIO_VOL_FLAG | AUDIO_RIGHT_FLAG)) {
+		int val;
+		if (i->flags & (AUDIO_LEFT_FLAG | AUDIO_VOL_FLAG))
+			mixerleftvol[i->dev] = (i->left * 100) / Audio_Max_Val;
+		if (i->flags & (AUDIO_RIGHT_FLAG | AUDIO_VOL_FLAG))
+			mixerrightvol[i->dev] = (i->right * 100) / Audio_Max_Val;
+		val = mixerleftvol[i->dev] | (mixerrightvol[i->dev] << 8);
+		doioctl(fd, MIXER_WRITE(i->dev), &val);
+	}
+
+	if (i->flags & AUDIO_DEV_FLAG) {
+	}
+	
+	return 1;
+}
+
+void 
+audio_swap_endian(char *p, int n)
+{
+	int b;
+
+	while (n > 1) {
+		b = p[0];
+		p[0] = p[1];
+		p[1] = b;
+		p += 2;
+		n -= 2;
+	}
+}
+
+static int
+audio_pause_out(int fd)
+{
+	USED(fd);
+	return 1;
+}
+
+static int
+audio_pause_in(int fd, int f)
+{
+	USED(fd);
+	USED(f);
+	return 1;
+}
+
+static int
+audio_flush(int fd, int d)
+{
+	int x;
+	return doioctl(fd, SNDCTL_DSP_SYNC, &x) >= 0;
+}
+
+static int
+audio_enforce(Audio_t *t)
+{
+	if((t->in.enc == Audio_Ulaw_Val || t->in.enc == Audio_Alaw_Val) && 
+		(t->in.rate != 8000 || t->in.chan != 1))
+		 return 0;
+	if((t->out.enc == Audio_Ulaw_Val || t->out.enc == Audio_Alaw_Val) && 
+		(t->out.rate != 8000 || t->out.chan != 1))
+		 return 0;
+	return 1;
+}
+
+Audio_t*
+getaudiodev(void)
+{
+	return &av;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/FreeBSD/cmd.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,213 @@
+#include	<sys/types.h>
+#include	<signal.h>
+#include 	<pwd.h>
+#include	<sys/resource.h>
+#include	<sys/wait.h>
+#include	<fcntl.h>
+
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+enum
+{
+	Debug = 0
+};
+
+/*
+ * os-specific devcmd support.
+ * this version should be reasonably portable across Unix systems.
+ */
+typedef struct Targ Targ;
+struct Targ
+{
+	int	fd[3];	/* fd[0] is standard input, fd[1] is standard output, fd[2] is standard error */
+	char**	args;
+	char*	dir;
+	int	pid;
+	int	wfd;	/* child writes errors that occur after the fork or on exec */
+	int	uid;
+	int	gid;
+};
+
+extern int gidnobody;
+extern int uidnobody;
+
+static int
+childproc(Targ *t)
+{
+	int i, nfd;
+
+	if(Debug)
+		print("devcmd: '%s'", t->args[0]);
+
+	nfd = getdtablesize();
+	for(i = 0; i < nfd; i++)
+		if(i != t->fd[0] && i != t->fd[1] && i != t->fd[2] && i != t->wfd)
+			close(i);
+
+	dup2(t->fd[0], 0);
+	dup2(t->fd[1], 1);
+	dup2(t->fd[2], 2);
+	close(t->fd[0]);
+	close(t->fd[1]);
+	close(t->fd[2]);
+
+	/* should have an auth file to do host-specific authorisation? */
+	if(t->gid != -1){
+		if(setgid(t->gid) < 0 && getegid() == 0){
+			fprint(t->wfd, "can't set gid %d: %s", t->gid, strerror(errno));
+			_exit(1);
+		}
+	}
+
+	if(t->uid != -1){
+		if(setuid(t->uid) < 0 && geteuid() == 0){
+			fprint(t->wfd, "can't set uid %d: %s", t->uid, strerror(errno));
+			_exit(1);
+		}
+	}
+
+	if(t->dir != nil && chdir(t->dir) < 0){
+		fprint(t->wfd, "can't chdir to %s: %s", t->dir, strerror(errno));
+		_exit(1);
+	}
+
+	signal(SIGPIPE, SIG_DFL);
+
+	execvp(t->args[0], t->args);
+	if(Debug)
+		print("execvp: %s\n",strerror(errno));
+	fprint(t->wfd, "exec failed: %s", strerror(errno));
+
+	_exit(1);
+}
+
+void*
+oscmd(char **args, int nice, char *dir, int *fd)
+{
+	Targ *t;
+	int r, fd0[2], fd1[2], fd2[2], wfd[2], n, pid;
+
+	t = mallocz(sizeof(*t), 1);
+	if(t == nil)
+		return nil;
+
+	fd0[0] = fd0[1] = -1;
+	fd1[0] = fd1[1] = -1;
+	fd2[0] = fd2[1] = -1;
+	wfd[0] = wfd[1] = -1;
+	if(pipe(fd0) < 0 || pipe(fd1) < 0 || pipe(fd2) < 0 || pipe(wfd) < 0)
+		goto Error;
+	if(fcntl(wfd[1], F_SETFD, FD_CLOEXEC) < 0)	/* close on exec to give end of file on success */
+		goto Error;
+
+	t->fd[0] = fd0[0];
+	t->fd[1] = fd1[1];
+	t->fd[2] = fd2[1];
+	t->wfd = wfd[1];
+	t->args = args;
+	t->dir = dir;
+	t->gid = up->env->gid;
+	if(t->gid == -1)
+		t->gid = gidnobody;
+	t->uid = up->env->uid;
+	if(t->uid == -1)
+		t->uid = uidnobody;
+
+	signal(SIGCHLD, SIG_DFL);
+	switch(pid = fork()) {
+	case -1:
+		goto Error;
+	case 0:
+		setpgid(0, getpid());
+		if(nice)
+			oslopri();
+		childproc(t);
+		_exit(1);
+	default:
+		t->pid = pid;
+		if(Debug)
+			print("cmd pid %d\n", t->pid);
+		break;
+	}
+
+	close(fd0[0]);
+	close(fd1[1]);
+	close(fd2[1]);
+	close(wfd[1]);
+
+	n = read(wfd[0], up->genbuf, sizeof(up->genbuf)-1);
+	close(wfd[0]);
+	if(n > 0){
+		close(fd0[1]);
+		close(fd1[0]);
+		close(fd2[0]);
+		free(t);
+		up->genbuf[n] = 0;
+		if(Debug)
+			print("oscmd: bad exec: %q\n", up->genbuf);
+		error(up->genbuf);
+		return nil;
+	}
+
+	fd[0] = fd0[1];
+	fd[1] = fd1[0];
+	fd[2] = fd2[0];
+	return t;
+
+Error:
+	r = errno;
+	if(Debug)
+		print("oscmd: %q\n",strerror(r));
+	close(fd0[0]);
+	close(fd0[1]);
+	close(fd1[0]);
+	close(fd1[1]);
+	close(fd2[0]);
+	close(fd2[1]);
+	close(wfd[0]);
+	close(wfd[1]);
+	error(strerror(r));
+	return nil;
+}
+
+int
+oscmdkill(void *a)
+{
+	Targ *t = a;
+
+	if(Debug)
+		print("kill: %d\n", t->pid);
+	return kill(-t->pid, SIGTERM);
+}
+
+int
+oscmdwait(void *a, char *buf, int n)
+{
+	Targ *t = a;
+	int s;
+
+	if(waitpid(t->pid, &s, 0) == -1){
+		if(Debug)
+			print("wait error: %d [in %d] %q\n", t->pid, getpid(), strerror(errno));
+		return -1;
+	}
+	if(WIFEXITED(s)){
+		if(WEXITSTATUS(s) == 0)
+			return snprint(buf, n, "%d 0 0 0 ''", t->pid);
+		return snprint(buf, n, "%d 0 0 0 'exit: %d'", t->pid, WEXITSTATUS(s));
+	}
+	if(WIFSIGNALED(s)){
+		if(WTERMSIG(s) == SIGTERM || WTERMSIG(s) == SIGKILL)
+			return snprint(buf, n, "%d 0 0 0 killed", t->pid);
+		return snprint(buf, n, "%d 0 0 0 'signal: %d'", t->pid, WTERMSIG(s));
+	}
+	return snprint(buf, n, "%d 0 0 0 'odd status: 0x%x'", t->pid, s);
+}
+
+void
+oscmdfree(void *a)
+{
+	free(a);
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/FreeBSD/deveia.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,39 @@
+/*
+ * FreeBSD serial port definitions
+ */
+
+static char *sysdev[] = {
+        "/dev/cuaa0",
+        "/dev/cuaa1",
+        "/dev/cuaa2",
+        "/dev/cuaa3",
+};
+
+#include <sys/ioctl.h>
+#include "deveia-posix.c"
+#include "deveia-bsd.c"
+
+
+static struct tcdef_t bps[] = {
+	{0,		B0},
+	{50,		B50},
+	{75,		B75},
+	{110,		B110},
+	{134,		B134},
+	{150,		B150},
+	{200,		B200},
+	{300,		B300},
+	{600,		B600},
+	{1200,	B1200},
+	{1800,	B1800},
+	{2400,	B2400},
+	{4800,	B4800},
+	{9600,	B9600},
+	{19200,	B19200},
+	{38400,	B38400},
+	{57600,	B57600},
+	{115200,	B115200},
+	{230400,	B230400},
+	{-1,		-1}
+};
+
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/FreeBSD/devfs.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,7 @@
+#include "devfs-posix.c"
+
+static vlong
+osdisksize(int fd)
+{
+	return 0;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/FreeBSD/emu	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,111 @@
+dev
+	root
+	cons
+	env
+	mnt
+	pipe
+	prog
+	prof
+	srv
+	dup
+	ssl
+	cap
+	fs
+	cmd	cmd
+	indir
+
+	draw	win-x11a
+	pointer
+	snarf
+
+	ip	ipif-posix ipaux
+	eia
+	audio	audio
+	mem
+
+lib
+	interp
+	tk
+	freetype
+	math
+	draw
+
+	memlayer
+	memdraw
+	keyring
+	sec
+	mp
+
+	9
+
+link
+
+mod
+	sys
+	draw
+
+	tk
+	math
+	srv	srv
+	keyring
+	crypt
+	ipints
+	loader
+	freetype
+
+port
+	alloc
+	cache
+	chan
+	dev
+	devtab
+
+	dial
+	dis
+	discall
+	env
+	error
+	errstr
+	exception
+	exportfs
+	inferno
+	latin1
+	main
+	parse
+	pgrp
+	print
+	proc
+	qio
+	random
+	sysfile
+	uqid
+
+code
+
+init
+	emuinit
+
+root
+	/dev	/
+	/fd	/
+	/prog	/
+	/prof	/
+	/net	/
+	/net.alt	/
+	/chan	/
+	/nvfs	/
+	/env	/
+#	/chan
+#	/dev
+#	/dis
+#	/env
+#	/n
+#	/net
+#	/nvfs /
+#	/prog
+#	/icons
+#	/osinit.dis
+#	/dis/emuinit.dis
+#	/dis/lib/auth.dis
+#	/dis/lib/ssl.dis
+#	/n/local /
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/FreeBSD/emu-g	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,102 @@
+env
+	X11LIBS=
+dev
+	root
+	cons
+	env
+	mnt
+	pipe
+	prog
+	prof
+	srv
+	dup
+	ssl
+	cap
+	fs
+	cmd	cmd
+	indir
+
+	ip	ipif-posix ipaux
+	eia
+	audio	audio
+	mem
+
+lib
+	interp
+	math
+
+	keyring
+	sec
+	mp
+
+	9
+
+link
+
+mod
+	sys
+	math
+	srv	srv
+	keyring
+	crypt
+	ipints
+	loader
+
+port
+	alloc
+	cache
+	chan
+	dev
+	devtab
+
+	dial
+	dis
+	discall
+	env
+	error
+	errstr
+	exception
+	exportfs
+	inferno
+	latin1
+	main
+	parse
+	pgrp
+	print
+	proc
+	qio
+	random
+	sysfile
+	uqid
+
+code
+	void setpointer(int x, int y){USED(x); USED(y);}
+	ulong strtochan(char *s){USED(s); return ~0;}
+
+init
+	emuinit
+
+root
+	/dev	/
+	/fd	/
+	/prog	/
+	/prof	/
+	/net	/
+	/net.alt	/
+	/chan	/
+	/nvfs	/
+	/env	/
+#	/chan
+#	/dev
+#	/dis
+#	/env
+#	/n
+#	/net
+#	/nvfs /
+#	/prog
+#	/icons
+#	/osinit.dis
+#	/dis/emuinit.dis
+#	/dis/lib/auth.dis
+#	/dis/lib/ssl.dis
+#	/n/local /
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/FreeBSD/mkfile	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,48 @@
+SYSTARG=FreeBSD
+OBJTYPE=386
+<../../mkconfig
+SYSTARG=FreeBSD
+OBJTYPE=386
+
+#Configurable parameters
+
+CONF=emu			#default configuration
+CONFLIST=emu
+CLEANCONFLIST=
+
+INSTALLDIR=$ROOT/$SYSTARG/$OBJTYPE/bin	#path of directory where kernel is installed
+
+X11LIBS= -lX11 -lXext
+
+#end configurable parameters
+
+<$ROOT/mkfiles/mkfile-$SYSTARG-$OBJTYPE	#set vars based on target system
+
+<| $SHELLNAME ../port/mkdevlist $CONF	#sets $IP, $DEVS, $PORT, $LIBS
+
+OBJ=\
+	asm-$OBJTYPE.$O\
+	os.$O\
+	$CONF.root.$O\
+	lock.$O\
+	$DEVS\
+	$PORT\
+
+HFILES=\
+
+CFLAGS='-DROOT="'$ROOT'"' -DEMU -I. -I../port -I$ROOT/$SYSTARG/$OBJTYPE/include -I$ROOT/include -I$ROOT/libinterp $CTHREADFLAGS $CFLAGS $EMUOPTIONS
+SYSLIBS= -lm $X11LIBS
+KERNDATE=`{$NDATE}
+
+default:V:	$O.$CONF
+
+<../port/portmkfile
+
+$O.$CONF:	$OBJ $CONF.c $CONF.root.h $LIBFILES
+	$CC $CFLAGS '-DKERNDATE='$KERNDATE $CONF.c
+	$LD $LDFLAGS -o $target $OBJ $CONF.$O $LIBFILES $SYSLIBS
+
+install:V: $O.$CONF
+	cp $O.$CONF $INSTALLDIR/$CONF
+
+devfs.$O:	../port/devfs-posix.c
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/FreeBSD/mkfile-FreeBSD	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,17 @@
+#
+#	architecture-dependent files for FreeBSD
+#
+
+LDFLAGS=
+
+TARGFILES=devfs-posix.$O\
+	deveia-FreeBSD.$O\
+	devip.$O\
+	ipif-posix.$O\
+	os-FreeBSD.$O\
+	win-x11.$O\
+	srv.$O\
+	lock.$O\
+	asm-FreeBSD-$OBJTYPE.$O
+
+SYSLIBS=/usr/X11R6/lib/libX11.a -lm 
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/FreeBSD/os.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,531 @@
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+#undef getwd
+#include	<signal.h>
+#include 	<sys/socket.h>
+#include	<time.h>
+#include	<sys/time.h>
+#include	<termios.h>
+#include	<sched.h>
+#include	<pwd.h>
+#include	<errno.h>
+#include	<unistd.h>
+#include	<sys/resource.h>
+
+enum
+{
+	DELETE  = 0x7F,
+	NSTACKSPERALLOC = 16,
+	X11STACK=	256*1024
+};
+char *hosttype = "FreeBSD";
+
+extern void unlockandexit(int*);
+extern void executeonnewstack(void*, void (*f)(void*), void*);
+static void *stackalloc(Proc *p, void **tos);
+static void stackfreeandexit(void *stack);
+
+extern int dflag;
+
+void
+pexit(char *msg, int t)
+{
+	Osenv *e;
+	Proc *p;
+	void *kstack;
+
+	lock(&procs.l);
+	p = up;
+	if(p->prev)
+		p->prev->next = p->next;
+	else
+		procs.head = p->next;
+
+	if(up->next)
+		p->next->prev = p->prev;
+	else
+		procs.tail = p->prev;
+	unlock(&procs.l);
+
+	if(0)
+		print("pexit: %s: %s\n", up->text, msg);
+
+	e = up->env;
+	if(e != nil) {
+		closefgrp(e->fgrp);
+		closepgrp(e->pgrp);
+		closeegrp(e->egrp);
+		closesigs(e->sigs);
+	}
+	kstack = p->kstack;
+	free(p->prog);
+	free(p);
+	if(kstack != nil)
+		stackfreeandexit(kstack);
+}
+
+void
+trapBUS(int signo, siginfo_t *info, void *context)
+{
+	if(info)
+		print("trapBUS: signo: %d code: %d addr: %lx\n",
+		info->si_signo, info->si_code, info->si_addr);
+	else
+		print("trapBUS: no info\n"); 
+	disfault(nil, "Bus error");
+}
+
+static void
+trapUSR1(int signo)
+{
+	int intwait;
+
+	USED(signo);
+
+	intwait = up->intwait;
+	up->intwait = 0;	/* clear it to let proc continue in osleave */
+
+	if(up->type != Interp)		/* Used to unblock pending I/O */
+		return;
+	if(intwait == 0)		/* Not posted so its a sync error */
+		disfault(nil, Eintr);	/* Should never happen */
+}
+
+static void
+trapUSR2(int signo)
+{
+	USED(signo);
+	/* we've done our work of interrupting sigsuspend */
+}
+
+static void
+trapILL(int signo)
+{
+	disfault(nil, "Illegal instruction");
+}
+
+static void
+trapSEGV(int signo)
+{
+	disfault(nil, "Segmentation violation");
+}
+
+static void
+trapFPE(int signo)
+{
+
+	char buf[64];
+	USED(signo);
+	snprint(buf, sizeof(buf), "sys: fp: exception status=%.4lux", getfsr());
+	disfault(nil, buf);
+}
+
+static sigset_t initmask;
+
+static void
+setsigs(void)
+{
+	struct sigaction act;
+	sigset_t mask;
+
+	memset(&act, 0 , sizeof(act));
+	sigemptyset(&initmask);
+	
+	signal(SIGPIPE, SIG_IGN);	/* prevent signal when devcmd child exits */
+	if(signal(SIGTERM, SIG_IGN) != SIG_IGN)
+		signal(SIGTERM, cleanexit);
+
+	act.sa_handler = trapUSR1;
+	act.sa_mask = initmask;
+	sigaction(SIGUSR1, &act, nil);
+
+	act.sa_handler = trapUSR2;
+	sigaction(SIGUSR2, &act, nil);
+	sigemptyset(&mask);
+	sigaddset(&mask, SIGUSR2);
+	sigaddset(&initmask, SIGUSR2);
+	sigprocmask(SIG_BLOCK, &mask, NULL);
+
+	/*
+ 	 * prevent Zombies forming when any process terminates
+	 */
+	act.sa_sigaction = 0;
+	act.sa_flags |= SA_NOCLDWAIT;
+	if(sigaction(SIGCHLD, &act, nil))
+		panic("sigaction SIGCHLD");
+
+	if(sflag == 0) {
+		act.sa_sigaction = trapBUS;
+		act.sa_flags |= SA_SIGINFO;
+		if(sigaction(SIGBUS, &act, nil))
+			panic("sigaction SIGBUS");
+		act.sa_handler = trapILL;
+		if(sigaction(SIGILL, &act, nil))
+			panic("sigaction SIGBUS");
+		act.sa_handler = trapSEGV;
+		if(sigaction(SIGSEGV, &act, nil))
+			panic("sigaction SIGSEGV");
+		act.sa_handler = trapFPE;
+		if(sigaction(SIGFPE, &act, nil))
+			panic("sigaction SIGFPE");
+		if(sigaddset(&initmask, SIGINT) == -1)
+			panic("sigaddset");
+	}
+	if(sigprocmask(SIG_BLOCK, &initmask, nil)!= 0)
+		panic("sigprocmask");
+}
+
+static int
+tramp(void *arg)
+{
+	Proc *p;
+
+	p = arg;
+	p->pid = p->sigid = getpid();
+	sigprocmask(SIG_BLOCK, &initmask, nil);	/* in 5.3, rfork_thread doesn't copy from parent, contrary to docs? */
+	(*p->func)(p->arg);
+	pexit("{Tramp}", 0);
+	_exit(0);
+}
+
+void
+kproc(char *name, void (*func)(void*), void *arg, int flags)
+{
+	Proc *p;
+	Pgrp *pg;
+	Fgrp *fg;
+	Egrp *eg;
+	int pid;
+	void *tos;
+
+	p = newproc();
+
+	if(flags & KPDUPPG) {
+		pg = up->env->pgrp;
+		incref(&pg->r);
+		p->env->pgrp = pg;
+	}
+	if(flags & KPDUPFDG) {
+		fg = up->env->fgrp;
+		incref(&fg->r);
+		p->env->fgrp = fg;
+	}
+	if(flags & KPDUPENVG) {
+		eg = up->env->egrp;
+		incref(&eg->r);
+		p->env->egrp = eg;
+	}
+
+	p->env->uid = up->env->uid;
+	p->env->gid = up->env->gid;
+	kstrdup(&p->env->user, up->env->user);
+
+	strcpy(p->text, name);
+
+	p->func = func;
+	p->arg = arg;
+
+	lock(&procs.l);
+	if(procs.tail != nil) {
+		p->prev = procs.tail;
+		procs.tail->next = p;
+	}
+	else {
+		procs.head = p;
+		p->prev = nil;
+	}
+	procs.tail = p;
+	unlock(&procs.l);
+
+	if(flags & KPX11){
+		p->kstack = nil;	/* never freed; also up not defined */
+		tos = (char*)mallocz(X11STACK, 0) + X11STACK - sizeof(void*);
+	}else
+		p->kstack = stackalloc(p, &tos);
+	pid = rfork_thread(RFPROC|RFMEM|RFNOWAIT, tos, tramp, p);
+	if(pid < 0)
+		panic("rfork");
+}
+
+void
+oshostintr(Proc *p)
+{
+	kill(p->sigid, SIGUSR1);
+}
+
+void
+osblock(void)
+{
+	sigset_t mask;
+
+	sigprocmask(SIG_SETMASK, NULL, &mask);
+	sigdelset(&mask, SIGUSR2);
+	sigsuspend(&mask);
+}
+
+void
+osready(Proc *p)
+{
+	if(kill(p->sigid, SIGUSR2) < 0)
+		fprint(2, "emu: osready failed: pid %d: %s\n", p->sigid, strerror(errno));
+}
+
+void
+oslongjmp(void *regs, osjmpbuf env, int val)
+{
+	USED(regs);
+	siglongjmp(env, val);
+}
+
+struct termios tinit;
+
+static void
+termset(void)
+{
+	struct termios t;
+
+	tcgetattr(0, &t);
+	tinit = t;
+	t.c_lflag &= ~(ICANON|ECHO|ISIG);
+	t.c_cc[VMIN] = 1;
+	t.c_cc[VTIME] = 0;
+	tcsetattr(0, TCSANOW, &t);
+}
+
+static void
+termrestore(void)
+{
+	tcsetattr(0, TCSANOW, &tinit);
+}
+
+void
+cleanexit(int x)
+{
+	USED(x);
+
+	if(up->intwait) {
+		up->intwait = 0;
+		return;
+	}
+
+	if(dflag == 0)
+		termrestore();
+
+	kill(0, SIGKILL);
+	exit(0);
+}
+
+void
+osreboot(char *file, char **argv)
+{
+	if(dflag == 0)
+		termrestore();
+	execvp(file, argv);
+	panic("reboot failure");
+}
+
+int gidnobody= -1, uidnobody= -1;
+
+void
+getnobody()
+{
+	struct passwd *pwd;
+	
+	if(pwd = getpwnam("nobody")) {
+		uidnobody = pwd->pw_uid;
+		gidnobody = pwd->pw_gid;
+	}
+}
+
+void
+libinit(char *imod)
+{
+	struct passwd *pw;
+	Proc *p;
+	void *tos;
+	char sys[64];
+
+	setsid();
+
+	gethostname(sys, sizeof(sys));
+	kstrdup(&ossysname, sys);
+	getnobody();
+
+	if(dflag == 0)
+		termset();
+
+	setsigs();
+
+	p = newproc();
+	p->kstack = stackalloc(p, &tos);
+
+	pw = getpwuid(getuid());
+	if(pw != nil)
+		kstrdup(&eve, pw->pw_name);
+	else
+		print("cannot getpwuid\n");
+ 
+	p->env->uid = getuid();
+	p->env->gid = getgid();
+
+	executeonnewstack(tos, emuinit, imod);
+}
+
+int
+readkbd(void)
+{
+	int n;
+	char buf[1];
+
+	n = read(0, buf, sizeof(buf));
+	if(n < 0)
+		print("keyboard close (n=%d, %s)\n", n, strerror(errno));
+	if(n <= 0)
+		pexit("keyboard thread", 0);
+
+	switch(buf[0]) {
+	case '\r':
+		buf[0] = '\n';
+		break;
+	case DELETE:
+		cleanexit(0);
+		break;
+	}
+	return buf[0];
+}
+
+/*
+ * Return an abitrary millisecond clock time
+ */
+long
+osmillisec(void)
+{
+	static long sec0 = 0, usec0;
+	struct timeval t;
+
+	if(gettimeofday(&t,(struct timezone*)0)<0)
+		return 0;
+	if(sec0==0) {
+		sec0 = t.tv_sec;
+		usec0 = t.tv_usec;
+	}
+	return (t.tv_sec-sec0)*1000+(t.tv_usec-usec0+500)/1000;
+}
+
+int
+limbosleep(ulong milsec)
+{
+	return osmillisleep(milsec);
+}
+
+/*
+ * Return the time since the epoch in nanoseconds and microseconds
+ * The epoch is defined at 1 Jan 1970
+ */
+vlong
+osnsec(void)
+{
+	struct timeval t;
+
+	gettimeofday(&t, nil);
+	return (vlong)t.tv_sec*1000000000L + t.tv_usec*1000;
+}
+
+vlong
+osusectime(void)
+{
+	struct timeval t;
+ 
+	gettimeofday(&t, nil);
+	return (vlong)t.tv_sec * 1000000 + t.tv_usec;
+}
+
+int
+osmillisleep(ulong milsec)
+{
+	struct timespec time;
+
+	time.tv_sec = milsec / 1000;
+	time.tv_nsec = (milsec % 1000) * 1000000;
+	nanosleep(&time, 0);
+	return 0;
+}
+
+void
+osyield(void)
+{
+	sched_yield();
+}
+
+void
+ospause(void)
+{
+	for(;;)
+		pause();
+}
+
+void
+oslopri(void)
+{
+	setpriority(PRIO_PROCESS, 0, getpriority(PRIO_PROCESS,0)+4);
+}
+
+static struct {
+	Lock l;
+	void *free;
+} stacklist;
+
+static void
+_stackfree(void *stack)
+{
+	*((void **)stack) = stacklist.free;
+	stacklist.free = stack;
+}
+
+static void
+stackfreeandexit(void *stack)
+{
+	lock(&stacklist.l);
+	_stackfree(stack);
+	unlockandexit(&stacklist.l.val);
+}
+
+static void *
+stackalloc(Proc *p, void **tos)
+{
+	void *rv;
+	lock(&stacklist.l);
+	if (stacklist.free == 0) {
+		int x;
+		/*
+		 * obtain some more by using sbrk()
+		 */
+		void *more = sbrk(KSTACK * (NSTACKSPERALLOC + 1));
+		if (more == 0)
+			panic("stackalloc: no more stacks");
+		/*
+		 * align to KSTACK
+		 */
+		more = (void *)((((unsigned long)more) + (KSTACK - 1)) & ~(KSTACK - 1));
+		/*
+		 * free all the new stacks onto the freelist
+		 */
+		for (x = 0; x < NSTACKSPERALLOC; x++)
+			_stackfree((char *)more + KSTACK * x);
+	}
+	rv = stacklist.free;
+	stacklist.free = *(void **)rv;
+	unlock(&stacklist.l);
+	*tos = rv + KSTACK - sizeof(void*);
+	*(Proc **)rv = p;
+	return rv;
+}
+
+int
+segflush(void *a, ulong n)
+{
+	USED(a);
+	USED(n);
+	return 0;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Irix/NOTE	Sun Nov  4 12:33:14 2018
@@ -0,0 +1 @@
+for some strange reason, it lacks deveia!
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Irix/asm-mips.s	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,31 @@
+#include <sys/regdef.h>
+#include <sys/asm.h>
+
+LEAF(FPsave)
+	cfc1	t0, $31
+	sw	t0, 0(a0)		/* a0 is argument */
+	j	$31
+	END(FPsave)
+
+LEAF(FPrestore)
+	lw	t0, 0(a0)		/* a0 is argument */
+	ctc1	t0, $31
+	j	$31
+	END(FPrestore)
+
+
+/*
+ * lock from r4000 book
+ */
+LEAF(_tas)
+	.set 	noreorder
+1:
+	ll	v0,0(a0)		/* a0 is argument */
+	or	t1, v0, 1
+	sc	t1,0(a0)
+	beq	t1,zero,1b	
+	nop
+	j	$31			/* lock held */
+	nop
+	.set 	reorder
+	END(_tas)
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Irix/cmd.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,213 @@
+#include	<sys/types.h>
+#include	<signal.h>
+#include 	<pwd.h>
+#include	<sys/resource.h>
+#include	<sys/wait.h>
+#include	<fcntl.h>
+
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+enum
+{
+	Debug = 0
+};
+
+/*
+ * os-specific devcmd support.
+ * this version should be reasonably portable across Unix systems.
+ */
+typedef struct Targ Targ;
+struct Targ
+{
+	int	fd[3];	/* fd[0] is standard input, fd[1] is standard output, fd[2] is standard error */
+	char**	args;
+	char*	dir;
+	int	pid;
+	int	wfd;	/* child writes errors that occur after the fork or on exec */
+	int	uid;
+	int	gid;
+};
+
+extern int gidnobody;
+extern int uidnobody;
+
+static int
+childproc(Targ *t)
+{
+	int i, nfd;
+
+	if(Debug)
+		print("devcmd: '%s'", t->args[0]);
+
+	nfd = getdtablesize();
+	for(i = 0; i < nfd; i++)
+		if(i != t->fd[0] && i != t->fd[1] && i != t->fd[2] && i != t->wfd)
+			close(i);
+
+	dup2(t->fd[0], 0);
+	dup2(t->fd[1], 1);
+	dup2(t->fd[2], 2);
+	close(t->fd[0]);
+	close(t->fd[1]);
+	close(t->fd[2]);
+
+	/* should have an auth file to do host-specific authorisation? */
+	if(t->gid != -1){
+		if(setgid(t->gid) < 0 && getegid() == 0){
+			fprint(t->wfd, "can't set gid %d: %s", t->gid, strerror(errno));
+			_exit(1);
+		}
+	}
+
+	if(t->uid != -1){
+		if(setuid(t->uid) < 0 && geteuid() == 0){
+			fprint(t->wfd, "can't set uid %d: %s", t->uid, strerror(errno));
+			_exit(1);
+		}
+	}
+
+	if(t->dir != nil && chdir(t->dir) < 0){
+		fprint(t->wfd, "can't chdir to %s: %s", t->dir, strerror(errno));
+		_exit(1);
+	}
+
+	signal(SIGPIPE, SIG_DFL);
+
+	execvp(t->args[0], t->args);
+	if(Debug)
+		print("execvp: %s\n",strerror(errno));
+	fprint(t->wfd, "exec failed: %s", strerror(errno));
+
+	_exit(1);
+}
+
+void*
+oscmd(char **args, int nice, char *dir, int *fd)
+{
+	Targ *t;
+	int r, fd0[2], fd1[2], fd2[2], wfd[2], n, pid;
+
+	t = mallocz(sizeof(*t), 1);
+	if(t == nil)
+		return nil;
+
+	fd0[0] = fd0[1] = -1;
+	fd1[0] = fd1[1] = -1;
+	fd2[0] = fd2[1] = -1;
+	wfd[0] = wfd[1] = -1;
+	if(pipe(fd0) < 0 || pipe(fd1) < 0 || pipe(fd2) < 0 || pipe(wfd) < 0)
+		goto Error;
+	if(fcntl(wfd[1], F_SETFD, FD_CLOEXEC) < 0)	/* close on exec to give end of file on success */
+		goto Error;
+
+	t->fd[0] = fd0[0];
+	t->fd[1] = fd1[1];
+	t->fd[2] = fd2[1];
+	t->wfd = wfd[1];
+	t->args = args;
+	t->dir = dir;
+	t->gid = up->env->gid;
+	if(t->gid == -1)
+		t->gid = gidnobody;
+	t->uid = up->env->uid;
+	if(t->uid == -1)
+		t->uid = uidnobody;
+
+	signal(SIGCHLD, SIG_DFL);
+	switch(pid = fork()) {
+	case -1:
+		goto Error;
+	case 0:
+		setpgrp();
+		if(nice)
+			oslopri();
+		childproc(t);
+		_exit(1);
+	default:
+		t->pid = pid;
+		if(Debug)
+			print("cmd pid %d\n", t->pid);
+		break;
+	}
+
+	close(fd0[0]);
+	close(fd1[1]);
+	close(fd2[1]);
+	close(wfd[1]);
+
+	n = read(wfd[0], up->genbuf, sizeof(up->genbuf)-1);
+	close(wfd[0]);
+	if(n > 0){
+		close(fd0[1]);
+		close(fd1[0]);
+		close(fd2[0]);
+		free(t);
+		up->genbuf[n] = 0;
+		if(Debug)
+			print("oscmd: bad exec: %q\n", up->genbuf);
+		error(up->genbuf);
+		return nil;
+	}
+
+	fd[0] = fd0[1];
+	fd[1] = fd1[0];
+	fd[2] = fd2[0];
+	return t;
+
+Error:
+	r = errno;
+	if(Debug)
+		print("oscmd: %q\n",strerror(r));
+	close(fd0[0]);
+	close(fd0[1]);
+	close(fd1[0]);
+	close(fd1[1]);
+	close(fd2[0]);
+	close(fd2[1]);
+	close(wfd[0]);
+	close(wfd[1]);
+	error(strerror(r));
+	return nil;
+}
+
+int
+oscmdkill(void *a)
+{
+	Targ *t = a;
+
+	if(Debug)
+		print("kill: %d\n", t->pid);
+	return kill(-t->pid, SIGTERM);
+}
+
+int
+oscmdwait(void *a, char *buf, int n)
+{
+	Targ *t = a;
+	int s;
+
+	if(waitpid(t->pid, &s, 0) == -1){
+		if(Debug)
+			print("wait error: %d [in %d] %q\n", t->pid, getpid(), strerror(errno));
+		return -1;
+	}
+	if(WIFEXITED(s)){
+		if(WEXITSTATUS(s) == 0)
+			return snprint(buf, n, "%d 0 0 0 ''", t->pid);
+		return snprint(buf, n, "%d 0 0 0 'exit: %d'", t->pid, WEXITSTATUS(s));
+	}
+	if(WIFSIGNALED(s)){
+		if(WTERMSIG(s) == SIGTERM || WTERMSIG(s) == SIGKILL)
+			return snprint(buf, n, "%d 0 0 0 killed", t->pid);
+		return snprint(buf, n, "%d 0 0 0 'signal: %d'", t->pid, WTERMSIG(s));
+	}
+	return snprint(buf, n, "%d 0 0 0 'odd status: 0x%x'", t->pid, s);
+}
+
+void
+oscmdfree(void *a)
+{
+	free(a);
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Irix/devfs.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,7 @@
+#include "devfs-posix.c"
+
+static vlong
+osdisksize(int fd)
+{
+	return 0;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Irix/emu	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,111 @@
+dev
+	root
+	cons
+	env
+	mnt
+	pipe
+	prog
+	prof
+	srv
+	dup
+	ssl
+	cap
+	fs
+	cmd	cmd
+	indir
+
+	draw	win-x11a
+	pointer
+	snarf
+
+	ip	ipif-posix ipaux
+#	eia
+#	audio	audio
+	mem
+
+lib
+	interp
+	tk
+	freetype
+	math
+	draw
+
+	memlayer
+	memdraw
+	keyring
+	sec
+	mp
+
+	9
+
+link
+
+mod
+	sys
+	draw
+
+	tk
+	math
+	srv	srv
+	keyring
+	crypt
+	ipints
+	loader
+	freetype
+
+port
+	alloc
+	cache
+	chan
+	dev
+	devtab
+
+	dial
+	dis
+	discall
+	env
+	error
+	errstr
+	exception
+	exportfs
+	inferno
+	latin1
+	main
+	parse
+	pgrp
+	print
+	proc
+	qio
+	random
+	sysfile
+	uqid
+
+code
+
+init
+	emuinit
+
+root
+	/dev	/
+	/fd	/
+	/prog	/
+	/prof	/
+	/net	/
+	/net.alt	/
+	/chan	/
+	/nvfs	/
+	/env	/
+#	/chan
+#	/dev
+#	/dis
+#	/env
+#	/n
+#	/net
+#	/nvfs /
+#	/prog
+#	/icons
+#	/osinit.dis
+#	/dis/emuinit.dis
+#	/dis/lib/auth.dis
+#	/dis/lib/ssl.dis
+#	/n/local /
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Irix/mkfile	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,49 @@
+SYSTARG=Irix
+OBJTYPE=mips
+<../../mkconfig
+SYSTARG=Irix
+OBJTYPE=mips
+
+#Configurable parameters
+
+CONF=emu			#default configuration
+CONFLIST=emu
+CLEANCONFLIST=
+
+INSTALLDIR=$ROOT/$SYSTARG/$OBJTYPE/bin	#path of directory where kernel is installed
+
+#end configurable parameters
+
+<$ROOT/mkfiles/mkfile-$SYSTARG-$OBJTYPE	#set vars based on target system
+
+<| $SHELLNAME ../port/mkdevlist $CONF	#sets $IP, $DEVS, $PORT, $LIBS
+
+OBJ=\
+	asm-$OBJTYPE.$O\
+	os.$O\
+	$CONF.root.$O\
+	lock.$O\
+	$DEVS\
+	$PORT\
+
+LIBNAMES=${LIBS:%=lib%.a}
+#libs=${LIBS:%=$ROOT/$OBJDIR/lib/lib%.a}
+
+HFILES=\
+
+CFLAGS='-DROOT="'$ROOT'"' -DEMU -I. -I../port -I$ROOT/$SYSTARG/$OBJTYPE/include -I$ROOT/include -I$ROOT/libinterp $CTHREADFLAGS $CFLAGS $EMUOPTIONS
+SYSLIBS= -lfpe -lm -lX11 -lXext
+KERNDATE=`{$NDATE}
+
+default:V:	$O.$CONF
+
+$O.$CONF:	$OBJ $CONF.c $CONF.root.h $LIBNAMES
+	$CC $CFLAGS '-DKERNDATE='$KERNDATE $CONF.c
+	$LD $LDFLAGS -o $target $OBJ $CONF.$O $LIBFILES $SYSLIBS
+
+install:V: $O.$CONF
+	cp $O.$CONF $INSTALLDIR/$CONF
+
+<../port/portmkfile
+
+devfs.$O:	../port/devfs-posix.c
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Irix/mkfile-Irix	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,14 @@
+#
+#	architecture-dependent files for IRIX
+#
+
+TARGFILES=asm-Irix.$O\
+	devfs-posix.$O\
+	devip.$O\
+	ipif-posix.$O\
+	os-Irix.$O\
+	win-x11.$O\
+	srv.$O\
+	lock.$O\
+
+SYSLIBS=	-lfpe -lm -lX11
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Irix/os.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,475 @@
+/* Link with -lfpe. See man pages for fpc
+ * and /usr/include/sigfpe.h, sys/fpu.h.
+ */
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+#include	<time.h>
+#include	<ulocks.h>
+#include	<termios.h>
+#include 	<sigfpe.h>
+#include	<sys/prctl.h>
+#include 	<sys/fpu.h>
+#include	<sys/cachectl.h>
+#undef _POSIX_SOURCE		/* SGI incompetence */
+#include	<signal.h>
+#define _BSD_TIME
+/* for gettimeofday(), which isn't POSIX,
+ * but is fairly common
+ */
+#include 	<sys/time.h> 
+#define _POSIX_SOURCE
+#include 	<pwd.h>
+
+extern	int	rebootargc;
+extern	char**	rebootargv;
+
+int	gidnobody = -1;
+int	uidnobody = -1;
+Proc**	Xup;
+
+#define MAXSPROC 30000	/* max procs == MAXPID */
+static int	sproctbl[MAXSPROC];
+
+enum
+{
+	KSTACK	= 64*1024,
+	DELETE	= 0x7F
+};
+char *hosttype = "Irix";
+char *cputype = "mips";
+
+extern int dflag;
+
+void
+pexit(char *msg, int t)
+{
+	Osenv *e;
+
+	lock(&procs.l);
+	if(up->prev) 
+		up->prev->next = up->next;
+	else
+		procs.head = up->next;
+
+	if(up->next)
+		up->next->prev = up->prev;
+	else
+		procs.tail = up->prev;
+
+	sproctbl[getpid()] = -1;
+
+	unlock(&procs.l);
+
+/*	print("pexit: %s: %s\n", up->text, msg); /**/
+	e = up->env;
+	if(e != nil) {
+		closefgrp(e->fgrp);
+		closepgrp(e->pgrp);
+		closeegrp(e->egrp);
+		closesigs(e->sigs);
+	}
+	free(up->prog);
+	free(up);
+	exit(0);
+}
+
+static void
+tramp(void *p, size_t stacksz)
+{
+	up = p;
+	up->sigid = getpid();
+	up->func(up->arg);
+	pexit("", 0);
+}
+
+void
+kproc(char *name, void (*func)(void*), void *arg, int flags)
+{
+	Proc *p;
+	Pgrp *pg;
+	Fgrp *fg;
+	Egrp *eg;
+	int pid;
+	int id;
+	int i;
+
+	p = newproc();
+
+	if(flags & KPDUPPG) {
+		pg = up->env->pgrp;
+		incref(&pg->r);
+		p->env->pgrp = pg;
+	}
+	if(flags & KPDUPFDG) {
+		fg = up->env->fgrp;
+		incref(&fg->r);
+		p->env->fgrp = fg;
+	}
+	if(flags & KPDUPENVG) {
+		eg = up->env->egrp;
+		incref(&eg->r);
+		p->env->egrp = eg;
+	}
+
+	p->env->uid = up->env->uid;
+	p->env->gid = up->env->gid;
+	kstrdup(&p->env->user, up->env->user);
+
+	strcpy(p->text, name);
+
+	p->func = func;
+	p->arg = arg;
+
+	lock(&procs.l);
+	if(procs.tail != nil) {
+		p->prev = procs.tail;
+		procs.tail->next = p;
+	}
+	else {
+		procs.head = p;
+		p->prev = nil;
+	}
+	procs.tail = p;
+
+	for(i = 1; i < MAXSPROC; i++) {
+		if(sproctbl[i] == -1) {
+			break;
+		}
+	}
+
+	if(i==MAXSPROC)
+		return -1;
+
+	sproctbl[i] = -i - 1; /* temporary hold of table index outside of lock */
+
+	unlock(&procs.l);
+
+	pid = sprocsp(tramp, PR_SALL, p, 0, KSTACK);
+
+	if(pid >= 0)
+		sproctbl[i] = pid;
+	else
+		sproctbl[i] = -1;
+}
+
+void
+osblock(void)
+{
+	blockproc(up->sigid);
+}
+
+void
+osready(Proc *p)
+{
+	unblockproc(p->sigid);
+}
+
+void
+trapUSR1(void)
+{
+	int intwait;
+
+	intwait = up->intwait;
+	up->intwait = 0;	/* clear it to let proc continue in osleave */
+
+	if(up->type != Interp)		/* Used to unblock pending I/O */
+		return;
+
+	if(intwait == 0)		/* Not posted so it's a sync error */
+		disfault(nil, Eintr);	/* Should never happen */
+}
+
+void
+trapILL(void)
+{
+	disfault(nil, "Illegal instruction");
+}
+
+void
+trapBUS(void)
+{
+	disfault(nil, "Bus error");
+}
+
+void
+trapSEGV(void)
+{
+	disfault(nil, "Segmentation violation");
+}
+
+/*
+ * This is not a signal handler but rather a vector from real/FPcontrol-Irix.c
+ */
+void
+trapFPE(unsigned exception[5], int value[2])
+{
+	disfault(nil, "Floating point exception");
+}
+
+void
+oshostintr(Proc *p)
+{
+	kill(p->sigid, SIGUSR1);
+}
+
+void
+oslongjmp(void *regs, osjmpbuf env, int val)
+{
+	USED(regs);
+	siglongjmp(env, val);
+}
+
+static struct termios tinit;
+
+static void
+termset(void)
+{
+	struct termios t;
+
+	tcgetattr(0, &t);
+	tinit = t;
+	t.c_lflag &= ~(ICANON|ECHO|ISIG);
+	t.c_cc[VMIN] = 1;
+	t.c_cc[VTIME] = 0;
+	tcsetattr(0, TCSANOW, &t);
+}
+
+static void
+termrestore(void)
+{
+	tcsetattr(0, TCSANOW, &tinit);
+
+/*	if(sproctbl[0] < 0)
+		panic("corrupt sproc tbl");
+
+	kill(sproctbl[0], SIGUSR2);
+	sginap(10000); */
+}
+
+void
+trapUSR2(void)
+{
+	int i;
+
+	for(i = MAXSPROC - 1; i > 0; i--) {
+		if(sproctbl[i] != -1) 
+			kill(sproctbl[i], SIGKILL);
+		sproctbl[i] = -1;
+	}
+
+	execvp(rebootargv[0], rebootargv);
+	panic("reboot failure");
+}
+
+void
+cleanexit(int x)
+{
+	USED(x);
+
+	if(up->intwait) {
+		up->intwait = 0;
+		return;
+	}
+
+	if(dflag == 0)
+		termrestore();
+	kill(0, SIGKILL);
+	exit(0);
+}
+
+void
+getnobody(void)
+{
+	struct passwd *pwd;
+
+	pwd = getpwnam("nobody");
+	if(pwd != nil) {
+		uidnobody = pwd->pw_uid;
+		gidnobody = pwd->pw_gid;
+	}
+}
+
+void
+osreboot(char *file, char **argv)
+{
+	if(dflag == 0)
+		termrestore();
+	execvp(file, argv);
+	panic("reboot failure");
+}
+
+void
+libinit(char *imod)
+{
+	struct sigaction act;
+	struct passwd *pw;
+	int i;
+	char sys[64];
+
+	setsid();
+
+	for(i=0; i<MAXSPROC; i++)
+		sproctbl[i] = -1;
+
+	sproctbl[0] = getpid();
+
+	gethostname(sys, sizeof(sys));
+	kstrdup(&ossysname, sys);
+
+	if(dflag == 0)
+		termset();
+
+	if(signal(SIGTERM, SIG_IGN) != SIG_IGN)
+		signal(SIGTERM, cleanexit);
+	if(signal(SIGINT, SIG_IGN) != SIG_IGN)
+		signal(SIGINT, cleanexit);
+	signal(SIGUSR2, trapUSR2);
+	/* For the correct functioning of devcmd in the
+	 * face of exiting slaves
+	 */
+	signal(SIGCLD, SIG_IGN);
+	signal(SIGPIPE, SIG_IGN);
+	memset(&act, 0 , sizeof(act));
+	act.sa_handler=trapUSR1;
+	sigaction(SIGUSR1, &act, nil);
+	if(sflag == 0) {
+		act.sa_handler=trapBUS;
+		sigaction(SIGBUS, &act, nil);
+		act.sa_handler=trapILL;
+		sigaction(SIGILL, &act, nil);
+		act.sa_handler=trapSEGV;
+		sigaction(SIGSEGV, &act, nil);
+	}
+
+	if(usconfig(CONF_INITUSERS, 1000) < 0)
+		panic("usconfig");
+
+	Xup = (Proc**)PRDA->usr_prda.fill;
+	up = newproc();
+
+	pw = getpwuid(getuid());
+	if(pw != nil) {
+		if (strlen(pw->pw_name) + 1 <= KNAMELEN)
+			strcpy(eve, pw->pw_name);
+		else
+			print("pw_name too long\n");
+	}
+	else
+		print("cannot getpwuid\n");
+
+	/* after setting up, since this takes locks */
+	getnobody();
+	up->env->uid = getuid();
+	up->env->gid = getgid();
+	emuinit(imod);
+}
+
+int
+readkbd(void)
+{
+	int n;
+	char buf[1];
+
+	n = read(0, buf, sizeof(buf));
+	if(n < 0)
+		fprint(2, "keyboard read error: %s\n", strerror(errno));
+	if(n <= 0)
+		pexit("keyboard thread", 0);
+	switch(buf[0]) {
+	case '\r':
+		buf[0] = '\n';
+		break;
+	case DELETE:
+		cleanexit(0);
+		break;
+	}
+	return buf[0];
+}
+
+int
+segflush(void *a, ulong n)
+{
+	cacheflush(a, n, BCACHE);
+	return 0;
+}
+
+/*
+ * Return an abitrary millisecond clock time
+ */
+long
+osmillisec(void)
+{
+	static long sec0 = 0, usec0;
+	struct timeval t;
+
+	if(gettimeofday(&t,(struct timezone*)0)<0)
+		return(0);
+	if(sec0==0){
+		sec0 = t.tv_sec;
+		usec0 = t.tv_usec;
+	}
+	return((t.tv_sec-sec0)*1000+(t.tv_usec-usec0+500)/1000);
+}
+
+/*
+ * Return the time since the epoch in nanoseconds and microseconds
+ * The epoch is defined at 1 Jan 1970
+ */
+vlong
+osnsec(void)
+{
+	struct timeval t;
+
+	gettimeofday(&t, nil);
+	return (vlong)t.tv_sec*1000000000L + t.tv_usec*1000;
+}
+
+vlong
+osusectime(void)
+{
+	struct timeval t;
+ 
+	gettimeofday(&t, nil);
+	return (vlong)t.tv_sec * 1000000 + t.tv_usec;
+}
+
+int
+osmillisleep(ulong milsec)
+{
+	static int tick;
+
+	/*
+	 * Posix-conforming CLK_TCK implementations tend to call sysconf,
+	 * and we don't need the overhead.
+	 */
+	if(tick == 0)
+		tick = CLK_TCK;
+	sginap((tick*milsec)/1000);
+	return 0;
+}
+
+int
+limbosleep(ulong milsec)
+{
+	return osmillisleep(milsec);
+}
+
+void
+osyield(void)
+{
+	sginap(0);
+}
+
+void
+ospause(void)
+{
+	for(;;)
+		pause();
+}
+
+void
+oslopri(void)
+{
+	nice(2);
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Linux/arm-tas-v5.S	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,18 @@
+
+	.file	"arm-tas-v5.S"
+/*
+ * ulong _tas(ulong*);
+ */
+	.align	2
+	.global	_tas
+	.type	_tas, %function
+_tas:
+	@ args = 0, pretend = 0, frame = 0
+	@ frame_needed = 0, uses_anonymous_args = 0
+	@ link register save eliminated.
+	@ lr needed for prologue
+	mov	r3, #1
+	mov	r1, r0
+	swp	r0, r3, [r1]
+	bx	lr
+	.size	_tas, .-_tas
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Linux/arm-tas-v7.S	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,30 @@
+	.file	"arm-tas-v7.S"
+#ifndef ARMv7
+#define	DMB	mcr     p15, 0, r0, c7, c10, 5
+#else
+#define	DMB	dmb
+#endif
+.align 2
+.global	_tas
+.type	_tas, %function
+_tas:
+	@ args = 0, pretend = 0, frame = 0
+	@ frame_needed = 0, uses_anonymous_args = 0
+	@ link register save eliminated.
+	@ lr needed for prologue
+	DMB
+	mov	r1, r0
+	mov	r2, #0xaa
+tas1:
+	ldrex	r0, [r1]
+	cmp	r0, #0
+	bne	lockbusy
+	strex	r3, r2, [r1]
+	cmp	r3, #0
+	bne	tas1
+	DMB
+	bx	lr
+lockbusy:
+	clrex
+	bx	lr
+	.size	_tas, .-_tas
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Linux/asm-386.S	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,51 @@
+	.file	"asm-Linux-386.S"
+	.text
+
+/*
+ * umult(ulong m1, ulong m2, ulong *hi)
+ */
+
+	.type	umult,@function
+	.global	umult
+umult:
+	pushl	%ebp
+	movl	%esp, %ebp
+	pushl	%ebx
+
+	movl	8(%ebp), %eax
+	movl	12(%ebp), %ebx
+	mull	%ebx
+	movl	16(%ebp), %ebx
+	movl	%edx, (%ebx)
+
+	popl	%ebx
+	popl	%ebp
+	ret
+
+	.type	FPsave,@function
+	.global	FPsave
+FPsave:
+	pushl	%ebp
+	movl	%esp, %ebp
+	movl	8(%ebp), %eax
+	fstenv	(%eax)
+	popl	%ebp
+	ret
+
+	.type	FPrestore,@function
+	.global	FPrestore
+FPrestore:
+	pushl	%ebp
+	movl	%esp, %ebp
+	movl	8(%ebp), %eax
+	fldenv	(%eax)
+	popl	%ebp
+	ret
+
+	.type	_tas,@function
+	.globl	_tas
+_tas:
+	movl	$1, %eax
+	movl	4(%esp), %ecx
+	xchgl	%eax, 0(%ecx)
+	ret
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Linux/asm-arm.S	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,62 @@
+	.file	"asm-Linux-arm.S"
+	.text
+
+/*
+ * ulong umult(ulong m1, ulong m2, ulong *hi)
+ */
+
+        .align  2
+        .global umult
+        .type   umult, %function
+umult:
+        @ args = 0, pretend = 0, frame = 12
+        @ frame_needed = 1, uses_anonymous_args = 0
+        mov     ip, sp
+        stmfd   sp!, {fp, ip, lr, pc}
+        sub     fp, ip, #4
+        sub     sp, sp, #12
+        str     r0, [fp, #-16]
+        str     r1, [fp, #-20]
+        str     r2, [fp, #-24]
+        ldr     r1, [fp, #-16]
+        ldr     r2, [fp, #-20]
+        umull   r0, r3, r1, r2
+        ldr     r1, [fp, #-24]
+        str     r3, [r1]
+        ldmea   fp, {fp, sp, pc}
+        .size   umult, .-umult
+
+/*
+ *  void		FPsave(void*);
+ */
+
+        .align  2
+        .global FPsave
+        .type   FPsave, %function
+FPsave:
+        @ args = 0, pretend = 0, frame = 4
+        @ frame_needed = 1, uses_anonymous_args = 0
+        mov     ip, sp
+        stmfd   sp!, {fp, ip, lr, pc}
+        sub     fp, ip, #4
+        sub     sp, sp, #4
+        str     r0, [fp, #-16]
+        ldmea   fp, {fp, sp, pc}
+        .size   FPsave, .-FPsave
+
+/*
+ * void		FPrestore(void*);
+ */
+        .align  2
+        .global FPrestore
+        .type   FPrestore, %function
+FPrestore:
+        @ args = 0, pretend = 0, frame = 4
+        @ frame_needed = 1, uses_anonymous_args = 0
+        mov     ip, sp
+        stmfd   sp!, {fp, ip, lr, pc}
+        sub     fp, ip, #4
+        sub     sp, sp, #4
+        str     r0, [fp, #-16]
+        ldmea   fp, {fp, sp, pc}
+        .size   FPrestore, .-FPrestore
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Linux/asm-mips.S	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,28 @@
+#include <sys/asm.h>
+#include <sys/regdef.h>
+#include <asm/cachectl.h>
+
+LEAF(FPsave)
+	cfc1	t0, $31
+	sw	t0, 0(a0)		/* a0 is argument */
+	j	$31
+	END(FPsave)
+
+LEAF(FPrestore)
+	lw	t0, 0(a0)		/* a0 is argument */
+	ctc1	t0, $31
+	j	$31
+	END(FPrestore)
+
+LEAF(_tas)
+	.set 	noreorder
+1:
+	ll	v0,0(a0)		/* a0 is argument */
+	or	t1, v0, 1
+	sc	t1,0(a0)
+	beq	t1,zero,1b	
+	nop
+	j	$31			/* lock held */
+	nop
+	.set 	reorder
+	END(_tas)
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Linux/asm-power.S	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,61 @@
+	.align	2
+	.global	FPsave
+FPsave:
+	stfd	%f14,0*8(%r3)
+	stfd	%f15,1*8(%r3)
+	stfd	%f16,2*8(%r3)
+	stfd	%f17,3*8(%r3)
+	stfd	%f18,4*8(%r3)
+	stfd	%f19,5*8(%r3)
+	stfd	%f20,6*8(%r3)
+	stfd	%f21,7*8(%r3)
+	stfd	%f22,8*8(%r3)
+	stfd	%f23,9*8(%r3)
+	stfd	%f24,10*8(%r3)
+	stfd	%f25,11*8(%r3)
+	stfd	%f26,12*8(%r3)
+	stfd	%f27,13*8(%r3)
+	stfd	%f28,14*8(%r3)
+	stfd	%f29,15*8(%r3)
+	stfd	%f30,16*8(%r3)
+	stfd	%f31,17*8(%r3)
+	blr
+
+	.align	2
+	.global	FPrestore
+FPrestore:
+	lfd		%f14,0*8(%r3)
+	lfd		%f15,1*8(%r3)
+	lfd		%f16,2*8(%r3)
+	lfd		%f17,3*8(%r3)
+	lfd		%f18,4*8(%r3)
+	lfd		%f19,5*8(%r3)
+	lfd		%f20,6*8(%r3)
+	lfd		%f21,7*8(%r3)
+	lfd		%f22,8*8(%r3)
+	lfd		%f23,9*8(%r3)
+	lfd		%f24,10*8(%r3)
+	lfd		%f25,11*8(%r3)
+	lfd		%f26,12*8(%r3)
+	lfd		%f27,13*8(%r3)
+	lfd		%f28,14*8(%r3)
+	lfd		%f29,15*8(%r3)
+	lfd		%f30,16*8(%r3)
+	lfd		%f31,17*8(%r3)
+	blr
+
+	.align	2
+	.global	_tas
+_tas:
+	sync
+	mr		%r4, %r3
+	addi		%r5,0,0x1	
+1:
+	lwarx	%r3, 0, %r4
+	cmpwi	%r3, 0
+	bne-	2f
+	stwcx.	%r5, 0, %r4
+	bne-	1b
+2:
+	sync
+	blr
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Linux/asm-spim.S	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,29 @@
+#include <sys/asm.h>
+#include <sys/regdef.h>
+#include <asm/cachectl.h>
+
+
+LEAF(FPsave)
+	cfc1	t0, $31
+	sw	t0, 0(a0)		/* a0 is argument */
+	j	$31
+	END(FPsave)
+
+LEAF(FPrestore)
+	lw	t0, 0(a0)		/* a0 is argument */
+	ctc1	t0, $31
+	j	$31
+	END(FPrestore)
+
+LEAF(_tas)
+	.set 	noreorder
+1:
+	ll	v0,0(a0)		/* a0 is argument */
+	or	t1, v0, 1
+	sc	t1,0(a0)
+	beq	t1,zero,1b	
+	nop
+	j	$31			/* lock held */
+	nop
+	.set 	reorder
+	END(_tas)
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Linux/audio-oss.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,441 @@
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+#include "audio.h"
+#include <sys/ioctl.h>
+#include <sys/soundcard.h>
+
+#define 	Audio_Mic_Val		SOUND_MIXER_MIC
+#define 	Audio_Linein_Val	SOUND_MIXER_LINE
+
+#define		Audio_Speaker_Val	SOUND_MIXER_PCM // SOUND_MIXER_VOLUME
+#define		Audio_Headphone_Val	SOUND_MIXER_ALTPCM
+#define		Audio_Lineout_Val	SOUND_MIXER_CD
+
+#define 	Audio_Pcm_Val		AFMT_S16_LE
+#define 	Audio_Ulaw_Val		AFMT_MU_LAW
+#define 	Audio_Alaw_Val		AFMT_A_LAW
+
+#include "audio-tbls.c"
+#define	min(a,b)	((a) < (b) ? (a) : (b))
+
+#define DEVAUDIO	"/dev/dsp"
+#define DEVMIXER	"/dev/mixer"
+
+#define DPRINT if(1)print
+
+enum {
+	A_Pause,
+	A_UnPause,
+	A_In,
+	A_Out,
+};
+
+static struct {
+	int data;	/* dsp data fd */
+	int ctl;	/* mixer fd */
+	int pause;
+	QLock lk;
+} afd = {.data = -1, .ctl = -1, .pause =A_UnPause };
+
+static Audio_t av;
+static QLock inlock;
+static QLock outlock;
+
+static int audio_open(int);
+static int audio_pause(int, int);
+static int audio_set_info(int, Audio_d*, int);
+
+Audio_t*
+getaudiodev(void)
+{
+	return &av;
+}
+
+void 
+audio_file_init(void)
+{
+	audio_info_init(&av);
+}
+
+void
+audio_file_open(Chan *c, int omode)
+{
+	USED(c);
+	DPRINT("audio_file_open %d %d %x\n", afd.data, afd.ctl, omode);
+	qlock(&afd.lk);
+	if(waserror()){
+		qunlock(&afd.lk);
+		nexterror();
+	}
+	if(afd.data >= 0)
+		error(Einuse);
+	if(afd.ctl < 0){
+		afd.ctl = open(DEVMIXER, ORDWR);
+		if(afd.ctl < 0)
+			oserror();
+	}
+	afd.data = audio_open(omode);
+	if(afd.data < 0)
+		oserror();
+	poperror();
+	qunlock(&afd.lk);
+}
+
+void    
+audio_file_close(Chan *c)
+{
+	USED(c);
+	DPRINT("audio_file_close %d %d\n", afd.data, afd.ctl);
+	qlock(&afd.lk);
+	if(waserror()){
+		qunlock(&afd.lk);
+		nexterror();
+	}
+	close(afd.data);
+	afd.data = -1;
+	qunlock(&afd.lk);
+	poperror();
+}
+
+long
+audio_file_read(Chan *c, void *va, long count, vlong offset)
+{
+	long ba, status, chunk, total;
+
+	USED(c);
+	USED(offset);
+	DPRINT("audio_file_read %d %d\n", afd.data, afd.ctl);
+	qlock(&inlock);
+	if(waserror()){
+		qunlock(&inlock);
+		nexterror();
+	}
+
+	if(afd.data < 0)
+		error(Eperm);
+
+	/* check block alignment */
+	ba = av.in.bits * av.in.chan / Bits_Per_Byte;
+
+	if(count % ba)
+		error(Ebadarg);
+		
+	if(!audio_pause(afd.data, A_UnPause))
+		error(Eio);
+	
+	total = 0;
+	while(total < count){
+		chunk = count - total;
+		status = read (afd.data, va + total, chunk);
+		if(status < 0)
+			error(Eio);
+		total += status;
+	}
+	
+	if(total != count)
+		error(Eio);
+
+	poperror();
+	qunlock(&inlock);
+	
+	return count;
+}
+
+long                                            
+audio_file_write(Chan *c, void *va, long count, vlong offset)
+{
+	long status = -1;
+	long ba, total, chunk, bufsz;
+	
+	USED(c);
+	USED(offset);
+	DPRINT("audio_file_write %d %d\n", afd.data, afd.ctl);
+	qlock(&outlock);
+	if(waserror()){
+		qunlock(&outlock);
+		nexterror();
+	}
+	
+	if(afd.data < 0)
+		error(Eperm);
+	
+	/* check block alignment */
+	ba = av.out.bits * av.out.chan / Bits_Per_Byte;
+
+	if(count % ba)
+		error(Ebadarg);
+
+	total = 0;
+	bufsz = av.out.buf * Audio_Max_Buf / Audio_Max_Val;
+
+	if(bufsz == 0)
+		error(Ebadarg);
+
+	while(total < count){
+		chunk = min(bufsz, count - total);
+		status = write(afd.data, va, chunk);
+		if(status <= 0)
+			error(Eio);
+		total += status;
+	}
+
+	poperror();
+	qunlock(&outlock);
+
+	return count;	
+}
+
+long
+audio_ctl_write(Chan *c, void *va, long count, vlong offset)
+{
+	Audio_t tmpav = av;
+	int tfd;
+
+	USED(c);
+	USED(offset);
+	tmpav.in.flags = 0;
+	tmpav.out.flags = 0;
+	
+	DPRINT ("audio_ctl_write %X %X\n", afd.data, afd.ctl);
+	if(!audioparse(va, count, &tmpav))
+		error(Ebadarg);
+
+	if(!canqlock(&inlock))
+		error("device busy");
+	if(waserror()){
+		qunlock(&inlock);
+		nexterror();
+	}
+	if(!canqlock(&outlock))
+		error("device busy");
+	if(waserror()){
+		qunlock(&outlock);
+		nexterror();
+	}
+
+	/* DEVAUDIO needs to be open to issue an ioctl */
+	tfd = afd.data;
+	if(tfd < 0){
+		tfd = open(DEVAUDIO, O_RDONLY|O_NONBLOCK);
+		if(tfd < 0)
+			oserror();
+	}
+	if(waserror()){
+		if(tfd != afd.data)
+			close(tfd);
+		nexterror();
+	}
+
+	if(tmpav.in.flags & AUDIO_MOD_FLAG){
+		if(!audio_pause(tfd, A_Pause))
+			error(Ebadarg);
+		if(!audio_set_info(tfd, &tmpav.in, A_In))
+			error(Ebadarg);
+	}
+
+	poperror();
+	if(tfd != afd.data)
+		close(tfd);
+
+	tmpav.in.flags = 0;
+	av = tmpav;
+
+	poperror();
+	qunlock(&outlock);
+	poperror();
+	qunlock(&inlock);
+	return count;
+}
+
+/* Linux/OSS specific stuff */
+
+static int
+choosefmt(Audio_d *i)
+{
+	switch(i->bits){
+	case 8:
+		switch(i->enc){
+		case Audio_Alaw_Val:
+			return AFMT_A_LAW;
+		case Audio_Ulaw_Val:
+			return AFMT_MU_LAW;
+		case Audio_Pcm_Val:
+			return AFMT_U8;
+		}
+		break;
+	case 16:
+		if(i->enc == Audio_Pcm_Val)
+			return AFMT_S16_LE;
+		break;
+	}
+	return -1;
+}
+
+static int
+setvolume(int fd, int what, int left, int right)
+{
+	int can, v;
+	
+	if(ioctl(fd, SOUND_MIXER_READ_DEVMASK, &can) < 0)
+		can = ~0;
+
+	DPRINT("setvolume fd%d %X can mix 0x%X (mask %X)\n", fd, what, (can & (1<<what)), can);
+	if(!(can & (1<<what)))
+		return 0;
+	v = left | (right<<8);
+	if(ioctl(afd.ctl, MIXER_WRITE(what), &v) < 0)
+		return 0;
+	return 1;
+}
+
+static int
+audio_set_info(int fd, Audio_d *i, int d)
+{
+	int status, arg;
+	int oldfmt, newfmt;
+
+	USED(d);
+	DPRINT("audio_set_info (%d) %d %d\n", fd, afd.data, afd.ctl);
+	if(fd < 0)
+		return 0;
+
+	/* sample rate */
+	if(i->flags & AUDIO_RATE_FLAG){
+		arg = i->rate;
+		if(ioctl(fd, SNDCTL_DSP_SPEED, &arg) < 0)
+			return 0;
+	}
+	
+	/* channels */
+	if(i->flags & AUDIO_CHAN_FLAG){
+		arg = i->chan;
+		if(ioctl(fd, SNDCTL_DSP_CHANNELS, &arg) < 0)
+			return 0;
+	}
+
+	/* precision */
+	if(i->flags & AUDIO_BITS_FLAG){
+		arg = i->bits;
+		if(ioctl(fd, SNDCTL_DSP_SAMPLESIZE, &arg) < 0)
+			return 0;
+	}
+	
+	/* encoding */
+	if(i->flags & AUDIO_ENC_FLAG){
+		ioctl(fd, SNDCTL_DSP_GETFMTS, &oldfmt);
+
+		newfmt = choosefmt(i);
+		if(newfmt < 0)
+			return 0;
+		if(newfmt != oldfmt){
+			status = ioctl(fd, SNDCTL_DSP_SETFMT, &arg);
+			DPRINT ("enc oldfmt newfmt %x status %d\n", oldfmt, newfmt, status);
+		}
+	}
+
+	/* dev volume */ 
+	if(i->flags & (AUDIO_LEFT_FLAG|AUDIO_VOL_FLAG))
+		return setvolume(afd.ctl, i->dev, i->left, i->right);
+
+	return 1;
+}
+
+static int
+audio_set_blocking(int fd)
+{
+	int val;
+
+	if((val = fcntl(fd, F_GETFL, 0)) == -1)
+		return 0;
+	
+	val &= ~O_NONBLOCK;
+
+	if(fcntl(fd, F_SETFL, val) < 0)
+		return 0;
+
+	return 1;
+}
+
+static int
+audio_open(int omode)
+{
+	int fd;
+	
+	/* open non-blocking in case someone already has it open */
+	/* otherwise we would block until they close! */
+	switch (omode){
+	case OREAD:
+		fd = open(DEVAUDIO, O_RDONLY|O_NONBLOCK);
+		break;
+	case OWRITE:
+		fd = open(DEVAUDIO, O_WRONLY|O_NONBLOCK);
+		break;
+	case ORDWR:
+		fd = open(DEVAUDIO, O_RDWR|O_NONBLOCK);
+		break;
+	}
+
+	DPRINT("audio_open %d\n", fd);
+	if(fd < 0)
+		oserror();
+
+	/* change device to be blocking */
+	if(!audio_set_blocking(fd)){
+		close(fd);
+		error("cannot set blocking mode");
+	}
+
+	if(!audio_pause(fd, A_Pause)){
+		close(fd);
+		error(Eio);
+	}
+
+	/* set audio info */
+	av.in.flags = ~0;
+	av.out.flags = ~0;
+
+	if(!audio_set_info(fd, &av.in, A_In)){
+		close(fd);
+		error(Ebadarg);
+	}
+
+	av.in.flags = 0;
+
+	/* tada, we're open, blocking, paused and flushed */
+	return fd;
+}
+
+static int
+dspsync(int fd)
+{
+	return ioctl(fd, SNDCTL_DSP_RESET, NULL) >= 0 &&
+		ioctl(fd, SNDCTL_DSP_SYNC, NULL) >= 0;
+}
+
+static int
+audio_pause(int fd, int f)
+{
+	int status;
+
+//	DPRINT ("audio_pause (%d) %d %d\n", fd, afd.data, afd.ctl);
+	if(fd < 0)
+		return 0;
+	if(fd != afd.data)
+		return dspsync(fd);
+	qlock(&afd.lk);
+	if(afd.pause == f){
+		qunlock(&afd.lk);
+		return 1;
+	}
+	if(waserror()){
+		qunlock(&afd.lk);
+		nexterror();
+	}
+	status = dspsync(afd.data);
+	if(status)
+		afd.pause = f;
+	poperror();
+	qunlock(&afd.lk);
+	return status;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Linux/cmd.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,213 @@
+#include	<sys/types.h>
+#include	<signal.h>
+#include 	<pwd.h>
+#include	<sys/resource.h>
+#include	<sys/wait.h>
+#include	<fcntl.h>
+
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+enum
+{
+	Debug = 0
+};
+
+/*
+ * os-specific devcmd support.
+ * this version should be reasonably portable across Unix systems.
+ */
+typedef struct Targ Targ;
+struct Targ
+{
+	int	fd[3];	/* fd[0] is standard input, fd[1] is standard output, fd[2] is standard error */
+	char**	args;
+	char*	dir;
+	int	pid;
+	int	wfd;	/* child writes errors that occur after the fork or on exec */
+	int	uid;
+	int	gid;
+};
+
+extern int gidnobody;
+extern int uidnobody;
+
+static int
+childproc(Targ *t)
+{
+	int i, nfd;
+
+	if(Debug)
+		print("devcmd: '%s'", t->args[0]);
+
+	nfd = getdtablesize();
+	for(i = 0; i < nfd; i++)
+		if(i != t->fd[0] && i != t->fd[1] && i != t->fd[2] && i != t->wfd)
+			close(i);
+
+	dup2(t->fd[0], 0);
+	dup2(t->fd[1], 1);
+	dup2(t->fd[2], 2);
+	close(t->fd[0]);
+	close(t->fd[1]);
+	close(t->fd[2]);
+
+	/* should have an auth file to do host-specific authorisation? */
+	if(t->gid != -1){
+		if(setgid(t->gid) < 0 && getegid() == 0){
+			fprint(t->wfd, "can't set gid %d: %s", t->gid, strerror(errno));
+			_exit(1);
+		}
+	}
+
+	if(t->uid != -1){
+		if(setuid(t->uid) < 0 && geteuid() == 0){
+			fprint(t->wfd, "can't set uid %d: %s", t->uid, strerror(errno));
+			_exit(1);
+		}
+	}
+
+	if(t->dir != nil && chdir(t->dir) < 0){
+		fprint(t->wfd, "can't chdir to %s: %s", t->dir, strerror(errno));
+		_exit(1);
+	}
+
+	signal(SIGPIPE, SIG_DFL);
+
+	execvp(t->args[0], t->args);
+	if(Debug)
+		print("execvp: %s\n",strerror(errno));
+	fprint(t->wfd, "exec failed: %s", strerror(errno));
+
+	_exit(1);
+}
+
+void*
+oscmd(char **args, int nice, char *dir, int *fd)
+{
+	Targ *t;
+	int r, fd0[2], fd1[2], fd2[2], wfd[2], n, pid;
+
+	t = mallocz(sizeof(*t), 1);
+	if(t == nil)
+		return nil;
+
+	fd0[0] = fd0[1] = -1;
+	fd1[0] = fd1[1] = -1;
+	fd2[0] = fd2[1] = -1;
+	wfd[0] = wfd[1] = -1;
+	if(pipe(fd0) < 0 || pipe(fd1) < 0 || pipe(fd2) < 0 || pipe(wfd) < 0)
+		goto Error;
+	if(fcntl(wfd[1], F_SETFD, FD_CLOEXEC) < 0)	/* close on exec to give end of file on success */
+		goto Error;
+
+	t->fd[0] = fd0[0];
+	t->fd[1] = fd1[1];
+	t->fd[2] = fd2[1];
+	t->wfd = wfd[1];
+	t->args = args;
+	t->dir = dir;
+	t->gid = up->env->gid;
+	if(t->gid == -1)
+		t->gid = gidnobody;
+	t->uid = up->env->uid;
+	if(t->uid == -1)
+		t->uid = uidnobody;
+
+	signal(SIGCHLD, SIG_DFL);
+	switch(pid = fork()) {
+	case -1:
+		goto Error;
+	case 0:
+		setpgrp();
+		if(nice)
+			setpriority(PRIO_PROCESS, 0, 19);
+		childproc(t);
+		_exit(1);
+	default:
+		t->pid = pid;
+		if(Debug)
+			print("cmd pid %d\n", t->pid);
+		break;
+	}
+
+	close(fd0[0]);
+	close(fd1[1]);
+	close(fd2[1]);
+	close(wfd[1]);
+
+	n = read(wfd[0], up->genbuf, sizeof(up->genbuf)-1);
+	close(wfd[0]);
+	if(n > 0){
+		close(fd0[1]);
+		close(fd1[0]);
+		close(fd2[0]);
+		free(t);
+		up->genbuf[n] = 0;
+		if(Debug)
+			print("oscmd: bad exec: %q\n", up->genbuf);
+		error(up->genbuf);
+		return nil;
+	}
+
+	fd[0] = fd0[1];
+	fd[1] = fd1[0];
+	fd[2] = fd2[0];
+	return t;
+
+Error:
+	r = errno;
+	if(Debug)
+		print("oscmd: %q\n",strerror(r));
+	close(fd0[0]);
+	close(fd0[1]);
+	close(fd1[0]);
+	close(fd1[1]);
+	close(fd2[0]);
+	close(fd2[1]);
+	close(wfd[0]);
+	close(wfd[1]);
+	error(strerror(r));
+	return nil;
+}
+
+int
+oscmdkill(void *a)
+{
+	Targ *t = a;
+
+	if(Debug)
+		print("kill: %d\n", t->pid);
+	return kill(-t->pid, SIGTERM);
+}
+
+int
+oscmdwait(void *a, char *buf, int n)
+{
+	Targ *t = a;
+	int s;
+
+	if(waitpid(t->pid, &s, 0) == -1){
+		if(Debug)
+			print("wait error: %d [in %d] %q\n", t->pid, getpid(), strerror(errno));
+		return -1;
+	}
+	if(WIFEXITED(s)){
+		if(WEXITSTATUS(s) == 0)
+			return snprint(buf, n, "%d 0 0 0 ''", t->pid);
+		return snprint(buf, n, "%d 0 0 0 'exit: %d'", t->pid, WEXITSTATUS(s));
+	}
+	if(WIFSIGNALED(s)){
+		if(WTERMSIG(s) == SIGTERM || WTERMSIG(s) == SIGKILL)
+			return snprint(buf, n, "%d 0 0 0 killed", t->pid);
+		return snprint(buf, n, "%d 0 0 0 'signal: %d'", t->pid, WTERMSIG(s));
+	}
+	return snprint(buf, n, "%d 0 0 0 'odd status: 0x%x'", t->pid, s);
+}
+
+void
+oscmdfree(void *a)
+{
+	free(a);
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Linux/deveia.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,44 @@
+/*
+ * Linux serial port definitions
+ */
+
+static char *sysdev[] = {
+        "/dev/ttyS0",
+        "/dev/ttyS1",
+        "/dev/ttyS2",
+        "/dev/ttyS3",
+        "/dev/ttyS4",
+        "/dev/ttyS5",
+        "/dev/ttyS6",
+        "/dev/ttyS7",
+};
+
+#include <sys/ioctl.h>
+#include "deveia-posix.c"
+#include "deveia-bsd.c"
+
+
+static struct tcdef_t bps[] = {
+	{0,		B0},
+	{50,		B50},
+	{75,		B75},
+	{110,		B110},
+	{134,		B134},
+	{150,		B150},
+	{200,		B200},
+	{300,		B300},
+	{600,		B600},
+	{1200,	B1200},
+	{1800,	B1800},
+	{2400,	B2400},
+	{4800,	B4800},
+	{9600,	B9600},
+	{19200,	B19200},
+	{38400,	B38400},
+	{57600,	B57600},
+	{115200,	B115200},
+	{230400,	B230400},
+	{460800,	B460800},
+	{-1,		-1}
+};
+
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Linux/devfs.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,26 @@
+#include "devfs-posix.c"
+
+#include	<linux/hdreg.h>
+#include	<linux/fs.h>
+#include	<sys/ioctl.h>
+
+static vlong
+osdisksize(int fd)
+{
+	uvlong u64;
+	long l;
+	struct hd_geometry geo;
+	
+	memset(&geo, 0, sizeof geo);
+	l = 0;
+	u64 = 0;
+#ifdef BLKGETSIZE64
+	if(ioctl(fd, BLKGETSIZE64, &u64) >= 0)
+		return u64;
+#endif
+	if(ioctl(fd, BLKGETSIZE, &l) >= 0)
+		return l*512;
+	if(ioctl(fd, HDIO_GETGEO, &geo) >= 0)
+		return (vlong)geo.heads*geo.sectors*geo.cylinders*512;
+	return 0;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Linux/emu	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,105 @@
+dev
+	root
+	cons
+	env
+	mnt
+	pipe
+	prog
+	prof
+	srv
+	dup
+	ssl
+	cap
+	fs
+	cmd	cmd
+	indir
+
+	draw	win-x11a
+	pointer
+	snarf
+
+	ip	ipif6-posix ipaux
+	eia
+#	audio	audio-oss
+	mem
+
+lib
+	interp
+	tk
+	freetype
+	math
+	draw
+
+	memlayer
+	memdraw
+	keyring
+	sec
+	mp
+
+	9
+
+link
+
+mod
+	sys
+	draw
+
+	tk
+	math
+	srv	srv
+	keyring
+	crypt
+	ipints
+	loader
+	freetype
+
+port
+	alloc
+	cache
+	chan
+	dev
+	devtab
+
+	dial
+	dis
+	discall
+	env
+	error
+	errstr
+	exception
+	exportfs
+	inferno
+	latin1
+	main
+	parse
+	pgrp
+	print
+	proc
+	qio
+	random
+	sysfile
+	uqid
+
+code
+
+init
+	emuinit
+
+root
+	/dev	/
+	/fd	/
+	/prog	/
+	/prof	/
+	/net	/
+	/net.alt	/
+	/chan	/
+	/nvfs	/
+	/env	/
+#	/dis
+#	/n
+#	/icons
+#	/osinit.dis
+#	/dis/emuinit.dis
+#	/dis/lib/auth.dis
+#	/dis/lib/ssl.dis
+#	/n/local /
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Linux/emu-g	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,101 @@
+env
+	X11LIBS=
+dev
+	root
+	cons
+	env
+	mnt
+	pipe
+	prog
+	prof
+	srv
+	dup
+	ssl
+	cap
+	fs
+	cmd	cmd
+	indir
+
+	ip	ipif6-posix ipaux
+	eia
+#	audio	audio
+	mem
+
+lib
+	interp
+	math
+	keyring
+	sec
+	mp
+
+	9
+
+link
+
+mod
+	sys
+	math
+	srv	srv
+	keyring
+	crypt
+	ipints
+	loader
+
+port
+	alloc
+	cache
+	chan
+	dev
+	devtab
+
+	dial
+	dis
+	discall
+	env
+	error
+	errstr
+	exception
+	exportfs
+	inferno
+	latin1
+	main
+	parse
+	pgrp
+	print
+	proc
+	qio
+	random
+	sysfile
+	uqid
+
+code
+	void setpointer(int x, int y){USED(x); USED(y);}
+	ulong strtochan(char *s){USED(s); return ~0;}
+
+init
+	emuinit
+
+root
+	/dev	/
+	/fd	/
+	/prog	/
+	/prof	/
+	/net	/
+	/net.alt	/
+	/chan	/
+	/nvfs	/
+	/env	/
+#	/chan
+#	/dev
+#	/dis
+#	/env
+#	/n
+#	/net
+#	/nvfs /
+#	/prog
+#	/icons
+#	/osinit.dis
+#	/dis/emuinit.dis
+#	/dis/lib/auth.dis
+#	/dis/lib/ssl.dis
+#	/n/local /
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Linux/emu-wrt	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,111 @@
+dev
+	root
+	cons
+	env
+	mnt
+	pipe
+	prog
+	prof
+	srv
+	dup
+	ssl
+	cap
+	fs
+	cmd	cmd
+	indir
+
+#	draw
+#	pointer
+#	snarf
+
+	ip	ipif6-posix ipaux
+#	eia
+#	audio	audio
+	mem
+
+lib
+	interp
+#	tk
+#	freetype
+	math
+#	draw
+
+#	memlayer
+#	memdraw
+	keyring
+	sec
+	mp
+
+	9
+
+link
+
+mod
+	sys
+#	draw
+
+#	tk
+	math
+	srv	srv
+	keyring
+	crypt
+	ipints
+	loader
+#	freetype
+
+port
+	alloc
+	cache
+	chan
+	dev
+	dial
+	dis
+	discall
+	env
+	error
+	errstr
+	exception
+	exportfs
+	inferno
+	latin1
+	main
+	parse
+	pgrp
+	print
+	proc
+	qio
+	random
+	sysfile
+	uqid
+
+code
+	int dontcompile = 1;
+	unsigned long strtochan(char *s) {return 0;}
+
+init
+	emuinit
+
+root
+	/dev	/
+	/fd	/
+	/prog	/
+	/prof	/
+	/net	/
+	/net.alt	/
+	/chan	/
+	/nvfs	/
+	/env	/
+#	/chan
+#	/dev
+#	/dis
+#	/env
+#	/n
+#	/net
+#	/nvfs /
+#	/prog
+#	/icons
+#	/osinit.dis
+#	/dis/emuinit.dis
+#	/dis/lib/auth.dis
+#	/dis/lib/ssl.dis
+#	/n/local /
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Linux/mk-wrt	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+OPENWRT=$HOME/OpenWrt-SDK-Linux-i686-1
+INFERNO=/usr/inferno
+
+PATH=$OPENWRT/staging_dir_mipsel/bin:$INFERNO/Linux/386/bin:$PATH
+
+mk OBJTYPE=spim CONF=emu-wrt CONFLIST=emu-wrt SYSLIBS=-lm WIN= $*
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Linux/mkfile	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,53 @@
+SYSTARG=Linux
+<../../mkconfig
+SYSTARG=Linux
+
+#Configurable parameters
+
+CONF=emu			#default configuration
+CONFLIST=emu
+CLEANCONFLIST=
+
+INSTALLDIR=$ROOT/$SYSTARG/$OBJTYPE/bin	#path of directory where kernel is installed
+
+#end configurable parameters
+
+X11LIBS= -lX11 -lXext	# can remove or override using env section in config files
+
+<$ROOT/mkfiles/mkfile-$SYSTARG-$OBJTYPE	#set vars based on target system
+
+<| $SHELLNAME ../port/mkdevlist $CONF	#sets $IP, $DEVS, $PORT, $LIBS
+<mkfile-$OBJTYPE	# sets $ARCHFILES
+
+OBJ=\
+	asm-$OBJTYPE.$O\
+	$ARCHFILES\
+	os.$O\
+	kproc-pthreads.$O\
+	segflush-$OBJTYPE.$O\
+	$CONF.root.$O\
+	lock.$O\
+	$DEVS\
+	$PORT\
+
+LIBNAMES=${LIBS:%=lib%.a}
+#libs=${LIBS:%=$ROOT/$OBJDIR/lib/lib%.a}
+
+HFILES=\
+
+CFLAGS='-DROOT="'$ROOT'"' -DEMU -I. -I../port -I$ROOT/$SYSTARG/$OBJTYPE/include -I$ROOT/include -I$ROOT/libinterp $CTHREADFLAGS $CFLAGS $EMUOPTIONS
+SYSLIBS= $X11LIBS -lm -lpthread
+KERNDATE=`{$NDATE}
+
+default:V:	$O.$CONF
+
+$O.$CONF:	$OBJ $CONF.c $CONF.root.h $LIBNAMES
+	$CC $CFLAGS '-DKERNDATE='$KERNDATE $CONF.c
+	$LD $LDFLAGS -o $target $OBJ $CONF.$O $LIBFILES $SYSLIBS
+
+install:V: $O.$CONF
+	cp $O.$CONF $INSTALLDIR/$CONF
+
+<../port/portmkfile
+
+devfs.$O:	../port/devfs-posix.c
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Linux/mkfile-arm	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,2 @@
+ARCHFILES=\
+	arm-tas-v7.$O\
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Linux/os.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,304 @@
+#include	<sys/types.h>
+#include	<time.h>
+#include	<termios.h>
+#include	<signal.h>
+#include 	<pwd.h>
+#include	<sched.h>
+#include	<sys/resource.h>
+#include	<sys/wait.h>
+#include	<sys/time.h>
+
+#include	<stdint.h>
+
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+#include <semaphore.h>
+
+#include	<raise.h>
+
+/* glibc 2.3.3-NTPL messes up getpid() by trying to cache the result, so we'll do it ourselves */
+#include	<sys/syscall.h>
+#define	getpid()	syscall(SYS_getpid)
+
+enum
+{
+	DELETE	= 0x7f,
+	CTRLC	= 'C'-'@',
+	NSTACKSPERALLOC = 16,
+	X11STACK=	256*1024
+};
+char *hosttype = "Linux";
+
+typedef sem_t	Sem;
+
+extern int dflag;
+
+int	gidnobody = -1;
+int	uidnobody = -1;
+static struct 	termios tinit;
+
+static void
+sysfault(char *what, void *addr)
+{
+	char buf[64];
+
+	snprint(buf, sizeof(buf), "sys: %s%#p", what, addr);
+	disfault(nil, buf);
+}
+
+static void
+trapILL(int signo, siginfo_t *si, void *a)
+{
+	USED(signo);
+	USED(a);
+	sysfault("illegal instruction pc=", si->si_addr);
+}
+
+static int
+isnilref(siginfo_t *si)
+{
+	return si != 0 && (si->si_addr == (void*)~(uintptr_t)0 || (uintptr_t)si->si_addr < 512);
+}
+
+static void
+trapmemref(int signo, siginfo_t *si, void *a)
+{
+	USED(a);	/* ucontext_t*, could fetch pc in machine-dependent way */
+	if(isnilref(si))
+		disfault(nil, exNilref);
+	else if(signo == SIGBUS)
+		sysfault("bad address addr=", si->si_addr);	/* eg, misaligned */
+	else
+		sysfault("segmentation violation addr=", si->si_addr);
+}
+
+static void
+trapFPE(int signo, siginfo_t *si, void *a)
+{
+	char buf[64];
+
+	USED(signo);
+	USED(a);
+	snprint(buf, sizeof(buf), "sys: fp: exception status=%.4lux pc=%#p", getfsr(), si->si_addr);
+	disfault(nil, buf);
+}
+
+static void
+trapUSR1(int signo)
+{
+	int intwait;
+
+	USED(signo);
+
+	intwait = up->intwait;
+	up->intwait = 0;	/* clear it to let proc continue in osleave */
+
+	if(up->type != Interp)		/* Used to unblock pending I/O */
+		return;
+
+	if(intwait == 0)		/* Not posted so it's a sync error */
+		disfault(nil, Eintr);	/* Should never happen */
+}
+
+void
+oslongjmp(void *regs, osjmpbuf env, int val)
+{
+	USED(regs);
+	siglongjmp(env, val);
+}
+
+static void
+termset(void)
+{
+	struct termios t;
+
+	tcgetattr(0, &t);
+	tinit = t;
+	t.c_lflag &= ~(ICANON|ECHO|ISIG);
+	t.c_cc[VMIN] = 1;
+	t.c_cc[VTIME] = 0;
+	tcsetattr(0, TCSANOW, &t);
+}
+
+static void
+termrestore(void)
+{
+	tcsetattr(0, TCSANOW, &tinit);
+}
+
+void
+cleanexit(int x)
+{
+	USED(x);
+
+	if(up->intwait) {
+		up->intwait = 0;
+		return;
+	}
+
+	if(dflag == 0)
+		termrestore();
+
+	kill(0, SIGKILL);
+	exit(0);
+}
+
+void
+osreboot(char *file, char **argv)
+{
+	if(dflag == 0)
+		termrestore();
+	execvp(file, argv);
+	error("reboot failure");
+}
+
+void
+libinit(char *imod)
+{
+	struct sigaction act;
+	struct passwd *pw;
+	Proc *p;
+	char sys[64];
+
+	setsid();
+
+	gethostname(sys, sizeof(sys));
+	kstrdup(&ossysname, sys);
+	pw = getpwnam("nobody");
+	if(pw != nil) {
+		uidnobody = pw->pw_uid;
+		gidnobody = pw->pw_gid;
+	}
+
+	if(dflag == 0)
+		termset();
+
+	memset(&act, 0, sizeof(act));
+	act.sa_handler = trapUSR1;
+	sigaction(SIGUSR1, &act, nil);
+
+	act.sa_handler = SIG_IGN;
+	sigaction(SIGCHLD, &act, nil);
+
+	/*
+	 * For the correct functioning of devcmd in the
+	 * face of exiting slaves
+	 */
+	signal(SIGPIPE, SIG_IGN);
+	if(signal(SIGTERM, SIG_IGN) != SIG_IGN)
+		signal(SIGTERM, cleanexit);
+	if(signal(SIGINT, SIG_IGN) != SIG_IGN)
+		signal(SIGINT, cleanexit);
+
+	if(sflag == 0) {
+		act.sa_flags = SA_SIGINFO;
+		act.sa_sigaction = trapILL;
+		sigaction(SIGILL, &act, nil);
+		act.sa_sigaction = trapFPE;
+		sigaction(SIGFPE, &act, nil);
+		act.sa_sigaction = trapmemref;
+		sigaction(SIGBUS, &act, nil);
+		sigaction(SIGSEGV, &act, nil);
+		act.sa_flags &= ~SA_SIGINFO;
+	}
+
+	p = newproc();
+	kprocinit(p);
+
+	pw = getpwuid(getuid());
+	if(pw != nil)
+		kstrdup(&eve, pw->pw_name);
+	else
+		print("cannot getpwuid\n");
+
+	p->env->uid = getuid();
+	p->env->gid = getgid();
+
+	emuinit(imod);
+}
+
+int
+readkbd(void)
+{
+	int n;
+	char buf[1];
+
+	n = read(0, buf, sizeof(buf));
+	if(n < 0)
+		print("keyboard close (n=%d, %s)\n", n, strerror(errno));
+	if(n <= 0)
+		pexit("keyboard thread", 0);
+
+	switch(buf[0]) {
+	case '\r':
+		buf[0] = '\n';
+		break;
+	case DELETE:
+		buf[0] = 'H' - '@';
+		break;
+	case CTRLC:
+		cleanexit(0);
+		break;
+	}
+	return buf[0];
+}
+
+/*
+ * Return an abitrary millisecond clock time
+ */
+long
+osmillisec(void)
+{
+	static long sec0 = 0, usec0;
+	struct timeval t;
+
+	if(gettimeofday(&t,(struct timezone*)0)<0)
+		return 0;
+
+	if(sec0 == 0) {
+		sec0 = t.tv_sec;
+		usec0 = t.tv_usec;
+	}
+	return (t.tv_sec-sec0)*1000+(t.tv_usec-usec0+500)/1000;
+}
+
+/*
+ * Return the time since the epoch in nanoseconds and microseconds
+ * The epoch is defined at 1 Jan 1970
+ */
+vlong
+osnsec(void)
+{
+	struct timeval t;
+
+	gettimeofday(&t, nil);
+	return (vlong)t.tv_sec*1000000000L + t.tv_usec*1000;
+}
+
+vlong
+osusectime(void)
+{
+	struct timeval t;
+ 
+	gettimeofday(&t, nil);
+	return (vlong)t.tv_sec * 1000000 + t.tv_usec;
+}
+
+int
+osmillisleep(ulong milsec)
+{
+	struct  timespec time;
+
+	time.tv_sec = milsec/1000;
+	time.tv_nsec= (milsec%1000)*1000000;
+	nanosleep(&time, NULL);
+	return 0;
+}
+
+int
+limbosleep(ulong milsec)
+{
+	return osmillisleep(milsec);
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Linux/segflush-386.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,11 @@
+#include <sys/types.h>
+#include <sys/syscall.h>
+
+#include "dat.h"
+
+int
+segflush(void *a, ulong n)
+{
+	USED(a); USED(n);
+	return 0;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Linux/segflush-arm.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,14 @@
+#include <sys/types.h>
+#include <sys/syscall.h>
+
+#include "dat.h"
+
+#define	SYS_cacheflush	__ARM_NR_cacheflush
+
+int
+segflush(void *a, ulong n)
+{
+	if(n)
+		syscall(SYS_cacheflush, a, (char*)a+n-1, 1);
+	return 0;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Linux/segflush-mips.S	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,16 @@
+#include "syscall.h"
+#include <sys/asm.h>
+#include <sys/regdef.h>
+#include <asm/cachectl.h>
+
+/*
+ * int segflush(void *p, ulong len)
+ */
+
+LEAF(segflush)
+	li	a2,BCACHE
+	li	v0,SYS_cacheflush
+	syscall
+	li	v0,0
+	j	$31
+	END(segflush)
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Linux/segflush-power.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,34 @@
+#include <sys/types.h>
+#include <sys/syscall.h>
+
+#include "dat.h"
+
+
+/*
+ * from geoff collyer's port
+ * invalidate instruction cache and write back data cache from a to a+n-1,
+ * at least.
+ */
+int
+segflush(void *a, ulong n)
+{
+    ulong *p;
+
+    // cache blocks are often eight words (32 bytes) long, sometimes 16 bytes.
+    // need to determine it dynamically?
+    for (p = (ulong *)((ulong)a & ~7UL); (char *)p < (char *)a + n; p++)
+        __asm__("dcbst	0,%0\n\t"	// not dcbf, which writes back, then invalidates
+            "icbi	0,%0\n\t"
+            : // no output
+            : "ar" (p)
+            );
+     __asm__("sync\n\t"
+            : // no output
+            :
+            );
+   __asm__("isync\n\t"
+            : // no output
+            :
+            );
+	return 0;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Linux/segflush-spim.S	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,16 @@
+#include "syscall.h"
+#include <sys/asm.h>
+#include <sys/regdef.h>
+#include <asm/cachectl.h>
+
+/*
+ * int segflush(void *p, ulong len)
+ */
+
+LEAF(segflush)
+	li	a2,BCACHE
+	li	v0,SYS_cacheflush
+	syscall
+	li	v0,0
+	j	$31
+	END(segflush)
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/MacOSX/NOTE	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,4 @@
+-	still interp mode only, jit not complete
+-	mkfile uses Carbon only; mkfile-x11 uses X11R6 from Apple
+-	both powerpc and x86 versions compiled from this source
+-	mkfile-g doesn't include graphics (uses emu-g not emu as configuration)
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/MacOSX/NOTICE	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,3 @@
+MacOSX port Copyright © 2001-2003 Corpus Callosum Corporation,
+with portions Copyright © 2001-2003 Geoff Collyer
+MacOSX-x86 Copyright © 2006 Corpus Callosum Corporation
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/MacOSX/asm-386.s	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,28 @@
+/*
+ * these are the same as on the PC (eg, Linux)
+*/
+
+	.globl	_FPsave
+_FPsave:
+	pushl	%ebp
+	movl	%esp, %ebp
+	movl	8(%ebp), %eax
+	fstenv	(%eax)
+	popl	%ebp
+	ret
+
+	.globl	_FPrestore
+_FPrestore:
+	pushl	%ebp
+	movl	%esp, %ebp
+	movl	8(%ebp), %eax
+	fldenv	(%eax)
+	popl	%ebp
+	ret
+
+	.globl	__tas
+__tas:
+	movl	$1, %eax
+	movl	4(%esp), %ecx
+	xchgl	%eax, 0(%ecx)
+	ret
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/MacOSX/asm-power.s	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,36 @@
+/*
+ * File: asm-power.s
+ *
+ * Copyright (c) 2003, Corpus Callosum Corporation.  All rights reserved.
+ */
+
+#include <architecture/ppc/asm_help.h>
+
+.text
+
+LEAF(_FPsave)
+	mffs	f0
+	stfd	f0,0(r3)
+	blr
+END(_FPsave)
+
+LEAF(_FPrestore)
+	lfd		f0,0(r3)
+	mtfsf 	0xff,f0
+	blr
+END(_FPrestore)
+
+LEAF(__tas)
+	sync
+	mr	r4,r3
+	addi    r5,0,0x1
+1:
+	lwarx	r3,0,r4
+	cmpwi   r3,0x0
+	bne-    2f
+	stwcx.	r5,0,r4
+	bne-    1b		/* Lost reservation, try again */
+2:
+	sync
+	blr
+END(__tas)
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/MacOSX/cmd.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,214 @@
+#include	<sys/types.h>
+#include	<signal.h>
+#include 	<pwd.h>
+#include	<sys/time.h>
+#include	<sys/resource.h>
+#include	<sys/wait.h>
+#include	<fcntl.h>
+
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+enum
+{
+	Debug = 0
+};
+
+/*
+ * os-specific devcmd support.
+ * this version should be reasonably portable across Unix systems.
+ */
+typedef struct Targ Targ;
+struct Targ
+{
+	int	fd[3];	/* fd[0] is standard input, fd[1] is standard output, fd[2] is standard error */
+	char**	args;
+	char*	dir;
+	int	pid;
+	int	wfd;	/* child writes errors that occur after the fork or on exec */
+	int	uid;
+	int	gid;
+};
+
+extern int gidnobody;
+extern int uidnobody;
+
+static int
+childproc(Targ *t)
+{
+	int i, nfd;
+
+	if(Debug)
+		print("devcmd: '%s'", t->args[0]);
+
+	nfd = getdtablesize();
+	for(i = 0; i < nfd; i++)
+		if(i != t->fd[0] && i != t->fd[1] && i != t->fd[2] && i != t->wfd)
+			close(i);
+
+	dup2(t->fd[0], 0);
+	dup2(t->fd[1], 1);
+	dup2(t->fd[2], 2);
+	close(t->fd[0]);
+	close(t->fd[1]);
+	close(t->fd[2]);
+
+	/* should have an auth file to do host-specific authorisation? */
+	if(t->gid != -1){
+		if(setgid(t->gid) < 0 && getegid() == 0){
+			fprint(t->wfd, "can't set gid %d: %s", t->gid, strerror(errno));
+			_exit(1);
+		}
+	}
+
+	if(t->uid != -1){
+		if(setuid(t->uid) < 0 && geteuid() == 0){
+			fprint(t->wfd, "can't set uid %d: %s", t->uid, strerror(errno));
+			_exit(1);
+		}
+	}
+
+	if(t->dir != nil && chdir(t->dir) < 0){
+		fprint(t->wfd, "can't chdir to %s: %s", t->dir, strerror(errno));
+		_exit(1);
+	}
+
+	signal(SIGPIPE, SIG_DFL);
+
+	execvp(t->args[0], t->args);
+	if(Debug)
+		print("execvp: %s\n",strerror(errno));
+	fprint(t->wfd, "exec failed: %s", strerror(errno));
+
+	_exit(1);
+}
+
+void*
+oscmd(char **args, int nice, char *dir, int *fd)
+{
+	Targ *t;
+	int r, fd0[2], fd1[2], fd2[2], wfd[2], n, pid;
+
+	t = mallocz(sizeof(*t), 1);
+	if(t == nil)
+		return nil;
+
+	fd0[0] = fd0[1] = -1;
+	fd1[0] = fd1[1] = -1;
+	fd2[0] = fd2[1] = -1;
+	wfd[0] = wfd[1] = -1;
+	if(pipe(fd0) < 0 || pipe(fd1) < 0 || pipe(fd2) < 0 || pipe(wfd) < 0)
+		goto Error;
+	if(fcntl(wfd[1], F_SETFD, FD_CLOEXEC) < 0)	/* close on exec to give end of file on success */
+		goto Error;
+
+	t->fd[0] = fd0[0];
+	t->fd[1] = fd1[1];
+	t->fd[2] = fd2[1];
+	t->wfd = wfd[1];
+	t->args = args;
+	t->dir = dir;
+	t->gid = up->env->gid;
+	if(t->gid == -1)
+		t->gid = gidnobody;
+	t->uid = up->env->uid;
+	if(t->uid == -1)
+		t->uid = uidnobody;
+
+	signal(SIGCHLD, SIG_DFL);
+	switch(pid = fork()) {
+	case -1:
+		goto Error;
+	case 0:
+		setpgid(0, getpid());
+		if(nice)
+			oslopri();
+		childproc(t);
+		_exit(1);
+	default:
+		t->pid = pid;
+		if(Debug)
+			print("cmd pid %d\n", t->pid);
+		break;
+	}
+
+	close(fd0[0]);
+	close(fd1[1]);
+	close(fd2[1]);
+	close(wfd[1]);
+
+	n = read(wfd[0], up->genbuf, sizeof(up->genbuf)-1);
+	close(wfd[0]);
+	if(n > 0){
+		close(fd0[1]);
+		close(fd1[0]);
+		close(fd2[0]);
+		free(t);
+		up->genbuf[n] = 0;
+		if(Debug)
+			print("oscmd: bad exec: %q\n", up->genbuf);
+		error(up->genbuf);
+		return nil;
+	}
+
+	fd[0] = fd0[1];
+	fd[1] = fd1[0];
+	fd[2] = fd2[0];
+	return t;
+
+Error:
+	r = errno;
+	if(Debug)
+		print("oscmd: %q\n",strerror(r));
+	close(fd0[0]);
+	close(fd0[1]);
+	close(fd1[0]);
+	close(fd1[1]);
+	close(fd2[0]);
+	close(fd2[1]);
+	close(wfd[0]);
+	close(wfd[1]);
+	error(strerror(r));
+	return nil;
+}
+
+int
+oscmdkill(void *a)
+{
+	Targ *t = a;
+
+	if(Debug)
+		print("kill: %d\n", t->pid);
+	return kill(-t->pid, SIGTERM);
+}
+
+int
+oscmdwait(void *a, char *buf, int n)
+{
+	Targ *t = a;
+	int s;
+
+	if(waitpid(t->pid, &s, 0) == -1){
+		if(Debug)
+			print("wait error: %d [in %d] %q\n", t->pid, getpid(), strerror(errno));
+		return -1;
+	}
+	if(WIFEXITED(s)){
+		if(WEXITSTATUS(s) == 0)
+			return snprint(buf, n, "%d 0 0 0 ''", t->pid);
+		return snprint(buf, n, "%d 0 0 0 'exit: %d'", t->pid, WEXITSTATUS(s));
+	}
+	if(WIFSIGNALED(s)){
+		if(WTERMSIG(s) == SIGTERM || WTERMSIG(s) == SIGKILL)
+			return snprint(buf, n, "%d 0 0 0 killed", t->pid);
+		return snprint(buf, n, "%d 0 0 0 'signal: %d'", t->pid, WTERMSIG(s));
+	}
+	return snprint(buf, n, "%d 0 0 0 'odd status: 0x%x'", t->pid, s);
+}
+
+void
+oscmdfree(void *a)
+{
+	free(a);
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/MacOSX/deveia.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,123 @@
+/*
+ * Darwin serial port definitions, uses IOKit to build sysdev
+ * Loosely based on FreeBSD/deveia.c
+ * Copyright © 1998, 1999 Lucent Technologies Inc.  All rights reserved.
+ * Revisions Copyright © 1999, 2000 Vita Nuova Limited.  All rights reserved.
+ * Revisions Copyright © 2003 Corpus Callosum Corporation.  All rights reserved.
+*/
+
+#include <termios.h>
+#include <sys/param.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <mach/mach.h>
+
+#include <CoreFoundation/CFNumber.h>
+
+#include <IOKit/IOKitLib.h>
+#include <IOKit/IOCFPlugIn.h>
+#include <IOKit/usb/IOUSBLib.h>
+#include <IOKit/serial/IOSerialKeys.h>
+#include <IOKit/IOBSD.h>
+
+#include <sys/ioctl.h>
+#include <sys/ttycom.h>
+
+#undef nil
+
+#define B14400	14400
+#define B28800	28800
+#define B57600	57600
+#define B76800	76800
+#define B115200	115200
+#define B230400	230400
+
+extern int vflag;
+
+#define MAXDEV 16
+static char *sysdev[MAXDEV];
+
+static void _buildsysdev(void);
+#define	buildsysdev()	_buildsysdev()	/* for devfs-posix.c */
+
+#include "deveia-posix.c"
+#include "deveia-bsd.c"
+
+static struct tcdef_t bps[] = {
+    {0,             B0},
+    {50,            B50},
+    {75,            B75},
+    {110,           B110},
+    {134,           B134},
+    {150,           B150},
+    {200,           B200},
+    {300,           B300},
+    {600,           B600},
+    {1200,	B1200},
+    {1800,	B1800},
+    {2400,	B2400},
+    {4800,	B4800},
+    {9600,	B9600},
+    {19200,	B19200},
+    {38400,	B38400},
+    {57600,	B57600},
+    {76800,	B76800},
+    {115200,	B115200},
+    {230400,	B230400},
+    {0,		-1}
+};
+
+static void
+_buildsysdev(void)
+{
+	mach_port_t port;
+	CFMutableDictionaryRef classesToMatch;
+	io_iterator_t serialPortIterator;
+	io_object_t serialDevice;
+	CFMutableArrayRef paths;
+	CFTypeRef path;
+	char	eiapath[MAXPATHLEN];
+	CFIndex i, o, npath;
+
+	if(IOMasterPort(MACH_PORT_NULL, &port) != KERN_SUCCESS)
+		return;
+	classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue);
+	if(classesToMatch == NULL){
+		printf("IOServiceMatching returned a NULL dictionary.\n");
+		goto Failed;
+	}
+	CFDictionarySetValue(classesToMatch,
+		CFSTR(kIOSerialBSDTypeKey),
+		CFSTR(kIOSerialBSDAllTypes));
+
+	if(IOServiceGetMatchingServices(port, classesToMatch, &serialPortIterator) != KERN_SUCCESS)
+		goto Failed;
+
+	paths = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
+	while((serialDevice = IOIteratorNext(serialPortIterator)) != 0){
+		path = IORegistryEntryCreateCFProperty(serialDevice, CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0);
+		if(path != NULL)
+			CFArrayAppendValue(paths, path);
+		IOObjectRelease(serialDevice);
+	}
+
+	npath = CFArrayGetCount(paths);
+	o = 0;
+	for(i = 0; i < npath && i < nelem(sysdev); i++){
+		if(CFStringGetCString(CFArrayGetValueAtIndex(paths, i), eiapath, sizeof(eiapath), kCFStringEncodingUTF8)){
+			sysdev[o] = strdup(eiapath);
+			if(vflag > 1)
+				print("BSD path: '%s'\n", sysdev[o]);
+			o++;
+		}
+	}
+
+	CFRelease(paths);
+	IOObjectRelease(serialPortIterator);
+
+Failed:
+	mach_port_deallocate(mach_task_self(), port);
+}
+
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/MacOSX/devfs.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,7 @@
+#include "devfs-posix.c"
+
+static vlong
+osdisksize(int fd)
+{
+	return 0;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/MacOSX/emu	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,113 @@
+dev
+	root
+	cons
+	env
+	mnt
+	pipe
+	prog
+	prof
+	srv
+	dup
+	ssl
+	cap
+	fs
+	cmd	cmd
+	indir
+
+	draw	win
+	pointer
+	snarf
+
+	ip	ipif-posix ipaux
+	eia
+#	audio	audio
+	mem
+
+lib
+	interp
+	tk
+	freetype
+	math
+	draw
+
+	memlayer
+	memdraw
+	keyring
+	sec
+	mp
+
+	9
+
+link
+
+mod
+	sys
+	draw
+
+	tk
+	math
+	srv	srv
+	keyring
+	crypt
+	ipints
+	loader
+	freetype
+
+port
+	alloc
+	cache
+	chan
+	dev
+	devtab
+
+	dial
+	dis
+	discall
+	env
+	error
+	errstr
+	exception
+	exportfs
+	inferno
+	latin1
+	main
+	parse
+	pgrp
+	print
+	proc
+	qio
+	random
+	sysfile
+	uqid
+
+code
+	int	dontcompile = 1;
+	int macjit = 1;
+
+init
+	emuinit
+
+root
+	/dev	/
+	/fd	/
+	/prog	/
+	/prof	/
+	/net	/
+	/net.alt	/
+	/chan	/
+	/nvfs	/
+	/env	/
+#	/chan
+#	/dev
+#	/dis
+#	/env
+#	/n
+#	/net
+#	/nvfs /
+#	/prog
+#	/icons
+#	/osinit.dis
+#	/dis/emuinit.dis
+#	/dis/lib/auth.dis
+#	/dis/lib/ssl.dis
+#	/n/local /
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/MacOSX/emu-g	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,101 @@
+dev
+	root
+	cons
+	env
+	mnt
+	pipe
+	prog
+	prof
+	srv
+	dup
+	ssl
+	cap
+	fs
+	cmd	cmd
+	indir
+
+	ip	ipif-posix ipaux
+	eia
+#	audio	audio
+	mem
+
+lib
+	interp
+	math
+	keyring
+	sec
+	mp
+
+	9
+
+link
+
+mod
+	sys
+	math
+	srv	srv
+	keyring
+	crypt
+	ipints
+	loader
+
+port
+	alloc
+	cache
+	chan
+	dev
+	devtab
+
+	dial
+	dis
+	discall
+	env
+	error
+	errstr
+	exception
+	exportfs
+	inferno
+	latin1
+	main
+	parse
+	pgrp
+	print
+	proc
+	qio
+	random
+	sysfile
+	uqid
+
+code
+	int	dontcompile = 1;
+	int macjit = 1;
+	void setpointer(int x, int y){USED(x); USED(y);}
+	ulong strtochan(char *s){USED(s); return ~0;}
+
+init
+	emuinit
+
+root
+	/dev	/
+	/fd	/
+	/prog	/
+	/prof	/
+	/net	/
+	/net.alt	/
+	/chan	/
+	/nvfs	/
+	/env	/
+#	/chan
+#	/dev
+#	/dis
+#	/env
+#	/n
+#	/net
+#	/nvfs /
+#	/prog
+#	/icons
+#	/osinit.dis
+#	/dis/emuinit.dis
+#	/dis/lib/auth.dis
+#	/dis/lib/ssl.dis
+#	/n/local /
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/MacOSX/keycodes.h	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,189 @@
+/* These are the Macintosh key scancode constants -- from Inside Macintosh */
+#define QZ_ESCAPE		0x35
+#define QZ_F1			0x7A
+#define QZ_F2			0x78
+#define QZ_F3			0x63
+#define QZ_F4			0x76
+#define QZ_F5			0x60
+#define QZ_F6			0x61
+#define QZ_F7			0x62
+#define QZ_F8			0x64
+#define QZ_F9			0x65
+#define QZ_F10			0x6D
+#define QZ_F11			0x67
+#define QZ_F12			0x6F
+#define QZ_PRINT		0x69
+#define QZ_SCROLLOCK    	0x6B
+#define QZ_PAUSE		0x71
+#define QZ_POWER		0x7F
+#define QZ_BACKQUOTE		0x32
+#define QZ_1			0x12
+#define QZ_2			0x13
+#define QZ_3			0x14
+#define QZ_4			0x15
+#define QZ_5			0x17
+#define QZ_6			0x16
+#define QZ_7			0x1A
+#define QZ_8			0x1C
+#define QZ_9			0x19
+#define QZ_0			0x1D
+#define QZ_MINUS		0x1B
+#define QZ_EQUALS		0x18
+#define QZ_BACKSPACE		0x33
+#define QZ_INSERT		0x72
+#define QZ_HOME			0x73
+#define QZ_PAGEUP		0x74
+#define QZ_NUMLOCK		0x47
+#define QZ_KP_EQUALS		0x51
+#define QZ_KP_DIVIDE		0x4B
+#define QZ_KP_MULTIPLY		0x43
+#define QZ_TAB			0x30
+#define QZ_q			0x0C
+#define QZ_w			0x0D
+#define QZ_e			0x0E
+#define QZ_r			0x0F
+#define QZ_t			0x11
+#define QZ_y			0x10
+#define QZ_u			0x20
+#define QZ_i			0x22
+#define QZ_o			0x1F
+#define QZ_p			0x23
+#define QZ_LEFTBRACKET		0x21
+#define QZ_RIGHTBRACKET		0x1E
+#define QZ_BACKSLASH		0x2A
+#define QZ_DELETE		0x75
+#define QZ_END			0x77
+#define QZ_PAGEDOWN		0x79
+#define QZ_KP7			0x59
+#define QZ_KP8			0x5B
+#define QZ_KP9			0x5C
+#define QZ_KP_MINUS		0x4E
+#define QZ_CAPSLOCK		0x39
+#define QZ_a			0x00
+#define QZ_s			0x01
+#define QZ_d			0x02
+#define QZ_f			0x03
+#define QZ_g			0x05
+#define QZ_h			0x04
+#define QZ_j			0x26
+#define QZ_k			0x28
+#define QZ_l			0x25
+#define QZ_SEMICOLON		0x29
+#define QZ_QUOTE		0x27
+#define QZ_RETURN		0x24
+#define QZ_KP4			0x56
+#define QZ_KP5			0x57
+#define QZ_KP6			0x58
+#define QZ_KP_PLUS		0x45
+#define QZ_LSHIFT		0x38
+#define QZ_z			0x06
+#define QZ_x			0x07
+#define QZ_c			0x08
+#define QZ_v			0x09
+#define QZ_b			0x0B
+#define QZ_n			0x2D
+#define QZ_m			0x2E
+#define QZ_COMMA		0x2B
+#define QZ_PERIOD		0x2F
+#define QZ_SLASH		0x2C
+/* These are the same as the left versions - use left by default */
+#if 0
+#define QZ_RSHIFT		0x38
+#endif
+#define QZ_UP			0x7E
+#define QZ_KP1			0x53
+#define QZ_KP2			0x54
+#define QZ_KP3			0x55
+#define QZ_KP_ENTER		0x4C
+#define QZ_LCTRL		0x3B
+#define QZ_LALT			0x3A
+#define QZ_LMETA		0x37
+#define QZ_SPACE		0x31
+/* These are the same as the left versions - use left by default */
+#if 0
+#define QZ_RMETA		0x37
+#define QZ_RALT			0x3A
+#define QZ_RCTRL		0x3B
+#endif
+#define QZ_LEFT			0x7B
+#define QZ_DOWN			0x7D
+#define QZ_RIGHT		0x7C
+#define QZ_KP0			0x52
+#define QZ_KP_PERIOD		0x41
+
+/* Wierd, these keys are on my iBook under MacOS X */
+#define QZ_IBOOK_ENTER		0x34
+#define QZ_IBOOK_LEFT		0x3B
+#define QZ_IBOOK_RIGHT		0x3C
+#define QZ_IBOOK_DOWN		0x3D
+#define QZ_IBOOK_UP		0x3E
+#define KEY_ENTER 13
+#define KEY_TAB 9
+
+#define KEY_BASE 0x100
+
+/*  Function keys  */
+#define KEY_F (KEY_BASE+64)
+
+/* Control keys */
+#define KEY_CTRL (KEY_BASE)
+#define KEY_BACKSPACE (KEY_CTRL+0)
+#define KEY_DELETE (KEY_CTRL+1)
+#define KEY_INSERT (KEY_CTRL+2)
+#define KEY_HOME (KEY_CTRL+3)
+#define KEY_END (KEY_CTRL+4)
+#define KEY_PAGE_UP (KEY_CTRL+5)
+#define KEY_PAGE_DOWN (KEY_CTRL+6)
+#define KEY_ESC (KEY_CTRL+7)
+
+/* Control keys short name */
+#define KEY_BS KEY_BACKSPACE
+#define KEY_DEL KEY_DELETE
+#define KEY_INS KEY_INSERT
+#define KEY_PGUP KEY_PAGE_UP
+#define KEY_PGDOWN KEY_PAGE_DOWN
+#define KEY_PGDWN KEY_PAGE_DOWN
+
+/* Cursor movement */
+#define KEY_CRSR (KEY_BASE+16)
+#define KEY_RIGHT (KEY_CRSR+0)
+#define KEY_LEFT (KEY_CRSR+1)
+#define KEY_DOWN (KEY_CRSR+2)
+#define KEY_UP (KEY_CRSR+3)
+
+/* Multimedia keyboard/remote keys */
+#define KEY_MM_BASE (0x100+384)
+#define KEY_POWER (KEY_MM_BASE+0)
+#define KEY_MENU (KEY_MM_BASE+1)
+#define KEY_PLAY (KEY_MM_BASE+2)
+#define KEY_PAUSE (KEY_MM_BASE+3)
+#define KEY_PLAYPAUSE (KEY_MM_BASE+4)
+#define KEY_STOP (KEY_MM_BASE+5)
+#define KEY_FORWARD (KEY_MM_BASE+6)
+#define KEY_REWIND (KEY_MM_BASE+7)
+#define KEY_NEXT (KEY_MM_BASE+8)
+#define KEY_PREV (KEY_MM_BASE+9)
+#define KEY_VOLUME_UP (KEY_MM_BASE+10)
+#define KEY_VOLUME_DOWN (KEY_MM_BASE+11)
+#define KEY_MUTE (KEY_MM_BASE+12)
+
+/* Keypad keys */
+#define KEY_KEYPAD (KEY_BASE+32)
+#define KEY_KP0 (KEY_KEYPAD+0)
+#define KEY_KP1 (KEY_KEYPAD+1)
+#define KEY_KP2 (KEY_KEYPAD+2)
+#define KEY_KP3 (KEY_KEYPAD+3)
+#define KEY_KP4 (KEY_KEYPAD+4)
+#define KEY_KP5 (KEY_KEYPAD+5)
+#define KEY_KP6 (KEY_KEYPAD+6)
+#define KEY_KP7 (KEY_KEYPAD+7)
+#define KEY_KP8 (KEY_KEYPAD+8)
+#define KEY_KP9 (KEY_KEYPAD+9)
+#define KEY_KPDEC (KEY_KEYPAD+10)
+#define KEY_KPINS (KEY_KEYPAD+11)
+#define KEY_KPDEL (KEY_KEYPAD+12)
+#define KEY_KPENTER (KEY_KEYPAD+13)
+
+/* Special keys */
+#define KEY_INTERN (0x1000)
+#define KEY_CLOSE_WIN (KEY_INTERN+0)
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/MacOSX/mkfile	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,56 @@
+SYSTARG=MacOSX
+<../../mkconfig
+SYSTARG=MacOSX
+
+#Configurable parameters
+
+CONF=emu			#default configuration
+CONFLIST=emu
+CLEANCONFLIST=
+
+INSTALLDIR=$ROOT/$SYSTARG/$OBJTYPE/bin	#path of directory where kernel is installed
+
+#end configurable parameters
+
+<$ROOT/mkfiles/mkfile-$SYSTARG-$OBJTYPE	#set vars based on target system
+
+<| $SHELLNAME ../port/mkdevlist $CONF	#sets $IP, $DEVS, $PORT, $LIBS
+
+OBJ=\
+	asm-$OBJTYPE.$O\
+	os.$O\
+	$CONF.root.$O\
+	lock.$O\
+	$DEVS\
+	$PORT\
+
+HFILES=\
+
+CFLAGS='-DROOT="'$ROOT'"'\
+	'-DOBJTYPE="'$OBJTYPE'"'\
+	-DEMU -I. -I../port\
+	-I$ROOT/$SYSTARG/$OBJTYPE/include\
+	-I$ROOT/include -I$ROOT/libinterp\
+	$CTHREADFLAGS $CFLAGS $EMUOPTIONS\
+
+KERNDATE=`{$NDATE}
+
+SYSLIBS= \
+	-lm\
+	-framework Carbon -framework QuickTime\
+	-lpthread\
+	-framework CoreFoundation\
+	-framework IOKit\
+
+default:V:	$O.$CONF
+
+<../port/portmkfile
+
+$O.$CONF:	$OBJ $CONF.c $CONF.root.h $LIBFILES
+	$CC $CFLAGS '-DKERNDATE='$KERNDATE $CONF.c
+	$LD $LDFLAGS -o $target $OBJ $CONF.$O $LIBFILES $SYSLIBS
+
+install:V: $O.$CONF
+	cp $O.$CONF $INSTALLDIR/$CONF
+
+devfs.c:N:	../port/devfs-posix.c
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/MacOSX/mkfile-g	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,55 @@
+SYSTARG=MacOSX
+<../../mkconfig
+SYSTARG=MacOSX
+
+#Configurable parameters
+
+CONF=emu-g			#default configuration
+CONFLIST=emu
+CLEANCONFLIST=
+
+INSTALLDIR=$ROOT/$SYSTARG/$OBJTYPE/bin	#path of directory where kernel is installed
+
+#end configurable parameters
+
+<$ROOT/mkfiles/mkfile-$SYSTARG-$OBJTYPE	#set vars based on target system
+
+<| $SHELLNAME ../port/mkdevlist $CONF	#sets $IP, $DEVS, $PORT, $LIBS
+
+OBJ=\
+	asm-$OBJTYPE.$O\
+	os.$O\
+	$CONF.root.$O\
+	lock.$O\
+	$DEVS\
+	$PORT\
+
+HFILES=\
+
+CFLAGS='-DROOT="'$ROOT'"'\
+	'-DOBJTYPE="'$OBJTYPE'"'\
+	-DEMU -I. -I../port\
+	-I$ROOT/$SYSTARG/$OBJTYPE/include\
+	-I$ROOT/include -I$ROOT/libinterp\
+	$CTHREADFLAGS $CFLAGS $EMUOPTIONS\
+
+KERNDATE=`{$NDATE}
+
+SYSLIBS= \
+	-lm\
+	-lpthread\
+	-framework CoreFoundation\
+	-framework IOKit\
+
+default:V:	$O.$CONF
+
+<../port/portmkfile
+
+$O.$CONF:	$OBJ $CONF.c $CONF.root.h $LIBFILES
+	$CC $CFLAGS '-DKERNDATE='$KERNDATE $CONF.c
+	$LD $LDFLAGS -o $target $OBJ $CONF.$O $LIBFILES $SYSLIBS
+
+install:V: $O.$CONF
+	cp $O.$CONF $INSTALLDIR/$CONF
+
+devfs.c:N:	../port/devfs-posix.c
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/MacOSX/mkfile-x11	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,59 @@
+SYSTARG=MacOSX
+<../../mkconfig
+SYSTARG=MacOSX
+
+#Configurable parameters
+
+CONF=emu			#default configuration
+CONFLIST=emu
+CLEANCONFLIST=
+
+INSTALLDIR=$ROOT/$SYSTARG/$OBJTYPE/bin	#path of directory where kernel is installed
+
+#end configurable parameters
+
+<$ROOT/mkfiles/mkfile-$SYSTARG-$OBJTYPE	#set vars based on target system
+
+<| $SHELLNAME ../port/mkdevlist $CONF	#sets $IP, $DEVS, $PORT, $LIBS
+
+OBJ=\
+	asm-$OBJTYPE.$O\
+	os.$O\
+	win-x11a.$O\
+	$CONF.root.$O\
+	lock.$O\
+	$DEVS\
+	$PORT\
+
+HFILES=\
+
+CFLAGS='-DROOT="'$ROOT'"'\
+	'-DOBJTYPE="'$OBJTYPE'"'\
+	-DEMU -I. -I../port\
+	-I$ROOT/$SYSTARG/$OBJTYPE/include\
+	-I$ROOT/include -I$ROOT/libinterp\
+	$CTHREADFLAGS $CFLAGS $EMUOPTIONS\
+	-I/usr/X11R6/include
+
+KERNDATE=`{$NDATE}
+
+LDFLAGS=$LDFLAGS -L/usr/X11R6/lib
+
+SYSLIBS= \
+	-lm -lX11 -lXext\
+	-lpthread\
+	-framework CoreFoundation\
+	-framework IOKit\
+
+default:V:	$O.$CONF
+
+<../port/portmkfile
+
+$O.$CONF:	$OBJ $CONF.c $CONF.root.h $LIBFILES
+	$CC $CFLAGS '-DKERNDATE='$KERNDATE $CONF.c
+	$LD $LDFLAGS -o $target $OBJ $CONF.$O $LIBFILES $SYSLIBS
+
+install:V: $O.$CONF
+	cp $O.$CONF $INSTALLDIR/$CONF
+
+devfs.c:N:	../port/devfs-posix.c
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/MacOSX/os.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,574 @@
+/*
+ * Loosely based on FreeBSD/os.c and Solaris/os.c
+ * Copyright © 1998, 1999 Lucent Technologies Inc.  All rights reserved.
+ * Revisions Copyright © 1999, 2000 Vita Nuova Limited.  All rights reserved.
+ * Revisions Copyright © 2002, 2003 Corpus Callosum Corporation.  All rights reserved.
+ */
+
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+#include <raise.h>
+
+#undef _POSIX_C_SOURCE 
+#undef getwd
+
+#include	<unistd.h>
+#include        <pthread.h>
+#include	<time.h>
+#include	<termios.h>
+#include	<signal.h>
+#include	<pwd.h>
+#include	<sys/resource.h>
+#include	<sys/time.h>
+
+#include 	<sys/socket.h>
+#include	<sched.h>
+#include	<errno.h>
+#include        <sys/ucontext.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <mach/mach_init.h>
+#include <mach/task.h>
+#include <mach/vm_map.h>
+
+#if defined(__ppc__)
+#include <architecture/ppc/cframe.h>
+#endif
+
+enum
+{
+    DELETE = 0x7F
+};
+char *hosttype = "MacOSX";
+char *cputype = OBJTYPE;
+
+typedef struct Sem Sem;
+struct Sem {
+	pthread_cond_t	c;
+	pthread_mutex_t	m;
+	int	v;
+};
+
+static pthread_key_t  prdakey;
+
+extern int dflag;
+
+Proc*
+getup(void)
+{
+	return pthread_getspecific(prdakey);
+}
+
+void
+pexit(char *msg, int t)
+{
+	Osenv *e;
+	Proc *p;
+	Sem *sem;
+
+	USED(t);
+	USED(msg);
+
+	lock(&procs.l);
+	p = up;
+	if(p->prev)
+		p->prev->next = p->next;
+	else
+		procs.head = p->next;
+
+	if(p->next)
+		p->next->prev = p->prev;
+	else
+		procs.tail = p->prev;
+	unlock(&procs.l);
+
+	if(0)
+		print("pexit: %s: %s\n", p->text, msg);
+
+	e = p->env;
+	if(e != nil) {
+		closefgrp(e->fgrp);
+		closepgrp(e->pgrp);
+		closeegrp(e->egrp);
+		closesigs(e->sigs);
+		free(e->user);
+	}
+	free(p->prog);
+	sem = p->os;
+	if(sem != nil){
+		pthread_cond_destroy(&sem->c);
+		pthread_mutex_destroy(&sem->m);
+	}
+	free(p->os);
+	free(p);
+	pthread_exit(0);
+}
+
+
+
+static void
+sysfault(char *what, void *addr)
+{
+	char buf[64];
+
+	snprint(buf, sizeof(buf), "sys: %s%#p", what, addr);
+	disfault(nil, buf);
+}
+
+static void
+trapILL(int signo, siginfo_t *si, void *a)
+{
+	USED(signo);
+	USED(a);
+	sysfault("illegal instruction pc=", si->si_addr);
+}
+
+static int
+isnilref(siginfo_t *si)
+{
+	return si != 0 && (si->si_addr == (void*)~(uintptr_t)0 || (uintptr_t)si->si_addr < 512);
+}
+
+static void
+trapmemref(int signo, siginfo_t *si, void *a)
+{
+	USED(a);	/* ucontext_t*, could fetch pc in machine-dependent way */
+	if(isnilref(si))
+		disfault(nil, exNilref);
+	else if(signo == SIGBUS)
+		sysfault("bad address addr=", si->si_addr);	/* eg, misaligned */
+	else
+		sysfault("segmentation violation addr=", si->si_addr);
+}
+
+static void
+trapFPE(int signo, siginfo_t *si, void *a)
+{
+	char buf[64];
+
+	USED(signo);
+	USED(a);
+	snprint(buf, sizeof(buf), "sys: fp: exception status=%.4lux pc=%#p", getfsr(), si->si_addr);
+	disfault(nil, buf);
+}
+
+void
+trapUSR1(int signo)
+{
+    USED(signo);
+    
+    if(up->type != Interp)      /* Used to unblock pending I/O */
+        return;
+    if(up->intwait == 0)        /* Not posted so its a sync error */
+        disfault(nil, Eintr);	/* Should never happen */
+    
+    up->intwait = 0;		/* Clear it so the proc can continue */
+}
+
+/* from geoff collyer's port */
+void
+printILL(int sig, siginfo_t *si, void *v)
+{
+	USED(sig);
+	USED(v);
+	panic("illegal instruction with code=%d at address=%p, opcode=%#x\n",
+		si->si_code, si->si_addr, *(uchar*)si->si_addr);
+}
+
+static void
+setsigs(void)
+{
+	struct sigaction act;
+
+	memset(&act, 0 , sizeof(act));
+
+	/*
+	  * For the correct functioning of devcmd in the
+	 * face of exiting slaves
+	 */
+	signal(SIGPIPE, SIG_IGN);
+	if(signal(SIGTERM, SIG_IGN) != SIG_IGN)
+		signal(SIGTERM, cleanexit);
+
+	act.sa_handler = trapUSR1;
+	sigaction(SIGUSR1, &act, nil);
+
+	if(sflag == 0) {
+		act.sa_flags = SA_SIGINFO;
+		act.sa_sigaction = trapILL;
+		sigaction(SIGILL, &act, nil);
+		act.sa_sigaction = trapFPE;
+		sigaction(SIGFPE, &act, nil);
+		act.sa_sigaction = trapmemref;
+		sigaction(SIGBUS, &act, nil);
+		sigaction(SIGSEGV, &act, nil);
+		if(signal(SIGINT, SIG_IGN) != SIG_IGN)
+			signal(SIGINT, cleanexit);
+	} else {
+		act.sa_sigaction = printILL;
+		act.sa_flags = SA_SIGINFO;
+		sigaction(SIGILL, &act, nil);
+	}
+}
+
+
+
+
+void *
+tramp(void *arg)
+{
+	Proc *p = arg;
+	p->sigid = (int)pthread_self();
+	if(pthread_setspecific(prdakey, arg)) {
+		print("set specific data failed in tramp\n");
+		pthread_exit(0);
+	}
+	p->func(p->arg);
+	pexit("{Tramp}", 0);
+	return NULL;
+}
+
+void
+kproc(char *name, void (*func)(void*), void *arg, int flags)
+{
+	pthread_t thread;
+	Proc *p;
+	Pgrp *pg;
+	Fgrp *fg;
+	Egrp *eg;
+	pthread_attr_t attr;
+	Sem *sem;
+
+	p = newproc();
+	if(p == nil)
+		panic("kproc: no memory");
+	sem = malloc(sizeof(*sem));
+	if(sem == nil)
+		panic("can't allocate semaphore");
+	pthread_cond_init(&sem->c, NULL);
+	pthread_mutex_init(&sem->m, NULL);
+	sem->v = 0;
+	p->os = sem;
+
+	if(flags & KPDUPPG) {
+		pg = up->env->pgrp;
+		incref(&pg->r);
+		p->env->pgrp = pg;
+	}
+	if(flags & KPDUPFDG) {
+		fg = up->env->fgrp;
+		incref(&fg->r);
+		p->env->fgrp = fg;
+	}
+	if(flags & KPDUPENVG) {
+		eg = up->env->egrp;
+		incref(&eg->r);
+		p->env->egrp = eg;
+	}
+
+	p->env->uid = up->env->uid;
+	p->env->gid = up->env->gid;
+	kstrdup(&p->env->user, up->env->user);
+
+	strcpy(p->text, name);
+
+	p->func = func;
+	p->arg = arg;
+
+	lock(&procs.l);
+	if(procs.tail != nil) {
+		p->prev = procs.tail;
+		procs.tail->next = p;
+	} else {
+		procs.head = p;
+		p->prev = nil;
+	}
+	procs.tail = p;
+	unlock(&procs.l);
+
+	if(pthread_attr_init(&attr) == -1)
+		panic("pthread_attr_init failed");
+
+	pthread_attr_setschedpolicy(&attr, SCHED_OTHER);
+	pthread_attr_setinheritsched(&attr, PTHREAD_INHERIT_SCHED);
+	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+
+	if(pthread_create(&thread, &attr, tramp, p))
+		panic("thr_create failed\n");
+	pthread_attr_destroy(&attr);
+}
+
+int
+segflush(void *va, ulong len)
+{
+	kern_return_t   err;
+	vm_machine_attribute_val_t value = MATTR_VAL_ICACHE_FLUSH;
+
+	err = vm_machine_attribute( (vm_map_t)mach_task_self(),
+		(vm_address_t)va,
+		(vm_size_t)len,
+		MATTR_CACHE,
+		&value);
+	if(err != KERN_SUCCESS)
+		print("segflush: failure (%d) address %lud\n", err, va);
+	return (int)err;
+}
+
+void
+oshostintr(Proc *p)
+{
+	pthread_kill((pthread_t)p->sigid, SIGUSR1);
+}
+
+void
+osblock(void)
+{
+	Sem *sem;
+
+	sem = up->os;
+	pthread_mutex_lock(&sem->m);
+	while(sem->v == 0)
+		pthread_cond_wait(&sem->c, &sem->m);
+	sem->v--;
+	pthread_mutex_unlock(&sem->m);
+}
+
+void
+osready(Proc *p)
+{
+	Sem *sem;
+
+	sem = p->os;
+	pthread_mutex_lock(&sem->m);
+	sem->v++;
+	pthread_cond_signal(&sem->c);
+	pthread_mutex_unlock(&sem->m);
+}
+
+void
+oslongjmp(void *regs, osjmpbuf env, int val)
+{
+	USED(regs);
+	siglongjmp(env, val);
+}
+
+struct termios tinit;
+
+static void
+termset(void)
+{
+	struct termios t;
+
+	tcgetattr(0, &t);
+	tinit = t;
+	t.c_lflag &= ~(ICANON | ECHO | ISIG);
+	t.c_cc[VMIN] = 1;
+	t.c_cc[VTIME] = 0;
+	tcsetattr(0, TCSANOW, &t);
+}
+
+static void
+termrestore(void)
+{
+	tcsetattr(0, TCSANOW, &tinit);
+}
+
+
+void
+cleanexit(int x)
+{
+	USED(x);
+
+	if(up->intwait) {
+		up->intwait = 0;
+		return;
+	}
+
+	if(dflag == 0)
+		termrestore();
+
+	exit(0);
+}
+
+
+
+void
+osreboot(char *file, char **argv)
+{
+	if(dflag == 0)
+		termrestore();
+	execvp(file, argv);
+	panic("reboot failure");
+}
+
+
+
+int gidnobody= -1, uidnobody= -1;
+
+void
+getnobody()
+{
+	struct passwd *pwd;
+
+	if((pwd = getpwnam("nobody"))) {
+		uidnobody = pwd->pw_uid;
+		gidnobody = pwd->pw_gid;
+	}
+}
+
+void
+libinit(char *imod)
+{
+	struct passwd *pw;
+	Proc *p;
+	char	sys[64];
+
+	setsid();
+
+	// setup personality
+	gethostname(sys, sizeof(sys));
+	kstrdup(&ossysname, sys);
+	getnobody();
+
+	if(dflag == 0)
+		termset();
+
+	setsigs();
+
+	if(pthread_key_create(&prdakey, NULL))
+		print("key_create failed\n");
+
+	p = newproc();
+	if(pthread_setspecific(prdakey, p))
+		panic("set specific thread data failed\n");
+
+	pw = getpwuid(getuid());
+	if(pw != nil)
+		kstrdup(&eve, pw->pw_name);
+	else
+		print("cannot getpwuid\n");
+
+	up->env->uid = getuid();
+	up->env->gid = getgid();
+
+	emuinit(imod);
+}
+
+
+
+int
+readkbd(void)
+{
+	int	n;
+	char	buf[1];
+
+	n = read(0, buf, sizeof(buf));
+	if(n < 0)
+		print("keyboard close (n=%d, %s)\n", n, strerror(errno));
+	if(n <= 0)
+		pexit("keyboard thread", 0);
+
+	switch(buf[0]) {
+	case '\r':
+		buf[0] = '\n';
+		break;
+	case DELETE:
+		cleanexit(0);
+		break;
+	}
+	return buf[0];
+}
+
+/*
+ * Return an abitrary millisecond clock time
+ */
+long
+osmillisec(void)
+{
+	static long	sec0 = 0, usec0;
+	struct timeval t;
+
+	if(gettimeofday(&t, NULL) < 0)
+		return(0);
+	if(sec0 == 0) {
+		sec0 = t.tv_sec;
+		usec0 = t.tv_usec;
+	}
+	return((t.tv_sec - sec0) * 1000 + (t.tv_usec - usec0 + 500) / 1000);
+}
+
+/*
+ * Return the time since the epoch in nanoseconds and microseconds
+ * The epoch is defined at 1 Jan 1970
+ */
+vlong
+osnsec(void)
+{
+	struct timeval t;
+
+	gettimeofday(&t, nil);
+	return (vlong)t.tv_sec*1000000000L + t.tv_usec*1000;
+}
+
+vlong
+osusectime(void)
+{
+	struct timeval t;
+
+	gettimeofday(&t, nil);
+	return (vlong)t.tv_sec * 1000000 + t.tv_usec;
+}
+int
+osmillisleep(ulong milsec)
+{
+	struct timespec time;
+	time.tv_sec = milsec / 1000;
+	time.tv_nsec = (milsec % 1000) * 1000000;
+	nanosleep(&time, nil);
+	return 0;
+}
+
+int
+limbosleep(ulong milsec)
+{
+	return osmillisleep(milsec);
+}
+
+void
+osyield(void)
+{
+	pthread_yield_np();
+}
+
+void
+ospause(void)
+{
+	for(;;)
+		pause();
+}
+
+void
+oslopri(void)
+{
+//	pthread_setschedparam(pthread_t thread,  int policy, const struct sched_param *param);
+	setpriority(PRIO_PROCESS, 0, getpriority(PRIO_PROCESS,0)+4);
+}
+
+__typeof__(sbrk(0))
+sbrk(int size)
+{
+	void *brk;
+	kern_return_t   err;
+    
+	err = vm_allocate( (vm_map_t) mach_task_self(),
+                       (vm_address_t *)&brk,
+                       size,
+                       VM_FLAGS_ANYWHERE);
+	if(err != KERN_SUCCESS)
+		brk = (void*)-1;
+	return brk;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/MacOSX/win.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,735 @@
+// in this file, _Rect is os x Rect,
+// _Point is os x Point
+#define Point _Point
+#define Rect _Rect
+
+#include <Carbon/Carbon.h>
+//#include <QuickTime/QuickTime.h> // for full screen
+
+#undef Rect
+#undef Point
+
+#undef nil
+
+#include "dat.h"
+#include "fns.h"
+#undef log2
+#include <draw.h>
+#include <memdraw.h>
+#include "cursor.h"
+#include "keyboard.h"
+#include "keycodes.h"
+
+#define	Kup	Up
+#define	Kleft	Left
+#define	Kdown	Down
+#define	Kright	Right
+#define	Kalt	LAlt
+#define	Kctl	LCtrl
+#define	Kshift	LShift
+#define	Kpgup	Pgup
+#define	Kpgdown	Pgdown
+#define	Khome	Home
+#define	Kins	Ins
+#define	Kend	End
+
+#define rWindowResource  128
+
+extern	void		flushmemscreen(Rectangle);
+
+Memimage	*gscreen;
+
+static int readybit;
+static Rendez	rend;
+static int triedscreen;
+
+///
+// menu
+//
+static MenuRef windMenu;
+static MenuRef viewMenu;
+
+enum {
+	kQuitCmd = 1,
+	kFullScreenCmd = 2,
+};
+
+static WindowGroupRef winGroup = NULL;
+static WindowRef theWindow = NULL;
+static CGContextRef context;
+static CGDataProviderRef dataProviderRef;
+static CGImageRef fullScreenImage;
+static CGRect devRect;
+static CGRect bounds;
+static PasteboardRef appleclip;
+static _Rect winRect;
+
+static Boolean altPressed = false;
+static Boolean button2 = false;
+static Boolean button3 = false;
+
+static Boolean needflush = false;
+
+
+static int
+isready(void*a)
+{
+	return readybit;
+}
+
+CGContextRef QuartzContext;
+
+static OSStatus MainWindowEventHandler(EventHandlerCallRef nextHandler, EventRef event, void *userData);
+static OSStatus MainWindowCommandHandler(EventHandlerCallRef nextHandler, EventRef event, void *userData);
+
+static void winproc(void *a);
+static void flushproc(void *a);
+
+void
+screeninit(void)
+{
+	int fmt;
+	int dx, dy;
+	ProcessSerialNumber psn = { 0, kCurrentProcess };
+	TransformProcessType(&psn, kProcessTransformToForegroundApplication);
+	SetFrontProcess(&psn);
+
+	fmt = XBGR32; //XRGB32;
+
+	devRect = CGDisplayBounds(CGMainDisplayID());
+//	devRect.origin.x = 0;
+//	devRect.origin.y = 0;
+//	devRect.size.width = 1024;
+//	devRect.size.height = 768;
+	dx = devRect.size.width;
+	dy = devRect.size.height;
+
+	if(1){	/* TO DO: new dev draw for changing screen size */
+		dx = Xsize;
+		dy = Ysize;
+	}
+
+	gscreen = allocmemimage(Rect(0,0,dx,dy), fmt);
+	dataProviderRef = CGDataProviderCreateWithData(0, gscreen->data->bdata,
+						dx * dy * 4, 0);
+	fullScreenImage = CGImageCreate(dx, dy, 8, 32, dx * 4,
+				CGColorSpaceCreateDeviceRGB(),
+				kCGImageAlphaNoneSkipLast,
+				dataProviderRef, 0, 0, kCGRenderingIntentDefault);
+
+	kproc("osxscreen", winproc, nil, 0);
+	kproc("osxflush", flushproc, nil, 0);
+	Sleep(&rend, isready, nil);
+}
+
+void
+window_resized(void)
+{
+	GetWindowBounds(theWindow, kWindowContentRgn, &winRect);
+
+	bounds = CGRectMake(0, 0, winRect.right-winRect.left, winRect.bottom - winRect.top);
+}
+
+static void
+flushproc(void *a)
+{
+	for(;;) {
+		if(needflush) {
+			drawqlock();
+			needflush = false;
+			QDBeginCGContext(GetWindowPort(theWindow), &context);
+			CGContextFlush(context);
+			QDEndCGContext(GetWindowPort(theWindow), &context);
+ 			drawqunlock();
+		}
+		usleep(33333);
+	}
+}
+
+static void
+winproc(void *a)
+{
+	MenuItemIndex index;
+	int dx, dy;
+
+	winRect.left = 30;
+	winRect.top = 60;
+	dx = devRect.size.width*0.75;	/* devRect is full screen; take only most of it */
+	dy = devRect.size.height*0.75;
+	if(1){	/* TO DO */
+		dx = Xsize;
+		dy = Ysize;
+	}
+	winRect.bottom = winRect.top + dy;
+	winRect.right = winRect.left + dx;
+
+	ClearMenuBar();
+	InitCursor();
+
+	CreateStandardWindowMenu(0, &windMenu);
+	InsertMenu(windMenu, 0);
+
+	CreateNewMenu(1004, 0, &viewMenu);
+	SetMenuTitleWithCFString(viewMenu, CFSTR("View"));
+	AppendMenuItemTextWithCFString(viewMenu, CFSTR("Full Screen"), 0,
+			kFullScreenCmd, &index);
+	SetMenuItemCommandKey(viewMenu, index, 0, 'F');
+	AppendMenuItemTextWithCFString(viewMenu, CFSTR("ctrl-opt to return"),
+			kMenuItemAttrDisabled,
+			kFullScreenCmd, &index);
+	InsertMenu(viewMenu, GetMenuID(windMenu));
+
+	DrawMenuBar();
+	uint32_t windowAttrs = 0
+				| kWindowCloseBoxAttribute
+				| kWindowCollapseBoxAttribute
+//				| kWindowResizableAttribute		// TO DO
+				| kWindowStandardHandlerAttribute
+//				| kWindowFullZoomAttribute		// TO DO
+		;
+
+	CreateNewWindow(kDocumentWindowClass, windowAttrs, &winRect, &theWindow);
+	CreateWindowGroup(0, &winGroup);
+	SetWindowGroup(theWindow, winGroup);
+
+	SetWindowTitleWithCFString(theWindow, CFSTR("Inferno"));
+
+	if(PasteboardCreate(kPasteboardClipboard, &appleclip) != noErr)
+		sysfatal("pasteboard create failed");
+
+	const EventTypeSpec commands[] = {
+		{ kEventClassWindow, kEventWindowClosed },
+		{ kEventClassWindow, kEventWindowBoundsChanged },
+		{ kEventClassCommand, kEventCommandProcess }
+	};
+	const EventTypeSpec events[] = {
+		{ kEventClassKeyboard, kEventRawKeyDown },
+		{ kEventClassKeyboard, kEventRawKeyModifiersChanged },
+		{ kEventClassKeyboard, kEventRawKeyRepeat },
+		{ kEventClassMouse, kEventMouseDown },
+		{ kEventClassMouse, kEventMouseUp },
+		{ kEventClassMouse, kEventMouseMoved },
+		{ kEventClassMouse, kEventMouseDragged },
+		{ kEventClassMouse, kEventMouseWheelMoved },
+	};
+
+ 	InstallApplicationEventHandler (
+ 								NewEventHandlerUPP (MainWindowEventHandler),
+								GetEventTypeCount(events),
+								events,
+								NULL,
+								NULL);
+	InstallWindowEventHandler (
+								theWindow,
+								NewEventHandlerUPP (MainWindowCommandHandler),
+								GetEventTypeCount(commands),
+								commands,
+								theWindow,
+								NULL);
+
+	ShowWindow(theWindow);
+	ShowMenuBar();
+	window_resized();
+	SelectWindow(theWindow);
+	// Run the event loop
+	readybit = 1;
+	Wakeup(&rend);
+	RunApplicationEventLoop();
+
+}
+
+static int
+convert_key(UInt32 key, UInt32 charcode)
+{
+	switch(key) {
+	case QZ_IBOOK_ENTER:
+	case QZ_RETURN: return '\n';
+	case QZ_ESCAPE: return 27;
+	case QZ_BACKSPACE: return '\b';
+	case QZ_LALT: return Kalt;
+	case QZ_LCTRL: return Kctl;
+	case QZ_LSHIFT: return Kshift;
+	case QZ_F1: return KF+1;
+	case QZ_F2: return KF+2;
+	case QZ_F3: return KF+3;
+	case QZ_F4: return KF+4;
+	case QZ_F5: return KF+5;
+	case QZ_F6: return KF+6;
+	case QZ_F7: return KF+7;
+	case QZ_F8: return KF+8;
+	case QZ_F9: return KF+9;
+	case QZ_F10: return KF+10;
+	case QZ_F11: return KF+11;
+	case QZ_F12: return KF+12;
+	case QZ_INSERT: return Kins;
+	case QZ_DELETE: return 0x7F;
+	case QZ_HOME: return Khome;
+	case QZ_END: return Kend;
+	case QZ_KP_PLUS: return '+';
+	case QZ_KP_MINUS: return '-';
+	case QZ_TAB: return '\t';
+	case QZ_PAGEUP: return Kpgup;
+	case QZ_PAGEDOWN: return Kpgdown;
+	case QZ_UP: return Kup;
+	case QZ_DOWN: return Kdown;
+	case QZ_LEFT: return Kleft;
+	case QZ_RIGHT: return Kright;
+	case QZ_KP_MULTIPLY: return '*';
+	case QZ_KP_DIVIDE: return '/';
+	case QZ_KP_ENTER: return '\n';
+	case QZ_KP_PERIOD: return '.';
+	case QZ_KP0: return '0';
+	case QZ_KP1: return '1';
+	case QZ_KP2: return '2';
+	case QZ_KP3: return '3';
+	case QZ_KP4: return '4';
+	case QZ_KP5: return '5';
+	case QZ_KP6: return '6';
+	case QZ_KP7: return '7';
+	case QZ_KP8: return '8';
+	case QZ_KP9: return '9';
+	default: return charcode;
+	}
+}
+
+void
+sendbuttons(int b, int x, int y)
+{
+	mousetrack(b, x, y, 0);
+}
+
+static Ptr fullScreenRestore;
+static int amFullScreen = 0;
+static WindowRef oldWindow = NULL;
+
+static void
+leave_full_screen(void)
+{
+	if(amFullScreen){
+		EndFullScreen(fullScreenRestore, 0);
+		theWindow = oldWindow;
+		ShowWindow(theWindow);
+		amFullScreen = 0;
+		window_resized();
+		Rectangle rect =  { { 0, 0 }, { bounds.size.width, bounds.size.height} };
+		drawqlock();
+ 		flushmemscreen(rect);
+ 		drawqunlock();
+	}
+}
+
+static void
+full_screen(void)
+{
+	if(!amFullScreen){
+		oldWindow = theWindow;
+		HideWindow(theWindow);
+		BeginFullScreen(&fullScreenRestore, 0, 0, 0, &theWindow, 0, 0);
+		amFullScreen = 1;
+		window_resized();
+		Rectangle rect =  { { 0, 0 },
+ 							{ bounds.size.width,
+ 							  bounds.size.height} };
+		drawqlock();
+ 		flushmemscreen(rect);
+ 		drawqunlock();
+	}
+}
+
+static OSStatus
+MainWindowEventHandler(EventHandlerCallRef nextHandler, EventRef event, void *userData)
+{
+	OSStatus result = noErr;
+	result = CallNextEventHandler(nextHandler, event);
+	UInt32 class = GetEventClass (event);
+	UInt32 kind = GetEventKind (event);
+	static uint32_t mousebuttons = 0; // bitmask of buttons currently down
+	static uint32_t mouseX = 0;
+	static uint32_t mouseY = 0;
+
+	if(class == kEventClassKeyboard) {
+		char macCharCodes;
+		UInt32 macKeyCode;
+		UInt32 macKeyModifiers;
+
+		GetEventParameter(event, kEventParamKeyMacCharCodes, typeChar,
+							NULL, sizeof(macCharCodes), NULL, &macCharCodes);
+		GetEventParameter(event, kEventParamKeyCode, typeUInt32, NULL,
+							sizeof(macKeyCode), NULL, &macKeyCode);
+		GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, NULL,
+							sizeof(macKeyModifiers), NULL, &macKeyModifiers);
+        switch(kind) {
+		case kEventRawKeyModifiersChanged:
+			if (macKeyModifiers == (controlKey | optionKey)) leave_full_screen();
+
+			switch(macKeyModifiers & (optionKey | cmdKey)) {
+			case (optionKey | cmdKey):
+				/* due to chording we need to handle the case when both
+				 * modifier keys are pressed at the same time.
+				 * currently it's only 2-3 snarf and the 3-2 noop
+				 */
+				altPressed = true;
+				if(mousebuttons & 1 || mousebuttons & 2 || mousebuttons & 4) {
+					mousebuttons |= 2;	/* set button 2 */
+					mousebuttons |= 4;	/* set button 3 */
+					button2 = true;
+					button3 = true;
+					sendbuttons(mousebuttons, mouseX, mouseY);
+				}
+				break;
+			case optionKey:
+				altPressed = true;
+				if(mousebuttons & 1 || mousebuttons & 4) {
+					mousebuttons |= 2;	/* set button 2 */
+					button2 = true;
+					sendbuttons(mousebuttons, mouseX, mouseY);
+				}
+				break;
+			case cmdKey:
+				if(mousebuttons & 1 || mousebuttons & 2) {
+					mousebuttons |= 4;	/* set button 3 */
+					button3 = true;
+					sendbuttons(mousebuttons, mouseX, mouseY);
+				}else
+					gkbdputc(gkbdq, Latin);
+				break;
+			case 0:
+			default:
+				if(button2 || button3) {
+					if(button2) {
+						mousebuttons &= ~2;	/* clear button 2 */
+						button2 = false;
+						altPressed = false;
+					}
+					if(button3) {
+						mousebuttons &= ~4;	/* clear button 3 */
+						button3 = false;
+					}
+					sendbuttons(mousebuttons, mouseX, mouseY);
+				}
+				if(altPressed) {
+					gkbdputc(gkbdq, Kalt);
+					altPressed = false;
+				}
+				break;
+			}
+			break;
+		case kEventRawKeyDown:
+		case kEventRawKeyRepeat:
+			if(macKeyModifiers != cmdKey) {
+				int key;
+				key = convert_key(macKeyCode, macCharCodes);
+				if(key != -1)
+					gkbdputc(gkbdq, key);
+			}else
+				result = eventNotHandledErr;
+			break;
+		default:
+			break;
+		}
+	}
+	else if(class == kEventClassMouse) {
+		_Point mousePos;
+
+		GetEventParameter(event, kEventParamMouseLocation, typeQDPoint,
+							0, sizeof mousePos, 0, &mousePos);
+		
+		switch(kind) {
+		case kEventMouseWheelMoved:
+		{
+		    int32_t wheeldelta;
+			GetEventParameter(event,kEventParamMouseWheelDelta,typeSInt32,
+								0,sizeof(wheeldelta), 0, &wheeldelta);
+			mouseX = mousePos.h - winRect.left;
+			mouseY = mousePos.v - winRect.top;
+			sendbuttons(wheeldelta>0 ? 8 : 16, mouseX, mouseY);
+			break;
+		}
+		case kEventMouseUp:
+		case kEventMouseDown:
+		{
+			uint32_t buttons;
+			uint32_t modifiers;
+			uint32_t clkcnt;
+
+			GetEventParameter(event, kEventParamKeyModifiers, typeUInt32,
+								0, sizeof(modifiers), 0, &modifiers);
+			GetEventParameter(event, kEventParamMouseChord, typeUInt32,
+								0, sizeof buttons, 0, &buttons);
+			GetEventParameter(event, kEventParamClickCount, typeUInt32,
+								0, sizeof(clkcnt), 0, &clkcnt);
+			
+			/* simulate other buttons via alt/apple key. like x11 */
+			if(modifiers & optionKey) {
+				mousebuttons = ((buttons & 1) ? 2 : 0);
+				altPressed = false;
+			} else if(modifiers & cmdKey)
+				mousebuttons = ((buttons & 1) ? 4 : 0);
+			else
+				mousebuttons = (buttons & 1);
+
+			mousebuttons |= ((buttons & 2)<<1);
+			mousebuttons |= ((buttons & 4)>>1);
+			if(clkcnt > 1)
+				mousebuttons |= 1<<8;
+
+		} /* Fallthrough */
+		case kEventMouseMoved:
+		case kEventMouseDragged:
+			mouseX = mousePos.h - winRect.left;
+			mouseY = mousePos.v - winRect.top;
+			sendbuttons(mousebuttons, mouseX, mouseY);
+			break;
+		default:
+			result = eventNotHandledErr;
+			break;
+		}
+	}
+	return result;
+}
+
+
+//default window command handler (from menus)
+static OSStatus
+MainWindowCommandHandler(EventHandlerCallRef nextHandler, EventRef event, void *userData)
+{
+	OSStatus result = noErr;
+	UInt32 class = GetEventClass (event);
+	UInt32 kind = GetEventKind (event);
+
+	result = CallNextEventHandler(nextHandler, event);
+
+	if(class == kEventClassCommand) {
+		HICommand theHICommand;
+		GetEventParameter(event, kEventParamDirectObject, typeHICommand,
+							NULL, sizeof(HICommand), NULL, &theHICommand);
+
+		switch(theHICommand.commandID) {
+		case kHICommandQuit:
+			cleanexit(0);
+			break;
+
+		case kFullScreenCmd:
+			full_screen();
+			break;
+
+		default:
+			result = eventNotHandledErr;
+			break;
+		}
+	} else if(class == kEventClassWindow) {
+		WindowRef     window;
+		_Rect          rectPort = {0,0,0,0};
+
+		GetEventParameter(event, kEventParamDirectObject, typeWindowRef,
+							NULL, sizeof(WindowRef), NULL, &window);
+
+		if(window)
+			GetPortBounds(GetWindowPort(window), &rectPort);
+
+		switch(kind) {
+		case kEventWindowClosed:
+			theWindow = NULL;
+			cleanexit(0); // only one window
+			break;
+
+		//resize window
+		case kEventWindowBoundsChanged:
+			window_resized();
+			Rectangle rect =  { { 0, 0 },
+ 									{ bounds.size.width,
+ 									  bounds.size.height} };
+			drawqlock();
+ 			flushmemscreen(rect);
+ 			drawqunlock();
+			break;
+
+		default:
+			result = eventNotHandledErr;
+			break;
+		}
+	}
+
+	return result;
+}
+
+void
+flushmemscreen(Rectangle r)
+{
+	CGRect rbounds;
+
+	// sanity check.  Trips from the initial "terminal"
+	if (r.max.x < r.min.x || r.max.y < r.min.y)
+		return;
+
+	rbounds.size.width = r.max.x - r.min.x;
+	rbounds.size.height = r.max.y - r.min.y;
+	rbounds.origin.x = r.min.x;
+	rbounds.origin.y = r.min.y;
+
+	if(rbounds.size.width <= 0 || rbounds.size.height <= 0)
+		return;
+		
+	QDBeginCGContext(GetWindowPort(theWindow), &context);
+	
+	// The sub-image is relative to our whole screen image.
+	CGImageRef subimg = CGImageCreateWithImageInRect(fullScreenImage, rbounds);
+	
+	// Drawing the sub-image is relative to the window.
+	rbounds.origin.y = winRect.bottom - winRect.top - r.min.y - rbounds.size.height;
+	CGContextDrawImage(context, rbounds, subimg);
+	CGImageRelease(subimg);
+	QDEndCGContext(GetWindowPort(theWindow), &context);
+
+	needflush = true;
+}
+
+uchar*
+attachscreen(Rectangle *r, ulong *chan, int *depth, int *width, int *softscreen)
+{	
+	if(!triedscreen) {
+		triedscreen = 1;
+		screeninit();	/* TO DO: call this elsewhere? */
+	}
+	*r = gscreen->r;
+	*chan = gscreen->chan;
+	*depth = gscreen->depth;
+	*width = gscreen->width;
+	*softscreen = 1;
+
+	return gscreen->data->bdata;
+}
+
+// PAL - no palette handling.  Don't intend to either.
+void
+getcolor(ulong i, ulong *r, ulong *g, ulong *b)
+{
+
+// PAL: Certainly wrong to return a grayscale.
+	 *r = i;
+	 *g = i;
+	 *b = i;
+}
+
+void
+setcolor(ulong index, ulong r, ulong g, ulong b)
+{
+	USED(index); USED(r); USED(g); USED(b);
+}
+
+enum{
+	SnarfSize=	100*1024
+};
+
+static char snarf[3*SnarfSize+1];
+static Rune rsnarf[SnarfSize+1];
+
+char*
+clipread(void)
+{
+	CFDataRef cfdata;
+	OSStatus err = noErr;
+	ItemCount nitems;
+	int i;
+	char *s;
+
+	if(appleclip == NULL)
+		return nil;
+	// Wow.  This is ridiculously complicated.
+	PasteboardSynchronize(appleclip);
+	if((err = PasteboardGetItemCount(appleclip, &nitems)) != noErr) {
+		fprint(2, "apple pasteboard GetItemCount failed - Error %d\n", err);
+		return 0;
+	}
+
+	// Yes, based at 1.  Silly API.
+	for(i = 1; i <= nitems; i++) {
+		PasteboardItemID itemID;
+		CFArrayRef flavorTypeArray;
+		CFIndex flavorCount;
+
+		if((err = PasteboardGetItemIdentifier(appleclip, i, &itemID)) != noErr){
+			fprint(2, "Can't get pasteboard item identifier: %d\n", err);
+			return 0;
+		}
+
+		if((err = PasteboardCopyItemFlavors(appleclip, itemID, &flavorTypeArray))!=noErr){
+			fprint(2, "Can't copy pasteboard item flavors: %d\n", err);
+			return 0;
+		}
+
+		flavorCount = CFArrayGetCount(flavorTypeArray);
+		CFIndex flavorIndex;
+		for(flavorIndex = 0; flavorIndex < flavorCount; ++flavorIndex){
+			CFStringRef flavorType;
+			flavorType = (CFStringRef)CFArrayGetValueAtIndex(flavorTypeArray, flavorIndex);
+			if (UTTypeConformsTo(flavorType, CFSTR("public.utf16-plain-text"))){
+				if((err = PasteboardCopyItemFlavorData(appleclip, itemID,
+					CFSTR("public.utf16-plain-text"), &cfdata)) != noErr){
+					fprint(2, "apple pasteboard CopyItem failed - Error %d\n", err);
+					return 0;
+				}
+				CFIndex length = CFDataGetLength(cfdata);
+				if (length > sizeof rsnarf) length = sizeof rsnarf;
+				CFDataGetBytes(cfdata, CFRangeMake(0, length), (uint8_t *)rsnarf);
+				snprint(snarf, sizeof snarf, "%.*S", length/sizeof(Rune), rsnarf);
+				for(s = snarf; *s; s++)
+					if(*s == '\r')
+						*s = '\n';
+				CFRelease(cfdata);
+				return strdup(snarf);
+			}
+		}
+	}
+	return 0;
+}
+
+int
+clipwrite(char *snarf)
+{
+	CFDataRef cfdata;
+	PasteboardSyncFlags flags;
+
+	if(appleclip == NULL)
+		return 0;
+	runeseprint(rsnarf, rsnarf+nelem(rsnarf), "%s", snarf);
+	if(PasteboardClear(appleclip) != noErr){
+		fprint(2, "apple pasteboard clear failed\n");
+		return 0;
+	}
+	flags = PasteboardSynchronize(appleclip);
+	if((flags&kPasteboardModified) || !(flags&kPasteboardClientIsOwner)){
+		fprint(2, "apple pasteboard cannot assert ownership\n");
+		return 0;
+	}
+	cfdata = CFDataCreate(kCFAllocatorDefault, (uchar*)rsnarf, runestrlen(rsnarf)*2);
+	if(cfdata == nil){
+		fprint(2, "apple pasteboard cfdatacreate failed\n");
+		return 0;
+	}
+	if(PasteboardPutItemFlavor(appleclip, (PasteboardItemID)1,
+		CFSTR("public.utf16-plain-text"), cfdata, 0) != noErr){
+		fprint(2, "apple pasteboard putitem failed\n");
+		CFRelease(cfdata);
+		return 0;
+	}
+	CFRelease(cfdata);
+	return 1;
+}
+
+void
+setpointer(int x, int y)
+{
+	CGPoint pnt;
+
+	pnt.x = x + winRect.left;
+	pnt.y = y + winRect.top;
+	CGWarpMouseCursorPosition(pnt);
+}
+
+void
+drawcursor(Drawcursor* c)
+{
+	USED(c);
+	/* removed, pending extensive change for newer MacOS X */
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/NOTICE	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,32 @@
+This copyright NOTICE applies to all files in this directory and
+subdirectories, unless another copyright notice appears in a given
+file or subdirectory.  If you take substantial code from this software to use in
+other programs, you must somehow include with it an appropriate
+copyright notice that includes the copyright notice and the other
+notices below.  It is fine (and often tidier) to do that in a separate
+file such as NOTICE, LICENCE or COPYING.
+
+	Copyright © 1994-1999 Lucent Technologies Inc.  All rights reserved.
+	Portions Copyright © 1997-1999 Vita Nuova Limited
+	Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
+	Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
+	Portions Copyright © 2005 Russ Cox, MIT
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/NetBSD/asm-386.S	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,101 @@
+	.file	"asm-NetBSD-386.S"
+#include <sys/syscall.h>
+
+/*
+ * executeonnewstack(void *tos, void (*tramp)(void *arg), void *arg)
+ */
+
+	.type	executeonnewstack,@function
+	.global	executeonnewstack
+executeonnewstack:
+	pushl	%ebp
+	movl	%esp, %ebp
+	pushl	%esi
+
+	movl	8(%ebp), %esi	/* get tos */
+	subl	$4, %esi
+	movl	16(%ebp), %eax
+	movl	%eax, (%esi)	/* stash arg on new stack */
+	subl	$4, %esi
+	movl	12(%ebp), %eax
+	movl	%eax, (%esi)	/* stash tramp on new stack */
+	mov	%esi, %esp	/* swap stacks pronto */
+	popl	%eax		/* recover the tramp address */
+	call	*%eax		/* and jump to it (ho ho) */
+
+	/* if we return here, tramp didn't do its job */
+
+	addl	$8, %esp	/* clean up for pose value */
+
+	leal	SYS_exit, %eax
+	int	$0x80
+
+/*
+ * unlockandexit(int *key)
+ *
+ * NB: the return status may be rubbish if the stack is reused
+ *	between the unlock and the system call, but this should
+ *	not matter since no task is waiting for the result
+ */
+
+	.type	unlockandexit,@function
+	.global	unlockandexit
+unlockandexit:
+	pushl	%ebp
+	movl	%esp, %ebp
+
+	movl	8(%ebp), %esi		/* get the key address */
+	pushl	$0			/* exit status 0 */
+	movl	$0, %eax		/* unlock the stack allocator */
+	movl	%eax, (%esi)
+	leal	SYS_exit, %eax		/* call exit */
+	int	$0x80
+
+/*
+ * umult(ulong m1, ulong m2, ulong *hi)
+ */
+
+	.type	umult,@function
+	.global	umult
+umult:
+	pushl	%ebp
+	movl	%esp, %ebp
+	pushl	%ebx
+
+	movl	8(%ebp), %eax
+	movl	12(%ebp), %ebx
+	mull	%ebx
+	movl	16(%ebp), %ebx
+	movl	%edx, (%ebx)
+
+	popl	%ebx
+	popl	%ebp
+	ret
+
+	.type	FPsave,@function
+	.global	FPsave
+FPsave:
+	pushl	%ebp
+	movl	%esp, %ebp
+	movl	8(%ebp), %eax
+	fstenv	(%eax)
+	popl	%ebp
+	ret
+
+	.type	FPrestore,@function
+	.global	FPrestore
+FPrestore:
+	pushl	%ebp
+	movl	%esp, %ebp
+	movl	8(%ebp), %eax
+	fldenv	(%eax)
+	popl	%ebp
+	ret
+
+	.type	_tas,@function
+	.globl	_tas
+_tas:
+	movl	$1, %eax
+	movl	4(%esp), %ecx
+	xchgl	%eax, 0(%ecx)
+	ret
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/NetBSD/audio.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,547 @@
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/filio.h>
+#include "audio.h"
+#include <sys/soundcard.h>
+
+#define 	Audio_Mic_Val		SOUND_MIXER_MIC
+#define 	Audio_Linein_Val	SOUND_MIXER_LINE
+
+#define	Audio_Speaker_Val	SOUND_MIXER_SPEAKER
+#define	Audio_Headphone_Val	SOUND_MIXER_PHONEOUT
+#define	Audio_Lineout_Val	SOUND_MIXER_VOLUME
+
+#define 	Audio_Pcm_Val		AFMT_S16_LE
+#define 	Audio_Ulaw_Val		AFMT_MU_LAW
+#define 	Audio_Alaw_Val		AFMT_A_LAW
+
+#include "audio-tbls.c"
+
+#define	min(a,b)	((a) < (b) ? (a) : (b))
+static int debug;
+
+#define AUDIO_FILE_STRING	"/dev/dsp"
+
+enum {
+	A_Pause,
+	A_UnPause
+};
+
+enum {
+	A_In,
+	A_Out
+};
+
+static QLock inlock;
+static QLock outlock;
+
+static	int	audio_file  = -1;	/* file in/out */
+static	int	audio_file_in  = -1;	/* copy of above when opened O_READ/O_RDWR */
+static	int	audio_file_out  = -1;	/* copy of above when opened O_WRITE/O_RDWR */
+
+static	int	audio_swap_flag = 0;	/* endian swap */
+
+static	int	audio_in_pause = A_UnPause;
+
+static Audio_t av;
+static int mixerleftvol[32];
+static int mixerrightvol[32];
+
+static int audio_enforce(Audio_t*);
+static int audio_open(void);
+static int audio_pause_in(int, int);
+static int audio_flush(int, int);
+static int audio_pause_out(int);
+static int audio_set_blocking(int);
+static int audio_set_info(int, Audio_d*, int);
+static void audio_swap_endian(char*, int);
+
+void
+audio_file_init(void)
+{
+	int i;
+	static ushort flag = 1;
+
+	audio_swap_flag = *((uchar*)&flag) == 0;	/* big-endian? */
+	audio_info_init(&av);
+	for (i = 0; i < 32; i++)
+		mixerleftvol[i] = mixerrightvol[i] = 100;
+}
+
+void
+audio_ctl_init(void)
+{
+}
+
+void
+audio_file_open(Chan *c, int omode)
+{
+	char ebuf[ERRMAX];
+
+	if (debug)
+		print("audio_file_open(0x%.8lux, %d)\n", c, omode);
+	switch(omode){
+	case OREAD:
+		qlock(&inlock);
+		if(waserror()){
+			qunlock(&inlock);
+			nexterror();
+		}
+
+		if(audio_file_in >= 0)
+			error(Einuse);
+		if (audio_file < 0)
+			audio_file = audio_open();
+		audio_file_in = audio_file;
+		poperror();
+		qunlock(&inlock);
+		break;
+	case OWRITE:
+		qlock(&outlock);
+		if(waserror()){
+			qunlock(&outlock);
+			nexterror();
+		}
+		if(audio_file_out >= 0)
+			error(Einuse);
+		if (audio_file < 0)
+			audio_file = audio_open();
+		audio_file_out = audio_file;
+		poperror();
+		qunlock(&outlock);
+		break;
+	case ORDWR:
+		qlock(&inlock);
+		qlock(&outlock);
+		if(waserror()){
+			qunlock(&outlock);
+			qunlock(&inlock);
+			nexterror();
+		}
+		if(audio_file_in >= 0 || audio_file_out >= 0)
+			error(Einuse);
+		if (audio_file < 0)
+			audio_file = audio_open();
+		audio_file_in = audio_file_out = audio_file;
+		poperror();
+		qunlock(&outlock);
+		qunlock(&inlock);
+		break;
+	}
+	if (debug)
+		print("audio_file_open: success\nin %d out %d both %d\n",
+			audio_file_out, audio_file_in, audio_file);
+}
+
+void
+audio_ctl_open(Chan *c, int omode)
+{
+	USED(c);
+	USED(omode);
+}
+
+void
+audio_file_close(Chan *c)
+{
+	switch(c->mode){
+	case OREAD:
+		qlock(&inlock);
+		qlock(&outlock);
+		if (audio_file_out < 0) {
+			close(audio_file);
+			audio_file = -1;
+		}
+		qunlock(&outlock);
+		audio_file_in = -1;
+		qunlock(&inlock);
+		break;
+	case OWRITE:
+		qlock(&inlock);
+		qlock(&outlock);
+		if (audio_file_in < 0) {
+			close(audio_file);
+			audio_file = -1;
+		}
+		audio_file_out = -1;
+		qunlock(&outlock);
+		qunlock(&inlock);
+		break;
+	case ORDWR:
+		qlock(&inlock);
+		qlock(&outlock);
+		close(audio_file);
+		audio_file_in = audio_file_out = audio_file = -1;
+		qunlock(&outlock);
+		qunlock(&inlock);
+		break;
+	}
+}
+
+void
+audio_ctl_close(Chan *c)
+{
+}
+
+long
+audio_file_read(Chan *c, void *va, long count, vlong offset)
+{
+	struct  timespec time;
+	long ba, status, chunk, total;
+	char *pva = (char *) va;
+
+	qlock(&inlock);
+	if(waserror()){
+		qunlock(&inlock);
+		nexterror();
+	}
+
+	if(audio_file_in < 0)
+		error(Eperm);
+
+	/* check block alignment */
+	ba = av.in.bits * av.in.chan / Bits_Per_Byte;
+
+	if(count % ba)
+		error(Ebadarg);
+
+	if(! audio_pause_in(audio_file_in, A_UnPause))
+		error(Eio);
+	
+	total = 0;
+	while(total < count) {
+		chunk = count - total;
+		osenter();
+		status = read(audio_file_in, pva + total, chunk);
+		osleave(); 
+		if(status < 0)
+			error(Eio);
+		total += status;
+	}
+
+	if(total != count)
+		error(Eio);
+
+	if(audio_swap_flag && av.out.bits == 16)
+		audio_swap_endian(pva, count); 
+
+	poperror();
+	qunlock(&inlock);
+
+	return count;
+}
+
+long
+audio_file_write(Chan *c, void *va, long count, vlong offset)
+{
+	struct  timespec time;
+	long status = -1;
+	long ba, total, chunk, bufsz;
+
+	if (debug > 1)
+		print("audio_file_write(0x%.8lux, 0x%.8lux, %ld, %uld)\n",
+			c, va, count, offset);
+
+	qlock(&outlock);
+	if(waserror()){
+		qunlock(&outlock);
+		nexterror();
+	}
+
+	if(audio_file_out < 0)
+		error(Eperm);
+
+	/* check block alignment */
+	ba = av.out.bits * av.out.chan / Bits_Per_Byte;
+
+	if(count % ba)
+		error(Ebadarg);
+
+	if(audio_swap_flag && av.out.bits == 16)
+		audio_swap_endian(va, count); 
+
+	total = 0;
+	bufsz = av.out.buf * Audio_Max_Buf / Audio_Max_Val;
+
+	if(bufsz == 0)
+		error(Ebadarg);
+
+	while(total < count) {
+		chunk = min(bufsz, count - total);
+		osenter();
+		status = write(audio_file_out, va, chunk);
+		osleave();
+		if(status <= 0)
+			error(Eio);
+		total += status;
+	}
+
+	poperror();
+	qunlock(&outlock);
+
+	return count;
+}
+
+static int
+audio_open(void)
+{
+	int fd;
+
+	/* open non-blocking in case someone already has it open */
+	/* otherwise we would block until they close! */
+	fd = open(AUDIO_FILE_STRING, O_RDWR|O_NONBLOCK);
+	if(fd < 0)
+		oserror();
+
+	/* change device to be blocking */
+	if(!audio_set_blocking(fd)) {
+		if (debug)
+			print("audio_open: failed to set blocking\n");
+		close(fd);
+		error("cannot set blocking mode");
+	}
+
+	if (debug)
+		print("audio_open: blocking set\n");
+
+	/* set audio info */
+	av.in.flags = ~0;
+	av.out.flags = 0;
+
+	if(! audio_set_info(fd, &av.in, A_In)) {
+		close(fd);
+		error(Ebadarg);
+	}
+
+	av.in.flags = 0;
+
+	/* tada, we're open, blocking, paused and flushed */
+	return fd;
+}
+
+long
+audio_ctl_write(Chan *c, void *va, long count, vlong offset)
+{
+	int	fd;
+	int	ff;
+	Audio_t tmpav = av;
+
+	tmpav.in.flags = 0;
+	tmpav.out.flags = 0;
+
+	if (!audioparse(va, count, &tmpav))
+		error(Ebadarg);
+
+	if (!audio_enforce(&tmpav))
+		error(Ebadarg);
+
+	qlock(&inlock);
+	if (waserror()) {
+		qunlock(&inlock);
+		nexterror();
+	}
+
+	if (audio_file_in >= 0 && (tmpav.in.flags & AUDIO_MOD_FLAG)) {
+		if (!audio_pause_in(audio_file_in, A_Pause))
+			error(Ebadarg);
+		if (!audio_flush(audio_file_in, A_In))
+			error(Ebadarg);
+		if (!audio_set_info(audio_file_in, &tmpav.in, A_In))
+			error(Ebadarg);
+	}
+	poperror();
+	qunlock(&inlock);
+
+	qlock(&outlock);
+	if (waserror()) {
+		qunlock(&outlock);
+		nexterror();
+	}
+	if (audio_file_out >= 0 && (tmpav.out.flags & AUDIO_MOD_FLAG)){
+		if (!audio_pause_out(audio_file_out))
+			error(Ebadarg);
+		if (!audio_set_info(audio_file_out, &tmpav.out, A_Out))
+			error(Ebadarg);
+	}
+	poperror();
+	qunlock(&outlock);
+
+	tmpav.in.flags = 0;
+	tmpav.out.flags = 0;
+
+	av = tmpav;
+
+	return count;
+}
+
+
+
+static int
+audio_set_blocking(int fd)
+{
+	int val;
+
+	if((val = fcntl(fd, F_GETFL, 0)) == -1)
+		return 0;
+	
+	val &= ~O_NONBLOCK;
+
+	if(fcntl(fd, F_SETFL, val) < 0)
+		return 0;
+
+	return 1;
+}
+
+static int
+doioctl(int fd, int ctl, int *info)
+{
+	int status;
+	osenter();
+	status = ioctl(fd, ctl, info);  /* qlock and load general stuff */
+	osleave();
+	if (status < 0)
+		print("doioctl(0x%.8lux, 0x%.8lux) failed %d\n", ctl, *info, errno);
+	return status;
+}
+
+static int
+choosefmt(Audio_d *i)
+{
+	int newbits, newenc;
+	
+	newbits = i->bits;
+	newenc = i->enc;
+	switch (newenc) {
+	case Audio_Alaw_Val:
+		if (newbits == 8)
+			return AFMT_A_LAW;
+		break;
+	case Audio_Ulaw_Val:
+		if (newbits == 8)
+			return AFMT_MU_LAW;
+		break;
+	case Audio_Pcm_Val:
+		if (newbits == 8)
+			return AFMT_U8;
+		else if (newbits == 16)
+			return AFMT_S16_LE;
+		break;
+	}
+	return -1;
+}
+
+static int
+audio_set_info(int fd, Audio_d *i, int d)
+{
+	int status;
+	int unequal_stereo = 0;
+
+	if(fd < 0)
+		return 0;
+
+	/* fmt */
+	if(i->flags & (AUDIO_BITS_FLAG || AUDIO_ENC_FLAG)) {
+		int oldfmt, newfmt;
+		oldfmt = AFMT_QUERY;
+		if (doioctl(fd, SNDCTL_DSP_SETFMT, &oldfmt) < 0)
+			return 0;
+		if (debug)
+			print("audio_set_info: current format 0x%.8lux\n", oldfmt);
+		newfmt = choosefmt(i);
+		if (debug)
+			print("audio_set_info: new format 0x%.8lux\n", newfmt);
+		if (newfmt == -1 || newfmt != oldfmt && doioctl(fd, SNDCTL_DSP_SETFMT, &newfmt) < 0)
+			return 0;
+	}
+
+	/* channels */
+	if(i->flags & AUDIO_CHAN_FLAG) {
+		int channels = i->chan;
+		if (debug)
+			print("audio_set_info: new channels %d\n", channels);
+		if (doioctl(fd, SNDCTL_DSP_CHANNELS, &channels) < 0
+			|| channels != i->chan)
+			return 0;
+	}
+
+	/* sample rate */
+	if(i->flags & AUDIO_RATE_FLAG) {
+		int speed = i->rate;
+		if (debug)
+			print("audio_set_info: new speed %d\n", speed);
+		if (doioctl(fd, SNDCTL_DSP_SPEED, &speed) < 0 || speed != i->rate)
+			return 0;
+	}
+
+	/* dev volume */
+	if(i->flags & (AUDIO_LEFT_FLAG | AUDIO_VOL_FLAG | AUDIO_RIGHT_FLAG)) {
+		int val;
+		if (i->flags & (AUDIO_LEFT_FLAG | AUDIO_VOL_FLAG))
+			mixerleftvol[i->dev] = (i->left * 100) / Audio_Max_Val;
+		if (i->flags & (AUDIO_RIGHT_FLAG | AUDIO_VOL_FLAG))
+			mixerrightvol[i->dev] = (i->right * 100) / Audio_Max_Val;
+		val = mixerleftvol[i->dev] | (mixerrightvol[i->dev] << 8);
+		doioctl(fd, MIXER_WRITE(i->dev), &val);
+	}
+
+	if (i->flags & AUDIO_DEV_FLAG) {
+	}
+	
+	return 1;
+}
+
+void 
+audio_swap_endian(char *p, int n)
+{
+	int b;
+
+	while (n > 1) {
+		b = p[0];
+		p[0] = p[1];
+		p[1] = b;
+		p += 2;
+		n -= 2;
+	}
+}
+
+static int
+audio_pause_out(int fd)
+{
+	USED(fd);
+	return 1;
+}
+
+static int
+audio_pause_in(int fd, int f)
+{
+	USED(fd);
+	USED(f);
+	return 1;
+}
+
+static int
+audio_flush(int fd, int d)
+{
+	int x;
+	return doioctl(fd, SNDCTL_DSP_SYNC, &x) >= 0;
+}
+
+static int
+audio_enforce(Audio_t *t)
+{
+	if((t->in.enc == Audio_Ulaw_Val || t->in.enc == Audio_Alaw_Val) && 
+		(t->in.rate != 8000 || t->in.chan != 1))
+		 return 0;
+	if((t->out.enc == Audio_Ulaw_Val || t->out.enc == Audio_Alaw_Val) && 
+		(t->out.rate != 8000 || t->out.chan != 1))
+		 return 0;
+	return 1;
+}
+
+Audio_t*
+getaudiodev(void)
+{
+	return &av;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/NetBSD/cmd.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,213 @@
+#include	<sys/types.h>
+#include	<signal.h>
+#include 	<pwd.h>
+#include	<sys/resource.h>
+#include	<sys/wait.h>
+#include	<fcntl.h>
+
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+enum
+{
+	Debug = 0
+};
+
+/*
+ * os-specific devcmd support.
+ * this version should be reasonably portable across Unix systems.
+ */
+typedef struct Targ Targ;
+struct Targ
+{
+	int	fd[3];	/* fd[0] is standard input, fd[1] is standard output, fd[2] is standard error */
+	char**	args;
+	char*	dir;
+	int	pid;
+	int	wfd;	/* child writes errors that occur after the fork or on exec */
+	int	uid;
+	int	gid;
+};
+
+extern int gidnobody;
+extern int uidnobody;
+
+static int
+childproc(Targ *t)
+{
+	int i, nfd;
+
+	if(Debug)
+		print("devcmd: '%s'", t->args[0]);
+
+	nfd = getdtablesize();
+	for(i = 0; i < nfd; i++)
+		if(i != t->fd[0] && i != t->fd[1] && i != t->fd[2] && i != t->wfd)
+			close(i);
+
+	dup2(t->fd[0], 0);
+	dup2(t->fd[1], 1);
+	dup2(t->fd[2], 2);
+	close(t->fd[0]);
+	close(t->fd[1]);
+	close(t->fd[2]);
+
+	/* should have an auth file to do host-specific authorisation? */
+	if(t->gid != -1){
+		if(setgid(t->gid) < 0 && getegid() == 0){
+			fprint(t->wfd, "can't set gid %d: %s", t->gid, strerror(errno));
+			_exit(1);
+		}
+	}
+
+	if(t->uid != -1){
+		if(setuid(t->uid) < 0 && geteuid() == 0){
+			fprint(t->wfd, "can't set uid %d: %s", t->uid, strerror(errno));
+			_exit(1);
+		}
+	}
+
+	if(t->dir != nil && chdir(t->dir) < 0){
+		fprint(t->wfd, "can't chdir to %s: %s", t->dir, strerror(errno));
+		_exit(1);
+	}
+
+	signal(SIGPIPE, SIG_DFL);
+
+	execvp(t->args[0], t->args);
+	if(Debug)
+		print("execvp: %s\n",strerror(errno));
+	fprint(t->wfd, "exec failed: %s", strerror(errno));
+
+	_exit(1);
+}
+
+void*
+oscmd(char **args, int nice, char *dir, int *fd)
+{
+	Targ *t;
+	int r, fd0[2], fd1[2], fd2[2], wfd[2], n, pid;
+
+	t = mallocz(sizeof(*t), 1);
+	if(t == nil)
+		return nil;
+
+	fd0[0] = fd0[1] = -1;
+	fd1[0] = fd1[1] = -1;
+	fd2[0] = fd2[1] = -1;
+	wfd[0] = wfd[1] = -1;
+	if(pipe(fd0) < 0 || pipe(fd1) < 0 || pipe(fd2) < 0 || pipe(wfd) < 0)
+		goto Error;
+	if(fcntl(wfd[1], F_SETFD, FD_CLOEXEC) < 0)	/* close on exec to give end of file on success */
+		goto Error;
+
+	t->fd[0] = fd0[0];
+	t->fd[1] = fd1[1];
+	t->fd[2] = fd2[1];
+	t->wfd = wfd[1];
+	t->args = args;
+	t->dir = dir;
+	t->gid = up->env->gid;
+	if(t->gid == -1)
+		t->gid = gidnobody;
+	t->uid = up->env->uid;
+	if(t->uid == -1)
+		t->uid = uidnobody;
+
+	signal(SIGCHLD, SIG_DFL);
+	switch(pid = fork()) {
+	case -1:
+		goto Error;
+	case 0:
+		setpgid(0, getpid());
+		if(nice)
+			oslopri();
+		childproc(t);
+		_exit(1);
+	default:
+		t->pid = pid;
+		if(Debug)
+			print("cmd pid %d\n", t->pid);
+		break;
+	}
+
+	close(fd0[0]);
+	close(fd1[1]);
+	close(fd2[1]);
+	close(wfd[1]);
+
+	n = read(wfd[0], up->genbuf, sizeof(up->genbuf)-1);
+	close(wfd[0]);
+	if(n > 0){
+		close(fd0[1]);
+		close(fd1[0]);
+		close(fd2[0]);
+		free(t);
+		up->genbuf[n] = 0;
+		if(Debug)
+			print("oscmd: bad exec: %q\n", up->genbuf);
+		error(up->genbuf);
+		return nil;
+	}
+
+	fd[0] = fd0[1];
+	fd[1] = fd1[0];
+	fd[2] = fd2[0];
+	return t;
+
+Error:
+	r = errno;
+	if(Debug)
+		print("oscmd: %q\n",strerror(r));
+	close(fd0[0]);
+	close(fd0[1]);
+	close(fd1[0]);
+	close(fd1[1]);
+	close(fd2[0]);
+	close(fd2[1]);
+	close(wfd[0]);
+	close(wfd[1]);
+	error(strerror(r));
+	return nil;
+}
+
+int
+oscmdkill(void *a)
+{
+	Targ *t = a;
+
+	if(Debug)
+		print("kill: %d\n", t->pid);
+	return kill(-t->pid, SIGTERM);
+}
+
+int
+oscmdwait(void *a, char *buf, int n)
+{
+	Targ *t = a;
+	int s;
+
+	if(waitpid(t->pid, &s, 0) == -1){
+		if(Debug)
+			print("wait error: %d [in %d] %q\n", t->pid, getpid(), strerror(errno));
+		return -1;
+	}
+	if(WIFEXITED(s)){
+		if(WEXITSTATUS(s) == 0)
+			return snprint(buf, n, "%d 0 0 0 ''", t->pid);
+		return snprint(buf, n, "%d 0 0 0 'exit: %d'", t->pid, WEXITSTATUS(s));
+	}
+	if(WIFSIGNALED(s)){
+		if(WTERMSIG(s) == SIGTERM || WTERMSIG(s) == SIGKILL)
+			return snprint(buf, n, "%d 0 0 0 killed", t->pid);
+		return snprint(buf, n, "%d 0 0 0 'signal: %d'", t->pid, WTERMSIG(s));
+	}
+	return snprint(buf, n, "%d 0 0 0 'odd status: 0x%x'", t->pid, s);
+}
+
+void
+oscmdfree(void *a)
+{
+	free(a);
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/NetBSD/deveia.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,39 @@
+/*
+ * NetBSD serial port definitions
+ */
+
+static char *sysdev[] = {
+        "/dev/dty00",
+        "/dev/dty01",
+        "/dev/dty02",
+        "/dev/dty03",
+};
+
+#include <sys/ioctl.h>
+#include "deveia-posix.c"
+#include "deveia-bsd.c"
+
+
+static struct tcdef_t bps[] = {
+	{0,		B0},
+	{50,		B50},
+	{75,		B75},
+	{110,		B110},
+	{134,		B134},
+	{150,		B150},
+	{200,		B200},
+	{300,		B300},
+	{600,		B600},
+	{1200,	B1200},
+	{1800,	B1800},
+	{2400,	B2400},
+	{4800,	B4800},
+	{9600,	B9600},
+	{19200,	B19200},
+	{38400,	B38400},
+	{57600,	B57600},
+	{115200,	B115200},
+	{230400,	B230400},
+	{-1,		-1}
+};
+
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/NetBSD/devfs.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,7 @@
+#include "devfs-posix.c"
+
+static vlong
+osdisksize(int fd)
+{
+	return 0;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/NetBSD/emu	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,111 @@
+dev
+	root
+	cons
+	env
+	mnt
+	pipe
+	prog
+	prof
+	srv
+	dup
+	ssl
+	cap
+	fs
+	cmd	cmd
+	indir
+
+	draw	win-x11a
+	pointer
+	snarf
+
+	ip	ipif-posix ipaux
+	eia
+	audio	audio
+	mem
+
+lib
+	interp
+	tk
+	freetype
+	math
+	draw
+
+	memlayer
+	memdraw
+	keyring
+	sec
+	mp
+
+	9
+
+link
+
+mod
+	sys
+	draw
+
+	tk
+	math
+	srv	srv
+	keyring
+	crypt
+	ipints
+	loader
+	freetype
+
+port
+	alloc
+	cache
+	chan
+	dev
+	devtab
+
+	dial
+	dis
+	discall
+	env
+	error
+	errstr
+	exception
+	exportfs
+	inferno
+	latin1
+	main
+	parse
+	pgrp
+	print
+	proc
+	qio
+	random
+	sysfile
+	uqid
+
+code
+
+init
+	emuinit
+
+root
+	/dev	/
+	/fd	/
+	/prog	/
+	/prof	/
+	/net	/
+	/net.alt	/
+	/chan	/
+	/nvfs	/
+	/env	/
+#	/chan
+#	/dev
+#	/dis
+#	/env
+#	/n
+#	/net
+#	/nvfs /
+#	/prog
+#	/icons
+#	/osinit.dis
+#	/dis/emuinit.dis
+#	/dis/lib/auth.dis
+#	/dis/lib/ssl.dis
+#	/n/local /
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/NetBSD/emu-g	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,102 @@
+env
+	X11LIBS=
+dev
+	root
+	cons
+	env
+	mnt
+	pipe
+	prog
+	prof
+	srv
+	dup
+	ssl
+	cap
+	fs
+	cmd	cmd
+	indir
+
+	ip	ipif-posix ipaux
+	eia
+	audio	audio
+	mem
+
+lib
+	interp
+	math
+
+	keyring
+	sec
+	mp
+
+	9
+
+link
+
+mod
+	sys
+	math
+	srv	srv
+	keyring
+	crypt
+	ipints
+	loader
+
+port
+	alloc
+	cache
+	chan
+	dev
+	devtab
+
+	dial
+	dis
+	discall
+	env
+	error
+	errstr
+	exception
+	exportfs
+	inferno
+	latin1
+	main
+	parse
+	pgrp
+	print
+	proc
+	qio
+	random
+	sysfile
+	uqid
+
+code
+	void setpointer(int x, int y){USED(x); USED(y);}
+	ulong strtochan(char *s){USED(s); return ~0;}
+
+init
+	emuinit
+
+root
+	/dev	/
+	/fd	/
+	/prog	/
+	/prof	/
+	/net	/
+	/net.alt	/
+	/chan	/
+	/nvfs	/
+	/env	/
+#	/chan
+#	/dev
+#	/dis
+#	/env
+#	/n
+#	/net
+#	/nvfs /
+#	/prog
+#	/icons
+#	/osinit.dis
+#	/dis/emuinit.dis
+#	/dis/lib/auth.dis
+#	/dis/lib/ssl.dis
+#	/n/local /
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/NetBSD/emu.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,88 @@
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+#include "interp.h"
+
+
+#include "emu.root.h"
+
+ulong ndevs = 29;
+
+extern Dev rootdevtab;
+extern Dev consdevtab;
+extern Dev envdevtab;
+extern Dev mntdevtab;
+extern Dev pipedevtab;
+extern Dev progdevtab;
+extern Dev profdevtab;
+extern Dev srvdevtab;
+extern Dev dupdevtab;
+extern Dev ssldevtab;
+extern Dev capdevtab;
+extern Dev fsdevtab;
+extern Dev cmddevtab;
+extern Dev indirdevtab;
+extern Dev drawdevtab;
+extern Dev pointerdevtab;
+extern Dev snarfdevtab;
+extern Dev ipdevtab;
+extern Dev eiadevtab;
+extern Dev audiodevtab;
+extern Dev memdevtab;
+Dev* devtab[]={
+	&rootdevtab,
+	&consdevtab,
+	&envdevtab,
+	&mntdevtab,
+	&pipedevtab,
+	&progdevtab,
+	&profdevtab,
+	&srvdevtab,
+	&dupdevtab,
+	&ssldevtab,
+	&capdevtab,
+	&fsdevtab,
+	&cmddevtab,
+	&indirdevtab,
+	&drawdevtab,
+	&pointerdevtab,
+	&snarfdevtab,
+	&ipdevtab,
+	&eiadevtab,
+	&audiodevtab,
+	&memdevtab,
+	nil,
+	nil,
+	nil,
+	nil,
+	nil,
+	nil,
+	nil,
+	nil,
+	nil,
+};
+
+void links(void){
+}
+
+extern void sysmodinit(void);
+extern void drawmodinit(void);
+extern void tkmodinit(void);
+extern void mathmodinit(void);
+extern void srvmodinit(void);
+extern void keyringmodinit(void);
+extern void loadermodinit(void);
+extern void freetypemodinit(void);
+void modinit(void){
+	sysmodinit();
+	drawmodinit();
+	tkmodinit();
+	mathmodinit();
+	srvmodinit();
+	keyringmodinit();
+	loadermodinit();
+	freetypemodinit();
+}
+
+char* conffile = "emu";
+ulong kerndate = KERNDATE;
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/NetBSD/mkfile	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,49 @@
+SYSTARG=NetBSD
+OBJTYPE=386
+<../../mkconfig
+SYSTARG=NetBSD
+OBJTYPE=386
+
+#Configurable parameters
+
+CONF=emu			#default configuration
+CONFLIST=emu
+CLEANCONFLIST=
+
+INSTALLDIR=$ROOT/$SYSTARG/$OBJTYPE/bin	#path of directory where kernel is installed
+
+#end configurable parameters
+
+# can remove or override X11LIBS using env section in config files
+X11LIBS= -L/usr/X11R7/lib -R/usr/X11R7/lib -lXext -lX11
+
+<$ROOT/mkfiles/mkfile-$SYSTARG-$OBJTYPE	#set vars based on target system
+
+<| $SHELLNAME ../port/mkdevlist $CONF	#sets $IP, $DEVS, $PORT, $LIBS
+
+OBJ=\
+	asm-$OBJTYPE.$O\
+	os.$O\
+	$CONF.root.$O\
+	lock.$O\
+	$DEVS\
+	$PORT\
+
+HFILES=\
+
+CFLAGS='-DROOT="'$ROOT'"' -DEMU -I. -I../port -I$ROOT/$SYSTARG/$OBJTYPE/include -I$ROOT/include -I$ROOT/libinterp $CTHREADFLAGS $CFLAGS $EMUOPTIONS
+SYSLIBS= ${X11LIBS} -lossaudio -lm
+KERNDATE=`{$NDATE}
+
+default:V:	$O.$CONF
+
+<../port/portmkfile
+
+$O.$CONF:	$OBJ $CONF.c $CONF.root.h $LIBFILES
+	$CC $CFLAGS '-DKERNDATE='$KERNDATE $CONF.c
+	$LD $LDFLAGS -o $target $OBJ $CONF.$O $LIBFILES $SYSLIBS
+
+install:V: $O.$CONF
+	cp $O.$CONF $INSTALLDIR/$CONF
+
+devfs.$O:	../port/devfs-posix.c
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/NetBSD/os.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,537 @@
+#include	<sys/types.h>
+#include	<time.h>
+#include	<termios.h>
+#include	<signal.h>
+#include 	<pwd.h>
+#include	<sched.h>
+#include	<sys/resource.h>
+#include	<sys/wait.h>
+#include	<sys/time.h>
+#include	<errno.h>
+
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+/* For dynamic linking init/fini code that needs malloc */
+void (*coherence)(void) = nofence;
+
+
+enum
+{
+	DELETE	= 0x7f,
+	CTRLC	= 'C'-'@',
+	NSTACKSPERALLOC = 16,
+	X11STACK=	256*1024
+};
+char *hosttype = "NetBSD";
+
+static void *stackalloc(Proc *p, void **tos);
+static void stackfreeandexit(void *stack);
+
+void executeonnewstack(void *tos, void (*tramp)(void *arg), void *arg);
+void unlockandexit(ulong *key);
+
+extern int dflag;
+
+int	gidnobody = -1;
+int	uidnobody = -1;
+static struct 	termios tinit;
+
+void
+pexit(char *msg, int t)
+{
+	Osenv *e;
+	void *kstack;
+
+	lock(&procs.l);
+	if(up->prev)
+		up->prev->next = up->next;
+	else
+		procs.head = up->next;
+
+	if(up->next)
+		up->next->prev = up->prev;
+	else
+		procs.tail = up->prev;
+	unlock(&procs.l);
+
+	if(0)
+		print("pexit: %s: %s\n", up->text, msg);
+
+	e = up->env;
+	if(e != nil) {
+		closefgrp(e->fgrp);
+		closepgrp(e->pgrp);
+		closeegrp(e->egrp);
+		closesigs(e->sigs);
+	}
+	kstack = up->kstack;
+	free(up->prog);
+	free(up);
+	if(kstack != nil)
+		stackfreeandexit(kstack);
+}
+
+int
+tramp(void *arg)
+{
+	Proc *p;
+	p = arg;
+	p->pid = p->sigid = getpid();
+	(*p->func)(p->arg);
+	pexit("{Tramp}", 0);
+	return 0;
+}
+
+void
+kproc(char *name, void (*func)(void*), void *arg, int flags)
+{
+	int pid;
+	Proc *p;
+	Pgrp *pg;
+	Fgrp *fg;
+	Egrp *eg;
+	void *tos;
+
+	p = newproc();
+	if(0)
+		print("start %s:%.8lx\n", name, p);
+	if(p == nil) {
+		print("kproc(%s): no memory", name);
+		panic("kproc: no memory");
+	}
+
+	if(flags & KPDUPPG) {
+		pg = up->env->pgrp;
+		incref(&pg->r);
+		p->env->pgrp = pg;
+	}
+	if(flags & KPDUPFDG) {
+		fg = up->env->fgrp;
+		incref(&fg->r);
+		p->env->fgrp = fg;
+	}
+	if(flags & KPDUPENVG) {
+		eg = up->env->egrp;
+		incref(&eg->r);
+		p->env->egrp = eg;
+	}
+
+	p->env->uid = up->env->uid;
+	p->env->gid = up->env->gid;
+	kstrdup(&p->env->user, up->env->user);
+
+	strcpy(p->text, name);
+
+	p->func = func;
+	p->arg = arg;
+
+	if(flags & KPX11){
+		p->kstack = nil;	/* never freed; also up not defined */
+		tos = (char*)mallocz(X11STACK, 0) + X11STACK - sizeof(void*);
+	}else
+		p->kstack = stackalloc(p, &tos);
+
+	lock(&procs.l);
+	if(procs.tail != nil) {
+		p->prev = procs.tail;
+		procs.tail->next = p;
+	}
+	else {
+		procs.head = p;
+		p->prev = nil;
+	}
+	procs.tail = p;
+	unlock(&procs.l);
+
+	if (__clone(tramp, tos, /*CLONE_PTRACE|*/CLONE_VM|CLONE_FS|CLONE_FILES|SIGCHLD, p) <= 0) {
+		fprint(2, "emu: clone failed: %s\n", strerror(errno));
+		panic("kproc: clone failed");
+	}
+}
+
+/*
+ * TO DO:
+ * To get pc on trap, use sigaction instead of signal and
+ * examine its siginfo structure
+ */
+
+/*
+static void
+diserr(char *s, int pc)
+{
+	char buf[ERRMAX];
+
+	snprint(buf, sizeof(buf), "%s: pc=0x%lux", s, pc);
+	disfault(nil, buf);
+}
+*/
+
+static void
+trapILL(int signo)
+{
+	USED(signo);
+	disfault(nil, "Illegal instruction");
+}
+
+static void
+trapBUS(int signo)
+{
+	USED(signo);
+	disfault(nil, "Bus error");
+}
+
+static void
+trapSEGV(int signo)
+{
+	USED(signo);
+	disfault(nil, "Segmentation violation");
+}
+
+static void
+trapFPE(int signo)
+{
+	char buf[64];
+	USED(signo);
+	snprint(buf, sizeof(buf), "sys: fp: exception status=%.4lux", getfsr());
+	disfault(nil, buf);
+}
+
+static void
+trapUSR1(int signo)
+{
+	int intwait;
+
+	USED(signo);
+
+	intwait = up->intwait;
+	up->intwait = 0;	/* clear it to let proc continue in osleave */
+
+	if(up->type != Interp)		/* Used to unblock pending I/O */
+		return;
+
+	if(intwait == 0)		/* Not posted so it's a sync error */
+		disfault(nil, Eintr);	/* Should never happen */
+}
+
+/* called to wake up kproc blocked on a syscall */
+void
+oshostintr(Proc *p)
+{
+	kill(p->sigid, SIGUSR1);
+}
+
+static void
+trapUSR2(int signo)
+{
+	USED(signo);
+	/* we've done our work of interrupting sigsuspend */
+}
+
+void
+osblock(void)
+{
+	sigset_t mask;
+
+	sigprocmask(SIG_SETMASK, NULL, &mask);
+	sigdelset(&mask, SIGUSR2);
+	sigsuspend(&mask);
+}
+
+void
+osready(Proc *p)
+{
+	if(kill(p->sigid, SIGUSR2) < 0)
+		fprint(2, "emu: osready failed: pid %d: %s\n", p->sigid, strerror(errno));
+}
+
+void
+oslongjmp(void *regs, osjmpbuf env, int val)
+{
+	USED(regs);
+	siglongjmp(env, val);
+}
+
+static void
+termset(void)
+{
+	struct termios t;
+
+	tcgetattr(0, &t);
+	tinit = t;
+	t.c_lflag &= ~(ICANON|ECHO|ISIG);
+	t.c_cc[VMIN] = 1;
+	t.c_cc[VTIME] = 0;
+	tcsetattr(0, TCSANOW, &t);
+}
+
+static void
+termrestore(void)
+{
+	tcsetattr(0, TCSANOW, &tinit);
+}
+
+void
+cleanexit(int x)
+{
+	USED(x);
+
+	if(up->intwait) {
+		up->intwait = 0;
+		return;
+	}
+
+	if(dflag == 0)
+		termrestore();
+
+	kill(0, SIGKILL);
+	exit(0);
+}
+
+void
+osreboot(char *file, char **argv)
+{
+	if(dflag == 0)
+		termrestore();
+	execvp(file, argv);
+	error("reboot failure");
+}
+
+void
+libinit(char *imod)
+{
+	struct termios t;
+	struct sigaction act;
+	sigset_t mask;
+	struct passwd *pw;
+	Proc *p;
+	void *tos;
+	char sys[64];
+
+	setsid();
+
+	gethostname(sys, sizeof(sys));
+	kstrdup(&ossysname, sys);
+
+	pw = getpwnam("nobody");
+	if(pw != nil) {
+		uidnobody = pw->pw_uid;
+		gidnobody = pw->pw_gid;
+	}
+
+	if(dflag == 0)
+		termset();
+
+	memset(&act, 0 , sizeof(act));
+	act.sa_handler = trapUSR1;
+	sigaction(SIGUSR1, &act, nil);
+
+	sigemptyset(&mask);
+	sigaddset(&mask, SIGUSR2);
+	sigprocmask(SIG_BLOCK, &mask, NULL);
+
+	memset(&act, 0 , sizeof(act));
+	act.sa_handler = trapUSR2;
+	sigaction(SIGUSR2, &act, nil);
+
+	act.sa_handler = SIG_IGN;
+	sigaction(SIGCHLD, &act, nil);
+
+	/*
+	 * For the correct functioning of devcmd in the
+	 * face of exiting slaves
+	 */
+	signal(SIGPIPE, SIG_IGN);
+	if(signal(SIGTERM, SIG_IGN) != SIG_IGN)
+		signal(SIGTERM, cleanexit);
+	if(signal(SIGINT, SIG_IGN) != SIG_IGN)
+		signal(SIGINT, cleanexit);
+
+	if(sflag == 0) {
+		act.sa_handler = trapBUS;
+		sigaction(SIGBUS, &act, nil);
+		act.sa_handler = trapILL;
+		sigaction(SIGILL, &act, nil);
+		act.sa_handler = trapSEGV;
+		sigaction(SIGSEGV, &act, nil);
+		act.sa_handler = trapFPE;
+		sigaction(SIGFPE, &act, nil);
+	}
+
+	p = newproc();
+	p->kstack = stackalloc(p, &tos);
+
+	pw = getpwuid(getuid());
+	if(pw != nil)
+		kstrdup(&eve, pw->pw_name);
+	else
+		print("cannot getpwuid\n");
+
+	p->env->uid = getuid();
+	p->env->gid = getgid();
+
+	executeonnewstack(tos, emuinit, imod);
+}
+
+int
+readkbd(void)
+{
+	int n;
+	char buf[1];
+
+	n = read(0, buf, sizeof(buf));
+	if(n < 0)
+		print("keyboard close (n=%d, %s)\n", n, strerror(errno));
+	if(n <= 0)
+		pexit("keyboard thread", 0);
+
+	switch(buf[0]) {
+	case '\r':
+		buf[0] = '\n';
+		break;
+	case DELETE:
+		buf[0] = 'H' - '@';
+		break;
+	case CTRLC:
+		cleanexit(0);
+		break;
+	}
+	return buf[0];
+}
+
+/*
+ * Return an abitrary millisecond clock time
+ */
+long
+osmillisec(void)
+{
+	static long sec0 = 0, usec0;
+	struct timeval t;
+
+	if(gettimeofday(&t,(struct timezone*)0)<0)
+		return 0;
+
+	if(sec0 == 0) {
+		sec0 = t.tv_sec;
+		usec0 = t.tv_usec;
+	}
+	return (t.tv_sec-sec0)*1000+(t.tv_usec-usec0+500)/1000;
+}
+
+/*
+ * Return the time since the epoch in nanoseconds and microseconds
+ * The epoch is defined at 1 Jan 1970
+ */
+vlong
+osnsec(void)
+{
+	struct timeval t;
+
+	gettimeofday(&t, nil);
+	return (vlong)t.tv_sec*1000000000L + t.tv_usec*1000;
+}
+
+vlong
+osusectime(void)
+{
+	struct timeval t;
+ 
+	gettimeofday(&t, nil);
+	return (vlong)t.tv_sec * 1000000 + t.tv_usec;
+}
+
+int
+osmillisleep(ulong milsec)
+{
+	struct  timespec time;
+
+	time.tv_sec = milsec/1000;
+	time.tv_nsec= (milsec%1000)*1000000;
+	nanosleep(&time, NULL);
+	return 0;
+}
+
+int
+limbosleep(ulong milsec)
+{
+	return osmillisleep(milsec);
+}
+
+void
+osyield(void)
+{
+	sched_yield();
+}
+
+void
+ospause(void)
+{
+	for(;;)
+		pause();
+}
+
+void
+oslopri(void)
+{
+	setpriority(PRIO_PROCESS, 0, getpriority(PRIO_PROCESS,0)+4);
+}
+
+static struct {
+	Lock l;
+	void *free;
+} stacklist;
+
+static void
+_stackfree(void *stack)
+{
+	*((void **)stack) = stacklist.free;
+	stacklist.free = stack;
+}
+
+static void
+stackfreeandexit(void *stack)
+{
+	lock(&stacklist.l);
+	_stackfree(stack);
+	unlockandexit(&stacklist.l.val);
+}
+
+static void *
+stackalloc(Proc *p, void **tos)
+{
+	void *rv;
+	lock(&stacklist.l);
+	if (stacklist.free == 0) {
+		int x;
+		/*
+		 * obtain some more by using sbrk()
+		 */
+		void *more = sbrk(KSTACK * (NSTACKSPERALLOC + 1));
+		if (more == 0)
+			panic("stackalloc: no more stacks");
+		/*
+		 * align to KSTACK
+		 */
+		more = (void *)((((unsigned long)more) + (KSTACK - 1)) & ~(KSTACK - 1));
+		/*
+		 * free all the new stacks onto the freelist
+		 */
+		for (x = 0; x < NSTACKSPERALLOC; x++)
+			_stackfree((char *)more + KSTACK * x);
+	}
+	rv = stacklist.free;
+	stacklist.free = *(void **)rv;
+	unlock(&stacklist.l);
+	*tos = rv + KSTACK - sizeof(void *);
+	*(Proc **)rv = p;
+	return rv;
+}
+
+int
+segflush(void *a, ulong n)
+{
+	USED(a);
+	USED(n);
+	return 0;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Nt/audio.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,810 @@
+#define Unknown win_Unknown
+#include <windows.h>
+#include <mmsystem.h>
+#undef Unknown
+
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+#define 	Audio_Mic_Val		0
+#define 	Audio_Linein_Val	-1
+
+#define	Audio_Speaker_Val	0
+#define	Audio_Headphone_Val	-1
+#define	Audio_Lineout_Val	-1
+
+#define 	Audio_Pcm_Val		WAVE_FORMAT_PCM
+#define 	Audio_Ulaw_Val		(WAVE_FORMAT_PCM+1)
+#define 	Audio_Alaw_Val		(WAVE_FORMAT_PCM+2)
+
+#define 	Audio_Max_Queue		8
+
+#define BUFLEN		1000
+
+#define INISOPEN	0x00000002 // the microphone is open
+#define OUTISOPEN	0x00000004 // the speaker is open
+#define INPUTISGOING	0x00000020 // microphone is being recorded/read
+
+#include "audio.h"
+#include "audio-tbls.c"
+
+static int debug = 0;
+
+/* TO DO: sensible expression of double-buffering */
+#define  Ping 0
+#define  Pong 1
+
+static HWAVEIN audio_file_in;
+static HWAVEOUT audio_file_out;
+
+static long out_buf_count;
+
+typedef struct _awin {
+	WAVEHDR hdr;
+	long	sz;
+	char*	ptr;
+	char	data[Audio_Max_Buf];
+} AWin;
+
+static AWin audio_ping;
+static AWin audio_pong;
+
+static long paddle = Ping;
+static int ping_is_filling;
+static int pong_is_filling;
+
+static long audio_flags = 0;
+static int audio_init = 0;
+
+static QLock flag_lock;
+
+static Audio_t av;
+
+static HANDLE outlock;
+static HANDLE inlock;
+
+static int audio_open_in(HWAVEIN*, Audio_d*);
+static int audio_open_out(HWAVEOUT*, Audio_d*);
+static void audio_close_in(void);
+static void audio_close_out(void);
+static void CALLBACK waveInProc(HWAVEIN, UINT, DWORD, DWORD, DWORD);
+static void CALLBACK waveOutProc(HWAVEOUT, UINT, DWORD, DWORD, DWORD);
+
+#define AUDIOIN  0
+#define AUDIOOUT  1
+
+/* 
+* Error routines
+*/
+static int
+audioerror(unsigned int code, int in_out, char *msg)
+{
+	char errorText[MAXERRORLENGTH];
+
+	if (code != MMSYSERR_NOERROR) {
+		switch(in_out) {
+		case AUDIOIN:
+			waveInGetErrorText(code, errorText, sizeof(errorText));
+			//print("ERROR -- %s: %s\n", msg, errorText);
+			return(-1);
+		case AUDIOOUT:
+			waveOutGetErrorText(code, errorText, sizeof(errorText));
+			//print("ERROR -- %s: %s\n", msg, errorText);
+			return(-1);
+		default:
+			print("%s: Unknown device\n", msg);
+		}
+	}
+	//print("TRACE %s\n", msg);
+	return 0;
+}
+
+void
+audio_file_init(void)
+{
+	audio_info_init(&av);
+}
+
+void
+audio_file_open(Chan *c, int omode)
+{
+	int in_is_open = 0;
+
+	switch(omode){
+	case OREAD:
+		qlock(&flag_lock);
+
+		if(waserror()) {
+			qunlock(&flag_lock);
+			nexterror();
+		}
+
+		if(audio_flags & INISOPEN)
+			error(Einuse);
+
+		inlock = CreateMutex(NULL, FALSE, NULL);
+		if(inlock == NULL)
+			error(Einuse);
+
+		if(!audio_open_in(&audio_file_in, &av.in) ) {
+			CloseHandle(inlock);
+			error(Ebadarg);
+		}
+
+		ping_is_filling = 0;
+		pong_is_filling = 0;
+		paddle = Ping;
+		audio_flags |= INISOPEN;
+
+		poperror();
+		qunlock(&flag_lock);
+		break;
+	case OWRITE:
+		qlock(&flag_lock);
+		if(waserror()){
+			qunlock(&flag_lock);
+			nexterror();
+		}
+
+		if(audio_flags & OUTISOPEN)
+			error(Einuse);
+
+		outlock = CreateMutex(NULL, FALSE, NULL);
+		if(outlock == NULL)
+			error(Einuse);
+
+		if(!audio_open_out(&audio_file_out, &av.out) ) {
+			CloseHandle(outlock);
+			error(Ebadarg);
+		}
+
+		out_buf_count = 0;
+		audio_flags |= OUTISOPEN;
+
+		poperror();
+		qunlock(&flag_lock);
+		break;
+	case ORDWR:
+		qlock(&flag_lock);
+		if(waserror()){
+			qunlock(&flag_lock);
+			if(in_is_open)
+				audio_close_in();
+			nexterror();
+		}
+
+		if((audio_flags & INISOPEN) || (audio_flags & OUTISOPEN))
+			error(Einuse);
+
+		if(!audio_open_in(&audio_file_in, &av.in) )
+			error(Ebadarg);
+
+		in_is_open = 1;
+
+		if(!audio_open_out(&audio_file_out, &av.out))  {
+			CloseHandle(outlock);
+			error(Ebadarg);
+		}
+
+		inlock = CreateMutex(NULL, FALSE, NULL);
+		if(inlock == NULL)
+			error(Einuse);
+
+		outlock = CreateMutex(NULL, FALSE, NULL);
+		if(outlock == NULL) {
+			CloseHandle(inlock);
+			error(Einuse);
+		}
+
+		audio_flags |= INISOPEN;
+		audio_flags |= OUTISOPEN;
+		ping_is_filling = 0;
+		pong_is_filling = 0;
+		paddle = Ping;
+		out_buf_count = 0;
+
+		poperror();
+		qunlock(&flag_lock);
+		break;
+	default:
+		error(Egreg);
+	}
+}
+
+static int
+audio_open_in(HWAVEIN* h, Audio_d* d)
+{
+	HWAVEIN th;
+	WAVEFORMATEX format; 
+
+	format.wFormatTag = d->enc;
+	format.nChannels = d->chan;
+	format.nSamplesPerSec = d->rate;
+	format.wBitsPerSample = d->bits;
+	format.nBlockAlign = (d->chan * d->bits) / Bits_Per_Byte;
+	format.nAvgBytesPerSec = 
+		format.nSamplesPerSec * format.nBlockAlign;
+	format.cbSize = 0;
+
+	if (audioerror(
+		waveInOpen(&th, WAVE_MAPPER, &format, (DWORD)waveInProc, 0, CALLBACK_FUNCTION), 
+		AUDIOIN,
+		"cannot open microphone/line-in") == 0) {
+		*h = th;
+		return 1;
+	}
+	return 0;
+}
+
+static int
+audio_open_out(HWAVEOUT* h, Audio_d* d)
+{
+	unsigned int code;
+	HWAVEOUT th;
+	WAVEFORMATEX format; 
+
+	format.wFormatTag = d->enc;
+	format.nChannels = d->chan;
+	format.nSamplesPerSec = d->rate;
+	format.wBitsPerSample = d->bits;
+	format.nBlockAlign = (d->chan * d->bits) / Bits_Per_Byte;
+	format.nAvgBytesPerSec = 
+		format.nSamplesPerSec * format.nBlockAlign;
+	format.cbSize = 0;
+
+	code = waveOutOpen(&th, WAVE_MAPPER, &format, (DWORD)waveOutProc, 0, CALLBACK_FUNCTION);
+
+	if (audioerror(code, AUDIOOUT, "cannot open speaker/line-out") == 0) {
+		out_buf_count = 0;
+		*h = th;
+		return 1;
+	}
+
+	return 0;
+}
+
+void
+audio_file_close(Chan *c)
+{
+	switch(c->mode){
+	case OREAD:
+		qlock(&flag_lock);
+		audio_close_in();
+		audio_flags &= ~(INISOPEN|INPUTISGOING);
+		CloseHandle(inlock);
+		qunlock(&flag_lock);
+		break;
+	case OWRITE:
+		qlock(&flag_lock);
+		audio_close_out();
+		audio_flags &= ~OUTISOPEN;
+		CloseHandle(outlock);
+		qunlock(&flag_lock);
+		break;
+	case ORDWR:
+		qlock(&flag_lock);
+		audio_close_in();
+		audio_close_out();
+
+		audio_flags &= ~(INISOPEN|INPUTISGOING|OUTISOPEN);
+
+		CloseHandle(outlock);
+		CloseHandle(inlock);
+		qunlock(&flag_lock);
+		break;
+	}
+}
+
+static void
+audio_close_in()
+{
+	audioerror(waveInStop(audio_file_in), AUDIOIN, "audio_close_in Stop");
+	audioerror(waveInReset(audio_file_in), AUDIOIN, "audio_close_in Reset");
+
+	audioerror(waveInUnprepareHeader(audio_file_in, &audio_ping.hdr, 
+			sizeof(WAVEHDR)), AUDIOIN, "in un prepare ping header");
+	audio_ping.sz = 0;
+	audio_ping.ptr = &audio_ping.data[0];
+	audioerror(waveInUnprepareHeader(audio_file_in, &audio_pong.hdr, 
+			sizeof(WAVEHDR)), AUDIOIN, "in un prepare pong header");
+	audio_pong.sz = 0;
+	audio_pong.ptr = &audio_pong.data[0];
+
+	audioerror(waveInClose(audio_file_in), AUDIOIN, "in close");
+
+}
+
+static void
+audio_close_out()
+{
+Again:
+	WaitForSingleObject(outlock, INFINITE);
+	while(out_buf_count > 0) {
+		ReleaseMutex(outlock);
+		sleep(0);
+		goto Again;
+	}
+	ReleaseMutex(outlock);
+
+	audioerror(waveOutReset(audio_file_out), AUDIOOUT, "close wave out reset");
+	audioerror(waveOutClose(audio_file_out), AUDIOOUT, "closing out device");
+}
+
+
+long
+audio_file_read(Chan *c, void *va, long count, vlong offset)
+{
+	MMRESULT status;
+	long len = av.in.buf * Audio_Max_Buf / Audio_Max_Val;
+	char *v = (char *) va;
+	char *p;
+	long ba, n, chunk, total;
+
+
+ 	qlock(&flag_lock);
+	WaitForSingleObject(inlock, INFINITE);
+
+	if(waserror()) {
+		audioerror(waveInStop(audio_file_in), AUDIOIN, 
+			"audio_file_read Stop 1");
+		audioerror(waveInReset(audio_file_in), AUDIOIN, 
+			"audio_file_read Reset 1");
+		audioerror(waveInUnprepareHeader(audio_file_in, 
+			&audio_ping.hdr, sizeof(WAVEHDR)), AUDIOIN, 
+			"in unprepare ping");
+		audioerror(waveInUnprepareHeader(audio_file_in, 
+			&audio_pong.hdr, sizeof(WAVEHDR)), 
+			AUDIOIN, "in unprepare pong");
+
+		audio_ping.sz = 0;
+		audio_ping.ptr = &audio_ping.data[0];
+		audio_pong.sz = 0;
+		audio_pong.ptr = &audio_pong.data[0];
+
+		ping_is_filling = pong_is_filling = 0;
+		paddle = Ping;
+
+		qunlock(&flag_lock);
+		ReleaseMutex(inlock);
+
+		nexterror();
+	}
+
+	if(!(audio_flags & INISOPEN))
+		error(Eperm);
+
+	/* check for block alignment */
+	ba = av.in.bits * av.in.chan / Bits_Per_Byte;
+
+	if(len < 1 || count % ba)
+		error(Ebadarg);
+
+	if(!(audio_flags & INPUTISGOING)) {
+		if(audioerror(waveInStart(audio_file_in), AUDIOIN, 
+			"in start") == -1)
+				error(Eio);
+
+		audio_ping.sz = 0;
+		audio_ping.ptr = &audio_ping.data[0];
+		audio_ping.hdr.lpData = audio_ping.ptr;
+		audio_ping.hdr.dwBufferLength = len;  
+		audio_ping.hdr.dwUser = Ping;
+		audio_ping.hdr.dwFlags = 0;
+
+		status = waveInPrepareHeader(audio_file_in, &audio_ping.hdr, 
+			sizeof(WAVEHDR));
+
+		if (audioerror(status, AUDIOIN, "in prepare header") == -1)
+			error(Eio);
+
+		audio_pong.sz = 0;
+		audio_pong.ptr = &audio_pong.data[0];
+		audio_pong.hdr.lpData = audio_pong.ptr;
+		audio_pong.hdr.dwBufferLength = len;  
+		audio_pong.hdr.dwUser = Pong;
+		audio_pong.hdr.dwFlags = 0;
+
+		status = waveInPrepareHeader(audio_file_in, &audio_pong.hdr, 
+			sizeof(WAVEHDR));
+
+		if (audioerror(status, AUDIOIN, "in prepare header") == -1)
+			error(Eio);
+
+		status = waveInAddBuffer(audio_file_in, &audio_ping.hdr, 
+			sizeof(WAVEHDR));
+		if (audioerror(status, AUDIOIN, "file_read Add Buffer")== -1){
+			waveInUnprepareHeader(audio_file_in, &audio_ping.hdr, 
+				sizeof(WAVEHDR));
+			audio_ping.sz = 0;
+			audio_ping.ptr = &audio_ping.data[0];
+			error(Eio);
+		}
+
+		ping_is_filling = 1;
+		pong_is_filling = 0;
+		paddle = Ping;
+		audio_flags |= INPUTISGOING;
+	}
+	poperror();
+	ReleaseMutex(inlock);
+
+	total = 0;
+
+Draining:
+
+	WaitForSingleObject(inlock, INFINITE);
+	if(waserror()) {
+		audioerror(waveInStop(audio_file_in), AUDIOIN, 
+			"audio_file_read Stop 2");
+		audioerror(waveInReset(audio_file_in), AUDIOIN, 
+			"audio_file_read Reset 2");
+		audioerror(waveInUnprepareHeader(audio_file_in, 
+			&audio_ping.hdr, sizeof(WAVEHDR)), AUDIOIN, 
+			"in unprepare ping");
+		audioerror(waveInUnprepareHeader(audio_file_in, 
+			&audio_pong.hdr, sizeof(WAVEHDR)), AUDIOIN, 
+			"in unprepare pong");
+
+		audio_ping.sz = 0;
+		audio_ping.ptr = &audio_ping.data[0];
+		audio_pong.sz = 0;
+		audio_pong.ptr = &audio_pong.data[0];
+
+		audio_flags &= ~INPUTISGOING;
+
+		ReleaseMutex(inlock);
+		qunlock(&flag_lock);
+		nexterror();
+	}
+
+	while((total < count) && ((audio_ping.sz > 0) || (audio_pong.sz > 0))) {
+		n  = paddle == Ping ? audio_ping.sz : audio_pong.sz;
+		p  = paddle == Ping ? audio_ping.ptr : audio_pong.ptr;
+
+		chunk = min(n, count - total);
+
+		memmove(v+total, p , chunk);
+
+		total += chunk;
+
+		if(paddle == Ping) {
+			if(!pong_is_filling) {
+
+				if(audioerror(waveInAddBuffer(audio_file_in,
+						&audio_pong.hdr, sizeof(WAVEHDR)), AUDIOIN, 
+						"draining ping calling add buffer pong") == -1)
+						error(Eio);
+
+				pong_is_filling = 1;
+			}
+
+			audio_ping.sz -= chunk;
+			if(audio_ping.sz > 0) {
+				audio_ping.ptr += chunk;
+			} else {
+				audio_ping.ptr = &audio_ping.data[0];
+				ping_is_filling = 0;
+				paddle = Pong;
+			}
+		} else {
+			if(!ping_is_filling) {
+
+				if(audioerror(waveInAddBuffer(audio_file_in,
+						&audio_ping.hdr, sizeof(WAVEHDR)), AUDIOIN, 
+						"draining pong calling add buffer ping") == -1)
+						error(Eio);
+
+				ping_is_filling = 1;
+			}
+
+			audio_pong.sz -= chunk;
+			if(audio_pong.sz > 0) {
+				audio_pong.ptr += chunk;
+			} else {
+				audio_pong.ptr = &audio_pong.data[0];
+				pong_is_filling = 0;
+				paddle = Ping;
+			}
+		}
+	}
+
+	poperror();
+
+	ReleaseMutex(inlock);
+
+	if(total == count) {
+		qunlock(&flag_lock);
+		return count;
+	}
+
+Filling:
+	WaitForSingleObject(inlock, INFINITE);
+	while((audio_ping.sz < 1) && (audio_pong.sz < 1)) {
+		ReleaseMutex(inlock);
+		sleep(0);
+		goto Filling;	
+	}
+	ReleaseMutex(inlock);
+
+	goto Draining;
+}
+
+
+long
+audio_file_write(Chan *c, void *va, long count, vlong offset)
+{
+	MMRESULT status;
+	WAVEHDR *hHdr = (WAVEHDR *) NULL;
+	char *hData = NULL;
+	char *p = (char *) va;
+	long ba;
+	long bufsz;
+	long chunk;
+	long total;
+
+	qlock(&flag_lock);
+	if(waserror()){
+		qunlock(&flag_lock);
+		nexterror();
+	}
+
+	if(!(audio_flags & OUTISOPEN))
+		error(Eperm);
+
+	/* check for block alignment */
+	ba = av.out.bits * av.out.chan / Bits_Per_Byte;
+
+	if(count % ba)
+		error(Ebadarg);
+
+	bufsz = av.out.buf * Audio_Max_Buf / Audio_Max_Val;
+
+	if(bufsz < 1)
+		error(Ebadarg);
+
+	total = 0;
+
+	while(total < count) {
+
+Again:
+	chunk = min(bufsz, count - total);
+
+Drain:
+	WaitForSingleObject(outlock, INFINITE);
+	while(out_buf_count > bufsz) {
+		ReleaseMutex(outlock);
+		sleep(0);
+		goto Drain;
+	}
+
+	if(out_buf_count == 0)
+		audioerror(waveOutReset(audio_file_out), AUDIOOUT, "wave out reset");
+	ReleaseMutex(outlock);
+
+	/* 
+	 * allocate and lock the memory for the wave header 
+	 * and data blocks 
+	 */
+	hHdr = (WAVEHDR *) malloc(sizeof(WAVEHDR));
+	if (!hHdr)
+		error(Enomem);
+
+	hData = malloc(chunk);
+	if (!hData) {
+		free(hHdr);
+		error(Enomem);
+	}
+
+	/*
+	 * initialize the wave header struct
+	 */
+
+	/*
+	 * copy user data into write Q 
+	 */
+	memmove(hData, p+total, chunk);  
+
+	hHdr->lpData = hData;
+	hHdr->dwBufferLength = chunk; 
+	hHdr->dwBytesRecorded = 0; 
+	hHdr->dwUser = chunk;
+	hHdr->dwFlags = 0;
+	hHdr->dwLoops = 0;
+	hHdr->lpNext = 0;
+	hHdr->reserved = 0;
+
+	status = waveOutPrepareHeader(audio_file_out, hHdr, sizeof(WAVEHDR));
+
+	if (audioerror(status, AUDIOOUT, "out prepare header") == -1) {
+		free(hHdr);
+		free(hData);
+		error(Eio);
+	}
+
+	status =
+	waveOutWrite(audio_file_out, hHdr, sizeof(WAVEHDR));
+
+	if (audioerror(status, AUDIOOUT, "out write data") == -1) {
+		waveOutUnprepareHeader(audio_file_out, hHdr, sizeof(WAVEHDR));
+		free(hHdr);
+		free(hData);
+		error(Eio);
+	}
+
+	WaitForSingleObject(outlock, INFINITE);
+	out_buf_count += chunk;
+	ReleaseMutex(outlock);
+
+	total += chunk;
+
+	}
+
+	poperror();
+	qunlock(&flag_lock);
+	osmillisleep(1);	/* hack to get around thread scheduler */
+
+	return count;
+}
+
+void CALLBACK
+waveInProc(HWAVEIN hwi, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
+{
+	LPWAVEHDR hHdr;
+	long count;
+
+	switch(uMsg) {
+	case WIM_OPEN:
+		break;
+	case WIM_CLOSE:
+		break;
+	case WIM_DATA:
+		hHdr = (LPWAVEHDR)dwParam1;
+		if(hHdr != NULL) {
+			count = hHdr->dwBytesRecorded;
+			if(count > 0) {
+				WaitForSingleObject(inlock, INFINITE);
+				if(hHdr->dwUser == Ping) 
+					audio_ping.sz = count;
+				else
+					audio_pong.sz = count;
+				ReleaseMutex(inlock);
+			}
+		}
+		break;
+	}
+	return;
+}
+
+
+void CALLBACK
+waveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD dwOutstance, DWORD dwParam1, DWORD dwParam2)
+{
+	LPWAVEHDR hHdr;
+
+	switch(uMsg) {
+	case WOM_DONE:
+		hHdr = (LPWAVEHDR)dwParam1;
+		if(hHdr != NULL) {
+		WaitForSingleObject(outlock, INFINITE);
+		out_buf_count -= hHdr->dwUser;
+		ReleaseMutex(outlock);
+		audioerror(
+			waveOutUnprepareHeader(
+			audio_file_out, hHdr, sizeof(WAVEHDR)),
+			AUDIOOUT, "out un prepare header");
+		if(hHdr->lpData != NULL) 
+			free(hHdr->lpData);
+		free(hHdr);
+		}
+		break;
+	case WOM_CLOSE:
+		WaitForSingleObject(outlock, INFINITE);
+		out_buf_count = 0;
+		ReleaseMutex(outlock);
+		break;
+	case WOM_OPEN:
+		break;
+	}
+}
+
+long
+audio_ctl_write(Chan *c, void *va, long count, vlong offset)
+{
+	WAVEFORMATEX format;
+	Audio_t tmpav = av;
+
+	tmpav.in.flags = 0;
+	tmpav.out.flags = 0;
+
+	if(!audioparse(va, count, &tmpav))
+		error(Ebadarg);
+
+	if((tmpav.in.enc != Audio_Pcm_Val) || (tmpav.out.enc != Audio_Pcm_Val))
+		error(Ebadarg);
+
+	if(tmpav.in.flags & AUDIO_MOD_FLAG) {
+		format.wFormatTag = tmpav.in.enc;
+		format.wBitsPerSample = tmpav.in.bits;
+		format.nChannels = tmpav.in.chan;
+		format.nSamplesPerSec = tmpav.in.rate;
+		format.nBlockAlign = 
+			(tmpav.in.chan * tmpav.in.bits) / Bits_Per_Byte;
+		format.nAvgBytesPerSec = 
+			format.nSamplesPerSec * format.nBlockAlign;
+		format.cbSize = 0;
+
+		if(audioerror(
+			waveInOpen(NULL, WAVE_MAPPER, &format, 0, 0,  WAVE_FORMAT_QUERY),
+			AUDIOIN, "cannot open microphone/line-in to test parameters") == -1)
+				error(Ebadarg);
+
+		qlock(&flag_lock);
+
+		if(waserror()){
+			qunlock(&flag_lock);
+			nexterror();
+		}
+
+		if(audio_flags & INISOPEN) {
+			audio_close_in();
+			audio_flags &= ~INISOPEN;
+			audio_flags &= ~INPUTISGOING;
+			if(!audio_open_in(&audio_file_in, &tmpav.in))
+				error(Eio);
+			audio_flags |= INISOPEN;
+		}
+		poperror();
+		qunlock(&flag_lock);
+	}
+
+	if(tmpav.out.flags & AUDIO_MOD_FLAG) {
+
+		format.wFormatTag = tmpav.out.enc;
+		format.wBitsPerSample = tmpav.out.bits;
+		format.nChannels = tmpav.out.chan;
+		format.nSamplesPerSec = tmpav.out.rate;
+		format.nBlockAlign = 
+			(tmpav.out.chan * tmpav.out.bits) / Bits_Per_Byte;
+		format.nAvgBytesPerSec = 
+			format.nSamplesPerSec * format.nBlockAlign;
+		format.cbSize = 0;
+
+		if (audioerror(waveOutOpen(NULL, WAVE_MAPPER, 
+			&format,
+			0, 0, WAVE_FORMAT_QUERY), 
+			AUDIOOUT, "cannot open output to test parameters") == -1)
+				error(Ebadarg);
+
+		qlock(&flag_lock);
+		if(waserror()){
+			qunlock(&flag_lock);
+			nexterror();
+		}
+		if(audio_flags & OUTISOPEN) {
+			audio_close_out(); 
+
+			audio_flags &= ~OUTISOPEN;
+			if(!audio_open_out(&audio_file_out, &tmpav.out)) {
+				error(Eio);
+				return -1;
+			}
+			audio_flags |= OUTISOPEN;
+		}
+		poperror();
+		qunlock(&flag_lock);
+	}
+
+	tmpav.in.flags = 0;
+	tmpav.out.flags = 0;
+
+	av = tmpav;
+
+	return count;
+}
+
+Audio_t*
+getaudiodev(void)
+{
+	return &av;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Nt/cmd.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,243 @@
+#define Unknown win_Unknown
+#define UNICODE
+#include	<windows.h>
+#include <winbase.h>
+#include	<winsock.h>
+#undef Unknown
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+extern int	nth2fd(HANDLE);
+extern wchar_t	*widen(char*);
+
+/*
+ * thanks to rcsh for these.
+ *
+ * windows quoting rules - I think
+ * Words are separated by space or tab
+ * Words containing a space or tab can be quoted using "
+ * 2N backslashes + " ==> N backslashes and end quote
+ * 2N+1 backslashes + " ==> N backslashes + literal "
+ * N backslashes not followed by " ==> N backslashes
+ */
+static char *
+dblquote(char *cmd, char *s)
+{
+	int nb;
+	char *p;
+
+	for(p=s; *p; p++)
+		if(*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r' || *p == '"')
+			break;
+
+	if(*p == 0){				/* easy case */
+		strcpy(cmd, s);
+		return cmd+(p-s);
+	}
+
+	*cmd++ = '"';
+	for(;;) {
+		for(nb=0; *s=='\\'; nb++)
+			*cmd++ = *s++;
+
+		if(*s == 0) {			/* trailing backslashes -> 2N */
+			while(nb-- > 0)
+				*cmd++ = '\\';
+			break;
+		}
+
+		if(*s == '"') {			/* literal quote -> 2N+1 backslashes */
+			while(nb-- > 0)
+				*cmd++ = '\\';
+			*cmd++ = '\\';		/* escape the quote */
+		}
+		*cmd++ = *s++;
+	}
+
+	*cmd++ = '"';
+	*cmd = 0;
+
+	return cmd;
+}
+
+static char *
+ntquotedcmd(char **argv)
+{
+	int i, n;
+	char *cmd, *p;
+
+		/* conservatively calculate length of command;
+		 * backslash expansion can cause growth in dblquote().
+		 */
+	for(i=0,n=0; argv[i]; i++)
+		n += 2*strlen(argv[i]);
+	n++;
+	
+	cmd = malloc(n);
+	if(cmd == nil)
+		return nil;
+	for(i=0,p=cmd; argv[i]; i++) {
+		p = dblquote(p, argv[i]);
+		*p++ = ' ';
+	}
+	if(p != cmd)
+		p--;
+	*p = 0;
+
+	return cmd;
+}
+
+static HANDLE
+exporthandle(HANDLE h, int close)
+{
+	HANDLE cp, dh;
+	DWORD flags = DUPLICATE_SAME_ACCESS;
+	if (close)
+		flags |= DUPLICATE_CLOSE_SOURCE;
+	cp = GetCurrentProcess();
+	if (!DuplicateHandle(cp, h, cp, &dh, DUPLICATE_SAME_ACCESS, 1, flags))
+		return nil;
+	return dh;
+}
+
+/* TO DO: check that oserrstr will have the right text on error */
+
+void*
+oscmd(char **args, int nice, char *dir, int *fd)
+{
+	STARTUPINFO si;
+	SECURITY_ATTRIBUTES sec;
+	HANDLE rh, wh, eh, srh, swh, seh;
+	PROCESS_INFORMATION pinfo;
+	char *cmd;
+	wchar_t *wcmd, *wdir;
+	int prio;
+
+	wdir = nil;
+	if(dir != nil)
+		wdir = widen(dir);
+
+	cmd = ntquotedcmd(args);
+	if(cmd == nil)
+		error(Enomem);
+
+	wcmd = widen(cmd);
+	sec.nLength = sizeof(sec);
+	sec.lpSecurityDescriptor = 0;
+	sec.bInheritHandle = 0;
+	rh = wh = eh = srh = swh = seh = nil;
+	if(!CreatePipe(&rh, &swh, &sec, 0))
+		goto Error;
+	if(!CreatePipe(&srh, &wh, &sec, 0))
+		goto Error;
+	if(!CreatePipe(&seh, &eh, &sec, 0))
+		goto Error;
+	rh = exporthandle(rh, 1);
+	if(rh == nil)
+		goto Error;
+	wh = exporthandle(wh, 1);
+	if(wh == nil)
+		goto Error;
+	eh = exporthandle(eh, 1);
+	if(eh == nil)
+		goto Error;
+
+	memset(&si, 0, sizeof(si));
+	si.cb = sizeof(si);
+	si.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
+	si.wShowWindow = SW_SHOW;
+	si.hStdInput = rh;
+	si.hStdOutput = wh;
+	si.hStdError = eh;
+
+	prio = 0;
+	if(nice){
+		prio = IDLE_PRIORITY_CLASS;
+		if(nice > 1)
+			prio |= CREATE_SUSPENDED;
+	}
+
+	/* default of nil for wpath seems to be what we want; nil for env exports our current one */
+	if(!CreateProcess(nil/*wpath*/, wcmd, 0, 0, 1,
+	   CREATE_NEW_PROCESS_GROUP|CREATE_DEFAULT_ERROR_MODE|prio,
+	   0 /*env*/, wdir, &si, &pinfo)){
+		//print("can't create process '%Q' %d\n", wcmd, GetLastError());
+		goto Error;
+	}
+
+	fd[0] = nth2fd(swh);
+	fd[1] = nth2fd(srh);
+	fd[2] = nth2fd(seh);
+	if(fd[1] == 1 || fd[2] == 2)
+		panic("invalid mapping of handle to fd");
+	CloseHandle(si.hStdInput);
+	CloseHandle(si.hStdOutput);
+	CloseHandle(si.hStdError);
+
+	if(prio & CREATE_SUSPENDED){
+		if(nice > 1)
+			SetThreadPriority(pinfo.hThread,
+				nice>3? THREAD_PRIORITY_IDLE:
+				nice>2? THREAD_PRIORITY_LOWEST:
+				THREAD_PRIORITY_BELOW_NORMAL);
+		ResumeThread(pinfo.hThread);
+	}
+	CloseHandle(pinfo.hThread);
+	/* don't close process handle */
+	free(cmd);
+	free(wcmd);
+	free(wdir);
+	return pinfo.hProcess;
+
+Error:
+	if(rh)
+		CloseHandle(rh);
+	if(wh)
+		CloseHandle(wh);
+	if(eh)
+		CloseHandle(eh);
+	if(srh)
+		CloseHandle(srh);
+	if(swh)
+		CloseHandle(swh);
+	if(seh)
+		CloseHandle(seh);
+	free(cmd);
+	free(wcmd);
+	free(wdir);
+	return nil;
+}
+
+int
+oscmdwait(void *v, char *buf, int n)
+{
+	int status;
+	HANDLE proc = (HANDLE)v;
+
+	/* need not worry about being interrupted */
+	if(WaitForSingleObject(proc, INFINITE) == WAIT_FAILED)
+		return -1;
+	if(!GetExitCodeProcess(proc, &status))
+		status = 1;
+	if(status)
+		n = snprint(buf, n, "0 0 0 0 'status %d'", status);
+	else
+		n = snprint(buf, n, "0 0 0 0 ''");
+	return n;
+
+}
+
+int
+oscmdkill(void *v)
+{
+	if(TerminateProcess((HANDLE)v, 666) == FALSE)
+		return -1;
+	return 0;
+}
+
+void
+oscmdfree(void *v)
+{
+	CloseHandle((HANDLE)v);
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Nt/devarch.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,442 @@
+/*
+ *  platform-specific interface
+ */
+
+#define Unknown win_Unknown
+#define UNICODE
+#include	<windows.h>
+#include <winbase.h>
+#include	<winsock.h>
+#undef Unknown
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+#include	"r16.h"
+
+enum{
+	Qdir,
+	Qarchctl,
+	Qcputype,
+	Qregquery,
+	Qhostmem
+};
+
+static
+Dirtab archtab[]={
+	".",		{Qdir, 0, QTDIR},	0,	0555,
+	"archctl",	{Qarchctl, 0},	0,	0444,
+	"cputype",	{Qcputype},	0,	0444,
+	"regquery",	{Qregquery}, 0,	0666,
+	"hostmem",	{Qhostmem},	0,	0444,
+};
+
+typedef struct Value Value;
+struct Value {
+	int	type;
+	int	size;
+	union {
+		ulong	w;
+		vlong	q;
+		char	data[1];	/* utf-8 */
+	};
+};
+
+typedef struct Regroot Regroot;
+struct Regroot {
+	char*	name;
+	HKEY	root;
+};
+
+static Regroot roots[] = {
+	{"HKEY_CLASSES_ROOT", HKEY_CLASSES_ROOT},
+	{"HKEY_CURRENT_CONFIG", HKEY_CURRENT_CONFIG},
+	{"HKEY_CURRENT_USER", HKEY_CURRENT_USER},
+	{"HKEY_LOCAL_MACHINE", HKEY_LOCAL_MACHINE},
+	{"HKEY_PERFORMANCE_DATA", HKEY_PERFORMANCE_DATA},
+	{"HKEY_USERS", HKEY_USERS},
+};
+
+static struct {
+	ulong	mhz;
+	int ncpu;
+	char	cpu[64];
+} arch;
+
+static	QLock	reglock;
+
+static	Value*	getregistry(HKEY, Rune16*, Rune16*);
+static int nprocs(void);
+
+static void
+archinit(void)
+{
+	Value *v;
+	char *p;
+
+	v = getregistry(HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", L"ProcessorNameString");
+	if(v != nil){
+		snprint(arch.cpu, sizeof(arch.cpu), "%s", v->data);
+		if((p = strrchr(arch.cpu, ' ')) != nil)
+			for(; p >= arch.cpu && *p == ' '; p--)
+				*p = '\0';
+		free(v);
+	}else{
+		v = getregistry(HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", L"VendorIdentifier");
+		if(v != nil){
+			snprint(arch.cpu, sizeof(arch.cpu), "%s", v->data);
+			free(v);
+		}else
+			snprint(arch.cpu, sizeof(arch.cpu), "unknown");
+	}
+	v = getregistry(HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", L"~MHz");
+	if(v != nil){
+		arch.mhz = v->w;
+		free(v);
+	}
+	arch.ncpu = nprocs();
+}
+
+static int
+nprocs(void)
+{
+	int n;
+	char *p;
+	Rune16 *r;
+	Value *v;
+	n = 0;
+	for(;;){
+		p = smprint("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\%d", n);
+		if(waserror()){
+			free(p);
+			nexterror();
+		}
+		r = widen(p);
+		free(p);
+		v = getregistry(HKEY_LOCAL_MACHINE, r, L"~MHz");
+		free(r);
+		if(v == nil)
+			break;
+		free(v);
+		n++;
+	}
+	return n;
+}
+
+static Chan*
+archattach(char* spec)
+{
+	return devattach('a', spec);
+}
+
+static Walkqid*
+archwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	return devwalk(c, nc, name, nname, archtab, nelem(archtab), devgen);
+}
+
+static int
+archstat(Chan* c, uchar *db, int n)
+{
+	return devstat(c, db, n, archtab, nelem(archtab), devgen);
+}
+
+static Chan*
+archopen(Chan* c, int omode)
+{
+	return devopen(c, omode, archtab, nelem(archtab), devgen);
+}
+
+static void
+archclose(Chan* c)
+{
+	if((ulong)c->qid.path == Qregquery && c->aux != nil)
+		free(c->aux);
+}
+
+static long
+archread(Chan* c, void* a, long n, vlong offset)
+{
+	char *p;
+	Value *v;
+	int i, l;
+	MEMORYSTATUS mem;
+
+	switch((ulong)c->qid.path){
+	case Qdir:
+		return devdirread(c, a, n, archtab, nelem(archtab), devgen);
+	case Qarchctl:
+	case Qcputype:
+		l = 0;
+		if((ulong)c->qid.path == Qcputype)
+			l = 4;
+		p = smalloc(READSTR);
+		if(waserror()){
+			free(p);
+			nexterror();
+		}
+		snprint(p, READSTR, "cpu %q %lud %d\n", arch.cpu, arch.mhz, arch.ncpu);
+		n = readstr(offset, a, n, p+l);
+		poperror();
+		free(p);
+		break;
+	case Qregquery:
+		v = c->aux;
+		if(v == nil)
+			return 0;
+		p = smalloc(READSTR);
+		if(waserror()){
+			free(p);
+			nexterror();
+		}
+		switch(v->type){
+		case REG_NONE:
+			n = readstr(offset, a, n, "nil");
+			break;
+		case REG_DWORD:
+			snprint(p, READSTR, "int %ld", v->w);
+			n = readstr(offset, a, n, p);
+			break;
+#ifdef REG_QWORD
+		case REG_QWORD:
+			snprint(p, READSTR, "int %lld", v->q);
+			n = readstr(offset, a, n, p);
+			break;
+#endif
+		case REG_SZ:
+		case REG_EXPAND_SZ:
+			if(v->data[0])
+				snprint(p, READSTR, "str %q", v->data);
+			n = readstr(offset, a, n, p);
+			break;
+		case REG_MULTI_SZ:
+			l = snprint(p, READSTR, "str");
+			for(i=0;;){
+				l += snprint(p+l, READSTR-l, " %q", v->data+i);
+				while(v->data[i++] != 0){
+					/* skip */
+				}
+				if(v->data[i] == 0)
+					break;	/* final terminator */
+			}
+			n = readstr(offset, a, n, p);
+			break;
+		case REG_BINARY:
+			l = n;
+			n = readstr(offset, a, l, "bin");
+			if(n >= 3){
+				offset -= 3;
+				if(offset+l > v->size)
+					l = v->size - offset;
+				memmove((char*)a+n, v->data+offset, l);
+				n += l;
+			}
+			break;
+		default:
+			error("unknown registry type");
+			n=0;
+			break;
+		}
+		poperror();
+		free(p);
+		c->aux = nil;
+		free(v);
+		break;
+	case Qhostmem:
+		mem.dwLength = sizeof(mem);
+		GlobalMemoryStatus(&mem);	/* GlobalMemoryStatusEx isn't on NT */
+		p = smalloc(READSTR);
+		if(waserror()){
+			free(p);
+			nexterror();
+		}
+		snprint(p, READSTR, "load %ld\nphys %lud %lud\nvirt %lud %lud\nswap %lud %lud\n",
+			mem.dwMemoryLoad,
+			mem.dwAvailPhys, mem.dwTotalPhys, mem.dwAvailVirtual, mem.dwTotalVirtual,
+			mem.dwAvailPageFile, mem.dwTotalPageFile);
+		n = readstr(offset, a, n, p);
+		poperror();
+		free(p);
+		break;
+	default:
+		n=0;
+		break;
+	}
+	return n;
+}
+
+static long
+archwrite(Chan* c, void* a, long n, vlong offset)
+{
+	Value *v;
+	int i;
+	Cmdbuf *cb;
+	Rune16 *key, *item;
+
+	if((ulong)c->qid.path != Qregquery)
+		error(Eperm);
+	USED(offset);
+	if(c->aux != nil){
+		free(c->aux);
+		c->aux = nil;
+	}
+	cb = parsecmd(a, n);
+	if(waserror()){
+		free(cb);
+		nexterror();
+	}
+	if(cb->nf < 3)
+		error(Ebadctl);
+	for(i=0; i<nelem(roots); i++)
+		if(strcmp(cb->f[0], roots[i].name) == 0)
+			break;
+	if(i >= nelem(roots))
+		errorf("unknown root: %s", cb->f[0]);
+	key = widen(cb->f[1]);
+	if(waserror()){
+		free(key);
+		nexterror();
+	}
+	item = widen(cb->f[2]);
+	if(waserror()){
+		free(item);
+		nexterror();
+	}
+	v = getregistry(roots[i].root, key, item);
+	if(v == nil)
+		error(up->env->errstr);
+	c->aux = v;
+	poperror();
+	free(item);
+	poperror();
+	free(key);
+	poperror();
+	free(cb);
+	return n;
+}
+
+Dev archdevtab = {
+	'a',
+	"arch",
+
+	archinit,
+	archattach,
+	archwalk,
+	archstat,
+	archopen,
+	devcreate,
+	archclose,
+	archread,
+	devbread,
+	archwrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};
+
+static void
+regerr(int rc)
+{
+	Rune16 err[64];
+	char emsg[sizeof(err)*UTFmax+1];
+
+	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
+		0, rc, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+		err, sizeof(err), 0);
+	runes16toutf(emsg, err, nelem(err));
+	error(emsg);
+}
+
+static Value*
+getregistry(HKEY root, Rune16 *keyname, Rune16 *name)
+{
+	long res;
+	HKEY key;
+	DWORD dtype, n;
+	int i, l, nb;
+	void* vp;
+	char *p;
+	Value *val;
+	Rune16 *rb;
+
+	qlock(&reglock);
+	if(waserror()){
+		qunlock(&reglock);
+		return nil;
+	}
+	res = RegOpenKey(root, keyname, &key);
+	if(res != ERROR_SUCCESS)
+		regerr(res);
+	if(waserror()){
+		RegCloseKey(key);
+		nexterror();
+	}
+	n = 0;
+	res = RegQueryValueEx(key, name, NULL, &dtype, NULL, &n);
+	if(res != ERROR_SUCCESS)
+		regerr(res);
+	nb = n;
+	if(dtype == REG_SZ || dtype == REG_EXPAND_SZ || dtype == REG_MULTI_SZ){
+		nb = n*UTFmax + 1;
+		rb = smalloc((n+2)*sizeof(Rune16));
+		memset(rb, 0, (n+2)*sizeof(Rune16));
+	}else
+		rb = nil;
+	if(waserror()){
+		free(rb);
+		nexterror();
+	}
+	val = smalloc(sizeof(Value)+nb);
+	if(waserror()){
+		free(val);
+		nexterror();
+	}
+	val->type = dtype;
+	val->size = n;
+	switch(dtype){
+	case REG_DWORD:
+		vp = &val->w;
+		break;
+#ifdef REG_QWORD
+	case REG_QWORD:
+		vp = &val->q;
+		break;
+#endif
+	case REG_SZ:
+	case REG_EXPAND_SZ:
+	case REG_MULTI_SZ:
+		vp = rb;
+		break;
+	case REG_BINARY:
+	case REG_NONE:
+		vp = val->data;
+		break;
+	default:
+		errorf("unsupported registry type: %d", dtype);
+		return nil;	/* for compiler */
+	}
+	res = RegQueryValueEx(key, name, NULL, NULL, vp, &n);
+	if(res != ERROR_SUCCESS)
+		regerr(res);
+	poperror();
+	if(rb != nil){
+		if(dtype == REG_MULTI_SZ){
+			p = val->data;
+			for(i=0;;){
+				l = runes16len(rb+i);
+				runes16toutf(p, rb+i, l);
+				i += l+1;
+				if(rb[i] == 0)
+					break;
+				p += strlen(p)+1;
+			}
+		}else
+			runes16toutf(val->data, rb, n);
+		free(rb);
+	}
+	poperror();
+	poperror();
+	RegCloseKey(key);
+	poperror();
+	qunlock(&reglock);
+	return val;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Nt/deveia.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,672 @@
+/*
+ *  Windows serial driver
+ *
+ * to do:
+ *	scan the registry for serial ports?
+ */
+
+#define Unknown win_Unknown
+#include	<windows.h>
+#undef Unknown
+#undef	Sleep
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+#include	<sys/types.h>
+#include	<sys/stat.h>
+#include	<fcntl.h>
+#include	<stdio.h>
+#include	<lm.h>
+#include	<direct.h>
+
+// local fcts
+static void openport(int);
+static void wrctl(int, char*);
+static long rdstat(int, void*, long, ulong );
+
+enum
+{
+	Devchar = 't',
+
+	Ndataqid = 1,
+	Nctlqid,
+	Nstatqid,
+	Nqid = 3,		/* number of QIDs */
+
+	Maxctl = 128,
+
+	// in/out buffer sizes for comm port (NT requires an even number)
+	// set it to x* the max styx message rounded up to the
+	// nearest 4 byte value
+	CommBufSize = ((((8192+128)*2)+3) & ~3)
+};
+
+/*
+ *  Macros to manage QIDs
+ */
+#define NETTYPE(x)	((x)&0x0F)
+#define NETID(x)	((x)>>4)
+#define NETQID(i,t)	(((i)<<4)|(t))
+
+static Dirtab *eiadir;
+static int ndir;
+
+typedef struct Eia Eia;
+struct Eia {
+	Ref	r;
+	HANDLE      comfh;          //handle to open port
+	int		restore;       //flag to restore prev. states 
+	DCB		dcb;           //win32 device control block used for restore
+	int		id;            //index to host port name in sysdev
+};
+
+// the same timeouts are used for all ports
+// currently there is no Inferno interface to
+// change the timeouts.
+static COMMTIMEOUTS  timeouts;  
+                   
+// std win32 serial port names are COM1..COM4
+// however there can be more and they can be
+// named anything. we should be more flexible
+// pehaps adding a ctl command to allow you to
+// access any win32 comm port
+static char* sysdev[] = {
+	"COM1:",
+	"COM2:",
+	"COM3:",
+	"COM4:",
+	"COM5:",
+	"COM6:",
+	"COM7:",
+	"COM8:",
+	NULL
+};
+    
+static Eia *eia;
+
+typedef struct OptTable OptTable;
+struct OptTable {
+	char   *str;
+	DWORD  flag;
+};
+
+#define BAD ((DWORD)-1)
+
+// valid bit-per-byte sizes
+static OptTable size[] = {
+	{"5",	5},
+	{"6",	6},
+	{"7",	7},
+	{"8",	8},
+	{NULL,  BAD}
+};
+
+// valid stop bits
+static OptTable stopbits[] = {
+	{"1",    ONESTOPBIT},
+	{"1.5",  ONE5STOPBITS},
+	{"2",    TWOSTOPBITS},
+	{NULL,   BAD}
+};
+
+// valid parity settings
+static OptTable parity[] = {
+	{"o",    ODDPARITY},
+	{"e",    EVENPARITY},
+	{"s",    SPACEPARITY},
+	{"m",    MARKPARITY},
+	{"n",    NOPARITY},
+	{NULL,   NOPARITY}
+};
+
+
+static char *
+ftos(OptTable *tbl, DWORD flag)
+{
+	while(tbl->str && tbl->flag != flag)
+		tbl++;
+	if(tbl->str == 0)
+		return "unknown";
+	return tbl->str;
+}
+
+static DWORD
+stof(OptTable *tbl, char *str)
+{
+	while(tbl->str && strcmp(tbl->str, str) != 0)
+		tbl++;
+	return tbl->flag;
+}
+
+static void
+eiainit(void)
+{
+	int     i,x;
+	byte    ports;   //bitmask of active host ports
+	int     nports;  //number of active host ports
+	int     max;     //number of highest port
+	Dirtab *dp;
+
+	// setup the timeouts; choose carefully
+	timeouts.ReadIntervalTimeout = 2;
+	timeouts.ReadTotalTimeoutMultiplier = 0;
+	timeouts.ReadTotalTimeoutConstant = 200;
+	timeouts.WriteTotalTimeoutMultiplier = 0;
+	timeouts.WriteTotalTimeoutConstant = 400;
+
+	// check to see which ports exist by trying to open them
+	// keep results in a bitmask
+	ports = nports = max = 0;
+	for(i=0; (sysdev[i] != NULL) && (i<8); i++) {
+		HANDLE comfh = CreateFile(sysdev[i], 0, 0, NULL,	/* no security attrs */
+			OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+
+		if(comfh != INVALID_HANDLE_VALUE) {
+			ports |= 1<<i;   
+			CloseHandle(comfh);
+			nports++;
+			max = i;
+		}
+	}
+
+	if(nports == 0)
+		return;  //no ports
+
+	// allocate directory table and eia structure
+	// for each active port.
+	ndir = Nqid*nports+1;
+	dp = eiadir = malloc(ndir*sizeof(Dirtab));
+	if(dp == 0)
+		panic("eiainit");
+	eia = malloc(nports*sizeof(Eia));
+	if(eia == 0) {
+		free(dp);
+		panic("eiainit");
+	}
+
+	// fill in the directory table and initialize
+	// the eia structure.  skip inactive ports.
+	sprint(dp->name, ".");
+	dp->qid.path = 0;
+	dp->qid.type = QTDIR;
+	dp->perm = DMDIR|0555;
+	dp++;
+	x = 0;  // index in eia[]
+	for(i = 0; i <= max; i++) {
+		if( (ports & (1<<i)) == 0)
+			continue;  //port 'i' is not active
+		sprint(dp->name, "eia%d", i);
+		dp->qid.path = NETQID(x, Ndataqid);
+		dp->perm = 0660;
+		dp++;
+		sprint(dp->name, "eia%dctl", i);
+		dp->qid.path = NETQID(x, Nctlqid);
+		dp->perm = 0660;
+		dp++;
+		sprint(dp->name, "eia%dstatus", i);
+		dp->qid.path = NETQID(x, Nstatqid);
+		dp->perm = 0660;
+		dp++;
+		// init the eia structure
+		eia[x].restore = 0;
+		eia[x].id = i;
+		x++;
+	}
+}
+
+static Chan*
+eiaattach(char *spec)
+{
+	if(eiadir == nil)
+		error(Enodev);
+
+	return devattach(Devchar, spec);
+}
+
+static Walkqid*
+eiawalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	return devwalk(c, nc, name, nname, eiadir, ndir, devgen);
+}
+
+static int
+eiastat(Chan *c, uchar *db, int n)
+{
+	return devstat(c, db, n, eiadir, ndir, devgen);
+}
+
+static Chan*
+eiaopen(Chan *c, int mode)
+{
+	int port = NETID(c->qid.path);
+
+	c = devopen(c, mode, eiadir, ndir, devgen);
+
+	switch(NETTYPE(c->qid.path)) {
+	case Nctlqid:
+	case Ndataqid:
+	case Nstatqid:
+		if(incref(&eia[port].r) != 1)
+			break;
+		if(waserror()) {
+			decref(&eia[port].r);
+			nexterror();
+		}
+		openport(port);
+		poperror();
+		break;
+	}
+	return c;
+}
+
+static void
+eiaclose(Chan *c)
+{
+	int port = NETID(c->qid.path);
+
+	if((c->flag & COPEN) == 0)
+		return;
+
+	switch(NETTYPE(c->qid.path)) {
+	case Nctlqid:
+	case Ndataqid:
+	case Nstatqid:
+		if(decref(&eia[port].r) == 0) {
+			osenter();
+			CloseHandle(eia[port].comfh);
+			osleave();
+		}
+		break;
+	}
+
+}
+
+static long
+eiaread(Chan *c, void *buf, long n, vlong offset)
+{
+	DWORD cnt;
+	int port = NETID(c->qid.path);
+	BOOL good;
+
+	if(c->qid.type & QTDIR)
+		return devdirread(c, buf, n, eiadir, ndir, devgen);
+
+	switch(NETTYPE(c->qid.path)) {
+	case Ndataqid:
+		cnt = 0;
+		// if ReadFile timeouts and cnt==0 then just re-read
+		// this will give osleave() a chance to detect an
+		// interruption (i.e. killprog)
+		while(cnt==0) {
+  			osenter(); 
+			good = ReadFile(eia[port].comfh, buf, n, &cnt, NULL);
+			SleepEx(0,FALSE);  //allow another thread access to port
+			osleave();
+			if(!good)
+				oserror();
+		}
+		return cnt;
+	case Nctlqid:
+		return readnum(offset, buf, n, eia[port].id, NUMSIZE);
+	case Nstatqid:
+		return rdstat(port, buf, n, offset);
+	}
+
+	return 0;
+}
+
+static long
+eiawrite(Chan *c, void *buf, long n, vlong offset)
+{
+	DWORD cnt;
+	char cmd[Maxctl];
+	int port = NETID(c->qid.path);
+	BOOL good;
+	uchar *data;
+
+	if(c->qid.type & QTDIR)
+		error(Eperm);
+
+	switch(NETTYPE(c->qid.path)) {
+	case Ndataqid:
+		cnt = 0;
+		data = (uchar*)buf;
+		// if WriteFile times out (i.e. return true; cnt<n) then
+		// allow osleave() to check for an interrupt otherwise try
+		// to send the unsent data.
+		while(n>0) {
+	  		osenter(); 
+			good = WriteFile(eia[port].comfh, data, n, &cnt, NULL);
+			osleave(); 
+			if(!good)
+				oserror();
+			data += cnt;
+			n -= cnt;
+		}
+		return (data-(uchar*)buf);
+	case Nctlqid:
+		if(n >= sizeof(cmd))
+			n = sizeof(cmd)-1;
+		memmove(cmd, buf, n);
+		cmd[n] = 0;
+		wrctl(port, cmd);
+		return n;
+	}
+	return 0;
+}
+
+static int
+eiawstat(Chan *c, uchar *dp, int n)
+{
+	Dir d;
+	int i;
+
+	if(!iseve())
+		error(Eperm);
+	if(c->qid.type & QTDIR)
+		error(Eperm);
+	if(NETTYPE(c->qid.path) == Nstatqid)
+		error(Eperm);
+
+	n = convM2D(dp, n, &d, nil);
+	i = Nqid*NETID(c->qid.path)+NETTYPE(c->qid.path)-Ndataqid;
+	if(d.mode != ~0UL)
+		eiadir[i+1].perm = d.mode&0666;
+	return n;
+}
+
+Dev eiadevtab = {
+        Devchar,
+        "eia",
+
+        eiainit,
+        eiaattach,
+        eiawalk,
+        eiastat,
+        eiaopen,
+        devcreate,
+        eiaclose,
+        eiaread,
+        devbread,
+        eiawrite,
+        devbwrite,
+        devremove,
+        eiawstat
+};
+
+
+//
+// local functions
+//
+
+/*
+ * open the indicated comm port and then set 
+ * the default settings for the port.
+ */
+static void
+openport(int port)
+{
+	Eia* p = &eia[port];
+
+	// open the port
+	p->comfh = CreateFile(sysdev[p->id], 
+		GENERIC_READ|GENERIC_WRITE,     //open underlying port for rd/wr
+		0,	                            //comm port can't be shared
+		NULL,	                        //no security attrs
+		OPEN_EXISTING,                  //a must for comm port
+		FILE_ATTRIBUTE_NORMAL,          //nonoverlapped io
+		NULL);                          //another must for comm port
+
+	if(p->comfh == INVALID_HANDLE_VALUE)
+		oserror();
+	if(waserror()){
+		CloseHandle(p->comfh);
+		p->comfh = INVALID_HANDLE_VALUE;
+		nexterror();
+	}
+
+	// setup in/out buffers (NT requires an even number)
+	if(!SetupComm(p->comfh, CommBufSize, CommBufSize))
+		oserror();
+
+	// either use existing settings or set defaults
+	if(!p->restore) {
+		// set default settings
+		if(!GetCommState(p->comfh, &p->dcb))
+			oserror();
+		p->dcb.BaudRate = 9600;
+		p->dcb.ByteSize = 8;
+		p->dcb.fParity = 0;
+		p->dcb.Parity = NOPARITY;
+		p->dcb.StopBits = ONESTOPBIT;
+		p->dcb.fInX = 0;  //default to xoff
+		p->dcb.fOutX = 0;  
+		p->dcb.fAbortOnError = 1; //read/write abort on err
+	}
+
+	// set state and timeouts
+	if(!SetCommState(p->comfh, &p->dcb) ||
+	   !SetCommTimeouts(p->comfh, &timeouts))
+		oserror();
+	poperror();
+}
+
+/*
+ * Obtain status information on the com port.
+ */
+static long
+rdstat(int port, void *buf, long n, ulong offset)
+{
+	HANDLE comfh = eia[port].comfh;
+	char str[Maxctl];
+	char *s;
+	DCB dcb;
+	DWORD modemstatus;
+	DWORD porterr;
+	COMSTAT  portstat;
+	int frame, overrun, i;
+
+	// valid line control ids
+	static enum {
+		L_CTS, L_DSR, L_RING, L_DCD, L_DTR, L_RTS, L_MAX
+	};
+	int status[L_MAX];
+
+	// line control strings (should match above id's)
+	static char* lines[] = {
+		"cts", "dsr", "ring", "dcd", "dtr",	"rts", NULL
+	};
+
+
+	// get any error conditions; also clears error flag
+	// and enables io
+	if(!ClearCommError(comfh, &porterr, &portstat))
+		oserror();
+
+	// get comm port state
+	if(!GetCommState(comfh, &dcb))
+		oserror();
+
+	// get modem line information
+	if(!GetCommModemStatus(comfh, &modemstatus))
+		oserror();
+
+	// now set our local flags
+	status[L_CTS] = MS_CTS_ON & modemstatus;
+	status[L_DSR] = MS_DSR_ON & modemstatus;
+	status[L_RING] = MS_RING_ON & modemstatus;
+	status[L_DCD] = MS_RLSD_ON & modemstatus;
+	status[L_DTR] = FALSE;  //?? cand this work: dcb.fDtrControl;
+	status[L_RTS] = FALSE;  //??   dcb.fRtsControl;
+	frame = porterr & CE_FRAME;
+	overrun = porterr & CE_OVERRUN;
+
+	/* TO DO: mimic native eia driver's first line */
+
+	s = seprint(str, str+sizeof(str), "opens %d ferr %d oerr %d baud %d", 
+		    eia[port].r.ref-1, 
+			frame, 
+			overrun,
+		    dcb.BaudRate);
+
+	// add line settings
+	for(i=0; i < L_MAX; i++) 
+		if(status[i])
+			s = seprint(s, str+sizeof(str), " %s", lines[i]);
+	seprint(s, str+sizeof(str), "\n");
+	return readstr(offset, buf, n, str);
+}
+
+//
+// write on ctl file. modify the settings for
+// the underlying port.
+//
+static void
+wrctl(int port, char *cmd)
+{
+	DCB dcb;
+	int nf, n,  i;
+	char *f[16];
+	HANDLE comfh = eia[port].comfh;
+	DWORD  flag, opt;
+	BOOL   rslt;
+	int chg;
+
+	// get the current settings for the port
+	if(!GetCommState(comfh, &dcb))
+		oserror();
+
+	chg = 0;
+	nf = tokenize(cmd, f, nelem(f));
+	for(i = 0; i < nf; i++){
+		if(strcmp(f[i], "break") == 0){
+			if(!SetCommBreak(comfh))
+				oserror();
+			SleepEx((DWORD)300, FALSE);
+			if(!ClearCommBreak(comfh))
+				oserror();
+			continue;
+		}
+
+		n = atoi(f[i]+1);
+		switch(*f[i]) {
+		case 'B':
+		case 'b':	// set the baud rate
+			if(n < 110)
+				error(Ebadarg);
+			dcb.BaudRate = n;
+			chg = 1;
+			break;
+		case 'C':
+		case 'c':
+			/* dcd */
+			break;
+		case 'D':
+		case 'd':  // set DTR
+			opt = n ? SETDTR : CLRDTR;
+			if(!EscapeCommFunction(comfh, opt))
+				oserror();
+			break;
+		case 'E':
+		case 'e':
+			/* dsr */
+			break;
+		case 'F':
+		case 'f':	// flush any untransmitted data
+			if(!PurgeComm(comfh, PURGE_TXCLEAR)) 
+				oserror();
+			break;
+		case 'H':
+		case 'h':
+			/* hangup */
+			/* TO DO: close handle */
+			break;
+		case 'I':
+		case 'i':
+			/* fifo: nothing to do */
+			break;
+		case 'K':
+		case 'k':
+			/* send a break */
+			if(!SetCommBreak(comfh))
+				oserror();
+			SleepEx((DWORD)300, FALSE);
+			if(!ClearCommBreak(comfh))
+				oserror();
+			break;
+		case 'L':
+		case 'l':	// set bits per byte 
+			flag = stof(size, f[0]+1);
+			if(flag == BAD)
+				error(Ebadarg);
+			dcb.ByteSize = (BYTE)flag;
+			chg = 1;
+			break;
+		case 'M':
+		case 'm':	// set CTS (modem control)
+			dcb.fOutxCtsFlow = (n!=0);
+			chg = 1;
+			break;
+		case 'N':
+		case 'n':
+			/* don't block on output */
+			break;
+		case 'P':
+		case 'p':	// set parity -- even or odd
+			flag = stof(parity, f[0]+1);
+			if(flag==BAD)
+				error(Ebadarg);
+			dcb.Parity = (BYTE)flag;
+			chg = 1;
+			break;
+		case 'Q':
+		case 'q':
+			/* set i/o queue limits */
+			break;
+		case 'R':
+		case 'r':	// set RTS
+			opt = n ? SETRTS : CLRRTS;
+			if(!EscapeCommFunction(comfh, opt))
+				oserror();
+			break;
+		case 'S':
+		case 's':	// set stop bits -- valid: 1 or 2 (win32 allows 1.5??)
+			flag = stof(stopbits, f[0]+1);
+			if(flag==BAD)
+				error(Ebadarg);
+			dcb.StopBits = flag;
+			chg = 1;
+			break;
+		case 'T':
+		case 't':
+			break;
+		case 'W':
+		case 'w':
+			/* set uart timer */
+			break;
+		case 'X':
+		case 'x':	// xon/xoff
+			opt = n ? SETXON : SETXOFF;
+			if(!EscapeCommFunction(comfh, opt))
+				oserror();
+			break;
+		default:
+			/* ignore */
+			break;
+		}
+	}
+
+	if(!chg)
+		return;
+	// make the changes on the underlying port, but flush
+	// outgoing chars down the port before
+	osenter();
+	rslt = FlushFileBuffers(comfh);
+	if(rslt)
+		rslt = SetCommState(comfh, &dcb);
+	osleave();
+	if(!rslt)
+		oserror();
+	eia[port].restore = 1;
+	eia[port].dcb = dcb;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Nt/devfs.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,2375 @@
+#define UNICODE
+#define Unknown win_Unknown
+#include	<windows.h>
+#include	<winbase.h>
+#undef Unknown
+#undef	Sleep
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+#include	"r16.h"
+#include	<lm.h>
+
+/* TODO: try using / in place of \ in path names */
+
+#ifndef SID_MAX_SUB_AUTHORITIES
+#define	SID_MAX_SUB_AUTHORITIES	15
+#endif
+
+enum
+{
+	MAX_SID		= sizeof(SID) + SID_MAX_SUB_AUTHORITIES*sizeof(DWORD),
+	ACL_ROCK	= sizeof(ACL) + 20*(sizeof(ACCESS_ALLOWED_ACE)+MAX_SID),
+	SD_ROCK		= SECURITY_DESCRIPTOR_MIN_LENGTH + MAX_SID + ACL_ROCK,
+	MAXCOMP		= 128,
+};
+
+typedef struct	User	User;
+typedef struct  Gmem	Gmem;
+typedef	struct	Stat	Stat;
+typedef	struct Fsinfo	Fsinfo;
+typedef	WIN32_FIND_DATA	Fsdir;
+
+#ifndef INVALID_SET_FILE_POINTER
+#define	INVALID_SET_FILE_POINTER	((DWORD)-1)
+#endif
+
+struct Fsinfo
+{
+	int	uid;
+	int	gid;
+	int	mode;
+	int	fd;
+	vlong	offset;
+	QLock	oq;
+	char*	spec;
+	Rune16*	srv;
+	Cname*	name;	/* Windows' idea of the file name */
+	ushort	usesec;
+	ushort	checksec;
+	Fsdir*	de;	/* non-nil for saved entry from last dirread at offset */
+};
+#define	FS(c)	((Fsinfo*)(c)->aux)
+
+/*
+ * info about a user or group
+ * there are two ways to specify a user:
+ *	by sid, a unique identifier
+ *	by user and domain names
+ * this structure is used to convert between the two,
+ * as well as figure out which groups a users belongs to.
+ * the user information never gets thrown away,
+ * but the group information gets refreshed with each setid.
+ */
+struct User
+{
+	QLock	lk;		/* locks the gotgroup and group fields */
+	SID	*sid;
+	Rune16	*name;
+	Rune16	*dom;
+	int	type;		/* the type of sid, ie SidTypeUser, SidTypeAlias, ... */
+	int	gotgroup;	/* tried to add group */
+	Gmem	*group;		/* global and local groups to which this user or group belongs. */
+	User	*next;
+};
+
+struct Gmem
+{
+	User	*user;
+	Gmem	*next;
+};
+
+/*
+ * intermediate stat information
+ */
+struct Stat
+{
+	User	*owner;
+	User	*group;
+	ulong	mode;
+};
+
+/*
+ * some "well-known" sids
+ */
+static	SID	*creatorowner;
+static	SID	*creatorgroup;
+static	SID	*everyone;
+static	SID	*ntignore;
+static	SID	*ntroot;	/* user who is supposed to run emu as a server */
+
+/*
+ * all users we ever see end up in this table
+ * users are never deleted, but we should update
+ * group information for users sometime
+ */
+static struct
+{
+	QLock	lk;
+	User	*u;
+}users;
+
+/*
+ * conversion from inferno permission modes to nt access masks
+ * is this good enough?  this is what nt sets, except for NOMODE
+ */
+#define	NOMODE	(READ_CONTROL|FILE_READ_EA|FILE_READ_ATTRIBUTES)
+#define	RMODE	(READ_CONTROL|SYNCHRONIZE\
+		|FILE_READ_DATA|FILE_READ_EA|FILE_READ_ATTRIBUTES)
+#define	XMODE	(READ_CONTROL|SYNCHRONIZE\
+		|FILE_EXECUTE|FILE_READ_ATTRIBUTES)
+#define	WMODE	(DELETE|READ_CONTROL|SYNCHRONIZE|WRITE_DAC|WRITE_OWNER\
+		|FILE_WRITE_DATA|FILE_APPEND_DATA|FILE_WRITE_EA\
+		|FILE_DELETE_CHILD|FILE_WRITE_ATTRIBUTES)
+
+static	int
+modetomask[] =
+{
+	NOMODE,
+	XMODE,
+	WMODE,
+	WMODE|XMODE,
+	RMODE,
+	RMODE|XMODE,
+	RMODE|WMODE,
+	RMODE|WMODE|XMODE,
+};
+
+extern	DWORD	PlatformId;
+	char    rootdir[MAXROOT] = "\\inferno";
+	Rune16	rootname[] = L"inferno-server";
+static	Qid	rootqid;
+static	User	*fsnone;
+static	User	*fsuser;
+static	Rune16	*ntsrv;
+static	int	usesec;
+static	int	checksec;
+static	int	isserver;
+static	int	file_share_delete;
+static	uchar	isntfrog[256];
+
+static	void		fsremove(Chan*);
+
+	wchar_t	*widen(char *s);
+	char		*narrowen(wchar_t *ws);
+	int		widebytes(wchar_t *ws);
+
+static char Etoolong[] = "file name too long";
+
+extern	int		nth2fd(HANDLE);
+extern	HANDLE		ntfd2h(int);
+static	int		cnisroot(Cname*);
+static	int		fsisroot(Chan*);
+static	int		okelem(char*, int);
+static	int		fsexist(char*, Qid*);
+static	char*	fspath(Cname*, char*, char*, char*);
+static	Cname*	fswalkpath(Cname*, char*, int);
+static	char*	fslastelem(Cname*);
+static	long		fsdirread(Chan*, uchar*, int, vlong);
+static	ulong		fsqidpath(char*);
+static	int		fsomode(int);
+static	int		fsdirset(char*, int, WIN32_FIND_DATA*, char*, Chan*, int isdir);
+static 	int		fsdirsize(WIN32_FIND_DATA*, char*, Chan*);
+static	void		fssettime(char*, long, long);
+static	long		unixtime(FILETIME);
+static	FILETIME	wintime(ulong);
+static	void		secinit(void);
+static	int		secstat(Dir*, char*, Rune16*);
+static	int		secsize(char*, Rune16*);
+static	void		seccheck(char*, ulong, Rune16*);
+static	int		sechasperm(char*, ulong, Rune16*);
+static	SECURITY_DESCRIPTOR* secsd(char*, char[SD_ROCK]);
+static	int		secsdhasperm(SECURITY_DESCRIPTOR*, ulong, Rune16*);
+static	int		secsdstat(SECURITY_DESCRIPTOR*, Stat*, Rune16*);
+static	SECURITY_DESCRIPTOR* secmksd(char[SD_ROCK], Stat*, ACL*, int);
+static	SID		*dupsid(SID*);
+static	int		ismembersid(Rune16*, User*, SID*);
+static	int		ismember(User*, User*);
+static	User		*sidtouser(Rune16*, SID*);
+static	User		*domnametouser(Rune16*, Rune16*, Rune16*);
+static	User		*nametouser(Rune16*, Rune16*);
+static	User		*unametouser(Rune16*, char*);
+static	void		addgroups(User*, int);
+static	User		*mkuser(SID*, int, Rune16*, Rune16*);
+static	Rune16		*domsrv(Rune16 *, Rune16[MAX_PATH]);
+static	Rune16		*filesrv(char*);
+static	int		fsacls(char*);
+static	User		*secuser(void);
+
+
+int
+winfilematch(char *path, WIN32_FIND_DATA *data)
+{
+	char *p;
+	wchar_t *wpath;
+	int r;
+
+	p = path+strlen(path);
+	while(p > path && p[-1] != '\\')
+		--p;
+	wpath = widen(p);
+	r = (data->cFileName[0] == '.' && runes16len(data->cFileName) == 1)
+			|| runes16cmp(data->cFileName, wpath) == 0;
+	free(wpath);
+	return r;
+}
+
+int
+winfileclash(char *path)
+{
+	HANDLE h;
+	WIN32_FIND_DATA data;
+	wchar_t *wpath;
+
+	wpath = widen(path);
+	h = FindFirstFile(wpath, &data);
+	free(wpath);
+	if (h != INVALID_HANDLE_VALUE) {
+		FindClose(h);
+		return !winfilematch(path, &data);
+	}
+	return 0;
+}
+
+
+/*
+ * this gets called to set up the environment when we switch users
+ */
+void
+setid(char *name, int owner)
+{
+	User *u;
+
+	if(owner && !iseve())
+		return;
+
+	kstrdup(&up->env->user, name);
+
+	if(!usesec)
+		return;
+
+	u = unametouser(ntsrv, up->env->user);
+	if(u == nil)
+		u = fsnone;
+	else {
+		qlock(&u->lk);
+		addgroups(u, 1);
+		qunlock(&u->lk);
+	}
+	if(u == nil)
+		panic("setid: user nil\n");
+
+	up->env->ui = u;
+}
+
+static void
+fsfree(Chan *c)
+{
+	cnameclose(FS(c)->name);
+	if(FS(c)->de != nil)
+		free(FS(c)->de);
+	free(FS(c));
+}
+
+void
+fsinit(void)
+{
+	int n, isvol;
+	ulong attr;
+	char *p, tmp[MAXROOT];
+	wchar_t *wp, *wpath, *last;
+	wchar_t wrootdir[MAXROOT];
+
+	isntfrog['/'] = 1;
+	isntfrog['\\'] = 1;
+	isntfrog[':'] = 1;
+	isntfrog['*'] = 1;
+	isntfrog['?'] = 1;
+	isntfrog['"'] = 1;
+	isntfrog['<'] = 1;
+	isntfrog['>'] = 1;
+
+	/*
+	 * vet the root
+	 */
+	strcpy(tmp, rootdir);
+	for(p = tmp; *p; p++)
+		if(*p == '/')
+			*p = '\\';
+	if(tmp[0] != 0 && tmp[1] == ':') {
+		if(tmp[2] == 0) {
+			tmp[2] = '\\';
+			tmp[3] = 0;
+		}
+		else if(tmp[2] != '\\') {
+			/* don't allow c:foo - only c:\foo */
+			panic("illegal root pathX");
+		}
+	}
+	wrootdir[0] = '\0';
+	wpath = widen(tmp);
+	for(wp = wpath; *wp; wp++) {
+		if(*wp < 32 || (*wp < 256 && isntfrog[*wp] && *wp != '\\' && *wp != ':'))
+			panic("illegal root path");
+	}
+	n = GetFullPathName(wpath, MAXROOT, wrootdir, &last);
+	free(wpath);	
+	runes16toutf(rootdir, wrootdir, MAXROOT);
+	if(n >= MAXROOT || n == 0)
+		panic("illegal root path");
+
+	/* get rid of trailing \ */
+	while(rootdir[n-1] == '\\') {
+		if(n <= 2) {
+			panic("illegal root path");
+		}
+		rootdir[--n] = '\0';
+	}
+
+	isvol = 0;
+	if(rootdir[1] == ':' && rootdir[2] == '\0')
+		isvol = 1;
+	else if(rootdir[0] == '\\' && rootdir[1] == '\\') {
+		p = strchr(&rootdir[2], '\\');
+		if(p == nil)
+			panic("inferno root can't be a server");
+		isvol = strchr(p+1, '\\') == nil;
+	}
+
+	if(strchr(rootdir, '\\') == nil)
+		strcat(rootdir, "\\.");
+	attr = GetFileAttributes(wrootdir);
+	if(attr == 0xFFFFFFFF)
+		panic("root path '%s' does not exist", narrowen(wrootdir));
+	rootqid.path = fsqidpath(rootdir);
+	if(attr & FILE_ATTRIBUTE_DIRECTORY)
+		rootqid.type |= QTDIR;
+	rootdir[n] = '\0';
+
+	rootqid.vers = time(0);
+
+	/*
+	 * set up for nt file security checking
+	 */
+	ntsrv = filesrv(rootdir);
+	usesec = PlatformId == VER_PLATFORM_WIN32_NT; 	/* true for NT and 2000 */
+	if(usesec){
+		file_share_delete = FILE_SHARE_DELETE;	/* sensible handling of shared files by delete and rename */
+		secinit();
+		if(!fsacls(rootdir))
+			usesec = 0;
+	}
+	checksec = usesec && isserver;
+}
+
+Chan*
+fsattach(char *spec)
+{
+	Chan *c;
+	static int devno;
+	static Lock l;
+	char *drive = (char *)spec;
+
+	if (!emptystr(drive) && (drive[1] != ':' || drive[2] != '\0'))
+		error(Ebadspec);
+
+	c = devattach('U', spec);
+	lock(&l);
+	c->dev = devno++;
+	unlock(&l);
+	c->qid = rootqid;
+	c->aux = smalloc(sizeof(Fsinfo));
+	FS(c)->srv = ntsrv;
+	if(!emptystr(spec)) {
+		char *s = smalloc(strlen(spec)+1);
+		strcpy(s, spec);
+		FS(c)->spec = s;
+		FS(c)->srv = filesrv(spec);
+		if(usesec)
+			FS(c)->usesec = fsacls(spec);
+		FS(c)->checksec = FS(c)->usesec && isserver;
+		c->qid.path = fsqidpath(spec);
+		c->qid.type = QTDIR;
+		c->qid.vers = 0;
+	}else{
+		FS(c)->usesec = usesec;
+		FS(c)->checksec = checksec;
+	}
+	FS(c)->name = newcname("/");
+	return c;
+}
+
+Walkqid*
+fswalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	int j, alloc;
+	Walkqid *wq;
+	char path[MAX_PATH], *p;
+	Cname *ph;
+	Cname *current, *next;
+
+	if(nname > 0)
+		isdir(c);
+
+	alloc = 0;
+	current = nil;
+	wq = smalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid));
+	if(waserror()){
+		if(alloc && wq->clone != nil)
+			cclose(wq->clone);
+		cnameclose(current);
+		free(wq);
+		return nil;
+	}
+	if(nc == nil){
+		nc = devclone(c);
+		nc->type = 0;
+		alloc = 1;
+	}
+	wq->clone = nc;
+	current = FS(c)->name;
+	if(current != nil)
+		incref(&current->r);
+	for(j = 0; j < nname; j++){
+		if(!(nc->qid.type&QTDIR)){
+			if(j==0)
+				error(Enotdir);
+			break;
+		}
+		if(!okelem(name[j], 0)){
+			if(j == 0)
+				error(Efilename);
+			break;
+		}
+		p = fspath(current, name[j], path, FS(c)->spec);
+		if(FS(c)->checksec) {
+			*p = '\0';
+			if(!sechasperm(path, XMODE, FS(c)->srv)){
+				if(j == 0)
+					error(Eperm);
+				break;
+			}
+			*p = '\\';
+		}
+
+		if(strcmp(name[j], "..") == 0) {
+			if(fsisroot(c))
+				nc->qid = rootqid;
+			else{
+				ph = fswalkpath(current, "..", 1);
+				if(cnisroot(ph)){
+					nc->qid = rootqid;
+					current = ph;
+					if(current != nil)
+						incref(&current->r);
+				}
+				else {
+					fspath(ph, 0, path, FS(c)->spec);
+					if(!fsexist(path, &nc->qid)){
+						cnameclose(ph);
+						if(j == 0)
+							error(Enonexist);
+						break;
+					}
+				}
+				next = fswalkpath(current, name[j], 1);
+				cnameclose(current);
+				current = next;
+				cnameclose(ph);
+			}
+		}
+		else{
+			if(!fsexist(path, &nc->qid)){
+				if(j == 0)
+					error(Enonexist);
+				break;
+			}
+			next = fswalkpath(current, name[j], 1);
+			cnameclose(current);
+			current = next;
+		}
+		wq->qid[wq->nqid++] = nc->qid;
+	}
+	poperror();
+	if(wq->nqid < nname){
+		cnameclose(current);
+		if(alloc)
+			cclose(wq->clone);
+		wq->clone = nil;
+	}else if(wq->clone){
+		nc->aux = smalloc(sizeof(Fsinfo));
+		nc->type = c->type;
+		FS(nc)->spec = FS(c)->spec;
+		FS(nc)->srv = FS(c)->srv;
+		FS(nc)->name = current;
+		FS(nc)->usesec = FS(c)->usesec;
+		FS(nc)->checksec = FS(c)->checksec;
+	}
+	return wq;
+}
+
+Chan*
+fsopen(Chan *c, int mode)
+{
+	HANDLE h;
+	int m, isdir, aflag, cflag;
+	char path[MAX_PATH];
+	wchar_t *wpath;
+
+	isdir = c->qid.type & QTDIR;
+	if(isdir && mode != OREAD)
+		error(Eperm);
+	fspath(FS(c)->name, 0, path, FS(c)->spec);
+
+	if(FS(c)->checksec) {
+		switch(mode & (OTRUNC|3)) {
+		case OREAD:
+			seccheck(path, RMODE, FS(c)->srv);
+			break;
+		case OWRITE:
+		case OWRITE|OTRUNC:
+			seccheck(path, WMODE, FS(c)->srv);
+			break;
+		case ORDWR:
+		case ORDWR|OTRUNC:
+		case OREAD|OTRUNC:
+			seccheck(path, RMODE|WMODE, FS(c)->srv);
+			break;
+		case OEXEC:
+			seccheck(path, XMODE, FS(c)->srv);
+			break;
+		default:
+			error(Ebadarg);
+		}
+	}
+
+	c->mode = openmode(mode);
+	if(isdir)
+		FS(c)->fd = nth2fd(INVALID_HANDLE_VALUE);
+	else {
+		m = fsomode(mode & 3);
+		cflag = OPEN_EXISTING;
+		if(mode & OTRUNC)
+			cflag = TRUNCATE_EXISTING;
+		aflag = FILE_FLAG_RANDOM_ACCESS;
+		if(mode & ORCLOSE)
+			aflag |= FILE_FLAG_DELETE_ON_CLOSE;
+		if (winfileclash(path))
+			error(Eexist);
+		wpath = widen(path);
+		h = CreateFile(wpath, m, FILE_SHARE_READ|FILE_SHARE_WRITE|file_share_delete, 0, cflag, aflag, 0);
+		free(wpath);
+		if(h == INVALID_HANDLE_VALUE)
+			oserror();
+		FS(c)->fd = nth2fd(h);
+	}
+
+	c->offset = 0;
+	FS(c)->offset = 0;
+	c->flag |= COPEN;
+	return c;
+}
+
+void
+fscreate(Chan *c, char *name, int mode, ulong perm)
+{
+	Stat st;
+	HANDLE h;
+	int m, aflag;
+	SECURITY_ATTRIBUTES sa;
+	SECURITY_DESCRIPTOR *sd;
+	BY_HANDLE_FILE_INFORMATION hi;
+	char *p, path[MAX_PATH], sdrock[SD_ROCK];
+	wchar_t *wpath;
+	ACL *acl;
+
+	if(!okelem(name, 1))
+		error(Efilename);
+
+	m = fsomode(mode & 3);
+	p = fspath(FS(c)->name, name, path, FS(c)->spec);
+	acl = (ACL*)smalloc(ACL_ROCK);
+	sd = nil;
+	if(FS(c)->usesec) {
+		*p = '\0';
+		sd = secsd(path, sdrock);
+		*p = '\\';
+		if(sd == nil){
+			free(acl);
+			oserror();
+		}
+		if(FS(c)->checksec && !secsdhasperm(sd, WMODE, FS(c)->srv)
+		|| !secsdstat(sd, &st, FS(c)->srv)){
+			if(sd != (void*)sdrock)
+				free(sd);
+			free(acl);
+			error(Eperm);
+		}
+		if(sd != (void*)sdrock)
+			free(sd);
+		if(perm & DMDIR)
+			st.mode = (perm & ~0777) | (st.mode & perm & 0777);
+		else
+			st.mode = (perm & ~0666) | (st.mode & perm & 0666);
+		st.owner = up->env->ui;
+		if(!isserver)
+			st.owner = fsuser;
+		sd = secmksd(sdrock, &st, acl, perm & DMDIR);
+		if(sd == nil){
+			free(acl);
+			oserror();
+		}
+	}
+	sa.nLength = sizeof(sa);
+	sa.lpSecurityDescriptor = sd;
+	sa.bInheritHandle = 0;
+
+	if(perm & DMDIR) {
+		if(mode != OREAD) {
+			free(acl);
+			error(Eisdir);
+		}
+		wpath = widen(path);
+		if(!CreateDirectory(wpath, &sa) || !fsexist(path, &c->qid)) {
+			free(wpath);
+			free(acl);
+			oserror();
+		}
+		free(wpath);
+		FS(c)->fd = nth2fd(INVALID_HANDLE_VALUE);
+	}
+	else {
+		aflag = 0;
+		if(mode & ORCLOSE)
+			aflag = FILE_FLAG_DELETE_ON_CLOSE;
+		if (winfileclash(path))
+			error(Eexist);
+		wpath = widen(path);
+		h = CreateFile(wpath, m, FILE_SHARE_READ|FILE_SHARE_WRITE|file_share_delete, &sa, CREATE_ALWAYS, aflag, 0);
+		free(wpath);
+		if(h == INVALID_HANDLE_VALUE) {
+			free(acl);
+			oserror();
+		}
+		FS(c)->fd = nth2fd(h);
+		c->qid.path = fsqidpath(path);
+		c->qid.type = 0;
+		c->qid.vers = 0;
+		if(GetFileInformationByHandle(h, &hi))
+			c->qid.vers = unixtime(hi.ftLastWriteTime);
+	}
+
+	c->mode = openmode(mode);
+	c->offset = 0;
+	FS(c)->offset = 0;
+	c->flag |= COPEN;
+	FS(c)->name = fswalkpath(FS(c)->name, name, 0);
+	free(acl);
+}
+
+void
+fsclose(Chan *c)
+{
+	HANDLE h;
+
+	if(c->flag & COPEN){
+		h = ntfd2h(FS(c)->fd);
+		if(h != INVALID_HANDLE_VALUE){
+			if(c->qid.type & QTDIR)
+				FindClose(h);
+			else
+				CloseHandle(h);
+		}
+	}
+	if(c->flag & CRCLOSE){
+		if(!waserror()){
+			fsremove(c);
+			poperror();
+		}
+		return;
+	}
+	fsfree(c);
+}
+
+/*
+ * 64-bit seeks, using SetFilePointer because SetFilePointerEx
+ * is not supported by NT
+ */
+static void
+fslseek(HANDLE h, vlong offset)
+{
+	LONG hi;
+
+	if(offset <= 0x7fffffff){
+		if(SetFilePointer(h, (LONG)offset, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
+			oserror();
+	}else{
+		hi = offset>>32;
+		if(SetFilePointer(h, (LONG)offset, &hi, FILE_BEGIN) == INVALID_SET_FILE_POINTER &&
+		   GetLastError() != NO_ERROR)
+			oserror();
+	}
+}
+
+long
+fsread(Chan *c, void *va, long n, vlong offset)
+{
+	DWORD n2;
+	HANDLE h;
+
+	qlock(&FS(c)->oq);
+	if(waserror()){
+		qunlock(&FS(c)->oq);
+		nexterror();
+	}
+	if(c->qid.type & QTDIR) {
+		n2 = fsdirread(c, va, n, offset);
+	}
+	else {
+		h = ntfd2h(FS(c)->fd);
+		if(FS(c)->offset != offset){
+			fslseek(h, offset);
+			FS(c)->offset = offset;
+		}
+		if(!ReadFile(h, va, n, &n2, NULL))
+			oserror();
+		FS(c)->offset += n2;
+	}
+	qunlock(&FS(c)->oq);
+	poperror();
+	return n2;
+}
+
+long
+fswrite(Chan *c, void *va, long n, vlong offset)
+{
+	DWORD n2;
+	HANDLE h;
+
+	qlock(&FS(c)->oq);
+	if(waserror()){
+		qunlock(&FS(c)->oq);
+		nexterror();
+	}
+	h = ntfd2h(FS(c)->fd);
+	if(FS(c)->offset != offset){
+		fslseek(h, offset);
+		FS(c)->offset = offset;
+	}
+	if(!WriteFile(h, va, n, &n2, NULL))
+		oserror();
+	FS(c)->offset += n2;
+	qunlock(&FS(c)->oq);
+	poperror();
+	return n2;
+}
+
+int
+fsstat(Chan *c, uchar *buf, int n)
+{
+	WIN32_FIND_DATA data;
+	char path[MAX_PATH];
+	wchar_t *wpath;
+
+	/*
+	 * have to fake up a data for volumes like
+	 * c: and \\server\share since you can't FindFirstFile them
+	 */
+	if(fsisroot(c)){
+		strcpy(path, rootdir);
+		if(strchr(path, '\\') == nil)
+			strcat(path, "\\.");
+		wpath = widen(path);
+		data.dwFileAttributes = GetFileAttributes(wpath);
+		free(wpath);
+		if(data.dwFileAttributes == 0xffffffff)
+			oserror();
+		data.ftCreationTime =
+		data.ftLastAccessTime =
+		data.ftLastWriteTime = wintime(time(0));
+		data.nFileSizeHigh = 0;
+		data.nFileSizeLow = 0;
+		utftorunes16(data.cFileName, ".", MAX_PATH);
+	} else {
+		HANDLE h = INVALID_HANDLE_VALUE;
+
+		fspath(FS(c)->name, 0, path, FS(c)->spec);
+		if (c->flag & COPEN)
+			h = ntfd2h(FS(c)->fd);
+
+		if (h != INVALID_HANDLE_VALUE) {
+			BY_HANDLE_FILE_INFORMATION fi;
+			if (c->mode & OWRITE)
+				FlushFileBuffers(h);
+			if (!GetFileInformationByHandle(h, &fi))
+				oserror();
+			data.dwFileAttributes = fi.dwFileAttributes;
+			data.ftCreationTime = fi.ftCreationTime;
+			data.ftLastAccessTime = fi.ftLastAccessTime;
+			data.ftLastWriteTime = fi.ftLastWriteTime;;
+			data.nFileSizeHigh = fi.nFileSizeHigh;
+			data.nFileSizeLow = fi.nFileSizeLow;
+		} else {
+			wpath = widen(path);
+			h = FindFirstFile(wpath, &data);
+			free(wpath);
+			if(h == INVALID_HANDLE_VALUE)
+				oserror();
+			if (!winfilematch(path, &data)) {
+				FindClose(h);
+				error(Enonexist);
+			}
+			FindClose(h);
+		}
+		utftorunes16(data.cFileName, fslastelem(FS(c)->name), MAX_PATH);
+	}
+
+	return fsdirset(buf, n, &data, path, c, 0);
+}
+
+int
+fswstat(Chan *c, uchar *buf, int n)
+{
+	int wsd;
+	Dir dir;
+	Stat st;
+	Cname * volatile ph;
+	HANDLE h;
+	ulong attr;
+	User *ou, *gu;
+	WIN32_FIND_DATA data;
+	SECURITY_DESCRIPTOR *sd;
+	char *last, sdrock[SD_ROCK], path[MAX_PATH], newpath[MAX_PATH], strs[4*256];
+	wchar_t wspath[MAX_PATH], wsnewpath[MAX_PATH];
+	wchar_t *wpath;
+	int nmatch;
+
+	n = convM2D(buf, n, &dir, strs);
+	if(n == 0)
+		error(Eshortstat);
+
+	last = fspath(FS(c)->name, 0, path, FS(c)->spec);
+	utftorunes16(wspath, path, MAX_PATH);
+
+	if(fsisroot(c)){
+		if(dir.atime != ~0)
+			data.ftLastAccessTime = wintime(dir.atime);
+		if(dir.mtime != ~0)
+			data.ftLastWriteTime = wintime(dir.mtime);
+		utftorunes16(data.cFileName, ".", MAX_PATH);
+	}else{
+		h = FindFirstFile(wspath, &data);
+		if(h == INVALID_HANDLE_VALUE)
+			oserror();
+		if (!winfilematch(path, &data)) {
+			FindClose(h);
+			error(Enonexist);
+		}
+		FindClose(h);
+	}
+
+	wsd = 0;
+	ou = nil;
+	gu = nil;
+	if(FS(c)->usesec) {
+		if(FS(c)->checksec && up->env->ui == fsnone)
+			error(Eperm);
+
+		/*
+		 * find new owner and group
+		 */
+		if(!emptystr(dir.uid)){
+			ou = unametouser(FS(c)->srv, dir.uid);
+			if(ou == nil)
+				oserror();
+		}
+		if(!emptystr(dir.gid)){
+			gu = unametouser(FS(c)->srv, dir.gid);
+			if(gu == nil){
+				if(strcmp(dir.gid, "unknown") != 0
+				&& strcmp(dir.gid, "deleted") != 0)
+					oserror();
+				gu = ou;
+			}
+		}
+
+		/*
+		 * find old stat info
+		 */
+		sd = secsd(path, sdrock);
+		if(sd == nil || !secsdstat(sd, &st, FS(c)->srv)){
+			if(sd != nil && sd != (void*)sdrock)
+				free(sd);
+			oserror();
+		}
+		if(sd != (void*)sdrock)
+			free(sd);
+
+		/*
+		 * permission rules:
+		 * if none, can't do anything
+		 * chown => no way
+		 * chgrp => current owner or group, and in new group
+		 * mode/time => owner or in either group
+		 * rename => write in parent
+		 */
+		if(ou == nil)
+			ou = st.owner;
+		if(FS(c)->checksec && st.owner != ou)
+			error(Eperm);
+
+		if(gu == nil)
+			gu = st.group;
+		if(st.group != gu){
+			if(FS(c)->checksec
+			&&(!ismember(up->env->ui, ou) && !ismember(up->env->ui, gu)
+			|| !ismember(up->env->ui, st.group)))
+				error(Eperm);
+			wsd = 1;
+		}
+
+		if(dir.atime != ~0 && unixtime(data.ftLastAccessTime) != dir.atime
+		|| dir.mtime != ~0 && unixtime(data.ftLastWriteTime) != dir.mtime
+		|| dir.mode != ~0 && st.mode != dir.mode){
+			if(FS(c)->checksec
+			&& !ismember(up->env->ui, ou)
+			&& !ismember(up->env->ui, gu)
+			&& !ismember(up->env->ui, st.group))
+				error(Eperm);
+			if(dir.mode != ~0 && st.mode != dir.mode)
+				wsd = 1;
+		}
+	}
+	wpath = widen(dir.name);
+	nmatch = runes16cmp(wpath, data.cFileName);
+	free(wpath);
+	if(!emptystr(dir.name) && nmatch != 0){
+		if(!okelem(dir.name, 1))
+			error(Efilename);
+		ph = fswalkpath(FS(c)->name, "..", 1);
+		if(waserror()){
+			cnameclose(ph);
+			nexterror();
+		}
+		ph = fswalkpath(ph, dir.name, 0);
+		fspath(ph, 0, newpath, FS(c)->spec);
+		utftorunes16(wsnewpath, newpath, MAX_PATH);
+		if(GetFileAttributes(wpath) != 0xffffffff && !winfileclash(newpath))
+			error("file already exists");
+		if(fsisroot(c))
+			error(Eperm);
+		if(FS(c)->checksec){
+			*last = '\0';
+			seccheck(path, WMODE, FS(c)->srv);
+			*last = '\\';
+		}
+		poperror();
+		cnameclose(ph);
+	}
+
+	if(dir.atime != ~0 && unixtime(data.ftLastAccessTime) != dir.atime
+	|| dir.mtime != ~0 && unixtime(data.ftLastWriteTime) != dir.mtime)
+		fssettime(path, dir.atime, dir.mtime);
+
+	attr = data.dwFileAttributes;
+	if(dir.mode & 0222)
+		attr &= ~FILE_ATTRIBUTE_READONLY;
+	else
+		attr |= FILE_ATTRIBUTE_READONLY;
+	if(!fsisroot(c)
+	&& attr != data.dwFileAttributes
+	&& (attr & FILE_ATTRIBUTE_READONLY))
+		SetFileAttributes(wspath, attr);
+	if(FS(c)->usesec && wsd){
+		ACL *acl = (ACL *) smalloc(ACL_ROCK);
+		st.owner = ou;
+		st.group = gu;
+		if(dir.mode != ~0)
+			st.mode = dir.mode;
+		sd = secmksd(sdrock, &st, acl, data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
+		if(sd == nil || !SetFileSecurity(wspath, DACL_SECURITY_INFORMATION, sd)){
+			free(acl);
+			oserror();
+		}
+		free(acl);
+	}
+
+	if(!fsisroot(c)
+	&& attr != data.dwFileAttributes
+	&& !(attr & FILE_ATTRIBUTE_READONLY))
+		SetFileAttributes(wspath, attr);
+
+	/* do last so path is valid throughout */
+	wpath = widen(dir.name);
+	nmatch = runes16cmp(wpath, data.cFileName);
+	free(wpath);
+	if(!emptystr(dir.name) && nmatch != 0) {
+		ph = fswalkpath(FS(c)->name, "..", 1);
+		if(waserror()){
+			cnameclose(ph);
+			nexterror();
+		}
+		ph = fswalkpath(ph, dir.name, 0);
+		fspath(ph, 0, newpath, FS(c)->spec);
+		utftorunes16(wsnewpath, newpath, MAX_PATH);
+		/*
+		 * can't rename if it is open: if this process has it open, close it temporarily.
+		 */
+		if(!file_share_delete && c->flag & COPEN){
+			h = ntfd2h(FS(c)->fd);
+			if(h != INVALID_HANDLE_VALUE)
+				CloseHandle(h);	/* woe betide it if ORCLOSE */
+			FS(c)->fd = nth2fd(INVALID_HANDLE_VALUE);
+		}
+		if(!MoveFile(wspath, wsnewpath)) {
+			oserror();
+		} else if(!file_share_delete && c->flag & COPEN) {
+			int	aflag;
+			SECURITY_ATTRIBUTES sa;
+			
+			/* The move succeeded, so open new file to maintain handle */
+			sa.nLength = sizeof(sa);
+			sa.lpSecurityDescriptor = sd;
+			sa.bInheritHandle = 0;
+			if(c->flag & CRCLOSE)
+				aflag = FILE_FLAG_DELETE_ON_CLOSE;
+			h = CreateFile(wsnewpath, fsomode(c->mode & 0x3), FILE_SHARE_READ|FILE_SHARE_WRITE|file_share_delete, &sa, OPEN_EXISTING, aflag, 0);
+			if(h == INVALID_HANDLE_VALUE)
+				oserror();
+			FS(c)->fd = nth2fd(h);
+		}
+		cnameclose(FS(c)->name);
+		poperror();
+		FS(c)->name = ph;
+	}
+	return n;
+}
+
+static void
+fsremove(Chan *c)
+{
+	int n;
+	char *p, path[MAX_PATH];
+	wchar_t wspath[MAX_PATH];
+
+	if(waserror()){
+		fsfree(c);
+		nexterror();
+	}
+	if(fsisroot(c))
+		error(Eperm);
+	p = fspath(FS(c)->name, 0, path, FS(c)->spec);
+	utftorunes16(wspath, path, MAX_PATH);
+	if(FS(c)->checksec){
+		*p = '\0';
+		seccheck(path, WMODE, FS(c)->srv);
+		*p = '\\';
+	}
+	if(c->qid.type & QTDIR)
+		n = RemoveDirectory(wspath);
+	else
+		n = DeleteFile(wspath);
+	if (!n) {
+		ulong attr, mode;
+		SECURITY_DESCRIPTOR *sd = nil;
+		char sdrock[SD_ROCK];
+		Stat st;
+		int secok;
+		attr = GetFileAttributes(wspath);
+		if(attr != 0xFFFFFFFF) {
+			if (FS(c)->usesec) {
+				sd = secsd(path, sdrock);
+				secok = (sd != nil) && secsdstat(sd, &st, FS(c)->srv);
+				if (secok) {
+					ACL *acl = (ACL *) smalloc(ACL_ROCK);
+					mode = st.mode;
+					st.mode |= 0660;
+					sd = secmksd(sdrock, &st, acl, attr & FILE_ATTRIBUTE_DIRECTORY);
+					if(sd != nil) {
+						SetFileSecurity(wspath, DACL_SECURITY_INFORMATION, sd);
+					}
+					free(acl);
+					if(sd != nil && sd != (void*)sdrock)
+						free(sd);
+					sd = nil;
+				}
+			}
+			SetFileAttributes(wspath, FILE_ATTRIBUTE_NORMAL);
+			if(c->qid.type & QTDIR)
+				n = RemoveDirectory(wspath);
+			else
+				n = DeleteFile(wspath);
+			if (!n) {
+				if (FS(c)->usesec && secok) {
+					ACL *acl = (ACL *) smalloc(ACL_ROCK);
+					st.mode =  mode;
+					sd = secmksd(sdrock, &st, acl, attr & FILE_ATTRIBUTE_DIRECTORY);
+					if(sd != nil) {
+						SetFileSecurity(wspath, DACL_SECURITY_INFORMATION, sd);
+					}
+					free(acl);
+				}
+				SetFileAttributes(wspath, attr);
+				if(sd != nil && sd != (void*)sdrock)
+					free(sd);
+			}
+		}
+	}
+	if(!n)
+		oserror();
+	poperror();
+	fsfree(c);
+}
+
+/*
+ * check elem for illegal characters /\:*?"<>
+ * ... and relatives are also disallowed,
+ * since they specify grandparents, which we
+ * are not prepared to handle
+ */
+static int
+okelem(char *elem, int nodots)
+{
+	int c, dots;
+
+	dots = 0;
+	while((c = *(uchar*)elem) != 0){
+		if(isntfrog[c])
+			return 0;
+		if(c == '.' && dots >= 0)
+			dots++;
+		else
+			dots = -1;
+		elem++;
+	}
+	if(nodots)
+		return dots <= 0;
+	return dots <= 2;
+}
+
+static int
+cnisroot(Cname *c)
+{
+	return strcmp(c->s, "/") == 0;
+}
+
+static int
+fsisroot(Chan *c)
+{
+	return strcmp(FS(c)->name->s, "/") == 0;
+}
+
+static char*
+fspath(Cname *c, char *ext, char *path, char *spec)
+{
+	char *p, *last, *rootd;
+	int extlen = 0;
+
+	rootd = spec != nil ? spec : rootdir;
+	if(ext)
+		extlen = strlen(ext) + 1;
+	if(strlen(rootd) + extlen >= MAX_PATH)
+		error(Etoolong);
+	strcpy(path, rootd);
+	if(cnisroot(c)){
+		if(ext) {
+			strcat(path, "\\");
+			strcat(path, ext);
+		}
+	}else{
+		if(*c->s != '/') {
+			if(strlen(path) + 1 >= MAX_PATH)
+				error(Etoolong);
+			strcat(path, "\\");
+		}
+		if(strlen(path) + strlen(c->s) + extlen >= MAX_PATH)
+			error(Etoolong);
+		strcat(path, c->s);
+		if(ext){
+			strcat(path, "\\");
+			strcat(path, ext);
+		}
+	}
+	last = path;
+	for(p = path; *p != '\0'; p++){
+		if(*p == '/' || *p == '\\'){
+			*p = '\\';
+			last = p;
+		}
+	}
+	return last;
+}
+
+extern void cleancname(Cname*);
+
+static Cname *
+fswalkpath(Cname *c, char *name, int dup)
+{
+	if(dup)
+		c = newcname(c->s);
+	c = addelem(c, name);
+	if(isdotdot(name))
+		cleancname(c);
+	return c;
+}
+
+static char *
+fslastelem(Cname *c)
+{
+	char *p;
+
+	p = c->s + c->len;
+	while(p > c->s && p[-1] != '/')
+		p--;
+	return p;
+}
+
+static int
+fsdirbadentry(WIN32_FIND_DATA *data)
+{
+	wchar_t *s;
+
+	s = data->cFileName;
+	if(s[0] == 0)
+		return 1;
+	if(s[0] == '.' && (s[1] == 0 || s[1] == '.' && s[2] == 0))
+		return 1;
+
+	return 0;
+}
+
+static Fsdir*
+fsdirent(Chan *c, char *path, Fsdir *data)
+{
+	wchar_t *wpath;
+	HANDLE h;
+
+	h = ntfd2h(FS(c)->fd);
+	if(data == nil)
+		data = smalloc(sizeof(*data));
+	if(FS(c)->offset == 0){
+		if(h != INVALID_HANDLE_VALUE)
+			FindClose(h);
+		wpath = widen(path);
+		h = FindFirstFile(wpath, data);
+		free(wpath);
+		FS(c)->fd = nth2fd(h);
+		if(h == INVALID_HANDLE_VALUE){
+			free(data);
+			return nil;
+		}
+		if(!fsdirbadentry(data))
+			return data;
+	}
+	do{
+		if(!FindNextFile(h, data)){
+			free(data);
+			return nil;
+		}
+	}while(fsdirbadentry(data));
+	return data;
+}
+
+static long
+fsdirread(Chan *c, uchar *va, int count, vlong offset)
+{
+	int i, r;
+	char path[MAX_PATH], *p;
+	Fsdir *de;
+	vlong o;
+
+	if(count == 0 || offset < 0)
+		return 0;
+	p = fspath(FS(c)->name, "*.*", path, FS(c)->spec);
+	p++;
+	de = nil;
+	if(FS(c)->offset != offset){
+		de = FS(c)->de;
+		if(FS(c)->de != nil){
+			free(FS(c)->de);
+			FS(c)->de = nil;
+		}
+		FS(c)->offset = 0;
+		for(o = 0; o < offset;){
+			de = fsdirent(c, path, de);
+			if(de == nil){
+				FS(c)->offset = o;
+				return 0;
+			}
+			runes16toutf(p, de->cFileName, &path[MAX_PATH]-p);
+			path[MAX_PATH-1] = '\0';
+			o += fsdirsize(de, path, c);
+		}
+		FS(c)->offset = offset;
+	}
+	for(i = 0; i < count;){
+		if(FS(c)->de != nil){	/* left over from previous read at offset */
+			de = FS(c)->de;
+			FS(c)->de = nil;
+		}else{
+			de = fsdirent(c, path, de);
+			if(de == nil)
+				break;
+		}
+		runes16toutf(p, de->cFileName, &path[MAX_PATH]-p);
+		path[MAX_PATH-1] = '\0';
+		r = fsdirset(va+i, count-i, de, path, c, 1);
+		if(r <= 0){
+			/* won't fit; save for next read at this offset */
+			FS(c)->de = de;
+			break;
+		}
+		i += r;
+		FS(c)->offset += r;
+	}
+	return i;
+}
+
+static ulong
+fsqidpath(char *p)
+{
+	ulong h;
+	int c;
+
+	h = 0;
+	while(*p != '\0'){
+		/* force case insensitive file names */
+		c = *p++;
+		if(c >= 'A' && c <= 'Z')
+			c += 'a'-'A';
+		h = h * 19 ^ c;
+	}
+	return h;
+}
+
+/* TO DO: substitute fixed, made-up (unlikely) names for these */
+static char* devf[] = { "aux", "com1", "com2", "lpt1", "nul", "prn", nil };
+
+static int
+devfile(char *p)
+{
+	char *s, *t, *u, **ss;
+
+	if((u = strrchr(p, '\\')) != nil)
+		u++;
+	else if((u = strrchr(p, '/')) != nil)
+		u++;
+	else
+		u = p;
+	for(ss = devf; *ss != nil; ss++){
+		for(s = *ss, t = u; *s != '\0' && *t != '\0' && *t != '.'; s++, t++)
+			if(*s != *t && *s != *t+'a'-'A')
+				break;
+		if(*s == '\0' && (*t == '\0' || *t == '.'))
+			return 1;
+	}
+	return 0;
+}
+
+/*
+ * there are other ways to figure out
+ * the attributes and times for a file.
+ * perhaps they are faster
+ */
+static int
+fsexist(char *p, Qid *q)
+{
+	HANDLE h;
+	WIN32_FIND_DATA data;
+	wchar_t *wpath;
+
+	if(devfile(p))
+		return 0;
+	wpath = widen(p);
+	h = FindFirstFile(wpath, &data);
+	free(wpath);
+	if(h == INVALID_HANDLE_VALUE)
+		return 0;
+			if (!winfilematch(p, &data)) {
+				FindClose(h);
+				return 0;
+			}
+	FindClose(h);
+
+	q->path = fsqidpath(p);
+	q->type = 0;
+
+	if(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+		q->type |= QTDIR;
+
+	q->vers = unixtime(data.ftLastWriteTime);
+
+	return 1;
+}
+
+static int
+fsdirset(char *edir, int n, WIN32_FIND_DATA *data, char *path, Chan *c, int isdir)
+{
+	Dir dir;
+	static char neveryone[] = "Everyone";
+
+	dir.name = narrowen(data->cFileName);
+	dir.muid = nil;
+	dir.qid.path = fsqidpath(path);
+	dir.qid.vers = 0;
+	dir.qid.type = 0;
+	dir.mode = 0;
+	dir.atime = unixtime(data->ftLastAccessTime);
+	dir.mtime = unixtime(data->ftLastWriteTime);
+	dir.qid.vers = dir.mtime;
+	dir.length = ((uvlong)data->nFileSizeHigh<<32) | ((uvlong)data->nFileSizeLow & ~((uvlong)0xFFFFFFFF<<32));
+	dir.type = 'U';
+	dir.dev = c->dev;
+
+	if(!FS(c)->usesec){
+		/* no NT security so make something up */
+		dir.uid = neveryone;
+		dir.gid = neveryone;
+		dir.mode = 0777;
+	}else if(!secstat(&dir, path, FS(c)->srv))
+		oserror();
+
+	if(data->dwFileAttributes & FILE_ATTRIBUTE_READONLY)
+		dir.mode &= ~0222;
+	if(data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){
+		dir.qid.type |= QTDIR;
+		dir.mode |= DMDIR;
+		dir.length = 0;
+	}
+
+	if(isdir && sizeD2M(&dir) > n)
+		n = -1;
+	else 
+		n = convD2M(&dir, edir, n);
+	if(dir.uid != neveryone)
+		free(dir.uid);
+	if(dir.gid != neveryone)
+		free(dir.gid);
+	free(dir.name);
+	return n;
+}
+
+static int
+fsdirsize(WIN32_FIND_DATA *data, char *path, Chan *c)
+{
+	int i, n;
+
+	n = widebytes(data->cFileName);
+	if(!FS(c)->usesec)
+		n += 8+8;
+	else{
+		i = secsize(path, FS(c)->srv);
+		if(i < 0)
+			oserror();
+		n += i;
+	}
+	return STATFIXLEN+n;
+}
+
+static void
+fssettime(char *path, long at, long mt)
+{
+	HANDLE h;
+	FILETIME atime, mtime;
+	wchar_t *wpath;
+
+	wpath = widen(path);
+	h = CreateFile(wpath, GENERIC_WRITE,
+		0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
+	free(wpath);
+	if(h == INVALID_HANDLE_VALUE)
+		return;
+	mtime = wintime(mt);
+	atime = wintime(at);
+	if(!SetFileTime(h, 0, &atime, &mtime)){
+		CloseHandle(h);
+		oserror();
+	}
+	CloseHandle(h);
+}
+
+static int
+fsomode(int m)
+{
+	switch(m & 0x3) {
+	case OREAD:
+	case OEXEC:
+		return GENERIC_READ;
+	case OWRITE:
+		return GENERIC_WRITE;
+	case ORDWR:
+		return GENERIC_READ|GENERIC_WRITE;
+	}
+	error(Ebadarg);
+	return 0;
+}
+
+static long
+unixtime(FILETIME ft)
+{
+	vlong t;
+
+	t = (vlong)ft.dwLowDateTime + ((vlong)ft.dwHighDateTime<<32);
+	t -= (vlong)10000000*134774*24*60*60;
+
+	return (long)(t/10000000);
+}
+
+static FILETIME
+wintime(ulong t)
+{
+	FILETIME ft;
+	vlong vt;
+
+	vt = (vlong)t*10000000+(vlong)10000000*134774*24*60*60;
+
+	ft.dwLowDateTime = vt;
+	ft.dwHighDateTime = vt>>32;
+
+	return ft;
+}
+
+/*
+ * the sec routines manage file permissions for nt.
+ * nt files have an associated security descriptor,
+ * which has in it an owner, a group,
+ * and a discretionary acces control list, or acl,
+ * which specifies the permissions for the file.
+ *
+ * the strategy for mapping between inferno owner,
+ * group, other, and mode and nt file security is:
+ *
+ *	inferno owner == nt file owner
+ *	inferno other == nt Everyone
+ *	inferno group == first non-owner,
+ *			non-Everyone user given in the acl,
+ *			or the owner if there is no such user.
+ * we examine the entire acl when check for permissions,
+ * but only report a subset.
+ *
+ * when we write an acl, we also give all permissions to
+ * the special user rootname, who is supposed to run emu in server mode.
+ */
+static void
+secinit(void)
+{
+	HANDLE token;
+	TOKEN_PRIVILEGES *priv;
+	char privrock[sizeof(TOKEN_PRIVILEGES) + 1*sizeof(LUID_AND_ATTRIBUTES)];
+	SID_IDENTIFIER_AUTHORITY id = SECURITY_CREATOR_SID_AUTHORITY;
+	SID_IDENTIFIER_AUTHORITY wid = SECURITY_WORLD_SID_AUTHORITY;
+	SID_IDENTIFIER_AUTHORITY ntid = SECURITY_NT_AUTHORITY;
+
+	if(!AllocateAndInitializeSid(&id, 1,
+		SECURITY_CREATOR_OWNER_RID,
+		1, 2, 3, 4, 5, 6, 7, &creatorowner)
+	|| !AllocateAndInitializeSid(&id, 1,
+		SECURITY_CREATOR_GROUP_RID,
+		1, 2, 3, 4, 5, 6, 7, &creatorgroup)
+	|| !AllocateAndInitializeSid(&wid, 1,
+		SECURITY_WORLD_RID,
+		1, 2, 3, 4, 5, 6, 7, &everyone)
+	|| !AllocateAndInitializeSid(&ntid, 1,
+		0,
+		1, 2, 3, 4, 5, 6, 7, &ntignore))
+		panic("can't initialize well-known sids");
+
+	fsnone = sidtouser(ntsrv, everyone);
+	if(fsnone == nil)
+		panic("can't make none user");
+
+	/*
+	 * see if we are running as the emu server user
+	 * if so, set up SE_RESTORE_NAME privilege,
+	 * which allows setting the owner field in a security descriptor.
+	 * other interesting privileges are SE_TAKE_OWNERSHIP_NAME,
+	 * which enables changing the ownership of a file to yourself
+	 * regardless of the permissions on the file, SE_BACKUP_NAME,
+	 * which enables reading any files regardless of permission,
+	 * and SE_CHANGE_NOTIFY_NAME, which enables walking through
+	 * directories without X permission.
+	 * SE_RESTORE_NAME and SE_BACKUP_NAME together allow writing
+	 * and reading any file data, regardless of permission,
+	 * if the file is opened with FILE_BACKUP_SEMANTICS.
+	 */
+	isserver = 0;
+	fsuser = secuser();
+	if(fsuser == nil)
+		fsuser = fsnone;
+	else if(runes16cmp(fsuser->name, rootname) == 0
+	     && OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &token)){
+		priv = (TOKEN_PRIVILEGES*)privrock;
+		priv->PrivilegeCount = 1;
+		priv->Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+		if(LookupPrivilegeValue(NULL, SE_RESTORE_NAME, &priv->Privileges[0].Luid)
+		&& AdjustTokenPrivileges(token, 0, priv, 0, NULL, NULL))
+			isserver = 1;
+		CloseHandle(token);
+	}
+}
+
+/*
+ * get the User for the executing process
+ */
+static User*
+secuser(void)
+{
+	DWORD need;
+	HANDLE token;
+	TOKEN_USER *tu;
+	char turock[sizeof(TOKEN_USER) + MAX_SID];
+
+	if(!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))
+		return nil;
+
+	tu = (TOKEN_USER*)turock;
+	if(!GetTokenInformation(token, TokenUser, tu, sizeof(turock), &need)){
+		CloseHandle(token);
+		return nil;
+	}
+	CloseHandle(token);
+	return sidtouser(nil, tu->User.Sid);
+}
+
+static int
+secstat(Dir *dir, char *file, Rune16 *srv)
+{
+	int ok, n;
+	Stat st;
+	char sdrock[SD_ROCK];
+	SECURITY_DESCRIPTOR *sd;
+
+	sd = secsd(file, sdrock);
+	if(sd == nil){
+		int e = GetLastError();
+		if(e == ERROR_ACCESS_DENIED || e == ERROR_SHARING_VIOLATION){
+			dir->uid = strdup("unknown");
+			dir->gid = strdup("unknown");
+			if(dir->uid == nil || dir->gid == nil){
+				free(dir->uid);
+				error(Enomem);	/* will change to use kstrdup */
+			}
+			dir->mode = 0;
+			return 1;
+		}
+		return 0;
+	}
+	ok = secsdstat(sd, &st, srv);
+	if(sd != (void*)sdrock)
+		free(sd);
+	if(ok){
+		dir->mode = st.mode;
+		n = rune16nlen(st.owner->name, runes16len(st.owner->name));
+		dir->uid = smalloc(n+1);
+		runes16toutf(dir->uid, st.owner->name, n+1);
+		n = rune16nlen(st.group->name, runes16len(st.group->name));
+		dir->gid = smalloc(n+1);
+		runes16toutf(dir->gid, st.group->name, n+1);
+	}
+	return ok;
+}
+
+static int
+secsize(char *file, Rune16 *srv)
+{
+	int ok;
+	Stat st;
+	char sdrock[SD_ROCK];
+	SECURITY_DESCRIPTOR *sd;
+
+	sd = secsd(file, sdrock);
+	if(sd == nil){
+		int e = GetLastError();
+		if(e == ERROR_ACCESS_DENIED || e == ERROR_SHARING_VIOLATION)
+			return 7+7;
+		return -1;
+	}
+	ok = secsdstat(sd, &st, srv);
+	if(sd != (void*)sdrock)
+		free(sd);
+	if(ok)
+		return rune16nlen(st.owner->name, runes16len(st.owner->name))+rune16nlen(st.group->name, runes16len(st.group->name));
+	return -1;
+}
+
+/*
+ * verify that u had access to file
+ */
+static void
+seccheck(char *file, ulong access, Rune16 *srv)
+{
+	if(!sechasperm(file, access, srv))
+		error(Eperm);
+}
+
+static int
+sechasperm(char *file, ulong access, Rune16 *srv)
+{
+	int ok;
+	char sdrock[SD_ROCK];
+	SECURITY_DESCRIPTOR *sd;
+
+	/*
+	 * only really needs dacl info
+	 */
+	sd = secsd(file, sdrock);
+	if(sd == nil)
+		return 0;
+	ok = secsdhasperm(sd, access, srv);
+	if(sd != (void*)sdrock)
+		free(sd);
+	return ok;
+}
+
+static SECURITY_DESCRIPTOR*
+secsd(char *file, char sdrock[SD_ROCK])
+{
+	DWORD need;
+	SECURITY_DESCRIPTOR *sd;
+	char *path, pathrock[6];
+	wchar_t *wpath;
+
+	path = file;
+	if(path[0] != '\0' && path[1] == ':' && path[2] == '\0'){
+		path = pathrock;
+		strcpy(path, "?:\\.");
+		path[0] = file[0];
+	}
+	sd = (SECURITY_DESCRIPTOR*)sdrock;
+	need = 0;
+	wpath = widen(path);
+	if(GetFileSecurity(wpath, OWNER_SECURITY_INFORMATION|DACL_SECURITY_INFORMATION, sd, SD_ROCK, &need)) {
+		free(wpath);
+		return sd;
+	}
+	 if(GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+		free(wpath);
+		return nil;
+	}
+	sd = malloc(need);
+	if(sd == nil) {
+		free(wpath);
+		error(Enomem);
+	}
+	if(GetFileSecurity(wpath, OWNER_SECURITY_INFORMATION|DACL_SECURITY_INFORMATION, sd, need, &need)) {
+		free(wpath);
+		return sd;
+	}
+	free(wpath);
+	free(sd);
+	return nil;
+}
+
+static int
+secsdstat(SECURITY_DESCRIPTOR *sd, Stat *st, Rune16 *srv)
+{
+	ACL *acl;
+	BOOL hasacl, b;
+	ACE_HEADER *aceh;
+	User *owner, *group;
+	SID *sid, *osid, *gsid;
+	ACCESS_ALLOWED_ACE *ace;
+	int i, allow, deny, *p, m;
+	ACL_SIZE_INFORMATION size;
+
+	st->mode = 0;
+
+	osid = nil;
+	gsid = nil;
+	if(!GetSecurityDescriptorOwner(sd, &osid, &b)
+	|| !GetSecurityDescriptorDacl(sd, &hasacl, &acl, &b))
+		return 0;
+
+	if(acl == 0)
+		size.AceCount = 0;
+	else if(!GetAclInformation(acl, &size, sizeof(size), AclSizeInformation))
+		return 0;
+
+	/*
+	 * first pass through acl finds group
+	 */
+	for(i = 0; i < size.AceCount; i++){
+		if(!GetAce(acl, i, &aceh))
+			continue;
+		if(aceh->AceFlags & INHERIT_ONLY_ACE)
+			continue;
+
+		if(aceh->AceType != ACCESS_ALLOWED_ACE_TYPE
+		&& aceh->AceType != ACCESS_DENIED_ACE_TYPE)
+			continue;
+
+		ace = (ACCESS_ALLOWED_ACE*)aceh;
+		sid = (SID*)&ace->SidStart;
+		if(EqualSid(sid, creatorowner) || EqualSid(sid, creatorgroup))
+			continue;
+
+		if(EqualSid(sid, everyone))
+			;
+		else if(EqualSid(sid, osid))
+			;
+		else if(EqualPrefixSid(sid, ntignore))
+			continue;		/* boring nt accounts */
+		else{
+			gsid = sid;
+			break;
+		}
+	}
+	if(gsid == nil)
+		gsid = osid;
+
+	owner = sidtouser(srv, osid);
+	if(owner == nil)
+		return 0;
+	group = sidtouser(srv, gsid);
+	if(group == nil)
+		return 0;
+
+	/* no acl means full access */
+	allow = 0;
+	if(acl == 0)
+		allow = 0777;
+	deny = 0;
+	for(i = 0; i < size.AceCount; i++){
+		if(!GetAce(acl, i, &aceh))
+			continue;
+		if(aceh->AceFlags & INHERIT_ONLY_ACE)
+			continue;
+
+		if(aceh->AceType == ACCESS_ALLOWED_ACE_TYPE)
+			p = &allow;
+		else if(aceh->AceType == ACCESS_DENIED_ACE_TYPE)
+			p = &deny;
+		else
+			continue;
+
+		ace = (ACCESS_ALLOWED_ACE*)aceh;
+		sid = (SID*)&ace->SidStart;
+		if(EqualSid(sid, creatorowner) || EqualSid(sid, creatorgroup))
+			continue;
+
+		m = 0;
+		if(ace->Mask & FILE_EXECUTE)
+			m |= 1;
+		if(ace->Mask & FILE_WRITE_DATA)
+			m |= 2;
+		if(ace->Mask & FILE_READ_DATA)
+			m |= 4;
+
+		if(ismembersid(srv, owner, sid))
+			*p |= (m << 6) & ~(allow|deny) & 0700;
+		if(ismembersid(srv, group, sid))
+			*p |= (m << 3) & ~(allow|deny) & 0070;
+		if(EqualSid(everyone, sid))
+			*p |= m & ~(allow|deny) & 0007;
+	}
+
+	st->mode = allow & ~deny;
+	st->owner = owner;
+	st->group = group;
+	return 1;
+}
+
+static int
+secsdhasperm(SECURITY_DESCRIPTOR *sd, ulong access, Rune16 *srv)
+{
+	User *u;
+	ACL *acl;
+	BOOL hasacl, b;
+	ACE_HEADER *aceh;
+	SID *sid, *osid, *gsid;
+	int i, allow, deny, *p, m;
+	ACCESS_ALLOWED_ACE *ace;
+	ACL_SIZE_INFORMATION size;
+
+	u = up->env->ui;
+	allow = 0;
+	deny = 0;
+	osid = nil;
+	gsid = nil;
+	if(!GetSecurityDescriptorDacl(sd, &hasacl, &acl, &b))
+		return 0;
+
+	/* no acl means full access */
+	if(acl == 0)
+		return 1;
+	if(!GetAclInformation(acl, &size, sizeof(size), AclSizeInformation))
+		return 0;
+	for(i = 0; i < size.AceCount; i++){
+		if(!GetAce(acl, i, &aceh))
+			continue;
+		if(aceh->AceFlags & INHERIT_ONLY_ACE)
+			continue;
+
+		if(aceh->AceType == ACCESS_ALLOWED_ACE_TYPE)
+			p = &allow;
+		else if(aceh->AceType == ACCESS_DENIED_ACE_TYPE)
+			p = &deny;
+		else
+			continue;
+
+		ace = (ACCESS_ALLOWED_ACE*)aceh;
+		sid = (SID*)&ace->SidStart;
+		if(EqualSid(sid, creatorowner) || EqualSid(sid, creatorgroup))
+			continue;
+
+		m = ace->Mask;
+
+		if(ismembersid(srv, u, sid))
+			*p |= m & ~(allow|deny);
+	}
+
+	allow &= ~deny;
+
+	return (allow & access) == access;
+}
+
+static SECURITY_DESCRIPTOR*
+secmksd(char *sdrock, Stat *st, ACL *dacl, int isdir)
+{
+	int m;
+
+	ulong mode;
+	ACE_HEADER *aceh;
+	SECURITY_DESCRIPTOR *sd;
+
+	sd = (SECURITY_DESCRIPTOR*)sdrock;
+	if(!InitializeAcl(dacl, ACL_ROCK, ACL_REVISION))
+		return nil;
+
+	mode = st->mode;
+	if(st->owner == st->group){
+		mode |= (mode >> 3) & 0070;
+		mode |= (mode << 3) & 0700;
+	}
+
+
+	m = modetomask[(mode>>6) & 7];
+	if(!AddAccessAllowedAce(dacl, ACL_REVISION, m, st->owner->sid))
+		return nil;
+
+	if(isdir && !AddAccessAllowedAce(dacl, ACL_REVISION, m, creatorowner))
+		return nil;
+
+	m = modetomask[(mode>>3) & 7];
+	if(!AddAccessAllowedAce(dacl, ACL_REVISION, m, st->group->sid))
+		return nil;
+
+	m = modetomask[(mode>>0) & 7];
+	if(!AddAccessAllowedAce(dacl, ACL_REVISION, m, everyone))
+		return nil;
+
+	if(isdir){
+		/* hack to add inherit flags */
+		if(!GetAce(dacl, 1, &aceh))
+			return nil;
+		aceh->AceFlags |= OBJECT_INHERIT_ACE|CONTAINER_INHERIT_ACE;
+		if(!GetAce(dacl, 2, &aceh))
+			return nil;
+		aceh->AceFlags |= OBJECT_INHERIT_ACE|CONTAINER_INHERIT_ACE;
+		if(!GetAce(dacl, 3, &aceh))
+			return nil;
+		aceh->AceFlags |= OBJECT_INHERIT_ACE|CONTAINER_INHERIT_ACE;
+	}
+
+	/*
+	 * allow server user to access any file
+	 */
+	if(isserver){
+		if(!AddAccessAllowedAce(dacl, ACL_REVISION, RMODE|WMODE|XMODE, fsuser->sid))
+			return nil;
+		if(isdir){
+			if(!GetAce(dacl, 4, &aceh))
+				return nil;
+			aceh->AceFlags |= OBJECT_INHERIT_ACE|CONTAINER_INHERIT_ACE;
+		}
+	}
+
+	if(!InitializeSecurityDescriptor(sd, SECURITY_DESCRIPTOR_REVISION))
+		return nil;
+	if(!SetSecurityDescriptorDacl(sd, 1, dacl, 0))
+		return nil;
+//	if(isserver && !SetSecurityDescriptorOwner(sd, st->owner->sid, 0))
+//		return nil;
+	return sd;
+}
+
+/*
+ * the user manipulation routines
+ * just make it easier to deal with user identities
+ */
+static User*
+sidtouser(Rune16 *srv, SID *s)
+{
+	SID_NAME_USE type;
+	Rune16 aname[100], dname[100];
+	DWORD naname, ndname;
+	User *u;
+
+	qlock(&users.lk);
+	for(u = users.u; u != 0; u = u->next)
+		if(EqualSid(s, u->sid))
+			break;
+	qunlock(&users.lk);
+
+	if(u != 0)
+		return u;
+
+	naname = sizeof(aname);
+	ndname = sizeof(dname);
+
+	if(!LookupAccountSidW(srv, s, aname, &naname, dname, &ndname, &type))
+		return mkuser(s, SidTypeUnknown, L"unknown", L"unknown");
+	return mkuser(s, type, aname, dname);
+}
+
+static User*
+domnametouser(Rune16 *srv, Rune16 *name, Rune16 *dom)
+{
+	User *u;
+
+	qlock(&users.lk);
+	for(u = users.u; u != 0; u = u->next)
+		if(runes16cmp(name, u->name) == 0 && runes16cmp(dom, u->dom) == 0)
+			break;
+	qunlock(&users.lk);
+	if(u == 0)
+		u = nametouser(srv, name);
+	return u;
+}
+
+static User*
+nametouser(Rune16 *srv, Rune16 *name)
+{
+	char sidrock[MAX_SID];
+	SID *sid;
+	SID_NAME_USE type;
+	Rune16 dom[MAX_PATH];
+	DWORD nsid, ndom;
+
+	sid = (SID*)sidrock;
+	nsid = sizeof(sidrock);
+	ndom = sizeof(dom);
+	if(!LookupAccountNameW(srv, name, sid, &nsid, dom, &ndom, &type))
+		return nil;
+
+	return mkuser(sid, type, name, dom);
+}
+
+/*
+ * this mapping could be cached
+ */
+static User*
+unametouser(Rune16 *srv, char *name)
+{
+	Rune16 rname[MAX_PATH];
+
+	utftorunes16(rname, name, MAX_PATH);
+	return nametouser(srv, rname);
+}
+
+/*
+ * make a user structure and add it to the global cache.
+ */
+static User*
+mkuser(SID *sid, int type, Rune16 *name, Rune16 *dom)
+{
+	User *u;
+
+	qlock(&users.lk);
+	for(u = users.u; u != 0; u = u->next){
+		if(EqualSid(sid, u->sid)){
+			qunlock(&users.lk);
+			return u;
+		}
+	}
+
+	switch(type) {
+	default:
+		break;
+	case SidTypeDeletedAccount:
+		name = L"deleted";
+		break;
+	case SidTypeInvalid:
+		name = L"invalid";
+		break;
+	case SidTypeUnknown:
+		name = L"unknown";
+		break;
+	}
+
+	u = malloc(sizeof(User));
+	if(u == nil){
+		qunlock(&users.lk);
+		return 0;
+	}
+	u->next = nil;
+	u->group = nil;
+	u->sid = dupsid(sid);
+	u->type = type;
+	u->name = nil;
+	if(name != nil)
+		u->name = runes16dup(name);
+	u->dom = nil;
+	if(dom != nil)
+		u->dom = runes16dup(dom);
+
+	u->next = users.u;
+	users.u = u;
+
+	qunlock(&users.lk);
+	return u;
+}
+
+/*
+ * check if u is a member of gsid,
+ * which might be a group.
+ */
+static int
+ismembersid(Rune16 *srv, User *u, SID *gsid)
+{
+	User *g;
+
+	if(EqualSid(u->sid, gsid))
+		return 1;
+
+	g = sidtouser(srv, gsid);
+	if(g == 0)
+		return 0;
+	return ismember(u, g);
+}
+
+static int
+ismember(User *u, User *g)
+{
+	Gmem *grps;
+
+	if(EqualSid(u->sid, g->sid))
+		return 1;
+
+	if(EqualSid(g->sid, everyone))
+		return 1;
+
+	qlock(&u->lk);
+	addgroups(u, 0);
+	for(grps = u->group; grps != 0; grps = grps->next){
+		if(EqualSid(grps->user->sid, g->sid)){
+			qunlock(&u->lk);
+			return 1;
+		}
+	}
+	qunlock(&u->lk);
+	return 0;
+}
+
+/*
+ * find out what groups a user belongs to.
+ * if force, throw out the old info and do it again.
+ *
+ * note that a global group is also know as a group,
+ * and a local group is also know as an alias.
+ * global groups can only contain users.
+ * local groups can contain global groups or users.
+ * this code finds all global groups to which a user belongs,
+ * and all the local groups to which the user or a global group
+ * containing the user belongs.
+ */
+static void
+addgroups(User *u, int force)
+{
+	LOCALGROUP_USERS_INFO_0 *loc;
+	GROUP_USERS_INFO_0 *grp;
+	DWORD i, n, rem;
+	User *gu;
+	Gmem *g, *next;
+	Rune16 *srv, srvrock[MAX_PATH];
+
+	if(force){
+		u->gotgroup = 0;
+		for(g = u->group; g != nil; g = next){
+			next = g->next;
+			free(g);
+		}
+		u->group = nil;
+	}
+	if(u->gotgroup)
+		return;
+	u->gotgroup = 1;
+
+	n = 0;
+	srv = domsrv(u->dom, srvrock);
+	i = NetUserGetGroups(srv, u->name, 0,
+		(BYTE**)&grp, MAX_PREFERRED_LENGTH, &n, &rem);
+	if(i == NERR_Success || i == ERROR_MORE_DATA){
+		for(i = 0; i < n; i++){
+			gu = domnametouser(srv, grp[i].grui0_name, u->dom);
+			if(gu == 0)
+				continue;
+			g = malloc(sizeof(Gmem));
+			if(g == nil)
+				error(Enomem);
+			g->user = gu;
+			g->next = u->group;
+			u->group = g;
+		}
+		NetApiBufferFree(grp);
+	}
+
+	n = 0;
+	i = NetUserGetLocalGroups(srv, u->name, 0, LG_INCLUDE_INDIRECT,
+		(BYTE**)&loc, MAX_PREFERRED_LENGTH, &n, &rem);
+	if(i == NERR_Success || i == ERROR_MORE_DATA){
+		for(i = 0; i < n; i++){
+			gu = domnametouser(srv, loc[i].lgrui0_name, u->dom);
+			if(gu == NULL)
+				continue;
+			g = malloc(sizeof(Gmem));
+			if(g == nil)
+				error(Enomem);
+			g->user = gu;
+			g->next = u->group;
+			u->group = g;
+		}
+		NetApiBufferFree(loc);
+	}
+}
+
+static SID*
+dupsid(SID *sid)
+{
+	SID *nsid;
+	int n;
+
+	n = GetLengthSid(sid);
+	nsid = malloc(n);
+	if(nsid == nil || !CopySid(n, nsid, sid))
+		panic("can't copy sid");
+	return nsid;
+}
+
+/*
+ * return the name of the server machine for file
+ */
+static Rune16*
+filesrv(char *file)
+{
+	int n;
+	Rune16 *srv;
+	char *p, uni[MAX_PATH], mfile[MAX_PATH];
+	wchar_t vol[3];
+
+	strcpy(mfile, file);
+	/* assume file is a fully qualified name - X: or \\server */
+	if(file[1] == ':') {
+		vol[0] = file[0];
+		vol[1] = file[1];
+		vol[2] = 0;
+		if(GetDriveType(vol) != DRIVE_REMOTE)
+			return 0;
+		n = sizeof(uni);
+		if(WNetGetUniversalName(vol, UNIVERSAL_NAME_INFO_LEVEL, uni, &n) != NO_ERROR)
+			return nil;
+		runes16toutf(mfile, ((UNIVERSAL_NAME_INFO*)uni)->lpUniversalName, MAX_PATH);
+		file = mfile;
+	}
+	file += 2;
+	p = strchr(file, '\\');
+	if(p == 0)
+		n = strlen(file);
+	else
+		n = p - file;
+	if(n >= MAX_PATH)
+		n = MAX_PATH-1;
+
+	memmove(uni, file, n);
+	uni[n] = '\0';
+
+	srv = malloc((n + 1) * sizeof(Rune16));
+	if(srv == nil)
+		panic("filesrv: no memory");
+	utftorunes16(srv, uni, n+1);
+	return srv;
+}
+
+/*
+ * does the file system support acls?
+ */
+static int
+fsacls(char *file)
+{
+	char *p;
+	DWORD flags;
+	char path[MAX_PATH];
+	wchar_t wpath[MAX_PATH];
+
+	/* assume file is a fully qualified name - X: or \\server */
+	if(file[1] == ':') {
+		path[0] = file[0];
+		path[1] = file[1];
+		path[2] = '\\';
+		path[3] = 0;
+	} else {
+		strcpy(path, file);
+		p = strchr(path+2, '\\');
+		if(p == 0)
+			return 0;
+		p = strchr(p+1, '\\');
+		if(p == 0)
+			strcat(path, "\\");
+		else
+			p[1] = 0;
+	}
+	utftorunes16(wpath, path, MAX_PATH);
+	if(!GetVolumeInformation(wpath, NULL, 0, NULL, NULL, &flags, NULL, 0))
+		return 0;
+
+	return flags & FS_PERSISTENT_ACLS;
+}
+
+/*
+ * given a domain, find out the server to ask about its users.
+ * we just ask the local machine to do the translation,
+ * so it might fail sometimes.  in those cases, we don't
+ * trust the domain anyway, and vice versa, so it's not
+ * clear what benifit we would gain by getting the answer "right".
+ */
+static Rune16*
+domsrv(Rune16 *dom, Rune16 srv[MAX_PATH])
+{
+	Rune16 *psrv;
+	int n, r;
+
+	if(dom[0] == 0)
+		return nil;
+
+	r = NetGetAnyDCName(NULL, dom, (LPBYTE*)&psrv);
+	if(r == NERR_Success) {
+		n = runes16len(psrv);
+		if(n >= MAX_PATH)
+			n = MAX_PATH-1;
+		memmove(srv, psrv, n*sizeof(Rune16));
+		srv[n] = 0;
+		NetApiBufferFree(psrv);
+		return srv;
+	}
+
+	return nil;
+}
+
+Dev fsdevtab = {
+	'U',
+	"fs",
+
+	fsinit,
+	fsattach,
+	fswalk,
+	fsstat,
+	fsopen,
+	fscreate,
+	fsclose,
+	fsread,
+	devbread,
+	fswrite,
+	devbwrite,
+	fsremove,
+	fswstat
+};
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Nt/emu	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,112 @@
+env
+	LDFLAGS=	-subsystem:console -entry:WinMainCRTStartup $LDFLAGS
+
+dev
+	root
+	cons
+	env
+	mnt
+	pipe
+	prog
+	prof
+	srv
+	dup
+	ssl
+	cap
+	fs
+	cmd	cmd
+	indir
+
+	draw	win
+
+	ip	ipif ipaux
+	eia
+	audio	audio
+	mem
+	arch
+	pointer
+	snarf
+
+lib
+	interp
+	tk
+	freetype
+	math
+	draw
+	memlayer
+	memdraw
+	keyring
+	sec
+	mp
+	9
+
+link
+
+mod
+	sys
+	draw
+	tk
+	math
+	srv	srv
+	keyring
+	crypt
+	ipints
+	loader
+	freetype
+
+port
+	alloc
+	cache
+	chan
+	dev
+	devtab
+
+	dial
+	dis
+	discall
+	env
+	error
+	errstr
+	exception
+	exportfs
+	inferno
+	latin1
+	main
+	parse
+	pgrp
+	print
+	proc
+	qio
+	random
+	sysfile
+	uqid
+
+code
+
+init
+	emuinit
+
+root
+	/dev	/
+	/fd	/
+	/prog	/
+	/prof	/
+	/net	/
+	/net.alt	/
+	/chan	/
+	/nvfs	/
+	/env	/
+#	/chan
+#	/dev
+#	/dis
+#	/env
+#	/n
+#	/net
+#	/nvfs /
+#	/prog
+#	/icons
+#	/osinit.dis
+#	/dis/emuinit.dis
+#	/dis/lib/auth.dis
+#	/dis/lib/ssl.dis
+#	/n/local /
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Nt/fp.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,102 @@
+typedef	unsigned long	ulong;
+typedef	unsigned int	uint;
+typedef	unsigned short	ushort;
+typedef	unsigned char	uchar;
+typedef	signed char	schar;
+
+#include <float.h>
+#include "mathi.h"
+
+#define	NANEXP	(2047<<20)
+#define	NANMASK	(2047<<20)
+#define	NANSIGN	(1<<31)
+
+int	isInf(double, int);
+
+double
+NaN(void)
+{
+	union
+	{
+		double	d;
+		long	x[2];
+	} a;
+
+	a.x[1] = NANEXP;
+	a.x[0] = 1;
+	return a.d;
+}
+
+int
+isNaN(double d)
+{
+	union
+	{
+		double	d;
+		long	x[2];
+	} a;
+
+	a.d = d;
+	if((a.x[1] & NANMASK) != NANEXP)
+		return 0;
+	return !isInf(d, 0);
+}
+
+double
+Inf(int sign)
+{
+	union
+	{
+		double	d;
+		long	x[2];
+	} a;
+
+	a.x[1] = NANEXP;
+	a.x[0] = 0;
+	if(sign < 0)
+		a.x[1] |= NANSIGN;
+	return a.d;
+}
+
+int
+isInf(double d, int sign)
+{
+	union
+	{
+		double	d;
+		long	x[2];
+	} a;
+
+	a.d = d;
+	if(a.x[0] != 0)
+		return 0;
+	if(a.x[1] == NANEXP)
+		return sign >= 0;
+	if(a.x[1] == (NANEXP|NANSIGN))
+		return sign <= 0;
+	return 0;
+}
+
+ulong
+getfcr(void)
+{
+	return getFPcontrol();
+}
+
+void
+setfcr(ulong m)
+{
+	FPcontrol(m, ~0);
+}
+
+ulong
+getfsr(void)
+{
+	return getFPstatus();
+}
+
+void
+setfsr(ulong m)
+{
+	FPstatus(m, ~0);
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Nt/ie	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,109 @@
+env
+	LDFLAGS=	-subsystem:windows $LDFLAGS
+	OSX=	ie-os
+
+dev
+	root
+	cons
+	env
+	mnt
+	pipe
+	prog
+	prof
+	srv
+	dup
+	ssl
+	cap
+	fs
+	indir
+
+	draw	ie-win
+
+	ip	ipif ipaux
+	eia
+	audio	audio
+	mem
+	pointer
+#	arch
+
+lib
+	interp
+	tk
+	freetype
+	math
+	draw
+	memlayer
+	memdraw
+	keyring
+	sec
+	mp
+	9
+
+link
+
+mod
+	sys
+	draw
+	tk
+	math
+	srv	srv
+	keyring
+	crypt
+	ipints
+	loader
+	freetype
+
+port
+	alloc
+	cache
+	chan
+	dev
+	devtab
+	dial
+	dis
+	discall
+	env
+	error
+	errstr
+	exception
+	exportfs
+	inferno
+	latin1
+	main
+	parse
+	pgrp
+	print
+	proc
+	qio
+	random
+	sysfile
+	uqid
+
+code
+
+init
+	emuinit
+
+root
+	/root /
+	/root/dev	/
+	/root/fd	/
+	/root/prog	/
+	/root/prof	/
+	/root/net	/
+	/root/net.alt	/
+	/root/chan	/
+	/root/nvfs	/
+	/root/env	/
+	/root/dis	/
+	/root/dis/lib	/
+	/root/dis/install	/
+	/root/dis/install/archfs.dis	/dis/install/archfs.dis
+	/root/dis/install/arch.dis	/dis/install/arch.dis
+	/root/dis/gunzip.dis	/dis/gunzip.dis
+	/root/dis/lib/bufio.dis	/dis/lib/bufio.dis
+	/root/dis/lib/inflate.dis	/dis/lib/inflate.dis
+	/root/dis/lib/string.dis	/dis/lib/string.dis
+	/root/dis/lib/daytime.dis	/dis/lib/daytime.dis
+	/root/dis/lib/arg.dis	/dis/lib/arg.dis
+	/root/dis/lib/styx.dis	/dis/lib/styx.dis
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Nt/ie-os.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,791 @@
+#define Unknown win_Unknown
+#define UNICODE
+#include	<windows.h>
+#include <winbase.h>
+#include	<winsock.h>
+#undef Unknown
+#include	<excpt.h>
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+#include	"r16.h"
+#include	"ieplugin.h"
+
+extern int	SYS_SLEEP = 2;
+extern int SOCK_SELECT = 3;
+#define	MAXSLEEPERS	1500
+
+extern Plugin*	plugin;
+extern void	newiop();
+extern int		sendiop();
+
+DWORD		PlatformId;
+static char*	path;
+static HANDLE	kbdh = INVALID_HANDLE_VALUE;
+static HANDLE	conh = INVALID_HANDLE_VALUE;
+static int		sleepers;
+
+static ulong erendezvous(void*, ulong);
+
+__declspec(thread)       Proc    *up;
+
+HANDLE	ntfd2h(int);
+int	nth2fd(HANDLE);
+char *hosttype = "Nt";
+
+static void
+pfree(Proc *p)
+{
+	Osenv *e;
+
+	lock(&procs.l);
+	if(p->prev)
+		p->prev->next = p->next;
+	else
+		procs.head = p->next;
+
+	if(p->next)
+		p->next->prev = p->prev;
+	else
+		procs.tail = p->prev;
+	unlock(&procs.l);
+
+	e = p->env;
+	if(e != nil) {
+		closefgrp(e->fgrp);
+		closepgrp(e->pgrp);
+		closeegrp(e->egrp);
+		closesigs(e->sigs);
+	}
+	free(e->user);
+	free(p->prog);
+	free(p);
+}
+
+void
+osblock(void)
+{
+	erendezvous(up, 0);
+}
+
+void
+osready(Proc *p)
+{
+	erendezvous(p, 0);
+}
+
+
+void
+pexit(char *msg, int t)
+{
+	pfree(up);
+	ExitThread(0);
+}
+
+LONG TrapHandler(LPEXCEPTION_POINTERS ureg);
+
+__cdecl
+Exhandler(EXCEPTION_RECORD *rec, void *frame, CONTEXT *context, void *dcon)
+{
+	EXCEPTION_POINTERS ep;
+	ep.ExceptionRecord = rec;
+	ep.ContextRecord = context;
+	TrapHandler(&ep);
+	return ExceptionContinueExecution;
+}
+
+DWORD WINAPI
+tramp(LPVOID p)
+{
+	// install our own exception handler
+	// replacing all others installed on this thread
+	DWORD handler = (DWORD)Exhandler;
+	_asm {
+		mov eax,handler
+		push eax
+		mov eax,-1
+		push eax
+		mov fs:[0],esp
+	}
+		
+	up = p;
+	up->func(up->arg);
+	pexit("", 0);
+	// should never get here but tidy up anyway
+	_asm {
+		mov fs:[0],-1
+		add esp, 8
+	}
+	return 0;
+}
+
+void
+kproc(char *name, void (*func)(void*), void *arg, int flags)
+{
+	DWORD h;
+	Proc *p;
+	Pgrp *pg;
+	Fgrp *fg;
+	Egrp *eg;
+
+	p = newproc();
+	if(p == nil)
+		panic("out of kernel processes");
+		
+	if(flags & KPDUPPG) {
+		pg = up->env->pgrp;
+		incref(&pg->r);
+		p->env->pgrp = pg;
+	}
+	if(flags & KPDUPFDG) {
+		fg = up->env->fgrp;
+		incref(&fg->r);
+		p->env->fgrp = fg;
+	}
+	if(flags & KPDUPENVG) {
+		eg = up->env->egrp;
+		incref(&eg->r);
+		p->env->egrp = eg;
+	}
+
+	p->env->ui = up->env->ui;
+	kstrdup(&p->env->user, up->env->user);
+	strcpy(p->text, name);
+
+	p->func = func;
+	p->arg = arg;
+
+	lock(&procs.l);
+	if(procs.tail != nil) {
+		p->prev = procs.tail;
+		procs.tail->next = p;
+	}
+	else {
+		procs.head = p;
+		p->prev = nil;
+	}
+	procs.tail = p;
+	unlock(&procs.l);
+
+	p->pid = (int)CreateThread(0, 16384, tramp, p, 0, &h);
+	if(p->pid <= 0)
+		panic("ran out of kernel processes");
+}
+
+#if(_WIN32_WINNT >= 0x0400)
+void APIENTRY sleepintr(DWORD param)
+{
+}
+#endif
+
+void
+oshostintr(Proc *p)
+{
+	if (p->syscall == SOCK_SELECT)
+		return;
+	p->intwait = 0;
+#if(_WIN32_WINNT >= 0x0400)
+	if(p->syscall == SYS_SLEEP) {
+		QueueUserAPC(sleepintr, (HANDLE) p->pid, (DWORD) p->pid);
+	}
+#endif
+}
+
+void
+oslongjmp(void *regs, osjmpbuf env, int val)
+{
+	USED(regs);
+	longjmp(env, val);
+}
+
+int
+readkbd(void)
+{
+	DWORD r;
+	char buf[1];
+
+	if(ReadFile(plugin->conin, buf, sizeof(buf), &r, 0) == FALSE)
+		panic("keyboard fail");
+	if (r == 0)
+		panic("keyboard EOF");
+
+	if(buf[0] == '\r')
+		buf[0] = '\n';
+	return buf[0];
+}
+
+void
+cleanexit(int x)
+{
+	newiop();
+	IOP.op = Iquit;
+	sendiop();
+	ExitProcess(x);
+}
+
+struct ecodes {
+	DWORD	code;
+	char*	name;
+} ecodes[] = {
+	EXCEPTION_ACCESS_VIOLATION,		"Segmentation violation",
+	EXCEPTION_DATATYPE_MISALIGNMENT,	"Data Alignment",
+	EXCEPTION_BREAKPOINT,                	"Breakpoint",
+	EXCEPTION_SINGLE_STEP,               	"SingleStep",
+	EXCEPTION_ARRAY_BOUNDS_EXCEEDED,	"Array Bounds Check",
+	EXCEPTION_FLT_DENORMAL_OPERAND,		"Denormalized Float",
+	EXCEPTION_FLT_DIVIDE_BY_ZERO,		"Floating Point Divide by Zero",
+	EXCEPTION_FLT_INEXACT_RESULT,		"Inexact Floating Point",
+	EXCEPTION_FLT_INVALID_OPERATION,	"Invalid Floating Operation",
+	EXCEPTION_FLT_OVERFLOW,			"Floating Point Result Overflow",
+	EXCEPTION_FLT_STACK_CHECK,		"Floating Point Stack Check",
+	EXCEPTION_FLT_UNDERFLOW,		"Floating Point Result Underflow",
+	EXCEPTION_INT_DIVIDE_BY_ZERO,		"Divide by Zero",
+	EXCEPTION_INT_OVERFLOW,			"Integer Overflow",
+	EXCEPTION_PRIV_INSTRUCTION,		"Privileged Instruction",
+	EXCEPTION_IN_PAGE_ERROR,		"Page-in Error",
+	EXCEPTION_ILLEGAL_INSTRUCTION,		"Illegal Instruction",
+	EXCEPTION_NONCONTINUABLE_EXCEPTION,	"Non-Continuable Exception",
+	EXCEPTION_STACK_OVERFLOW,		"Stack Overflow",
+	EXCEPTION_INVALID_DISPOSITION,		"Invalid Disposition",
+	EXCEPTION_GUARD_PAGE,			"Guard Page Violation",
+	0,					nil
+};
+
+void
+dodisfault(void)
+{
+	disfault(nil, up->env->errstr);
+}
+
+typedef struct Ereg Ereg;
+struct Ereg {
+	Ereg *prev;
+	FARPROC handler;
+};
+
+void
+dumpex()
+{
+	Ereg *er;
+	int i;
+	_asm { mov eax,fs:[0] };
+	_asm { mov [er],eax };
+
+	i = 0;
+	while ((unsigned)er != ~0) {
+		print("handler %ux\n", er->handler);
+		i++;
+	er = er->prev;
+	}
+	print("EXCEPTION CHAIN LENGTH = %d\n", i);
+}
+
+LONG
+TrapHandler(LPEXCEPTION_POINTERS ureg)
+{
+	int i;
+	char *name;
+	DWORD code;
+	// WORD pc;
+	char buf[ERRMAX];
+
+	code = ureg->ExceptionRecord->ExceptionCode;
+	// pc = ureg->ContextRecord->Eip;
+
+	name = nil;
+	for(i = 0; i < nelem(ecodes); i++) {
+		if(ecodes[i].code == code) {
+			name = ecodes[i].name;
+			break;
+		}
+	}
+
+	if(name == nil) {
+		snprint(buf, sizeof(buf), "Unrecognized Machine Trap (%.8lux)\n", code);
+		name = buf;
+	}
+/*
+	if(pc != 0) {
+		snprint(buf, sizeof(buf), "%s: pc=0x%lux", name, pc);
+		name = buf;
+	}
+*/
+	/* YUCK! */
+	strncpy(up->env->errstr, name, ERRMAX);
+	switch (code) {
+	case EXCEPTION_FLT_DENORMAL_OPERAND:
+	case EXCEPTION_FLT_DIVIDE_BY_ZERO:
+	case EXCEPTION_FLT_INEXACT_RESULT:
+	case EXCEPTION_FLT_INVALID_OPERATION:
+	case EXCEPTION_FLT_OVERFLOW:
+	case EXCEPTION_FLT_STACK_CHECK:
+	case EXCEPTION_FLT_UNDERFLOW:
+		/* clear exception flags and register stack */
+		_asm { fnclex };
+		ureg->ContextRecord->FloatSave.StatusWord = 0x0000;
+		ureg->ContextRecord->FloatSave.TagWord = 0xffff;
+	}
+	ureg->ContextRecord->Eip = (DWORD)dodisfault;
+	return EXCEPTION_CONTINUE_EXECUTION;
+}
+
+static	int	rebootok = 0;	/* is shutdown -r supported? */
+
+void
+osreboot(char *file, char **argv)
+{
+	if(rebootok){
+		execvp(file, argv);
+		panic("reboot failure");
+	}else
+		error("reboot option not supported on this system");
+}
+
+void
+libinit(char *imod)
+{
+	WSADATA wasdat;
+//	DWORD lasterror, namelen;
+	OSVERSIONINFO os;
+//	char sys[64];
+
+	os.dwOSVersionInfoSize = sizeof(os);
+	if(!GetVersionEx(&os))
+		panic("can't get os version");
+	PlatformId = os.dwPlatformId;
+	if (PlatformId == VER_PLATFORM_WIN32_NT) {	/* true for NT and 2000 */
+		rebootok = 1;
+	} else {
+		rebootok = 0;
+	}
+
+	if((int)INVALID_HANDLE_VALUE != -1 || sizeof(HANDLE) != sizeof(int))
+		panic("invalid handle value or size");
+
+	if(WSAStartup(MAKEWORD(1, 1), &wasdat) != 0)
+		panic("no winsock.dll");
+
+//	gethostname(sys, sizeof(sys));
+//	kstrdup(&ossysname, sys);
+	kstrdup(&ossysname, "plugin");
+
+//	if(sflag == 0)
+//		SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)TrapHandler);
+
+	path = getenv("PATH");
+	if(path == nil)
+		path = ".";
+
+	up = newproc();
+	if(up == nil)
+		panic("cannot create kernel process");
+
+	kstrdup(&eve, "system");
+	emuinit(imod);
+}
+
+enum
+{
+	NHLOG	= 7,
+	NHASH	= (1<<NHLOG)
+};
+
+typedef struct Tag Tag;
+struct Tag
+{
+	void*	tag;
+	ulong	val;
+	HANDLE	pid;
+	Tag*	next;
+};
+
+static	Tag*	ht[NHASH];
+static	Tag*	ft;
+static	Lock	hlock;
+static	int	nsema;
+
+ulong
+erendezvous(void *tag, ulong value)
+{
+	int h;
+	ulong rval;
+	Tag *t, **l, *f;
+
+
+	h = (ulong)tag & (NHASH-1);
+
+	lock(&hlock);
+	l = &ht[h];
+	for(t = ht[h]; t; t = t->next) {
+		if(t->tag == tag) {
+			rval = t->val;
+			t->val = value;
+			t->tag = 0;
+			unlock(&hlock);
+			if(SetEvent(t->pid) == FALSE)
+				panic("Release failed\n");
+			return rval;		
+		}
+	}
+
+	t = ft;
+	if(t == 0) {
+		t = malloc(sizeof(Tag));
+		if(t == nil)
+			panic("rendezvous: no memory");
+		t->pid = CreateEvent(0, 0, 0, 0);
+	}
+	else
+		ft = t->next;
+
+	t->tag = tag;
+	t->val = value;
+	t->next = *l;
+	*l = t;
+	unlock(&hlock);
+
+	if(WaitForSingleObject(t->pid, INFINITE) != WAIT_OBJECT_0)
+		panic("WaitForSingleObject failed\n");
+
+	lock(&hlock);
+	rval = t->val;
+	for(f = *l; f; f = f->next) {
+		if(f == t) {
+			*l = f->next;
+			break;
+		}
+		l = &f->next;
+	}
+	t->next = ft;
+	ft = t;
+	unlock(&hlock);
+
+	return rval;
+}
+
+void
+FPsave(void *fptr)
+{
+	_asm {
+		mov	eax, fptr
+		fstenv	[eax]
+	}
+}
+
+void
+FPrestore(void *fptr)
+{
+	_asm {
+		mov	eax, fptr
+		fldenv	[eax]
+	}
+}
+
+ulong
+umult(ulong a, ulong b, ulong *high)
+{
+	ulong lo, hi;
+
+	_asm {
+		mov	eax, a
+		mov	ecx, b
+		MUL	ecx
+		mov	lo, eax
+		mov	hi, edx
+	}
+	*high = hi;
+	return lo;
+}
+
+int
+close(int fd)
+{
+	if(fd != -1)
+		CloseHandle(ntfd2h(fd));
+	return 0;
+}
+
+int
+read(int fd, void *buf, uint n)
+{
+	if(!ReadFile(ntfd2h(fd), buf, n, &n, NULL))
+		return -1;
+	return n;
+}
+
+int
+write(int fd, void *buf, uint n)
+{
+	if(fd == 1 || fd == 2){
+		int w;
+		if (plugin->conout == NULL)
+			return n;
+		if (!WriteFile(plugin->conout, buf, n, &w, NULL) || n != w)
+			abort();
+		return n;
+	}
+	if(!WriteFile(ntfd2h(fd), buf, n, &n, NULL))
+		return -1;
+	return n;
+}
+
+/*
+ * map handles and fds.
+ * this code assumes sizeof(HANDLE) == sizeof(int),
+ * that INVALID_HANDLE_VALUE is -1, and assumes
+ * that all tests of invalid fds check only for -1, not < 0
+ */
+int
+nth2fd(HANDLE h)
+{
+	return (int)h;
+}
+
+HANDLE
+ntfd2h(int fd)
+{
+	return (HANDLE)fd;
+}
+
+void
+oslopri(void)
+{
+	SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
+}
+
+/* Resolve system header name conflict */
+#undef Sleep
+void
+sleep(int secs)
+{
+	Sleep(secs*1000);
+}
+
+void*
+sbrk(int size)
+{
+	void *brk;
+
+	brk = VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE); 	
+	if(brk == 0)
+		return (void*)-1;
+
+	return brk;
+}
+
+ulong
+getcallerpc(void *arg)
+{
+	ulong cpc;
+	_asm {
+		mov eax, dword ptr [ebp]
+		mov eax, dword ptr [eax+4]
+		mov dword ptr cpc, eax
+	}
+	return cpc;
+}
+
+/*
+ulong
+getpc(void *arg, ulong *narg)
+{
+	ulong *a = arg, *fp, pc;
+
+	if(a == nil){
+		*narg = 0;
+		return 0;
+	}
+	fp = a-2;
+	pc = fp[1];
+	fp = *(ulong**)fp;
+	if(fp == nil)
+		*narg = 0;
+	else
+		*narg = (ulong)(fp+2);
+	return pc;
+}
+*/
+	
+/*
+ * Return an abitrary millisecond clock time
+ */
+long
+osmillisec(void)
+{
+	return GetTickCount();
+}
+
+#define SEC2MIN 60L
+#define SEC2HOUR (60L*SEC2MIN)
+#define SEC2DAY (24L*SEC2HOUR)
+
+/*
+ *  days per month plus days/year
+ */
+static	int	dmsize[] =
+{
+	365, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+};
+static	int	ldmsize[] =
+{
+	366, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+};
+
+/*
+ *  return the days/month for the given year
+ */
+static int*
+yrsize(int yr)
+{
+	/* a leap year is a multiple of 4, excluding centuries
+	 * that are not multiples of 400 */
+	if( (yr % 4 == 0) && (yr % 100 != 0 || yr % 400 == 0) )
+		return ldmsize;
+	else
+		return dmsize;
+}
+
+static long
+tm2sec(SYSTEMTIME *tm)
+{
+	long secs;
+	int i, *d2m;
+
+	secs = 0;
+
+	/*
+	 *  seconds per year
+	 */
+	for(i = 1970; i < tm->wYear; i++){
+		d2m = yrsize(i);
+		secs += d2m[0] * SEC2DAY;
+	}
+
+	/*
+	 *  seconds per month
+	 */
+	d2m = yrsize(tm->wYear);
+	for(i = 1; i < tm->wMonth; i++)
+		secs += d2m[i] * SEC2DAY;
+
+	/*
+	 * secs in last month
+	 */
+	secs += (tm->wDay-1) * SEC2DAY;
+
+	/*
+	 * hours, minutes, seconds
+	 */
+	secs += tm->wHour * SEC2HOUR;
+	secs += tm->wMinute * SEC2MIN;
+	secs += tm->wSecond;
+
+	return secs;
+}
+
+/*
+ * Return the time since the epoch in microseconds
+ * The epoch is defined at 1 Jan 1970
+ */
+vlong
+osusectime(void)
+{
+	SYSTEMTIME tm;
+	vlong secs;
+
+	GetSystemTime(&tm);
+	secs = tm2sec(&tm);
+	return secs * 1000000 + tm.wMilliseconds * 1000;
+}
+
+vlong
+osnsec(void)
+{
+	return osusectime()*1000;	/* TO DO better */
+}
+
+int
+osmillisleep(ulong milsec)
+{
+	up->syscall = 1;
+	SleepEx(milsec, FALSE);
+	up->syscall = 0;
+	return 0;
+}
+
+int
+limbosleep(ulong milsec)
+{
+	if (sleepers > MAXSLEEPERS)
+		return -1;
+	sleepers++;
+	up->syscall = SYS_SLEEP;
+	SleepEx(milsec, TRUE);
+	up->syscall = 0;
+	sleepers--;
+	return 0;
+}
+
+void
+osyield(void)
+{	
+	sleep(0);
+}
+
+void
+ospause(void)
+{
+      for(;;)
+              sleep(1000000);
+}
+
+/*
+ * these should never be called, and are included
+ * as stubs since we are linking against a library which defines them
+ */
+int
+open(const char *path, int how, ...)
+{
+	panic("open");
+	return -1;
+}
+
+int
+creat(const char *path, int how)
+{
+	panic("creat");
+	return -1;
+}
+
+int
+stat(const char *path, struct stat *sp)
+{
+	panic("stat");
+	return -1;
+}
+
+int
+chown(const char *path, int uid, int gid)
+{
+	panic("chown");
+	return -1;
+}
+
+int
+chmod(const char *path, int mode)
+{
+	panic("chmod");
+	return -1;
+}
+
+void
+link(char *path, char *next)
+{
+	panic("link");
+}
+
+int
+segflush(void *a, ulong n)
+{
+	return 0;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Nt/ie-win.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,224 @@
+#define Unknown win_Unknown
+#include	<windows.h>
+#undef Unknown
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+#include	"keyboard.h"
+#include	"cursor.h"
+#include	"ieplugin.h"
+
+/*
+ * image channel descriptors - copied from draw.h as it clashes with windows.h on many things
+ */
+enum {
+	CRed = 0,
+	CGreen,
+	CBlue,
+	CGrey,
+	CAlpha,
+	CMap,
+	CIgnore,
+	NChan,
+};
+
+#define __DC(type, nbits)	((((type)&15)<<4)|((nbits)&15))
+#define CHAN1(a,b)	__DC(a,b)
+#define CHAN2(a,b,c,d)	(CHAN1((a),(b))<<8|__DC((c),(d)))
+#define CHAN3(a,b,c,d,e,f)	(CHAN2((a),(b),(c),(d))<<8|__DC((e),(f)))
+#define CHAN4(a,b,c,d,e,f,g,h)	(CHAN3((a),(b),(c),(d),(e),(f))<<8|__DC((g),(h)))
+
+#define NBITS(c) ((c)&15)
+#define TYPE(c) (((c)>>4)&15)
+
+enum {
+	GREY1	= CHAN1(CGrey, 1),
+	GREY2	= CHAN1(CGrey, 2),
+	GREY4	= CHAN1(CGrey, 4),
+	GREY8	= CHAN1(CGrey, 8),
+	CMAP8	= CHAN1(CMap, 8),
+	RGB15	= CHAN4(CIgnore, 1, CRed, 5, CGreen, 5, CBlue, 5),
+	RGB16	= CHAN3(CRed, 5, CGreen, 6, CBlue, 5),
+	RGB24	= CHAN3(CRed, 8, CGreen, 8, CBlue, 8),
+	RGBA32	= CHAN4(CRed, 8, CGreen, 8, CBlue, 8, CAlpha, 8),
+	ARGB32	= CHAN4(CAlpha, 8, CRed, 8, CGreen, 8, CBlue, 8),	/* stupid VGAs */
+	XRGB32  = CHAN4(CIgnore, 8, CRed, 8, CGreen, 8, CBlue, 8),
+};
+
+extern ulong displaychan;
+
+extern void drawend(void);
+
+extern	int	chantodepth(ulong);
+extern	int	main(int argc, char **argv);
+static	void	dprint(char*, ...);
+static	DWORD WINAPI	winproc(LPVOID);
+
+static	HINSTANCE	inst;
+static	HINSTANCE	previnst;
+static	int		attached;
+static	ulong	*data;
+
+extern	DWORD	PlatformId;
+char*	gkscanid = "emu_win32vk";
+
+extern int cflag;
+Plugin *plugin = NULL;
+
+DWORD WINAPI
+pluginproc(LPVOID p)
+{
+	int x, y, b;
+
+	for (;;) {
+		WaitForSingleObject(plugin->dopop, INFINITE);
+		switch (POP.op) {
+		case Pgfxkey:
+			if(gkbdq != nil)
+				gkbdputc(gkbdq, POP.u.key);
+			break;
+		case Pmouse:
+			x = POP.u.m.x;
+			y = POP.u.m.y;
+			b = POP.u.m.b;
+			mousetrack(b, x, y, 0);
+			break;
+		}
+		SetEvent(plugin->popdone);
+	}
+}
+
+int WINAPI
+WinMain(HINSTANCE winst, HINSTANCE wprevinst, LPSTR cmdline, int wcmdshow)
+{
+	HANDLE sharedmem;
+	uint pid = _getpid();
+	char iname[16];
+	inst = winst;
+	previnst = wprevinst;
+	sprint(iname, "%uX", pid);
+	sharedmem = OpenFileMapping(FILE_MAP_WRITE, FALSE, iname);
+	if (sharedmem != NULL)
+		plugin = MapViewOfFile(sharedmem, FILE_MAP_WRITE, 0, 0, 0);
+	if (plugin != NULL) {
+		DWORD tid;
+		int i;
+		Xsize = plugin->Xsize;
+		Ysize = plugin->Ysize;
+		displaychan = plugin->cdesc;
+		cflag = plugin->cflag;
+		for (i = 0; i < PI_NCLOSE; i++)
+			CloseHandle(plugin->closehandles[i]);
+		CreateThread(0, 0, pluginproc, 0, 0, &tid);
+	
+		/* cmdline passed into WinMain does not contain name of executable.
+		 * The globals __argc and __argv to include this info - like UNIX
+		 */
+		main(__argc, __argv);
+		UnmapViewOfFile(plugin);
+		plugin = NULL;
+	}
+	if (sharedmem != NULL)
+		CloseHandle(sharedmem);
+	return 0;
+}
+
+static Lock ioplock;
+
+void
+newiop()
+{
+	lock(&ioplock);
+}
+
+int
+sendiop()
+{
+	int val;
+	SetEvent(plugin->doiop);
+	WaitForSingleObject(plugin->iopdone, INFINITE);
+	val = plugin->iop.val;
+	unlock(&ioplock);
+	return val;
+}
+
+void
+dprint(char *fmt, ...)
+{
+	va_list arg;
+	char buf[128];
+
+	va_start(arg, fmt);
+	vseprint(buf, buf+sizeof(buf), fmt, (LPSTR)arg);
+	va_end(arg);
+	OutputDebugString("inferno: ");
+	OutputDebugString(buf);
+}
+
+uchar*
+attachscreen(IRectangle *r, ulong *chan, int *d, int *width, int *softscreen)
+{
+	int k;
+
+	if (!attached) {
+		newiop();
+		IOP.op = Iattachscr;
+		if (sendiop() != 0)
+			return nil;
+		data = plugin->screen;
+		attached = 1;
+	}
+	r->min.x = 0;
+	r->min.y = 0;
+	r->max.x = Xsize;
+	r->max.y = Ysize;
+
+	if(displaychan == 0)
+		displaychan = CMAP8;
+	*chan = displaychan;
+
+	k = chantodepth(displaychan);
+	*d = k;
+	*width = (Xsize/4)*(k/8);
+	*softscreen = 1;
+	return (uchar*)data;
+}
+
+void
+flushmemscreen(IRectangle r)
+{
+	if(r.max.x<=r.min.x || r.max.y<=r.min.y)
+		return;
+	newiop();
+	IOP.op = Iflushscr;
+	IOP.u.r = r;
+	sendiop();
+}
+
+void
+setpointer(int x, int y)
+{
+	USED(x); USED(y);
+	// TODO
+}
+
+void
+drawcursor(Drawcursor* c)
+{
+	USED(c);
+	// TODO
+}
+
+char*
+clipread(void)
+{
+	return nil;
+}
+
+int
+clipwrite(char *p)
+{
+	USED(p);
+	return -1;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Nt/ieplugin.h	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,75 @@
+typedef struct Pop Pop;
+typedef struct Iop Iop;
+typedef struct IPoint IPoint;
+typedef struct IRectangle IRectangle;
+typedef struct Plugin Plugin;
+
+enum {
+	// messages from Plugin to Inferno
+	Pgfxkey,
+	Pmouse,
+
+	// message from Inferno to Plugin
+	Iattachscr,
+	Iflushscr,
+	Isetcur,
+	Idrawcur,
+	Iquit,
+};
+
+struct Pop {
+	int	op;
+	union {
+		int key;			// Pgfxkey
+		struct {
+			int	x;
+			int	y;
+			int	b;
+			int	modify;
+		} m;				// Pmouse
+	} u;
+};
+
+struct IPoint
+{
+	LONG	x;
+	LONG	y;
+};
+
+struct IRectangle
+{
+	IPoint	min;
+	IPoint	max;
+};
+
+struct Iop {
+	int	op;
+	int	val;
+	union {
+		IRectangle	r;		// Iflushscr
+		// need additional support for Isetcur & Idrawcur
+	} u;
+};
+#define PI_NCLOSE	2
+
+struct Plugin {
+	LONG sz;				// size of this data struct (including screen mem)
+	HANDLE	conin;		// console input (from plugin) - never NULL
+	HANDLE	conout;		// console output (to plugin) - can be NULL
+	HANDLE	datain;		// #C data file for initialisation (HACK!)
+	HANDLE	dopop;		// new Pop available
+	HANDLE	popdone;		// acknowledgement of Pop
+	HANDLE	doiop;		// new Iop available
+	HANDLE	iopdone;		// acknowledgement of Iop
+	HANDLE	closehandles[PI_NCLOSE];
+	Pop pop;
+	Iop iop;
+	int Xsize;				// screen dimensions
+	int Ysize;
+	ULONG cdesc;			// display chans descriptor
+	int cflag;
+	ULONG screen[1];
+};
+
+#define IOP	(plugin->iop)
+#define POP	(plugin->pop)
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Nt/ipif.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,409 @@
+#define Unknown win_Unknown
+#include        <windows.h>
+#include        <winbase.h>
+#include        <sys/types.h>
+#include        <winsock.h>
+#undef Unknown
+#include        "dat.h"
+#include        "fns.h"
+#include        "ip.h"
+#include        "error.h"
+
+typedef int socklen_t;	/* Windows is leading edge as always */
+
+
+extern int SOCK_SELECT;
+
+char Enotv4[] = "address not IPv4";
+
+static void
+ipw6(uchar *a, ulong w)
+{
+	memmove(a, v4prefix, IPv4off);
+	memmove(a+IPv4off, &w, IPv4addrlen);
+}
+
+int
+so_socket(int type)
+{
+	int fd, one;
+
+	switch(type) {
+	default:
+		error("bad protocol type");
+	case S_TCP:
+		type = SOCK_STREAM;
+		break;
+	case S_UDP:
+		type = SOCK_DGRAM;
+		break;
+	}
+	fd = socket(AF_INET, type, 0);
+	if(fd < 0)
+		oserror();
+	if(type == SOCK_DGRAM){
+		one = 1;
+		setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (char*)&one, sizeof(one));
+	}else{
+		one = 1;
+		setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char*)&one, sizeof(one));
+	}
+	return fd;
+}
+
+int
+so_send(int sock, void *va, int len, void *hdr, int hdrlen)
+{
+	int r;
+	struct sockaddr sa;
+	struct sockaddr_in *sin;
+	uchar *h = hdr;
+
+
+	osenter();
+	if(hdr == 0)
+		r = send(sock, va, len, 0);
+	else {
+		memset(&sa, 0, sizeof(sa));
+		sin = (struct sockaddr_in*)&sa;
+		sin->sin_family = AF_INET;
+		switch(hdrlen){
+		case OUdphdrlenv4:
+			memmove(&sin->sin_addr, h,  4);
+			memmove(&sin->sin_port, h+8, 2);
+			break;
+		case OUdphdrlen:
+			v6tov4((uchar*)&sin->sin_addr, h);
+			memmove(&sin->sin_port, h+2*IPaddrlen, 2);	/* rport */
+			break;
+		default:
+			v6tov4((uchar*)&sin->sin_addr, h);
+			memmove(&sin->sin_port, h+3*IPaddrlen, 2);
+			break;
+		}
+		r = sendto(sock, va, len, 0, &sa, sizeof(sa));
+	}
+	osleave();
+	return r;
+}
+
+static int
+doselect(int sock)
+{
+	fd_set	waitr;
+	struct timeval seltime;
+
+	up->syscall = SOCK_SELECT;
+	FD_ZERO(&waitr);
+	FD_SET(sock, &waitr);
+	for(;;){
+		int nfds;
+		fd_set in, exc;
+
+		in = waitr;
+		exc = waitr;
+		seltime.tv_sec = 1;
+		seltime.tv_usec = 0L;
+		nfds = select(sizeof(fd_set)*8, &in, (fd_set*)0, &exc, &seltime);
+		if(up->intwait) {
+			up->intwait = 0;
+			return -1;
+		}
+		if(nfds < 0) {
+			print("select error\n");
+			return 0;
+		}
+		if(FD_ISSET(sock, &in) || FD_ISSET(sock, &exc)){
+			return 0;
+		}
+	}
+}
+
+int
+so_recv(int sock, void *va, int len, void *hdr, int hdrlen)
+{
+	int r;
+	socklen_t l;
+	struct sockaddr sa;
+	struct sockaddr_in *sin;
+	uchar h[Udphdrlen];
+
+	osenter();
+	if(doselect(sock) < 0) {
+		osleave();
+		return -1;
+	}
+	if(hdr == 0)
+		r = recv(sock, va, len, 0);
+	else {
+		sin = (struct sockaddr_in*)&sa;
+		l = sizeof(sa);
+		r = recvfrom(sock, va, len, 0, &sa, &l);
+		if(r >= 0) {
+			memset(h, 0, sizeof(h));
+			switch(hdrlen){
+			case OUdphdrlenv4:
+				memmove(h, &sin->sin_addr, IPv4addrlen);
+				memmove(h+2*IPv4addrlen, &sin->sin_port, 2);
+				break;
+			case OUdphdrlen:
+				v4tov6(h, (uchar*)&sin->sin_addr);
+				memmove(h+2*IPaddrlen, &sin->sin_port, 2);
+				break;
+			default:
+				v4tov6(h, (uchar*)&sin->sin_addr);
+				memmove(h+3*IPaddrlen, &sin->sin_port, 2);
+				break;
+			}
+
+			/* alas there's no way to get the local addr/port correctly.  Pretend. */
+			memset(&sa, 0, sizeof(sa));
+			getsockname(sock, &sa, &l);
+			switch(hdrlen){
+			case OUdphdrlenv4:
+				memmove(h+IPv4addrlen, &sin->sin_addr, IPv4addrlen);
+				memmove(h+2*IPv4addrlen+2, &sin->sin_port, 2);
+				break;
+			case OUdphdrlen:
+				v4tov6(h+IPaddrlen, (uchar*)&sin->sin_addr);
+				memmove(h+2*IPaddrlen+2, &sin->sin_port, 2);
+				break;
+			default:
+				v4tov6(h+IPaddrlen, (uchar*)&sin->sin_addr);
+				v4tov6(h+2*IPaddrlen, (uchar*)&sin->sin_addr);	/* ifcaddr */
+				memmove(h+3*IPaddrlen+2, &sin->sin_port, 2);
+				break;
+			}
+			memmove(hdr, h, hdrlen);
+		}
+	}
+	osleave();
+	return r;
+}
+
+void
+so_close(int sock)
+{
+	closesocket(sock);
+}
+
+void
+so_connect(int fd, uchar *raddr, ushort rport)
+{
+	int r;
+	struct sockaddr sa;
+	struct sockaddr_in *sin;
+
+	if(!isv4(raddr))
+		error(Enotv4);
+
+	memset(&sa, 0, sizeof(sa));
+	sin = (struct sockaddr_in*)&sa;
+	sin->sin_family = AF_INET;
+	hnputs(&sin->sin_port, rport);
+	memmove(&sin->sin_addr.s_addr, raddr+IPv4off, IPv4addrlen);
+
+	osenter();
+	r = connect(fd, &sa, sizeof(sa));
+	osleave();
+	if(r < 0)
+		oserror();
+}
+
+void
+so_getsockname(int fd, uchar *laddr, ushort *lport)
+{
+	socklen_t len;
+	struct sockaddr sa;
+	struct sockaddr_in *sin;
+
+	len = sizeof(sa);
+	if(getsockname(fd, &sa, &len) < 0)
+		oserror();
+
+	sin = (struct sockaddr_in*)&sa;
+	if(sin->sin_family != AF_INET || len != sizeof(*sin))
+		error(Enotv4);
+
+	ipw6(laddr, sin->sin_addr.s_addr);
+	*lport = nhgets(&sin->sin_port);
+}
+
+void
+so_listen(int fd)
+{
+	int r;
+
+	osenter();
+	r = listen(fd, 256);
+	osleave();
+	if(r < 0)
+		oserror();
+}
+
+int
+so_accept(int fd, uchar *raddr, ushort *rport)
+{
+	int nfd;
+	socklen_t len;
+	struct sockaddr sa;
+	struct sockaddr_in *sin;
+
+	sin = (struct sockaddr_in*)&sa;
+
+	len = sizeof(sa);
+	osenter();
+	if(doselect(fd) < 0) {
+		osleave();
+		return -1;
+	}
+	nfd = accept(fd, &sa, &len);
+	osleave();
+	if(nfd < 0)
+		oserror();
+
+	if(sin->sin_family != AF_INET || len != sizeof(*sin))
+		error(Enotv4);
+
+	ipw6(raddr, sin->sin_addr.s_addr);
+	*rport = nhgets(&sin->sin_port);
+	return nfd;
+}
+
+void
+so_bind(int fd, int su, uchar *addr, ushort port)
+{
+	int i, one;
+	struct sockaddr sa;
+	struct sockaddr_in *sin;
+
+	sin = (struct sockaddr_in*)&sa;
+
+	one = 1;
+//	if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one)) < 0) {
+//		oserrstr(up->genbuf, sizeof(up->genbuf));
+//		print("setsockopt: %s", err);
+//	}
+
+	if(su) {
+		for(i = 600; i < 1024; i++) {
+			memset(&sa, 0, sizeof(sa));
+			sin->sin_family = AF_INET;
+			memmove(&sin->sin_addr.s_addr, addr+IPv4off, IPv4addrlen);
+			hnputs(&sin->sin_port, i);
+
+			if(bind(fd, &sa, sizeof(sa)) >= 0)	
+				return;
+		}
+		oserror();
+	}
+
+	memset(&sa, 0, sizeof(sa));
+	sin->sin_family = AF_INET;
+	memmove(&sin->sin_addr.s_addr, addr+IPv4off, IPv4addrlen);
+	hnputs(&sin->sin_port, port);
+
+	if(bind(fd, &sa, sizeof(sa)) < 0)
+		oserror();
+}
+
+int
+so_gethostbyname(char *host, char**hostv, int n)
+{
+	int i;
+	char buf[32];
+	uchar *p;
+	struct hostent *hp;
+
+	hp = gethostbyname(host);
+	if(hp == 0)
+		return 0;
+
+	for(i = 0; hp->h_addr_list[i] && i < n; i++) {
+		p = (uchar*)hp->h_addr_list[i];
+		sprint(buf, "%ud.%ud.%ud.%ud", p[0], p[1], p[2], p[3]);
+		hostv[i] = strdup(buf);
+		if(hostv[i] == 0)
+			break;
+	}
+	return i;
+}
+
+int
+so_gethostbyaddr(char *addr, char **hostv, int n)
+{
+	int i;
+	struct hostent *hp;
+	unsigned long straddr;
+
+	straddr = inet_addr(addr);
+	if(straddr == -1)
+		return 0;
+
+	hp = gethostbyaddr((char *)&straddr, sizeof(straddr), AF_INET);
+	if(hp == 0)
+		return 0;
+
+	hostv[0] = strdup(hp->h_name);
+	if(hostv[0] == 0)
+		return 0;
+	for(i = 1; hp->h_aliases[i-1] && i < n; i++) {
+		hostv[i] = strdup(hp->h_aliases[i-1]);
+		if(hostv[i] == 0)
+			break;
+	}
+	return i;
+}
+
+int
+so_getservbyname(char *service, char *net, char *port)
+{
+	ushort p;
+	struct servent *s;
+
+	s = getservbyname(service, net);
+	if(s == 0)
+		return -1;
+	p = s->s_port;
+	sprint(port, "%d", nhgets(&p));	
+	return 0;
+}
+
+int
+so_hangup(int fd, int nolinger)
+{
+	int r;
+	static struct linger l = {1, 0};
+
+	osenter();
+	if(nolinger)
+		setsockopt(fd, SOL_SOCKET, SO_LINGER, (char*)&l, sizeof(l));
+	r = closesocket(fd);
+	osleave();
+	return r;
+}
+
+void
+arpadd(char *ipaddr, char *eaddr, int n)
+{
+	error("arp not implemented");
+}
+
+int
+so_mustbind(int restricted, int port)
+{
+	USED(restricted);
+	USED(port);
+	/* Windows requires bound sockets, even on port 0 */
+	return 1;
+}
+
+void
+so_keepalive(int fd, int ms)
+{
+	int on;
+
+	USED(ms);
+	on = 1;
+	setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char*)&on, sizeof(on));
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Nt/ipif6.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,413 @@
+#define Unknown win_Unknown
+
+/* vista (0x0600) was the first to support IPPROTO_IPV6 */
+#undef _WIN32_WINNT
+#define _WIN32_WINNT 0x0600
+
+#include	<winsock2.h>
+#include	<ws2def.h>
+#include	<ws2tcpip.h>
+#include	<wspiapi.h>
+#undef Unknown
+
+#include	"dat.h"
+#include	"fns.h"
+#include	"ip.h"
+#include	"error.h"
+
+extern int SOCK_SELECT;
+
+int
+so_socket(int type)
+{
+	int fd, one;
+	int v6only;
+
+	switch(type) {
+	default:
+		error("bad protocol type");
+	case S_TCP:
+		type = SOCK_STREAM;
+		break;
+	case S_UDP:
+		type = SOCK_DGRAM;
+		break;
+	}
+
+	fd = socket(AF_INET6, type, 0);
+	if(fd < 0)
+		oserror();
+
+	/* OpenBSD has v6only fixed to 1, so the following will fail */
+	v6only = 0;
+	if(setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (void*)&v6only, sizeof v6only) < 0)
+		oserror();
+
+	if(type == SOCK_DGRAM){
+		one = 1;
+		setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (char*)&one, sizeof(one));
+	}else{
+		one = 1;
+		setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char*)&one, sizeof(one));
+	}
+	return fd;
+}
+
+int
+so_send(int sock, void *va, int len, void *hdr, int hdrlen)
+{
+	int r;
+	struct sockaddr_storage sa;
+	struct sockaddr_in6 *sin6;
+	char *h = hdr;
+
+	osenter();
+	if(hdr == 0)
+		r = send(sock, va, len, 0);
+	else {
+		memset(&sa, 0, sizeof(sa));
+		sin6 = (struct sockaddr_in6*)&sa;
+		sin6->sin6_family = AF_INET6;
+		switch(hdrlen){
+		case OUdphdrlenv4:
+			v4tov6((uchar*)&sin6->sin6_addr, h);
+			memmove(&sin6->sin6_port, h+8, 2);
+			break;
+		case OUdphdrlen:
+			memmove((uchar*)&sin6->sin6_addr, h, IPaddrlen);
+			memmove(&sin6->sin6_port, h+2*IPaddrlen, 2);	/* rport */
+			break;
+		default:
+			memmove((uchar*)&sin6->sin6_addr, h, IPaddrlen);
+			memmove(&sin6->sin6_port, h+3*IPaddrlen, 2);
+			break;
+		}
+		r = sendto(sock, va, len, 0, (struct sockaddr*)sin6, sizeof(*sin6));
+	}
+	osleave();
+	return r;
+}
+
+static int
+doselect(int sock)
+{
+	fd_set	waitr;
+	struct timeval seltime;
+
+	up->syscall = SOCK_SELECT;
+	FD_ZERO(&waitr);
+	FD_SET(sock, &waitr);
+	for(;;){
+		int nfds;
+		fd_set in, exc;
+
+		in = waitr;
+		exc = waitr;
+		seltime.tv_sec = 1;
+		seltime.tv_usec = 0L;
+		nfds = select(sizeof(fd_set)*8, &in, (fd_set*)0, &exc, &seltime);
+		if(up->intwait) {
+			up->intwait = 0;
+			return -1;
+		}
+		if(nfds < 0) {
+			print("select error\n");
+			return 0;
+		}
+		if(FD_ISSET(sock, &in) || FD_ISSET(sock, &exc)){
+			return 0;
+		}
+	}
+}
+
+int
+so_recv(int sock, void *va, int len, void *hdr, int hdrlen)
+{
+	int r, l;
+	struct sockaddr_storage sa;
+	struct sockaddr_in6 *sin6;
+	char h[Udphdrlen];
+
+	osenter();
+	if(doselect(sock) < 0) {
+		osleave();
+		return -1;
+	}
+	if(hdr == 0)
+		r = recv(sock, va, len, 0);
+	else {
+		sin6 = (struct sockaddr_in6*)&sa;
+		l = sizeof(sa);
+		r = recvfrom(sock, va, len, 0, (struct sockaddr*)&sa, &l);
+		if(r >= 0) {
+			memset(h, 0, sizeof h);
+			switch(hdrlen){
+			case OUdphdrlenv4:
+				if(v6tov4(h, (uchar*)&sin6->sin6_addr) < 0) {
+					osleave();
+					error("OUdphdrlenv4 with IPv6 address");
+				}
+				memmove(h+2*IPv4addrlen, &sin6->sin6_port, 2);
+				break;
+			case OUdphdrlen:
+				memmove(h, (uchar*)&sin6->sin6_addr, IPaddrlen);
+				memmove(h+2*IPaddrlen, &sin6->sin6_port, 2);
+				break;
+			default:
+				memmove(h, (uchar*)&sin6->sin6_addr, IPaddrlen);
+				memmove(h+3*IPaddrlen, &sin6->sin6_port, 2);
+				break;
+			}
+
+			/* alas there's no way to get the local addr/port correctly.  Pretend. */
+			memset(&sa, 0, sizeof sa);
+			l = sizeof(sa);
+			getsockname(sock, (struct sockaddr*)&sa, &l);
+			switch(hdrlen){
+			case OUdphdrlenv4:
+				/*
+				 * we get v6Unspecified/noaddr if local address cannot be determined.
+				 * that's reasonable for ipv4 too.
+				 */
+				if(ipcmp(v6Unspecified, (uchar*)&sin6->sin6_addr) != 0
+				&& v6tov4(h+IPv4addrlen, (uchar*)&sin6->sin6_addr) < 0) {
+					osleave();
+					error("OUdphdrlenv4 with IPv6 address");
+				}
+				memmove(h+2*IPv4addrlen+2, &sin6->sin6_port, 2);
+				break;
+			case OUdphdrlen:
+				memmove(h+IPaddrlen, (uchar*)&sin6->sin6_addr, IPaddrlen);
+				memmove(h+2*IPaddrlen+2, &sin6->sin6_port, 2);
+				break;
+			default:
+				memmove(h+IPaddrlen, (uchar*)&sin6->sin6_addr, IPaddrlen);
+				memmove(h+2*IPaddrlen, (uchar*)&sin6->sin6_addr, IPaddrlen);	/* ifcaddr */
+				memmove(h+3*IPaddrlen+2, &sin6->sin6_port, 2);
+				break;
+			}
+			memmove(hdr, h, hdrlen);
+		}
+	}
+	osleave();
+	return r;
+}
+
+void
+so_close(int sock)
+{
+	closesocket(sock);
+}
+
+void
+so_connect(int fd, unsigned char *raddr, unsigned short rport)
+{
+	int r;
+	struct sockaddr_storage sa;
+	struct sockaddr_in6 *sin6;
+
+	memset(&sa, 0, sizeof(sa));
+	sin6 = (struct sockaddr_in6*)&sa;
+	sin6->sin6_family = AF_INET6;
+	hnputs(&sin6->sin6_port, rport);
+	memmove((uchar*)&sin6->sin6_addr, raddr, IPaddrlen);
+
+	osenter();
+	r = connect(fd, (struct sockaddr*)sin6, sizeof(*sin6));
+	osleave();
+	if(r < 0)
+		oserror();
+}
+
+void
+so_getsockname(int fd, unsigned char *laddr, unsigned short *lport)
+{
+	int len;
+	struct sockaddr_storage sa;
+	struct sockaddr_in6 *sin6;
+
+	len = sizeof(*sin6);
+	if(getsockname(fd, (struct sockaddr*)&sa, &len) < 0)
+		oserror();
+
+	sin6 = (struct sockaddr_in6*)&sa;
+	if(sin6->sin6_family != AF_INET6 || len != sizeof(*sin6))
+		error("not AF_INET6");
+
+	memmove(laddr, &sin6->sin6_addr, IPaddrlen);
+	*lport = nhgets(&sin6->sin6_port);
+}
+
+void
+so_listen(int fd)
+{
+	int r;
+
+	osenter();
+	r = listen(fd, 256);
+	osleave();
+	if(r < 0)
+		oserror();
+}
+
+int
+so_accept(int fd, unsigned char *raddr, unsigned short *rport)
+{
+	int nfd, len;
+	struct sockaddr_storage sa;
+	struct sockaddr_in6 *sin6;
+
+	sin6 = (struct sockaddr_in6*)&sa;
+
+	len = sizeof(*sin6);
+	osenter();
+	if(doselect(fd) < 0) {
+		osleave();
+		return -1;
+	}
+	nfd = accept(fd, (struct sockaddr*)&sa, &len);
+	osleave();
+	if(nfd < 0)
+		oserror();
+
+	if(sin6->sin6_family != AF_INET6 || len != sizeof(*sin6))
+		error("not AF_INET6");
+
+	memmove(raddr, &sin6->sin6_addr, IPaddrlen);
+	*rport = nhgets(&sin6->sin6_port);
+	return nfd;
+}
+
+void
+so_bind(int fd, int su, unsigned char *addr, unsigned short port)
+{
+	int i, one;
+	struct sockaddr_storage sa;
+	struct sockaddr_in6 *sin6;
+
+	sin6 = (struct sockaddr_in6*)&sa;
+
+	one = 1;
+	if(0 && setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one)) < 0) {
+		oserrstr(up->genbuf, sizeof(up->genbuf));
+		print("setsockopt: %s", up->genbuf);
+	}
+
+	if(su) {
+		for(i = 600; i < 1024; i++) {
+			memset(&sa, 0, sizeof(sa));
+			sin6->sin6_family = AF_INET6;
+			memmove(&sin6->sin6_addr, addr, IPaddrlen);
+			hnputs(&sin6->sin6_port, i);
+
+			if(bind(fd, (struct sockaddr*)sin6, sizeof(*sin6)) >= 0)	
+				return;
+		}
+		oserror();
+	}
+
+	memset(&sa, 0, sizeof(sa));
+	sin6->sin6_family = AF_INET6;
+	memmove(&sin6->sin6_addr, addr, IPaddrlen);
+	hnputs(&sin6->sin6_port, port);
+
+	if(bind(fd, (struct sockaddr*)sin6, sizeof(*sin6)) < 0)
+		oserror();
+}
+
+static int
+resolve(char *name, char **hostv, int n, int isnumeric)
+{
+	int i;
+	struct addrinfo *res0, *r;
+	char buf[5*8];
+	uchar addr[IPaddrlen];
+	struct addrinfo hints;
+
+	memset(&hints, 0, sizeof hints);
+	hints.ai_flags = isnumeric ? AI_NUMERICHOST : 0;
+	hints.ai_family = PF_UNSPEC;
+	hints.ai_socktype = SOCK_STREAM;
+	if(getaddrinfo(name, nil, &hints, &res0) < 0)
+		return 0;
+	i = 0;
+	for(r = res0; r != nil && i < n; r = r->ai_next) {
+		if(r->ai_family == AF_INET)
+			v4tov6(addr, (uchar*)&((struct sockaddr_in*)r->ai_addr)->sin_addr);
+		else if(r->ai_family == AF_INET6)
+			memmove(addr, &((struct sockaddr_in6*)r->ai_addr)->sin6_addr, IPaddrlen);
+		else
+			continue;
+
+		snprint(buf, sizeof buf, "%I", addr);
+		hostv[i++] = strdup(buf);
+	}
+
+	freeaddrinfo(res0);
+	return i;
+}
+
+int
+so_gethostbyname(char *host, char **hostv, int n)
+{
+	return resolve(host, hostv, n, 0);
+}
+
+int
+so_gethostbyaddr(char *addr, char **hostv, int n)
+{
+	return resolve(addr, hostv, n, 1);
+}
+
+int
+so_getservbyname(char *service, char *net, char *port)
+{
+	ushort p;
+	struct servent *s;
+
+	s = getservbyname(service, net);
+	if(s == 0)
+		return -1;
+	p = s->s_port;
+	sprint(port, "%d", nhgets(&p));	
+	return 0;
+}
+
+int
+so_hangup(int fd, int nolinger)
+{
+	int r;
+	static struct linger l = {1, 0};
+
+	osenter();
+	if(nolinger)
+		setsockopt(fd, SOL_SOCKET, SO_LINGER, (char*)&l, sizeof(l));
+	r = closesocket(fd);
+	osleave();
+	return r;
+}
+
+void
+arpadd(char *ipaddr, char *eaddr, int n)
+{
+	error("arp not implemented");
+}
+
+int
+so_mustbind(int restricted, int port)
+{
+	USED(restricted);
+	USED(port);
+	/* Windows requires bound sockets, even on port 0 */
+	return 1;
+}
+
+void
+so_keepalive(int fd, int ms)
+{
+	int on;
+
+	USED(ms);
+	on = 1;
+	setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char*)&on, sizeof(on));
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Nt/mkfile	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,51 @@
+SYSTARG=Nt
+OBJTYPE=386
+#uncomment following line for full Microsoft debug symbols
+#LDEBUG=-debug -debugtype:cv -pdb:none
+<../../mkconfig
+SYSTARG=Nt
+OBJTYPE=386
+
+#Configurable parameters
+
+CONF=emu			#default configuration
+CONFLIST=emu ie
+CLEANCONFLIST=
+
+INSTALLDIR=$ROOT/$SYSTARG/$OBJTYPE/bin	#path of directory where kernel is installed
+
+#end configurable parameters
+
+OSX=os
+
+<$ROOT/mkfiles/mkfile-$SYSTARG-$OBJTYPE	#set vars based on target system
+
+<| $SHELLNAME ../port/mkdevlist $CONF	#sets $IP, $DEVS, $PORT, $LIBS
+
+OBJ=\
+	$OSX.$O\
+	$CONF.root.$O\
+	lock.$O\
+	fp.$O\
+	r16.$O\
+	vlrt.$O\
+	$DEVS\
+	$PORT\
+
+HFILES=\
+	r16.h\
+
+CFLAGS='-DROOT="'$ROOT'"' -DEMU -I. -I../port -I$ROOT/$SYSTARG/$OBJTYPE/include -I$ROOT/include -I$ROOT/libinterp $CTHREADFLAGS $CFLAGS $EMUOPTIONS
+SYSLIBS=	$SYSLIBS netapi32.lib wsock32.lib user32.lib gdi32.lib advapi32.lib winmm.lib mpr.lib
+KERNDATE=`{$NDATE}
+
+default:V:	i$CONF.exe
+
+<../port/portmkfile
+
+i$CONF.exe:	$OBJ $CONF.c $CONF.root.h $LIBFILES
+	$CC $CFLAGS '-DKERNDATE='$KERNDATE $CONF.c
+	$LD $LDFLAGS -out:$target $OBJ $CONF.$O $LIBFILES $SYSLIBS
+
+install:V: i$CONF.exe
+	cp i$CONF.exe $INSTALLDIR/$CONF.exe
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Nt/nt.rc	Sun Nov  4 12:33:14 2018
@@ -0,0 +1 @@
+100 ICON inferno.ico
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Nt/os.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,715 @@
+#define Unknown win_Unknown
+#define UNICODE
+#include	<windows.h>
+#include <winbase.h>
+#include	<winsock.h>
+#undef Unknown
+#include	<excpt.h>
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+#include	"r16.h"
+
+int	SYS_SLEEP = 2;
+int SOCK_SELECT = 3;
+#define	MAXSLEEPERS	1500
+
+extern	int	cflag;
+
+DWORD	PlatformId;
+DWORD	consolestate;
+static	char*	path;
+static	HANDLE	kbdh = INVALID_HANDLE_VALUE;
+static	HANDLE	conh = INVALID_HANDLE_VALUE;
+static	HANDLE	errh = INVALID_HANDLE_VALUE;
+static	int	donetermset = 0;
+static	int sleepers = 0;
+
+
+__declspec(thread)       Proc    *up;
+
+HANDLE	ntfd2h(int);
+int	nth2fd(HANDLE);
+void	termrestore(void);
+char *hosttype = "Nt";
+char *cputype = "386";
+void	(*coherence)(void) = nofence;
+
+static void
+pfree(Proc *p)
+{
+	Osenv *e;
+
+	lock(&procs.l);
+	if(p->prev)
+		p->prev->next = p->next;
+	else
+		procs.head = p->next;
+
+	if(p->next)
+		p->next->prev = p->prev;
+	else
+		procs.tail = p->prev;
+	unlock(&procs.l);
+
+	e = p->env;
+	if(e != nil) {
+		closefgrp(e->fgrp);
+		closepgrp(e->pgrp);
+		closeegrp(e->egrp);
+		closesigs(e->sigs);
+	}
+	free(e->user);
+	free(p->prog);
+	CloseHandle((HANDLE)p->os);
+	free(p);
+}
+
+void
+osblock(void)
+{
+	if(WaitForSingleObject((HANDLE)up->os, INFINITE) != WAIT_OBJECT_0)
+		panic("osblock failed");
+}
+
+void
+osready(Proc *p)
+{
+	if(SetEvent((HANDLE)p->os) == FALSE)
+		panic("osready failed");
+}
+
+void
+pexit(char *msg, int t)
+{
+	pfree(up);
+	ExitThread(0);
+}
+
+LONG TrapHandler(LPEXCEPTION_POINTERS ureg);
+
+__cdecl
+Exhandler(EXCEPTION_RECORD *rec, void *frame, CONTEXT *context, void *dcon)
+{
+	EXCEPTION_POINTERS ep;
+	ep.ExceptionRecord = rec;
+	ep.ContextRecord = context;
+	TrapHandler(&ep);
+	return ExceptionContinueExecution;
+}
+
+DWORD WINAPI
+tramp(LPVOID p)
+{
+	up = p;
+	up->func(up->arg);
+	pexit("", 0);
+	/* not reached */
+	for(;;)
+		panic("tramp");
+	return 0;
+}
+
+void
+kproc(char *name, void (*func)(void*), void *arg, int flags)
+{
+	DWORD h;
+	Proc *p;
+	Pgrp *pg;
+	Fgrp *fg;
+	Egrp *eg;
+
+	p = newproc();
+	if(p == nil)
+		panic("out of kernel processes");
+	p->os = CreateEvent(NULL, FALSE, FALSE, NULL);
+	if(p->os == NULL)
+		panic("can't allocate os event");
+		
+	if(flags & KPDUPPG) {
+		pg = up->env->pgrp;
+		incref(&pg->r);
+		p->env->pgrp = pg;
+	}
+	if(flags & KPDUPFDG) {
+		fg = up->env->fgrp;
+		incref(&fg->r);
+		p->env->fgrp = fg;
+	}
+	if(flags & KPDUPENVG) {
+		eg = up->env->egrp;
+		incref(&eg->r);
+		p->env->egrp = eg;
+	}
+
+	p->env->ui = up->env->ui;
+	kstrdup(&p->env->user, up->env->user);
+	strcpy(p->text, name);
+
+	p->func = func;
+	p->arg = arg;
+
+	lock(&procs.l);
+	if(procs.tail != nil) {
+		p->prev = procs.tail;
+		procs.tail->next = p;
+	}
+	else {
+		procs.head = p;
+		p->prev = nil;
+	}
+	procs.tail = p;
+	unlock(&procs.l);
+
+	p->pid = (int)CreateThread(0, 16384, tramp, p, 0, &h);
+	if(p->pid <= 0)
+		panic("ran out of  kernel processes");
+}
+
+#if(_WIN32_WINNT >= 0x0400)
+void APIENTRY sleepintr(DWORD param)
+{
+}
+#endif
+
+void
+oshostintr(Proc *p)
+{
+	if (p->syscall == SOCK_SELECT)
+		return;
+	p->intwait = 0;
+#if(_WIN32_WINNT >= 0x0400)
+	if(p->syscall == SYS_SLEEP) {
+		QueueUserAPC(sleepintr, (HANDLE) p->pid, (DWORD) p->pid);
+	}
+#endif
+}
+
+void
+oslongjmp(void *regs, osjmpbuf env, int val)
+{
+	USED(regs);
+	longjmp(env, val);
+}
+
+int
+readkbd(void)
+{
+	DWORD r;
+	char buf[1];
+
+	if(ReadFile(kbdh, buf, sizeof(buf), &r, 0) == FALSE)
+		panic("keyboard fail");
+	if (r == 0)
+		panic("keyboard EOF");
+
+	if (buf[0] == 0x03) {
+		// INTR (CTRL+C)
+		termrestore();
+		ExitProcess(0);
+	}
+	if(buf[0] == '\r')
+		buf[0] = '\n';
+	return buf[0];
+}
+
+void
+cleanexit(int x)
+{
+	sleep(2);		/* give user a chance to see message */
+	termrestore();
+	ExitProcess(x);
+}
+
+struct ecodes {
+	DWORD	code;
+	char*	name;
+} ecodes[] = {
+	EXCEPTION_ACCESS_VIOLATION,		"segmentation violation",
+	EXCEPTION_DATATYPE_MISALIGNMENT,	"data alignment",
+	EXCEPTION_BREAKPOINT,                	"breakpoint",
+	EXCEPTION_SINGLE_STEP,               	"single step",
+	EXCEPTION_ARRAY_BOUNDS_EXCEEDED,	"array bounds check",
+	EXCEPTION_FLT_DENORMAL_OPERAND,		"denormalized float",
+	EXCEPTION_FLT_DIVIDE_BY_ZERO,		"floating point divide by zero",
+	EXCEPTION_FLT_INEXACT_RESULT,		"inexact floating point",
+	EXCEPTION_FLT_INVALID_OPERATION,	"invalid floating operation",
+	EXCEPTION_FLT_OVERFLOW,			"floating point result overflow",
+	EXCEPTION_FLT_STACK_CHECK,		"floating point stack check",
+	EXCEPTION_FLT_UNDERFLOW,		"floating point result underflow",
+	EXCEPTION_INT_DIVIDE_BY_ZERO,		"divide by zero",
+	EXCEPTION_INT_OVERFLOW,			"integer overflow",
+	EXCEPTION_PRIV_INSTRUCTION,		"privileged instruction",
+	EXCEPTION_IN_PAGE_ERROR,		"page-in error",
+	EXCEPTION_ILLEGAL_INSTRUCTION,		"illegal instruction",
+	EXCEPTION_NONCONTINUABLE_EXCEPTION,	"non-continuable exception",
+	EXCEPTION_STACK_OVERFLOW,		"stack overflow",
+	EXCEPTION_INVALID_DISPOSITION,		"invalid disposition",
+	EXCEPTION_GUARD_PAGE,			"guard page violation",
+	0,					nil
+};
+
+LONG
+TrapHandler(LPEXCEPTION_POINTERS ureg)
+{
+	int i;
+	char *name;
+	DWORD code;
+	// WORD pc;
+	char buf[ERRMAX];
+
+	code = ureg->ExceptionRecord->ExceptionCode;
+	// pc = ureg->ContextRecord->Eip;
+
+	name = nil;
+	for(i = 0; i < nelem(ecodes); i++) {
+		if(ecodes[i].code == code) {
+			name = ecodes[i].name;
+			break;
+		}
+	}
+
+	if(name == nil) {
+		snprint(buf, sizeof(buf), "unknown trap type (%#.8lux)\n", code);
+		name = buf;
+	}
+/*
+	if(pc != 0) {
+		snprint(buf, sizeof(buf), "%s: pc=0x%lux", name, pc);
+		name = buf;
+	}
+*/
+	switch (code) {
+	case EXCEPTION_FLT_DENORMAL_OPERAND:
+	case EXCEPTION_FLT_DIVIDE_BY_ZERO:
+	case EXCEPTION_FLT_INEXACT_RESULT:
+	case EXCEPTION_FLT_INVALID_OPERATION:
+	case EXCEPTION_FLT_OVERFLOW:
+	case EXCEPTION_FLT_STACK_CHECK:
+	case EXCEPTION_FLT_UNDERFLOW:
+		/* clear exception flags and ensure safe empty state */
+		_asm { fnclex };
+		_asm { fninit };
+	}
+	disfault(nil, name);
+	/* not reached */
+	return EXCEPTION_CONTINUE_EXECUTION;
+}
+
+static void
+termset(void)
+{
+	DWORD flag;
+
+	if(donetermset)
+		return;
+	donetermset = 1;
+	conh = GetStdHandle(STD_OUTPUT_HANDLE);
+	kbdh = GetStdHandle(STD_INPUT_HANDLE);
+	errh = GetStdHandle(STD_ERROR_HANDLE);
+	if(errh == INVALID_HANDLE_VALUE)
+		errh = conh;
+
+	// The following will fail if kbdh not from console (e.g. a pipe)
+	// in which case we don't care
+	GetConsoleMode(kbdh, &consolestate);
+	flag = consolestate;
+	flag = flag & ~(ENABLE_PROCESSED_INPUT|ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT);
+	SetConsoleMode(kbdh, flag);
+}
+
+void
+termrestore(void)
+{
+	if(kbdh != INVALID_HANDLE_VALUE)
+		SetConsoleMode(kbdh, consolestate);
+}
+
+static	int	rebootok = 0;	/* is shutdown -r supported? */
+
+void
+osreboot(char *file, char **argv)
+{
+	if(rebootok){
+		termrestore();
+		execvp(file, argv);
+		panic("reboot failure");
+	}
+}
+
+void
+libinit(char *imod)
+{
+	WSADATA wasdat;
+	DWORD lasterror, namelen;
+	OSVERSIONINFO os;
+	char sys[64], uname[64];
+	wchar_t wuname[64];
+	char *uns;
+
+	os.dwOSVersionInfoSize = sizeof(os);
+	if(!GetVersionEx(&os))
+		panic("can't get os version");
+	PlatformId = os.dwPlatformId;
+	if (PlatformId == VER_PLATFORM_WIN32_NT) {	/* true for NT and 2000 */
+		rebootok = 1;
+	} else {
+		rebootok = 0;
+	}
+	termset();
+
+	if((int)INVALID_HANDLE_VALUE != -1 || sizeof(HANDLE) != sizeof(int))
+		panic("invalid handle value or size");
+
+	if(WSAStartup(MAKEWORD(1, 1), &wasdat) != 0)
+		panic("no winsock.dll");
+
+	gethostname(sys, sizeof(sys));
+	kstrdup(&ossysname, sys);
+	if(sflag == 0)
+		SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)TrapHandler);
+
+	path = getenv("PATH");
+	if(path == nil)
+		path = ".";
+
+	up = newproc();
+	if(up == nil)
+		panic("cannot create kernel process");
+
+	strcpy(uname, "inferno");
+	namelen = sizeof(wuname);
+	if(GetUserName(wuname, &namelen) != TRUE) {
+		lasterror = GetLastError();	
+		if(PlatformId == VER_PLATFORM_WIN32_NT || lasterror != ERROR_NOT_LOGGED_ON)
+			print("cannot GetUserName: %d\n", lasterror);
+	}else{
+		uns = narrowen(wuname);
+		snprint(uname, sizeof(uname), "%s", uns);
+		free(uns);
+	}
+	kstrdup(&eve, uname);
+
+	emuinit(imod);
+}
+
+void
+FPsave(void *fptr)
+{
+	_asm {
+		mov	eax, fptr
+		fstenv	[eax]
+	}
+}
+
+void
+FPrestore(void *fptr)
+{
+	_asm {
+		mov	eax, fptr
+		fldenv	[eax]
+	}
+}
+
+ulong
+umult(ulong a, ulong b, ulong *high)
+{
+	ulong lo, hi;
+
+	_asm {
+		mov	eax, a
+		mov	ecx, b
+		MUL	ecx
+		mov	lo, eax
+		mov	hi, edx
+	}
+	*high = hi;
+	return lo;
+}
+
+int
+close(int fd)
+{
+	if(fd == -1)
+		return 0;
+	CloseHandle(ntfd2h(fd));
+	return 0;
+}
+
+int
+read(int fd, void *buf, uint n)
+{
+	HANDLE h;
+
+	if(fd == 0)
+		h = kbdh;
+	else
+		h = ntfd2h(fd);
+	if(h == INVALID_HANDLE_VALUE)
+		return -1;
+	if(!ReadFile(h, buf, n, &n, NULL))
+		return -1;
+	return n;
+}
+
+int
+write(int fd, void *buf, uint n)
+{
+	HANDLE h;
+
+	if(fd == 1 || fd == 2){
+		if(!donetermset)
+			termset();
+		if(fd == 1)
+			h = conh;
+		else
+			h = errh;
+		if(h == INVALID_HANDLE_VALUE)
+			return -1;
+		if(!WriteFile(h, buf, n, &n, NULL))
+			return -1;
+		return n;
+	}
+	if(!WriteFile(ntfd2h(fd), buf, n, &n, NULL))
+		return -1;
+	return n;
+}
+
+/*
+ * map handles and fds.
+ * this code assumes sizeof(HANDLE) == sizeof(int),
+ * that INVALID_HANDLE_VALUE is -1, and assumes
+ * that all tests of invalid fds check only for -1, not < 0
+ */
+int
+nth2fd(HANDLE h)
+{
+	return (int)h;
+}
+
+HANDLE
+ntfd2h(int fd)
+{
+	return (HANDLE)fd;
+}
+
+void
+oslopri(void)
+{
+	SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
+}
+
+/* Resolve system header name conflict */
+#undef Sleep
+void
+sleep(int secs)
+{
+	Sleep(secs*1000);
+}
+
+void*
+sbrk(int size)
+{
+	void *brk;
+
+	brk = VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE); 	
+	if(brk == 0)
+		return (void*)-1;
+
+	return brk;
+}
+
+ulong
+getcallerpc(void *arg)
+{
+	ulong cpc;
+	_asm {
+		mov eax, dword ptr [ebp]
+		mov eax, dword ptr [eax+4]
+		mov dword ptr cpc, eax
+	}
+	return cpc;
+}
+
+/*
+ * Return an abitrary millisecond clock time
+ */
+long
+osmillisec(void)
+{
+	return GetTickCount();
+}
+
+#define SEC2MIN 60L
+#define SEC2HOUR (60L*SEC2MIN)
+#define SEC2DAY (24L*SEC2HOUR)
+
+/*
+ *  days per month plus days/year
+ */
+static	int	dmsize[] =
+{
+	365, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+};
+static	int	ldmsize[] =
+{
+	366, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+};
+
+/*
+ *  return the days/month for the given year
+ */
+static int*
+yrsize(int yr)
+{
+	/* a leap year is a multiple of 4, excluding centuries
+	 * that are not multiples of 400 */
+	if( (yr % 4 == 0) && (yr % 100 != 0 || yr % 400 == 0) )
+		return ldmsize;
+	else
+		return dmsize;
+}
+
+static long
+tm2sec(SYSTEMTIME *tm)
+{
+	long secs;
+	int i, *d2m;
+
+	secs = 0;
+
+	/*
+	 *  seconds per year
+	 */
+	for(i = 1970; i < tm->wYear; i++){
+		d2m = yrsize(i);
+		secs += d2m[0] * SEC2DAY;
+	}
+
+	/*
+	 *  seconds per month
+	 */
+	d2m = yrsize(tm->wYear);
+	for(i = 1; i < tm->wMonth; i++)
+		secs += d2m[i] * SEC2DAY;
+
+	/*
+	 * secs in last month
+	 */
+	secs += (tm->wDay-1) * SEC2DAY;
+
+	/*
+	 * hours, minutes, seconds
+	 */
+	secs += tm->wHour * SEC2HOUR;
+	secs += tm->wMinute * SEC2MIN;
+	secs += tm->wSecond;
+
+	return secs;
+}
+
+/*
+ * Return the time since the epoch in microseconds
+ * The epoch is defined at 1 Jan 1970
+ */
+vlong
+osusectime(void)
+{
+	SYSTEMTIME tm;
+	vlong secs;
+
+	GetSystemTime(&tm);
+	secs = tm2sec(&tm);
+	return secs * 1000000 + tm.wMilliseconds * 1000;
+}
+
+vlong
+osnsec(void)
+{
+	return osusectime()*1000;	/* TO DO better */
+}
+
+int
+osmillisleep(ulong milsec)
+{
+	SleepEx(milsec, FALSE);
+	return 0;
+}
+
+int
+limbosleep(ulong milsec)
+{
+	if (sleepers > MAXSLEEPERS)
+		return -1;
+	sleepers++;
+	up->syscall = SYS_SLEEP;
+	SleepEx(milsec, TRUE);
+	up->syscall = 0;
+	sleepers--;
+	return 0;
+}
+
+void
+osyield(void)
+{	
+	SwitchToThread();
+}
+
+void
+ospause(void)
+{
+      for(;;)
+              sleep(1000000);
+}
+
+/*
+ * these should never be called, and are included
+ * as stubs since we are linking against a library which defines them
+ */
+int
+open(const char *path, int how, ...)
+{
+	panic("open");
+	return -1;
+}
+
+int
+creat(const char *path, int how)
+{
+	panic("creat");
+	return -1;
+}
+
+int
+stat(const char *path, struct stat *sp)
+{
+	panic("stat");
+	return -1;
+}
+
+int
+chown(const char *path, int uid, int gid)
+{
+	panic("chown");
+	return -1;
+}
+
+int
+chmod(const char *path, int mode)
+{
+	panic("chmod");
+	return -1;
+}
+
+void
+link(char *path, char *next)
+{
+	panic("link");
+}
+
+int
+segflush(void *a, ulong n)
+{
+	return 0;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Nt/r16.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,186 @@
+#define UNICODE
+#define Unknown win_Unknown
+#include	<windows.h>
+#include	<winbase.h>
+#undef Unknown
+#undef	Sleep
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+#include	"r16.h"
+
+enum
+{
+	Bits10 = 0x03ff,		/* 0011 1111 1111 */
+
+	R16self = 0x10000,
+
+	HSurrogateMin = 0xd800,
+	HSurrogateMax = 0xdbff,
+	LSurrogateMin = 0xdc00,
+	LSurrogateMax = 0xdfff,
+};
+
+Rune16*
+runes16dup(Rune16 *r)
+{
+	int n;
+	Rune16 *s;
+
+	n = runes16len(r) + 1;
+	s = malloc(n * sizeof(Rune16));
+	if(s == nil)
+		error(Enomem);
+	memmove(s, r, n * sizeof(Rune16));
+	return s;
+}
+
+int
+runes16len(Rune16 *r)
+{
+	int n;
+
+	n = 0;
+	while(*r++ != 0)
+		n++;
+	return n;
+}
+
+char*
+runes16toutf(char *p, Rune16 *r, int nc)
+{
+	char *op, *ep;
+	int n;
+	Rune c, lc;
+
+	op = p;
+	ep = p + nc;
+	while(c = *r++) {
+		if(c > Runemax)
+			c = Runeerror;
+		if(c >= LSurrogateMin && c <= LSurrogateMax)
+			c = Runeerror;
+		if(c >= HSurrogateMin && c<= HSurrogateMax){
+			lc = *r++;
+			if(lc >= LSurrogateMin || lc <= LSurrogateMax)
+				c = (c&Bits10)<<10 | (lc&Bits10) + R16self;
+			else
+				c = Runeerror;
+		}
+		n = runelen(c);
+		if(p + n >= ep)
+			break;
+		p += runetochar(p, &c);
+	}
+	*p = '\0';
+	return op;
+}
+
+int
+rune16nlen(Rune16 *r, int nrune)
+{
+	int nb;
+	Rune c;
+
+	nb = 0;
+	while(nrune--) {
+		c = *r++;
+		if(c < R16self)
+			nb += runelen(c);
+		else {
+			c -= R16self;
+			nb += runelen(HSurrogateMin | (c>>10));
+			nb += runelen(LSurrogateMin | (c&Bits10));
+		}
+	}
+	return nb;
+}
+
+Rune16*
+utftorunes16(Rune16 *r, char *p, int nc)
+{
+	Rune16 *or, *er;
+	Rune rc;
+
+	or = r;
+	er = r + nc;
+	while(*p != '\0' && r + 1 < er){
+		p += chartorune(&rc, p);
+		if(rc < R16self){
+			*r++ = rc;
+			continue;
+		}
+		if(rc > Runemax || er-r < 2){
+			*r++ = Runeerror;
+			continue;
+		}
+		rc -= R16self;
+		*r++ = HSurrogateMin | (rc>>10);
+		*r++ = LSurrogateMin | (rc&Bits10);
+	}
+	*r = '\0';
+	return or;
+}
+
+int
+runes16cmp(Rune16 *s1, Rune16 *s2)
+{
+	Rune16 r1, r2;
+
+	for(;;) {
+		r1 = *s1++;
+		r2 = *s2++;
+		if(r1 != r2) {
+			if(r1 > r2)
+				return 1;
+			return -1;
+		}
+		if(r1 == 0)
+			return 0;
+	}
+}
+
+wchar_t *
+widen(char *s)
+{
+	int n;
+	wchar_t *ws;
+
+	n = utflen(s) + 1;
+	ws = smalloc(n*sizeof(wchar_t));
+	utftorunes16(ws, s, n);
+	return ws;
+}
+
+
+char *
+narrowen(wchar_t *ws)
+{
+	char *s;
+	int n;
+
+	n = widebytes(ws);
+	s = smalloc(n);
+	runes16toutf(s, ws, n);
+	return s;
+}
+
+
+int
+widebytes(wchar_t *ws)
+{
+	int n = 0;
+	wchar_t c;
+
+	while (*ws){
+		c = *ws++;
+		if(c < R16self)
+			n += runelen(c);
+		else {
+			c -= R16self;
+			n += runelen(HSurrogateMin | (c>>10));
+			n += runelen(LSurrogateMin | (c&Bits10));
+		}
+	}
+	return n+1;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Nt/r16.h	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,11 @@
+typedef unsigned short Rune16;
+
+wchar_t	*widen(char *s);
+char		*narrowen(wchar_t *ws);
+int		widebytes(wchar_t *ws);
+int		runes16len(Rune16*);
+int		rune16nlen(Rune16*, int);
+Rune16*	runes16dup(Rune16*);
+Rune16*	utftorunes16(Rune16*, char*, int);
+char*	runes16toutf(char*, Rune16*, int);
+int		runes16cmp(Rune16*, Rune16*);
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Nt/vlrt.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,751 @@
+#include "dat.h"
+
+/*
+ * typedef	unsigned long	ulong;
+ * typedef	unsigned int	uint;
+ * typedef	unsigned short	ushort;
+ * typedef	unsigned char	uchar;
+ * typedef	signed char	schar;
+*/
+
+#define	SIGN(n)	(1UL<<(n-1))
+
+typedef	struct	Vlong	Vlong;
+struct	Vlong
+{
+	ulong	lo;
+	ulong	hi;
+};
+
+void	abort(void);
+
+void
+_addv(Vlong *r, Vlong a, Vlong b)
+{
+	ulong lo, hi;
+
+	lo = a.lo + b.lo;
+	hi = a.hi + b.hi;
+	if(lo < a.lo)
+		hi++;
+	r->lo = lo;
+	r->hi = hi;
+}
+
+void
+_subv(Vlong *r, Vlong a, Vlong b)
+{
+	ulong lo, hi;
+
+	lo = a.lo - b.lo;
+	hi = a.hi - b.hi;
+	if(lo > a.lo)
+		hi--;
+	r->lo = lo;
+	r->hi = hi;
+}
+
+void
+_mulv(Vlong *r, Vlong a, Vlong b)
+{
+	ulong ahi, alo, chi, clo, x;
+	int i;
+
+	ahi = a.hi;
+	alo = a.lo;
+	chi = 0;
+	clo = 0;
+
+	x = b.lo;
+	for(i=0; i<32; i++) {
+		if(x & 1) {
+			chi += ahi;
+			clo += alo;
+			if(clo < alo)
+				chi++;
+		}
+		ahi <<= 1;
+		if(alo & SIGN(32))
+			ahi += 1;
+		alo <<= 1;
+		x >>= 1;
+	}
+
+	/*
+	 * same, but
+	 *	alo is known to be 0
+	 *	can stop when x == 0
+	 */
+	for(x=b.hi; x; x>>=1) {
+		if(x & 1)
+			chi += ahi;
+		ahi <<= 1;
+	}
+
+	r->hi = chi;
+	r->lo = clo;
+}
+
+void
+_d2v(Vlong *y, double d)
+{
+	Vlong x;
+	ulong xhi, xlo, ylo, yhi;
+	int sh;
+
+	*(double*)&x = d;
+
+	xhi = (x.hi & 0xfffff) | 0x100000;
+	xlo = x.lo;
+	sh = 1075 - ((x.hi >> 20) & 0x7ff);
+
+	ylo = 0;
+	yhi = 0;
+	if(sh >= 0) {
+		/* v = (hi||lo) >> sh */
+		if(sh < 32) {
+			if(sh == 0) {
+				ylo = xlo;
+				yhi = xhi;
+			} else {
+				ylo = (xlo >> sh) | (xhi << (32-sh));
+				yhi = xhi >> sh;
+			}
+		} else {
+			if(sh == 32) {
+				ylo = xhi;
+			} else
+			if(sh < 64) {
+				ylo = xhi >> (sh-32);
+			}
+		}
+	} else {
+		/* v = (hi||lo) << -sh */
+		sh = -sh;
+		if(sh <= 10) {
+			ylo = xlo << sh;
+			yhi = (xhi << sh) | (xlo >> (32-sh));
+		} else {
+			/* overflow */
+			yhi = d;	/* causes something awful */
+		}
+	}
+	if(x.hi & SIGN(32)) {
+		if(ylo != 0) {
+			ylo = -ylo;
+			yhi = ~yhi;
+		} else
+			yhi = -yhi;
+	}
+
+	y->hi = yhi;
+	y->lo = ylo;
+}
+
+void
+_f2v(Vlong *y, float f)
+{
+
+	_d2v(y, f);
+}
+
+double
+_v2d(Vlong x)
+{
+	if(x.hi & SIGN(32)) {
+		if(x.lo) {
+			x.lo = -x.lo;
+			x.hi = ~x.hi;
+		} else
+			x.hi = -x.hi;
+		return -((long)x.hi*4294967296. + x.lo);
+	}
+	return (long)x.hi*4294967296. + x.lo;
+}
+
+float
+_v2f(Vlong x)
+{
+	return _v2d(x);
+}
+
+static void
+dodiv(Vlong num, Vlong den, Vlong *q, Vlong *r)
+{
+	ulong numlo, numhi, denhi, denlo, quohi, quolo, t;
+	int i;
+
+	numhi = num.hi;
+	numlo = num.lo;
+	denhi = den.hi;
+	denlo = den.lo;
+
+	/*
+	 * get a divide by zero
+	 */
+	if(denlo==0 && denhi==0) {
+		numlo = numlo / denlo;
+	}
+
+	/*
+	 * set up the divisor and find the number of iterations needed
+	 */
+	if(numhi >= SIGN(32)) {
+		quohi = SIGN(32);
+		quolo = 0;
+	} else {
+		quohi = numhi;
+		quolo = numlo;
+	}
+	i = 0;
+	while(denhi < quohi || (denhi == quohi && denlo < quolo)) {
+		denhi = (denhi<<1) | (denlo>>31);
+		denlo <<= 1;
+		i++;
+	}
+
+	quohi = 0;
+	quolo = 0;
+	for(; i >= 0; i--) {
+		quohi = (quohi<<1) | (quolo>>31);
+		quolo <<= 1;
+		if(numhi > denhi || (numhi == denhi && numlo >= denlo)) {
+			t = numlo;
+			numlo -= denlo;
+			if(numlo > t)
+				numhi--;
+			numhi -= denhi;
+			quolo |= 1;
+		}
+		denlo = (denlo>>1) | (denhi<<31);
+		denhi >>= 1;
+	}
+
+	if(q) {
+		q->lo = quolo;
+		q->hi = quohi;
+	}
+	if(r) {
+		r->lo = numlo;
+		r->hi = numhi;
+	}
+}
+
+void
+_divvu(Vlong *q, Vlong n, Vlong d)
+{
+
+	if(n.hi == 0 && d.hi == 0) {
+		q->hi = 0;
+		q->lo = n.lo / d.lo;
+		return;
+	}
+	dodiv(n, d, q, 0);
+}
+
+void
+_modvu(Vlong *r, Vlong n, Vlong d)
+{
+
+	if(n.hi == 0 && d.hi == 0) {
+		r->hi = 0;
+		r->lo = n.lo % d.lo;
+		return;
+	}
+	dodiv(n, d, 0, r);
+}
+
+static void
+vneg(Vlong *v)
+{
+
+	if(v->lo == 0) {
+		v->hi = -v->hi;
+		return;
+	}
+	v->lo = -v->lo;
+	v->hi = ~v->hi;
+}
+
+void
+_divv(Vlong *q, Vlong n, Vlong d)
+{
+	long nneg, dneg;
+
+	if(n.hi == (((long)n.lo)>>31) && d.hi == (((long)d.lo)>>31)) {
+		q->lo = (long)n.lo / (long)d.lo;
+		q->hi = ((long)q->lo) >> 31;
+		return;
+	}
+	nneg = n.hi >> 31;
+	if(nneg)
+		vneg(&n);
+	dneg = d.hi >> 31;
+	if(dneg)
+		vneg(&d);
+	dodiv(n, d, q, 0);
+	if(nneg != dneg)
+		vneg(q);
+}
+
+void
+_modv(Vlong *r, Vlong n, Vlong d)
+{
+	long nneg, dneg;
+
+	if(n.hi == (((long)n.lo)>>31) && d.hi == (((long)d.lo)>>31)) {
+		r->lo = (long)n.lo % (long)d.lo;
+		r->hi = ((long)r->lo) >> 31;
+		return;
+	}
+	nneg = n.hi >> 31;
+	if(nneg)
+		vneg(&n);
+	dneg = d.hi >> 31;
+	if(dneg)
+		vneg(&d);
+	dodiv(n, d, 0, r);
+	if(nneg)
+		vneg(r);
+}
+
+void
+_rshav(Vlong *r, Vlong a, int b)
+{
+	long t;
+
+	t = a.hi;
+	if(b >= 32) {
+		r->hi = t>>31;
+		if(b >= 64) {
+			/* this is illegal re C standard */
+			r->lo = t>>31;
+			return;
+		}
+		r->lo = t >> (b-32);
+		return;
+	}
+	if(b <= 0) {
+		r->hi = t;
+		r->lo = a.lo;
+		return;
+	}
+	r->hi = t >> b;
+	r->lo = (t << (32-b)) | (a.lo >> b);
+}
+
+void
+_rshlv(Vlong *r, Vlong a, int b)
+{
+	ulong t;
+
+	t = a.hi;
+	if(b >= 32) {
+		r->hi = 0;
+		if(b >= 64) {
+			/* this is illegal re C standard */
+			r->lo = 0;
+			return;
+		}
+		r->lo = t >> (b-32);
+		return;
+	}
+	if(b <= 0) {
+		r->hi = t;
+		r->lo = a.lo;
+		return;
+	}
+	r->hi = t >> b;
+	r->lo = (t << (32-b)) | (a.lo >> b);
+}
+
+void
+_lshv(Vlong *r, Vlong a, int b)
+{
+	ulong t;
+
+	t = a.lo;
+	if(b >= 32) {
+		r->lo = 0;
+		if(b >= 64) {
+			/* this is illegal re C standard */
+			r->hi = 0;
+			return;
+		}
+		r->hi = t << (b-32);
+		return;
+	}
+	if(b <= 0) {
+		r->lo = t;
+		r->hi = a.hi;
+		return;
+	}
+	r->lo = t << b;
+	r->hi = (t >> (32-b)) | (a.hi << b);
+}
+
+void
+_andv(Vlong *r, Vlong a, Vlong b)
+{
+	r->hi = a.hi & b.hi;
+	r->lo = a.lo & b.lo;
+}
+
+void
+_orv(Vlong *r, Vlong a, Vlong b)
+{
+	r->hi = a.hi | b.hi;
+	r->lo = a.lo | b.lo;
+}
+
+void
+_xorv(Vlong *r, Vlong a, Vlong b)
+{
+	r->hi = a.hi ^ b.hi;
+	r->lo = a.lo ^ b.lo;
+}
+
+void
+_vpp(Vlong *l, Vlong *r)
+{
+
+	l->hi = r->hi;
+	l->lo = r->lo;
+	r->lo++;
+	if(r->lo == 0)
+		r->hi++;
+}
+
+void
+_vmm(Vlong *l, Vlong *r)
+{
+
+	l->hi = r->hi;
+	l->lo = r->lo;
+	if(r->lo == 0)
+		r->hi--;
+	r->lo--;
+}
+
+void
+_ppv(Vlong *l, Vlong *r)
+{
+
+	r->lo++;
+	if(r->lo == 0)
+		r->hi++;
+	l->hi = r->hi;
+	l->lo = r->lo;
+}
+
+void
+_mmv(Vlong *l, Vlong *r)
+{
+
+	if(r->lo == 0)
+		r->hi--;
+	r->lo--;
+	l->hi = r->hi;
+	l->lo = r->lo;
+}
+
+void
+_vasop(Vlong *ret, void *lv, void fn(Vlong*, Vlong, Vlong), int type, Vlong rv)
+{
+	Vlong t, u;
+
+	u.lo = 0;
+	u.hi = 0;
+	switch(type) {
+	default:
+		abort();
+		break;
+
+	case 1:	/* schar */
+		t.lo = *(schar*)lv;
+		t.hi = t.lo >> 31;
+		fn(&u, t, rv);
+		*(schar*)lv = u.lo;
+		break;
+
+	case 2:	/* uchar */
+		t.lo = *(uchar*)lv;
+		t.hi = 0;
+		fn(&u, t, rv);
+		*(uchar*)lv = u.lo;
+		break;
+
+	case 3:	/* short */
+		t.lo = *(short*)lv;
+		t.hi = t.lo >> 31;
+		fn(&u, t, rv);
+		*(short*)lv = u.lo;
+		break;
+
+	case 4:	/* ushort */
+		t.lo = *(ushort*)lv;
+		t.hi = 0;
+		fn(&u, t, rv);
+		*(ushort*)lv = u.lo;
+		break;
+
+	case 9:	/* int */
+		t.lo = *(int*)lv;
+		t.hi = t.lo >> 31;
+		fn(&u, t, rv);
+		*(int*)lv = u.lo;
+		break;
+
+	case 10:	/* uint */
+		t.lo = *(uint*)lv;
+		t.hi = 0;
+		fn(&u, t, rv);
+		*(uint*)lv = u.lo;
+		break;
+
+	case 5:	/* long */
+		t.lo = *(long*)lv;
+		t.hi = t.lo >> 31;
+		fn(&u, t, rv);
+		*(long*)lv = u.lo;
+		break;
+
+	case 6:	/* ulong */
+		t.lo = *(ulong*)lv;
+		t.hi = 0;
+		fn(&u, t, rv);
+		*(ulong*)lv = u.lo;
+		break;
+
+	case 7:	/* vlong */
+	case 8:	/* uvlong */
+		fn(&u, *(Vlong*)lv, rv);
+		*(Vlong*)lv = u;
+		break;
+	}
+	*ret = u;
+}
+
+void
+_p2v(Vlong *ret, void *p)
+{
+	long t;
+
+	t = (ulong)p;
+	ret->lo = t;
+	ret->hi = 0;
+}
+
+void
+_sl2v(Vlong *ret, long sl)
+{
+	long t;
+
+	t = sl;
+	ret->lo = t;
+	ret->hi = t >> 31;
+}
+
+void
+_ul2v(Vlong *ret, ulong ul)
+{
+	long t;
+
+	t = ul;
+	ret->lo = t;
+	ret->hi = 0;
+}
+
+void
+_si2v(Vlong *ret, int si)
+{
+	long t;
+
+	t = si;
+	ret->lo = t;
+	ret->hi = t >> 31;
+}
+
+void
+_ui2v(Vlong *ret, uint ui)
+{
+	long t;
+
+	t = ui;
+	ret->lo = t;
+	ret->hi = 0;
+}
+
+void
+_sh2v(Vlong *ret, long sh)
+{
+	long t;
+
+	t = (sh << 16) >> 16;
+	ret->lo = t;
+	ret->hi = t >> 31;
+}
+
+void
+_uh2v(Vlong *ret, ulong ul)
+{
+	long t;
+
+	t = ul & 0xffff;
+	ret->lo = t;
+	ret->hi = 0;
+}
+
+void
+_sc2v(Vlong *ret, long uc)
+{
+	long t;
+
+	t = (uc << 24) >> 24;
+	ret->lo = t;
+	ret->hi = t >> 31;
+}
+
+void
+_uc2v(Vlong *ret, ulong ul)
+{
+	long t;
+
+	t = ul & 0xff;
+	ret->lo = t;
+	ret->hi = 0;
+}
+
+long
+_v2sc(Vlong rv)
+{
+	long t;
+
+	t = rv.lo & 0xff;
+	return (t << 24) >> 24;
+}
+
+long
+_v2uc(Vlong rv)
+{
+
+	return rv.lo & 0xff;
+}
+
+long
+_v2sh(Vlong rv)
+{
+	long t;
+
+	t = rv.lo & 0xffff;
+	return (t << 16) >> 16;
+}
+
+long
+_v2uh(Vlong rv)
+{
+
+	return rv.lo & 0xffff;
+}
+
+long
+_v2sl(Vlong rv)
+{
+
+	return rv.lo;
+}
+
+long
+_v2ul(Vlong rv)
+{
+
+	return rv.lo;
+}
+
+long
+_v2si(Vlong rv)
+{
+
+	return rv.lo;
+}
+
+long
+_v2ui(Vlong rv)
+{
+
+	return rv.lo;
+}
+
+int
+_testv(Vlong rv)
+{
+	return rv.lo || rv.hi;
+}
+
+int
+_eqv(Vlong lv, Vlong rv)
+{
+	return lv.lo == rv.lo && lv.hi == rv.hi;
+}
+
+int
+_nev(Vlong lv, Vlong rv)
+{
+	return lv.lo != rv.lo || lv.hi != rv.hi;
+}
+
+int
+_ltv(Vlong lv, Vlong rv)
+{
+	return (long)lv.hi < (long)rv.hi || 
+		(lv.hi == rv.hi && lv.lo < rv.lo);
+}
+
+int
+_lev(Vlong lv, Vlong rv)
+{
+	return (long)lv.hi < (long)rv.hi || 
+		(lv.hi == rv.hi && lv.lo <= rv.lo);
+}
+
+int
+_gtv(Vlong lv, Vlong rv)
+{
+	return (long)lv.hi > (long)rv.hi || 
+		(lv.hi == rv.hi && lv.lo > rv.lo);
+}
+
+int
+_gev(Vlong lv, Vlong rv)
+{
+	return (long)lv.hi > (long)rv.hi || 
+		(lv.hi == rv.hi && lv.lo >= rv.lo);
+}
+
+int
+_lov(Vlong lv, Vlong rv)
+{
+	return lv.hi < rv.hi || 
+		(lv.hi == rv.hi && lv.lo < rv.lo);
+}
+
+int
+_lsv(Vlong lv, Vlong rv)
+{
+	return lv.hi < rv.hi || 
+		(lv.hi == rv.hi && lv.lo <= rv.lo);
+}
+
+int
+_hiv(Vlong lv, Vlong rv)
+{
+	return lv.hi > rv.hi || 
+		(lv.hi == rv.hi && lv.lo > rv.lo);
+}
+
+int
+_hsv(Vlong lv, Vlong rv)
+{
+	return lv.hi > rv.hi || 
+		(lv.hi == rv.hi && lv.lo >= rv.lo);
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Nt/win.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,798 @@
+#define Unknown WUnknown
+#define Colormap	WColormap
+#define Cursor		WCursor
+#define Display		WDisplay
+#define Drawable	WDrawable
+#define Font		WFont
+#define GC		WGC
+#define Point		WPoint
+#define Rectangle	WRectangle
+#define Screen		WScreen
+#define Visual		WVisual
+#define Window		WWindow
+
+#include	<windows.h>
+
+#undef Colormap
+#undef Cursor
+#undef Display
+#undef XDrawable
+#undef Font
+#undef GC
+#undef Point
+#undef Rectangle
+#undef Screen
+#undef Visual
+#undef Window
+#undef Unknown
+
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+#include	<draw.h>
+#include	"keyboard.h"
+#include	"cursor.h"
+#include	"r16.h"
+
+extern ulong displaychan;
+
+extern	int	bytesperline(Rectangle, int);
+extern	int	main(int argc, char **argv);
+static	void	dprint(char*, ...);
+static	DWORD WINAPI	winproc(LPVOID);
+
+static	HINSTANCE	inst;
+static	HINSTANCE	previnst;
+static	int		cmdshow;
+static	HWND		window;
+static	HDC		screen;
+static	HPALETTE	palette;
+static	int		maxxsize;
+static	int		maxysize;
+static	int		attached;
+static	int		isunicode = 1;
+static	HCURSOR		hcursor;
+
+char	*argv0 = "inferno";
+static	ulong	*data;
+
+extern	DWORD	PlatformId;
+char*	gkscanid = "emu_win32vk";
+
+int WINAPI
+WinMain(HINSTANCE winst, HINSTANCE wprevinst, LPSTR cmdline, int wcmdshow)
+{
+	inst = winst;
+	previnst = wprevinst;
+	cmdshow = wcmdshow;
+
+	/* cmdline passed into WinMain does not contain name of executable.
+	 * The globals __argc and __argv to include this info - like UNIX
+	 */
+	main(__argc, __argv);
+	return 0;
+}
+
+static void
+dprint(char *fmt, ...)
+{
+	va_list arg;
+	char buf[128];
+
+	va_start(arg, fmt);
+	vseprint(buf, buf+sizeof(buf), fmt, (LPSTR)arg);
+	va_end(arg);
+	OutputDebugString("inferno: ");
+	OutputDebugString(buf);
+}
+
+static void
+graphicscmap(PALETTEENTRY *pal)
+{
+	int r, g, b, cr, cg, cb, v, p;
+	int num, den;
+	int i, j;
+	for(r=0,i=0;r!=4;r++) for(v=0;v!=4;v++,i+=16){
+		for(g=0,j=v-r;g!=4;g++) for(b=0;b!=4;b++,j++){
+			den=r;
+			if(g>den) den=g;
+			if(b>den) den=b;
+			if(den==0)	/* divide check -- pick grey shades */
+				cr=cg=cb=v*17;
+			else{
+				num=17*(4*den+v);
+				cr=r*num/den;
+				cg=g*num/den;
+				cb=b*num/den;
+			}
+			p = i+(j&15);
+			pal[p].peRed = cr*0x01010101;
+			pal[p].peGreen = cg*0x01010101;
+			pal[p].peBlue = cb*0x01010101;
+			pal[p].peFlags = 0;
+		}
+	}
+}
+
+static void
+graphicsgmap(PALETTEENTRY *pal, int d)
+{
+	int i, j, s, m, p;
+
+	s = 8-d;
+	m = 1;
+	while(--d >= 0)
+		m *= 2;
+	m = 255/(m-1);
+	for(i=0; i < 256; i++){
+		j = (i>>s)*m;
+		p = 255-i;
+		pal[p].peRed = pal[p].peGreen = pal[p].peBlue = (255-j)*0x01010101;
+		pal[p].peFlags = 0;
+	}
+}
+
+static ulong
+autochan(void)
+{
+	HDC dc;
+	int bpp;
+
+	dc = GetDC(NULL);
+	if (dc == NULL)
+		return CMAP8;
+
+	bpp = GetDeviceCaps(dc, BITSPIXEL);
+	if (bpp < 15)
+		return CMAP8;
+	if (bpp < 24)
+		return RGB15;
+	if (bpp < 32)
+		return RGB24;
+	return XRGB32;
+}
+
+uchar*
+attachscreen(Rectangle *r, ulong *chan, int *d, int *width, int *softscreen)
+{
+	int i, k;
+	ulong c;
+	DWORD h;
+	RECT bs;
+	RGBQUAD *rgb;
+	HBITMAP bits;
+	BITMAPINFO *bmi;
+	LOGPALETTE *logpal;
+	PALETTEENTRY *pal;
+	int bsh, bsw, sx, sy;
+
+	if(attached)
+		goto Return;
+
+	/* Compute bodersizes */
+	memset(&bs, 0, sizeof(bs));
+	AdjustWindowRect(&bs, WS_OVERLAPPEDWINDOW, 0);
+	bsw = bs.right - bs.left;
+	bsh = bs.bottom - bs.top;
+	sx = GetSystemMetrics(SM_CXFULLSCREEN) - bsw;
+	Xsize -= Xsize % 4;	/* Round down */
+	if(Xsize > sx)
+		Xsize = sx;
+	sy = GetSystemMetrics(SM_CYFULLSCREEN) - bsh + 20;
+	if(Ysize > sy)
+		Ysize = sy;
+
+	logpal = malloc(sizeof(LOGPALETTE) + 256*sizeof(PALETTEENTRY));
+	if(logpal == nil)
+		return nil;
+	logpal->palVersion = 0x300;
+	logpal->palNumEntries = 256;
+	pal = logpal->palPalEntry;
+
+	c = displaychan;
+	if(c == 0)
+		c = autochan();
+	k = 8;
+	if(TYPE(c) == CGrey){
+		graphicsgmap(pal, NBITS(c));
+		c = GREY8;
+	}else{
+		if(c == RGB15)
+			k = 16;
+		else if(c == RGB24)
+			k = 24;
+		else if(c == XRGB32)
+			k = 32;
+		else
+			c = CMAP8;
+		graphicscmap(pal);
+	}
+
+	palette = CreatePalette(logpal);
+
+	if(k == 8)
+		bmi = malloc(sizeof(BITMAPINFOHEADER) + 256*sizeof(RGBQUAD));
+	else
+		bmi = malloc(sizeof(BITMAPINFOHEADER));
+	if(bmi == nil)
+		return nil;
+	bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+	bmi->bmiHeader.biWidth = Xsize;
+	bmi->bmiHeader.biHeight = -Ysize;	/* - => origin upper left */
+	bmi->bmiHeader.biPlanes = 1;	/* always 1 */
+	bmi->bmiHeader.biBitCount = k;
+	bmi->bmiHeader.biCompression = BI_RGB;
+	bmi->bmiHeader.biSizeImage = 0;	/* Xsize*Ysize*(k/8) */
+	bmi->bmiHeader.biXPelsPerMeter = 0;
+	bmi->bmiHeader.biYPelsPerMeter = 0;
+	bmi->bmiHeader.biClrUsed = 0;
+	bmi->bmiHeader.biClrImportant = 0;	/* number of important colors: 0 means all */
+
+	if(k == 8){
+		rgb = bmi->bmiColors;
+		for(i = 0; i < 256; i++){
+			rgb[i].rgbRed = pal[i].peRed;
+			rgb[i].rgbGreen = pal[i].peGreen;
+			rgb[i].rgbBlue = pal[i].peBlue;
+		}
+	}
+
+	screen = CreateCompatibleDC(NULL);
+	if(screen == nil){
+		fprint(2, "screen dc nil\n");
+		return nil;
+	}
+
+	if(SelectPalette(screen, palette, 1) == nil){
+		fprint(2, "select pallete failed\n");
+	}
+	i = RealizePalette(screen);
+	GdiFlush();
+	bits = CreateDIBSection(screen, bmi, DIB_RGB_COLORS, &data, nil, 0);
+	if(bits == nil){
+		fprint(2, "CreateDIBSection failed\n");
+		return nil;
+	}
+
+	SelectObject(screen, bits);
+	GdiFlush();
+	CreateThread(0, 16384, winproc, nil, 0, &h);
+	attached = 1;
+
+    Return:
+	r->min.x = 0;
+	r->min.y = 0;
+	r->max.x = Xsize;
+	r->max.y = Ysize;
+	displaychan = c;
+	*chan = c;
+	*d = k;
+	*width = (Xsize/4)*(k/8);
+	*softscreen = 1;
+	return (uchar*)data;
+}
+
+void
+flushmemscreen(Rectangle r)
+{
+	RECT wr;
+
+	if(r.max.x<=r.min.x || r.max.y<=r.min.y)
+		return;
+	wr.left = r.min.x;
+	wr.top = r.min.y;
+	wr.right = r.max.x;
+	wr.bottom = r.max.y;
+	InvalidateRect(window, &wr, 0);
+}
+
+static void
+scancode(WPARAM wparam, LPARAM lparam, int keyup)
+{
+	uchar buf[2];
+
+	if(!(lparam & (1<<30))) {		/* don't auto-repeat chars */
+		buf[0] = wparam;
+		buf[1] = wparam >> 8;
+		if (keyup)
+			buf[1] |= 0x80;
+		qproduce(gkscanq, buf, sizeof buf);
+	}
+}
+
+LRESULT CALLBACK
+WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
+{
+	PAINTSTRUCT paint;
+	HDC hdc;
+	LPMINMAXINFO mmi;
+	LONG x, y, w, h, b;
+	HCURSOR dcurs;
+	POINT m;
+
+	switch(msg) {
+	case WM_SETCURSOR:
+		/* User set */
+		if(hcursor != NULL) {
+			SetCursor(hcursor);
+			break;
+		}
+		/* Pick the default */
+		dcurs = LoadCursor(NULL, IDC_ARROW);
+		SetCursor(dcurs);
+		break;
+	case WM_MOUSEWHEEL:
+		if((int)wparam>0)
+			b = 8;
+		else
+			b = 16;
+		m.x = LOWORD(lparam);
+		m.y = HIWORD(lparam);
+		ScreenToClient(hwnd, &m);
+		goto mok;
+	case WM_LBUTTONDBLCLK:
+		b = (1<<8) | 1;
+		goto process;
+	case WM_MBUTTONDBLCLK:
+		b = (1<<8) | 2;
+		goto process;
+	case WM_RBUTTONDBLCLK:
+		b = (1<<8) | 4;
+		goto process;
+	case WM_MOUSEMOVE:
+	case WM_LBUTTONUP:
+	case WM_MBUTTONUP:
+	case WM_RBUTTONUP:
+	case WM_LBUTTONDOWN:
+	case WM_MBUTTONDOWN:
+	case WM_RBUTTONDOWN:
+		b = 0;
+	process:
+		m.x = LOWORD(lparam);
+		m.y = HIWORD(lparam);
+	mok:
+		if(wparam & MK_LBUTTON)
+			b |= 1;
+		if(wparam & MK_MBUTTON)
+			b |= 2;
+		if(wparam & MK_RBUTTON) {
+			if(wparam & MK_CONTROL)
+				b |= 2;  //simulate middle button
+			else
+				b |= 4;  //right button
+		}
+		mousetrack(b, m.x, m.y, 0);
+		break;
+	case WM_SYSKEYDOWN:
+		if(gkscanq)
+			scancode(wparam, lparam, 0);
+		break;
+	case WM_SYSKEYUP:
+		if(gkscanq)
+			scancode(wparam, lparam, 1);
+		else if(wparam == VK_MENU)
+			gkbdputc(gkbdq, Latin);
+		break;
+	case WM_KEYDOWN:
+		if(gkscanq) {
+			scancode(wparam, lparam, 0);
+			break;
+		}
+		switch(wparam) {
+		default:
+			return 0;
+		case VK_HOME:
+			wparam = Home;
+			break;
+		case VK_END:
+			wparam = End;
+			break;
+		case VK_UP:
+			wparam = Up;
+			break;
+		case VK_DOWN:
+			wparam = Down;
+			break;
+		case VK_LEFT:
+			wparam = Left;
+			break;
+		case VK_RIGHT:
+			wparam = Right;
+			break;
+		case VK_PRIOR:	/* VK_PAGE_UP */
+			wparam = Pgup;
+			break;
+		case VK_NEXT:		/* VK_PAGE_DOWN */
+			wparam = Pgdown;
+			break;
+		case VK_PRINT:
+			wparam = Print;
+			break;
+		case VK_SCROLL:
+			wparam = Scroll;
+			break;
+		case VK_PAUSE:
+			wparam = Pause;
+			break;
+		case VK_INSERT:
+			wparam = Ins;
+			break;
+		case VK_DELETE:
+			wparam = Del;
+			break;
+/*
+		case VK_TAB:
+			if(GetKeyState(VK_SHIFT)<0)
+				wparam = BackTab;
+			else
+				wparam = '\t';
+			break;
+*/
+		}
+		gkbdputc(gkbdq, wparam);
+		break;
+	case WM_KEYUP:
+		if(gkscanq)
+			scancode(wparam, lparam, 1);
+		break;
+	case WM_CHAR:
+		if(gkscanq)
+			break;
+		switch(wparam) {
+		case '\n':
+		  	wparam = '\r';
+		  	break;
+		case '\r':
+		  	wparam = '\n';
+		  	break;
+		case '\t':
+			if(GetKeyState(VK_SHIFT)<0)
+				wparam = BackTab;
+			else
+				wparam = '\t';
+			break;
+		}
+		if(lparam & KF_ALTDOWN) 
+		    	wparam = APP | (wparam & 0xFF);
+		gkbdputc(gkbdq, wparam);
+		break;
+	case WM_CLOSE:
+		DestroyWindow(hwnd);
+		break;
+	case WM_DESTROY:
+		PostQuitMessage(0);
+		cleanexit(0);
+		break;
+	case WM_PALETTECHANGED:
+		if((HWND)wparam == hwnd)
+			break;
+	/* fall through */
+	case WM_QUERYNEWPALETTE:
+		hdc = GetDC(hwnd);
+		SelectPalette(hdc, palette, 0);
+		if(RealizePalette(hdc) != 0)
+			InvalidateRect(hwnd, nil, 0);
+		ReleaseDC(hwnd, hdc);
+		break;
+	case WM_PAINT:
+		hdc = BeginPaint(hwnd, &paint);
+		SelectPalette(hdc, palette, 0);
+		RealizePalette(hdc);
+		x = paint.rcPaint.left;
+		y = paint.rcPaint.top;
+		w = paint.rcPaint.right - x;
+		h = paint.rcPaint.bottom - y;
+		BitBlt(hdc, x, y, w, h, screen, x, y, SRCCOPY);
+		EndPaint(hwnd, &paint);
+		break;
+	case WM_GETMINMAXINFO:
+		mmi = (LPMINMAXINFO)lparam;
+		mmi->ptMaxSize.x = maxxsize;
+		mmi->ptMaxSize.y = maxysize;
+		mmi->ptMaxTrackSize.x = maxxsize;
+		mmi->ptMaxTrackSize.y = maxysize;
+		break;
+	case WM_SYSCHAR:
+	case WM_COMMAND:
+	case WM_CREATE:
+	case WM_SETFOCUS:
+	case WM_DEVMODECHANGE:
+	case WM_WININICHANGE:
+	case WM_INITMENU:
+	default:
+		if(isunicode)
+			return DefWindowProcW(hwnd, msg, wparam, lparam);
+		return DefWindowProcA(hwnd, msg, wparam, lparam);
+	}
+	return 0;
+}
+
+static DWORD WINAPI
+winproc(LPVOID x)
+{
+	MSG msg;
+	RECT size;
+	WNDCLASSW wc;
+	WNDCLASSA wca;
+	DWORD ws;
+
+	if(!previnst){
+		wc.style = CS_DBLCLKS;
+		wc.lpfnWndProc = WindowProc;
+		wc.cbClsExtra = 0;
+		wc.cbWndExtra = 0;
+		wc.hInstance = inst;
+		wc.hIcon = LoadIcon(inst, MAKEINTRESOURCE(100));
+		wc.hCursor = NULL;
+		wc.hbrBackground = GetStockObject(WHITE_BRUSH);
+
+		wc.lpszMenuName = 0;
+		wc.lpszClassName = L"inferno";
+
+		if(RegisterClassW(&wc) == 0){
+			wca.style = wc.style;
+			wca.lpfnWndProc = wc.lpfnWndProc;
+			wca.cbClsExtra = wc.cbClsExtra;
+			wca.cbWndExtra = wc.cbWndExtra;
+			wca.hInstance = wc.hInstance;
+			wca.hIcon = wc.hIcon;
+			wca.hCursor = wc.hCursor;
+			wca.hbrBackground = wc.hbrBackground;
+
+			wca.lpszMenuName = 0;
+			wca.lpszClassName = "inferno";
+			isunicode = 0;
+
+			RegisterClassA(&wca);
+		}
+	}
+
+	size.left = 0;
+	size.top = 0;
+	size.right = Xsize;
+	size.bottom = Ysize;
+
+	ws = WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX;
+
+	if(AdjustWindowRect(&size, ws, 0)) {
+		maxxsize = size.right - size.left;
+		maxysize = size.bottom - size.top;
+	}else{
+		maxxsize = Xsize + 40;
+		maxysize = Ysize + 40;
+	}
+
+	if(isunicode) {
+		window = CreateWindowExW(
+			0,			/* extended style */
+			L"inferno",		/* class */
+			L"Inferno",		/* caption */
+			ws,	/* style */
+			CW_USEDEFAULT,		/* init. x pos */
+			CW_USEDEFAULT,		/* init. y pos */
+			maxxsize,		/* init. x size */
+			maxysize,		/* init. y size */
+			NULL,			/* parent window (actually owner window for overlapped) */
+			NULL,			/* menu handle */
+			inst,			/* program handle */
+			NULL			/* create parms */
+			);
+	}else{
+		window = CreateWindowExA(
+			0,			/* extended style */
+			"inferno",		/* class */
+			"Inferno",		/* caption */
+			ws,	/* style */
+			CW_USEDEFAULT,		/* init. x pos */
+			CW_USEDEFAULT,		/* init. y pos */
+			maxxsize,		/* init. x size */
+			maxysize,		/* init. y size */
+			NULL,			/* parent window (actually owner window for overlapped) */
+			NULL,			/* menu handle */
+			inst,			/* program handle */
+			NULL			/* create parms */
+			);
+	}
+
+	if(window == nil){
+		fprint(2, "can't make window\n");
+		ExitThread(0);
+	}
+
+	SetForegroundWindow(window);
+	ShowWindow(window, cmdshow);
+	UpdateWindow(window);
+	// CloseWindow(window);
+
+	if(isunicode) {
+		while(GetMessageW(&msg, NULL, 0, 0)) {
+			TranslateMessage(&msg);
+			DispatchMessageW(&msg);
+		}
+	}else{
+		while(GetMessageA(&msg, NULL, 0, 0)) {
+			TranslateMessage(&msg);
+			DispatchMessageA(&msg);
+		}
+	}
+	attached = 0;
+	ExitThread(msg.wParam);
+	return 0;
+}
+
+void
+setpointer(int x, int y)
+{
+	POINT pt; 
+ 
+	pt.x = x; pt.y = y;
+	ClientToScreen(window, &pt);
+	SetCursorPos(pt.x, pt.y);
+}
+
+void
+drawcursor(Drawcursor* c)
+{
+	HCURSOR nh, oh;
+	Rectangle ir;
+	int i, h, j, bpl, ch, cw;
+	uchar *bs, *bc, *and, *xor, *cand, *cxor;
+
+	/* Set the default system cursor */
+	if(c->data == nil) {
+		oh = hcursor;
+		hcursor = NULL;
+		if(oh != NULL) {
+			SendMessage(window, WM_SETCURSOR, (int)window, 0);
+			DestroyCursor(oh);
+		}
+		return;
+	}
+
+	ir.min.x = c->minx;
+	ir.min.y = c->miny;
+	ir.max.x = c->maxx;
+	ir.max.y = c->maxy;
+	bpl = bytesperline(ir, 1);
+
+	h = (c->maxy-c->miny)/2;
+
+	ch = GetSystemMetrics(SM_CYCURSOR);
+	cw = (GetSystemMetrics(SM_CXCURSOR)+7)/8;
+
+	i = ch*cw;
+	and = malloc(2*i);
+	if(and == nil)
+		return;
+	xor = and + i;
+	memset(and, 0xff, i);
+	memset(xor, 0, i);
+
+	cand = and;
+	cxor = xor;
+	bc = c->data;
+	bs = c->data + h*bpl;	
+
+	for(i = 0; i < ch && i < h; i++) {
+		for(j = 0; j < cw && j < bpl; j++) {
+			cand[j] = ~(bs[j] | bc[j]);
+			cxor[j] = ~bs[j] & bc[j];
+		}
+		cand += cw;
+		cxor += cw;
+		bs += bpl;
+		bc += bpl;
+	}
+	nh = CreateCursor(inst, -c->hotx, -c->hoty, 8*cw, ch, and, xor);
+	if(nh != NULL) {
+		oh = hcursor;
+		hcursor = nh;
+		SendMessage(window, WM_SETCURSOR, (int)window, 0);
+		if(oh != NULL)
+			DestroyCursor(oh);
+	}else{
+		print("CreateCursor error %d\n", GetLastError());
+		print("CXCURSOR=%d\n", GetSystemMetrics(SM_CXCURSOR));
+		print("CYCURSOR=%d\n", GetSystemMetrics(SM_CYCURSOR));
+	}
+	free(and);
+}
+
+/*
+ * thanks to drawterm for these
+ */
+
+static char*
+clipreadunicode(HANDLE h)
+{
+	Rune16 *p;
+	int n;
+	char *q;
+	
+	p = GlobalLock(h);
+	n = rune16nlen(p, runes16len(p)+1);
+	q = malloc(n);
+	if(q != nil)
+		runes16toutf(q, p, n);
+	GlobalUnlock(h);
+
+	if(q == nil)
+		error(Enovmem);
+	return q;
+}
+
+static char *
+clipreadutf(HANDLE h)
+{
+	uchar *p;
+
+	p = GlobalLock(h);
+	p = strdup(p);
+	GlobalUnlock(h);
+
+	if(p == nil)
+		error(Enovmem);
+	return p;
+}
+
+char*
+clipread(void)
+{
+	HANDLE h;
+	char *p;
+
+	if(!OpenClipboard(window))
+		return strdup("");
+
+	if((h = GetClipboardData(CF_UNICODETEXT)))
+		p = clipreadunicode(h);
+	else if((h = GetClipboardData(CF_TEXT)))
+		p = clipreadutf(h);
+	else
+		p = strdup("");
+	
+	CloseClipboard();
+	return p;
+}
+
+int
+clipwrite(char *buf)
+{
+	HANDLE h;
+	char *p;
+	Rune16 *rp;
+	int n;
+
+	n = 0;
+	if(buf != nil)
+		n = strlen(buf);
+	if(!OpenClipboard(window))
+		return -1;
+
+	if(!EmptyClipboard()){
+		CloseClipboard();
+		return -1;
+	}
+
+	h = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, (n+1)*sizeof(Rune16));
+	if(h == NULL)
+		error(Enovmem);
+	rp = GlobalLock(h);
+	utftorunes16(rp, buf, n+1);
+	GlobalUnlock(h);
+
+	SetClipboardData(CF_UNICODETEXT, h);
+
+	h = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, n+1);
+	if(h == NULL)
+		error(Enovmem);
+	p = GlobalLock(h);
+	memmove(p, buf, n);
+	p[n] = 0;
+	GlobalUnlock(h);
+	
+	SetClipboardData(CF_TEXT, h);
+
+	CloseClipboard();
+	return n;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/OpenBSD/asm-386.S	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,111 @@
+	.file	"asm-OpenBSD-386.S"
+
+#include <sys/syscall.h>
+#include <machine/asm.h>
+
+#include "rfork_thread.S"
+
+/*
+ * executeonnewstack(void *tos, void (*tramp)(void *arg), void *arg)
+ */
+
+	.type	 ournewstack,@function
+	.global	executeonnewstack
+executeonnewstack:
+	pushl	%ebp
+	movl	%esp, %ebp
+	pushl	%esi
+
+	movl	8(%ebp), %esi	/* get tos */
+	subl	$4, %esi
+	movl	16(%ebp), %eax
+	movl	%eax, (%esi)	/* stash arg on new stack */
+	subl	$4, %esi
+	movl	12(%ebp), %eax
+	movl	%eax, (%esi)	/* stash tramp on new stack */
+	mov	%esi, %esp	/* swap stacks pronto */
+	popl	%eax		/* recover the tramp address */
+	call	*%eax		/* and jump to it (ho ho) */
+
+	/* if we return here, tramp didn't do it's job */
+
+	addl	$8, %esp	/* clean up for pose value */
+
+	leal	SYS_exit, %eax
+	int	$0x80
+
+/*
+ * unlockandexit(int *key)
+ *
+ * NB: the return status may be rubbish if the stack is reused
+ *	between the unlock and the system call, but this should
+ *	not matter since no task is waiting for the result
+ */
+
+	.type	unlockandexit,@function
+	.global	unlockandexit
+unlockandexit:
+	pushl	%ebp
+	movl	%esp, %ebp
+
+	movl	8(%ebp), %esi		/* get the key address */
+	pushl	$0			/* exit status 0 */
+	movl	$0, %eax		/* unlock the stack allocator */
+	movl	%eax, (%esi)
+	leal	SYS_exit, %eax		/* call exit */
+	int	$0x80
+
+/*
+ * umult(ulong m1, ulong m2, ulong *hi)
+ */
+
+	.type	umult,@function
+	.global	umult
+umult:
+	pushl	%ebp
+	movl	%esp, %ebp
+	pushl	%ebx
+
+	movl	8(%ebp), %eax
+	movl	12(%ebp), %ebx
+	mull	%ebx
+	movl	16(%ebp), %ebx
+	movl	%edx, (%ebx)
+
+	popl	%ebx
+	popl	%ebp
+	ret
+
+	.type	FPsave,@function
+	.global	FPsave
+FPsave:
+	pushl	%ebp
+	movl	%esp, %ebp
+	movl	8(%ebp), %eax
+	fstenv	(%eax)
+	popl	%ebp
+	ret
+
+	.type	FPrestore,@function
+	.global	FPrestore
+FPrestore:
+	pushl	%ebp
+	movl	%esp, %ebp
+	movl	8(%ebp), %eax
+	fldenv	(%eax)
+	popl	%ebp
+	ret
+
+	.type	getcallerpc,@function
+	.global	getcallerpc
+getcallerpc:
+	movl	4(%ebp), %eax
+	ret
+
+	.type	_tas,@function
+	.globl	_tas
+_tas:
+	movl	$1, %eax
+	movl	4(%esp), %ecx
+	xchgl	%eax, 0(%ecx)
+	ret
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/OpenBSD/audio.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,547 @@
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/filio.h>
+#include "audio.h"
+#include <soundcard.h>
+
+#define 	Audio_Mic_Val		SOUND_MIXER_MIC
+#define 	Audio_Linein_Val	SOUND_MIXER_LINE
+
+#define	Audio_Speaker_Val	SOUND_MIXER_SPEAKER
+#define	Audio_Headphone_Val	SOUND_MIXER_PHONEOUT
+#define	Audio_Lineout_Val	SOUND_MIXER_VOLUME
+
+#define 	Audio_Pcm_Val		AFMT_S16_LE
+#define 	Audio_Ulaw_Val		AFMT_MU_LAW
+#define 	Audio_Alaw_Val		AFMT_A_LAW
+
+#include "audio-tbls.c"
+
+#define	min(a,b)	((a) < (b) ? (a) : (b))
+static int debug;
+
+#define AUDIO_FILE_STRING	"/dev/dsp"
+
+enum {
+	A_Pause,
+	A_UnPause
+};
+
+enum {
+	A_In,
+	A_Out
+};
+
+static QLock inlock;
+static QLock outlock;
+
+static	int	audio_file  = -1;	/* file in/out */
+static	int	audio_file_in  = -1;	/* copy of above when opened O_READ/O_RDWR */
+static	int	audio_file_out  = -1;	/* copy of above when opened O_WRITE/O_RDWR */
+
+static	int	audio_swap_flag = 0;	/* endian swap */
+
+static	int	audio_in_pause = A_UnPause;
+
+static Audio_t av;
+static int mixerleftvol[32];
+static int mixerrightvol[32];
+
+static int audio_enforce(Audio_t*);
+static int audio_open(void);
+static int audio_pause_in(int, int);
+static int audio_flush(int, int);
+static int audio_pause_out(int);
+static int audio_set_blocking(int);
+static int audio_set_info(int, Audio_d*, int);
+static void audio_swap_endian(char*, int);
+
+void
+audio_file_init(void)
+{
+	int i;
+	static ushort flag = 1;
+
+	audio_swap_flag = *((uchar*)&flag) == 0;	/* big-endian? */
+	audio_info_init(&av);
+	for (i = 0; i < 32; i++)
+		mixerleftvol[i] = mixerrightvol[i] = 100;
+}
+
+void
+audio_ctl_init(void)
+{
+}
+
+void
+audio_file_open(Chan *c, int omode)
+{
+	char ebuf[ERRMAX];
+
+	if (debug)
+		print("audio_file_open(0x%.8lux, %d)\n", c, omode);
+	switch(omode){
+	case OREAD:
+		qlock(&inlock);
+		if(waserror()){
+			qunlock(&inlock);
+			nexterror();
+		}
+
+		if(audio_file_in >= 0)
+			error(Einuse);
+		if (audio_file < 0)
+			audio_file = audio_open();
+		audio_file_in = audio_file;
+		poperror();
+		qunlock(&inlock);
+		break;
+	case OWRITE:
+		qlock(&outlock);
+		if(waserror()){
+			qunlock(&outlock);
+			nexterror();
+		}
+		if(audio_file_out >= 0)
+			error(Einuse);
+		if (audio_file < 0)
+			audio_file = audio_open();
+		audio_file_out = audio_file;
+		poperror();
+		qunlock(&outlock);
+		break;
+	case ORDWR:
+		qlock(&inlock);
+		qlock(&outlock);
+		if(waserror()){
+			qunlock(&outlock);
+			qunlock(&inlock);
+			nexterror();
+		}
+		if(audio_file_in >= 0 || audio_file_out >= 0)
+			error(Einuse);
+		if (audio_file < 0)
+			audio_file = audio_open();
+		audio_file_in = audio_file_out = audio_file;
+		poperror();
+		qunlock(&outlock);
+		qunlock(&inlock);
+		break;
+	}
+	if (debug)
+		print("audio_file_open: success\nin %d out %d both %d\n",
+			audio_file_out, audio_file_in, audio_file);
+}
+
+void
+audio_ctl_open(Chan *c, int omode)
+{
+	USED(c);
+	USED(omode);
+}
+
+void
+audio_file_close(Chan *c)
+{
+	switch(c->mode){
+	case OREAD:
+		qlock(&inlock);
+		qlock(&outlock);
+		if (audio_file_out < 0) {
+			close(audio_file);
+			audio_file = -1;
+		}
+		qunlock(&outlock);
+		audio_file_in = -1;
+		qunlock(&inlock);
+		break;
+	case OWRITE:
+		qlock(&inlock);
+		qlock(&outlock);
+		if (audio_file_in < 0) {
+			close(audio_file);
+			audio_file = -1;
+		}
+		audio_file_out = -1;
+		qunlock(&outlock);
+		qunlock(&inlock);
+		break;
+	case ORDWR:
+		qlock(&inlock);
+		qlock(&outlock);
+		close(audio_file);
+		audio_file_in = audio_file_out = audio_file = -1;
+		qunlock(&outlock);
+		qunlock(&inlock);
+		break;
+	}
+}
+
+void
+audio_ctl_close(Chan *c)
+{
+}
+
+long
+audio_file_read(Chan *c, void *va, long count, vlong offset)
+{
+	struct  timespec time;
+	long ba, status, chunk, total;
+	char *pva = (char *) va;
+
+	qlock(&inlock);
+	if(waserror()){
+		qunlock(&inlock);
+		nexterror();
+	}
+
+	if(audio_file_in < 0)
+		error(Eperm);
+
+	/* check block alignment */
+	ba = av.in.bits * av.in.chan / Bits_Per_Byte;
+
+	if(count % ba)
+		error(Ebadarg);
+
+	if(! audio_pause_in(audio_file_in, A_UnPause))
+		error(Eio);
+	
+	total = 0;
+	while(total < count) {
+		chunk = count - total;
+		osenter();
+		status = read(audio_file_in, pva + total, chunk);
+		osleave(); 
+		if(status < 0)
+			error(Eio);
+		total += status;
+	}
+
+	if(total != count)
+		error(Eio);
+
+	if(audio_swap_flag && av.out.bits == 16)
+		audio_swap_endian(pva, count); 
+
+	poperror();
+	qunlock(&inlock);
+
+	return count;
+}
+
+long
+audio_file_write(Chan *c, void *va, long count, vlong offset)
+{
+	struct  timespec time;
+	long status = -1;
+	long ba, total, chunk, bufsz;
+
+	if (debug > 1)
+		print("audio_file_write(0x%.8lux, 0x%.8lux, %ld, %uld)\n",
+			c, va, count, offset);
+
+	qlock(&outlock);
+	if(waserror()){
+		qunlock(&outlock);
+		nexterror();
+	}
+
+	if(audio_file_out < 0)
+		error(Eperm);
+
+	/* check block alignment */
+	ba = av.out.bits * av.out.chan / Bits_Per_Byte;
+
+	if(count % ba)
+		error(Ebadarg);
+
+	if(audio_swap_flag && av.out.bits == 16)
+		audio_swap_endian(va, count); 
+
+	total = 0;
+	bufsz = av.out.buf * Audio_Max_Buf / Audio_Max_Val;
+
+	if(bufsz == 0)
+		error(Ebadarg);
+
+	while(total < count) {
+		chunk = min(bufsz, count - total);
+		osenter();
+		status = write(audio_file_out, va, chunk);
+		osleave();
+		if(status <= 0)
+			error(Eio);
+		total += status;
+	}
+
+	poperror();
+	qunlock(&outlock);
+
+	return count;
+}
+
+static int
+audio_open(void)
+{
+	int fd;
+
+	/* open non-blocking in case someone already has it open */
+	/* otherwise we would block until they close! */
+	fd = open(AUDIO_FILE_STRING, O_RDWR|O_NONBLOCK);
+	if(fd < 0)
+		oserror();
+
+	/* change device to be blocking */
+	if(!audio_set_blocking(fd)) {
+		if (debug)
+			print("audio_open: failed to set blocking\n");
+		close(fd);
+		error("cannot set blocking mode");
+	}
+
+	if (debug)
+		print("audio_open: blocking set\n");
+
+	/* set audio info */
+	av.in.flags = ~0;
+	av.out.flags = 0;
+
+	if(! audio_set_info(fd, &av.in, A_In)) {
+		close(fd);
+		error(Ebadarg);
+	}
+
+	av.in.flags = 0;
+
+	/* tada, we're open, blocking, paused and flushed */
+	return fd;
+}
+
+long
+audio_ctl_write(Chan *c, void *va, long count, vlong offset)
+{
+	int	fd;
+	int	ff;
+	Audio_t tmpav = av;
+
+	tmpav.in.flags = 0;
+	tmpav.out.flags = 0;
+
+	if (!audioparse(va, count, &tmpav))
+		error(Ebadarg);
+
+	if (!audio_enforce(&tmpav))
+		error(Ebadarg);
+
+	qlock(&inlock);
+	if (waserror()) {
+		qunlock(&inlock);
+		nexterror();
+	}
+
+	if (audio_file_in >= 0 && (tmpav.in.flags & AUDIO_MOD_FLAG)) {
+		if (!audio_pause_in(audio_file_in, A_Pause))
+			error(Ebadarg);
+		if (!audio_flush(audio_file_in, A_In))
+			error(Ebadarg);
+		if (!audio_set_info(audio_file_in, &tmpav.in, A_In))
+			error(Ebadarg);
+	}
+	poperror();
+	qunlock(&inlock);
+
+	qlock(&outlock);
+	if (waserror()) {
+		qunlock(&outlock);
+		nexterror();
+	}
+	if (audio_file_out >= 0 && (tmpav.out.flags & AUDIO_MOD_FLAG)){
+		if (!audio_pause_out(audio_file_out))
+			error(Ebadarg);
+		if (!audio_set_info(audio_file_out, &tmpav.out, A_Out))
+			error(Ebadarg);
+	}
+	poperror();
+	qunlock(&outlock);
+
+	tmpav.in.flags = 0;
+	tmpav.out.flags = 0;
+
+	av = tmpav;
+
+	return count;
+}
+
+
+
+static int
+audio_set_blocking(int fd)
+{
+	int val;
+
+	if((val = fcntl(fd, F_GETFL, 0)) == -1)
+		return 0;
+	
+	val &= ~O_NONBLOCK;
+
+	if(fcntl(fd, F_SETFL, val) < 0)
+		return 0;
+
+	return 1;
+}
+
+static int
+doioctl(int fd, int ctl, int *info)
+{
+	int status;
+	osenter();
+	status = ioctl(fd, ctl, info);  /* qlock and load general stuff */
+	osleave();
+	if (status < 0)
+		print("doioctl(0x%.8lux, 0x%.8lux) failed %d\n", ctl, *info, errno);
+	return status;
+}
+
+static int
+choosefmt(Audio_d *i)
+{
+	int newbits, newenc;
+	
+	newbits = i->bits;
+	newenc = i->enc;
+	switch (newenc) {
+	case Audio_Alaw_Val:
+		if (newbits == 8)
+			return AFMT_A_LAW;
+		break;
+	case Audio_Ulaw_Val:
+		if (newbits == 8)
+			return AFMT_MU_LAW;
+		break;
+	case Audio_Pcm_Val:
+		if (newbits == 8)
+			return AFMT_U8;
+		else if (newbits == 16)
+			return AFMT_S16_LE;
+		break;
+	}
+	return -1;
+}
+
+static int
+audio_set_info(int fd, Audio_d *i, int d)
+{
+	int status;
+	int unequal_stereo = 0;
+
+	if(fd < 0)
+		return 0;
+
+	/* fmt */
+	if(i->flags & (AUDIO_BITS_FLAG || AUDIO_ENC_FLAG)) {
+		int oldfmt, newfmt;
+		oldfmt = AFMT_QUERY;
+		if (doioctl(fd, SNDCTL_DSP_SETFMT, &oldfmt) < 0)
+			return 0;
+		if (debug)
+			print("audio_set_info: current format 0x%.8lux\n", oldfmt);
+		newfmt = choosefmt(i);
+		if (debug)
+			print("audio_set_info: new format 0x%.8lux\n", newfmt);
+		if (newfmt == -1 || newfmt != oldfmt && doioctl(fd, SNDCTL_DSP_SETFMT, &newfmt) < 0)
+			return 0;
+	}
+
+	/* channels */
+	if(i->flags & AUDIO_CHAN_FLAG) {
+		int channels = i->chan;
+		if (debug)
+			print("audio_set_info: new channels %d\n", channels);
+		if (doioctl(fd, SNDCTL_DSP_CHANNELS, &channels) < 0
+			|| channels != i->chan)
+			return 0;
+	}
+
+	/* sample rate */
+	if(i->flags & AUDIO_RATE_FLAG) {
+		int speed = i->rate;
+		if (debug)
+			print("audio_set_info: new speed %d\n", speed);
+		if (doioctl(fd, SNDCTL_DSP_SPEED, &speed) < 0 || speed != i->rate)
+			return 0;
+	}
+
+	/* dev volume */
+	if(i->flags & (AUDIO_LEFT_FLAG | AUDIO_VOL_FLAG | AUDIO_RIGHT_FLAG)) {
+		int val;
+		if (i->flags & (AUDIO_LEFT_FLAG | AUDIO_VOL_FLAG))
+			mixerleftvol[i->dev] = (i->left * 100) / Audio_Max_Val;
+		if (i->flags & (AUDIO_RIGHT_FLAG | AUDIO_VOL_FLAG))
+			mixerrightvol[i->dev] = (i->right * 100) / Audio_Max_Val;
+		val = mixerleftvol[i->dev] | (mixerrightvol[i->dev] << 8);
+		doioctl(fd, MIXER_WRITE(i->dev), &val);
+	}
+
+	if (i->flags & AUDIO_DEV_FLAG) {
+	}
+	
+	return 1;
+}
+
+void 
+audio_swap_endian(char *p, int n)
+{
+	int b;
+
+	while (n > 1) {
+		b = p[0];
+		p[0] = p[1];
+		p[1] = b;
+		p += 2;
+		n -= 2;
+	}
+}
+
+static int
+audio_pause_out(int fd)
+{
+	USED(fd);
+	return 1;
+}
+
+static int
+audio_pause_in(int fd, int f)
+{
+	USED(fd);
+	USED(f);
+	return 1;
+}
+
+static int
+audio_flush(int fd, int d)
+{
+	int x;
+	return doioctl(fd, SNDCTL_DSP_SYNC, &x) >= 0;
+}
+
+static int
+audio_enforce(Audio_t *t)
+{
+	if((t->in.enc == Audio_Ulaw_Val || t->in.enc == Audio_Alaw_Val) && 
+		(t->in.rate != 8000 || t->in.chan != 1))
+		 return 0;
+	if((t->out.enc == Audio_Ulaw_Val || t->out.enc == Audio_Alaw_Val) && 
+		(t->out.rate != 8000 || t->out.chan != 1))
+		 return 0;
+	return 1;
+}
+
+Audio_t*
+getaudiodev(void)
+{
+	return &av;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/OpenBSD/cmd.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,213 @@
+#include	<sys/types.h>
+#include	<signal.h>
+#include 	<pwd.h>
+#include	<sys/resource.h>
+#include	<sys/wait.h>
+#include	<fcntl.h>
+
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+enum
+{
+	Debug = 0
+};
+
+/*
+ * os-specific devcmd support.
+ * this version should be reasonably portable across Unix systems.
+ */
+typedef struct Targ Targ;
+struct Targ
+{
+	int	fd[3];	/* fd[0] is standard input, fd[1] is standard output, fd[2] is standard error */
+	char**	args;
+	char*	dir;
+	int	pid;
+	int	wfd;	/* child writes errors that occur after the fork or on exec */
+	int	uid;
+	int	gid;
+};
+
+extern int gidnobody;
+extern int uidnobody;
+
+static int
+childproc(Targ *t)
+{
+	int i, nfd;
+
+	if(Debug)
+		print("devcmd: '%s'", t->args[0]);
+
+	nfd = getdtablesize();
+	for(i = 0; i < nfd; i++)
+		if(i != t->fd[0] && i != t->fd[1] && i != t->fd[2] && i != t->wfd)
+			close(i);
+
+	dup2(t->fd[0], 0);
+	dup2(t->fd[1], 1);
+	dup2(t->fd[2], 2);
+	close(t->fd[0]);
+	close(t->fd[1]);
+	close(t->fd[2]);
+
+	/* should have an auth file to do host-specific authorisation? */
+	if(t->gid != -1){
+		if(setgid(t->gid) < 0 && getegid() == 0){
+			fprint(t->wfd, "can't set gid %d: %s", t->gid, strerror(errno));
+			_exit(1);
+		}
+	}
+
+	if(t->uid != -1){
+		if(setuid(t->uid) < 0 && geteuid() == 0){
+			fprint(t->wfd, "can't set uid %d: %s", t->uid, strerror(errno));
+			_exit(1);
+		}
+	}
+
+	if(t->dir != nil && chdir(t->dir) < 0){
+		fprint(t->wfd, "can't chdir to %s: %s", t->dir, strerror(errno));
+		_exit(1);
+	}
+
+	signal(SIGPIPE, SIG_DFL);
+
+	execvp(t->args[0], t->args);
+	if(Debug)
+		print("execvp: %s\n",strerror(errno));
+	fprint(t->wfd, "exec failed: %s", strerror(errno));
+
+	_exit(1);
+}
+
+void*
+oscmd(char **args, int nice, char *dir, int *fd)
+{
+	Targ *t;
+	int r, fd0[2], fd1[2], fd2[2], wfd[2], n, pid;
+
+	t = mallocz(sizeof(*t), 1);
+	if(t == nil)
+		return nil;
+
+	fd0[0] = fd0[1] = -1;
+	fd1[0] = fd1[1] = -1;
+	fd2[0] = fd2[1] = -1;
+	wfd[0] = wfd[1] = -1;
+	if(pipe(fd0) < 0 || pipe(fd1) < 0 || pipe(fd2) < 0 || pipe(wfd) < 0)
+		goto Error;
+	if(fcntl(wfd[1], F_SETFD, FD_CLOEXEC) < 0)	/* close on exec to give end of file on success */
+		goto Error;
+
+	t->fd[0] = fd0[0];
+	t->fd[1] = fd1[1];
+	t->fd[2] = fd2[1];
+	t->wfd = wfd[1];
+	t->args = args;
+	t->dir = dir;
+	t->gid = up->env->gid;
+	if(t->gid == -1)
+		t->gid = gidnobody;
+	t->uid = up->env->uid;
+	if(t->uid == -1)
+		t->uid = uidnobody;
+
+	signal(SIGCHLD, SIG_DFL);
+	switch(pid = fork()) {
+	case -1:
+		goto Error;
+	case 0:
+		setpgid(0, getpid());
+		if(nice)
+			oslopri();
+		childproc(t);
+		_exit(1);
+	default:
+		t->pid = pid;
+		if(Debug)
+			print("cmd pid %d\n", t->pid);
+		break;
+	}
+
+	close(fd0[0]);
+	close(fd1[1]);
+	close(fd2[1]);
+	close(wfd[1]);
+
+	n = read(wfd[0], up->genbuf, sizeof(up->genbuf)-1);
+	close(wfd[0]);
+	if(n > 0){
+		close(fd0[1]);
+		close(fd1[0]);
+		close(fd2[0]);
+		free(t);
+		up->genbuf[n] = 0;
+		if(Debug)
+			print("oscmd: bad exec: %q\n", up->genbuf);
+		error(up->genbuf);
+		return nil;
+	}
+
+	fd[0] = fd0[1];
+	fd[1] = fd1[0];
+	fd[2] = fd2[0];
+	return t;
+
+Error:
+	r = errno;
+	if(Debug)
+		print("oscmd: %q\n",strerror(r));
+	close(fd0[0]);
+	close(fd0[1]);
+	close(fd1[0]);
+	close(fd1[1]);
+	close(fd2[0]);
+	close(fd2[1]);
+	close(wfd[0]);
+	close(wfd[1]);
+	error(strerror(r));
+	return nil;
+}
+
+int
+oscmdkill(void *a)
+{
+	Targ *t = a;
+
+	if(Debug)
+		print("kill: %d\n", t->pid);
+	return kill(-t->pid, SIGTERM);
+}
+
+int
+oscmdwait(void *a, char *buf, int n)
+{
+	Targ *t = a;
+	int s;
+
+	if(waitpid(t->pid, &s, 0) == -1){
+		if(Debug)
+			print("wait error: %d [in %d] %q\n", t->pid, getpid(), strerror(errno));
+		return -1;
+	}
+	if(WIFEXITED(s)){
+		if(WEXITSTATUS(s) == 0)
+			return snprint(buf, n, "%d 0 0 0 ''", t->pid);
+		return snprint(buf, n, "%d 0 0 0 'exit: %d'", t->pid, WEXITSTATUS(s));
+	}
+	if(WIFSIGNALED(s)){
+		if(WTERMSIG(s) == SIGTERM || WTERMSIG(s) == SIGKILL)
+			return snprint(buf, n, "%d 0 0 0 killed", t->pid);
+		return snprint(buf, n, "%d 0 0 0 'signal: %d'", t->pid, WTERMSIG(s));
+	}
+	return snprint(buf, n, "%d 0 0 0 'odd status: 0x%x'", t->pid, s);
+}
+
+void
+oscmdfree(void *a)
+{
+	free(a);
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/OpenBSD/deveia.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,39 @@
+/*
+ * FreeBSD serial port definitions
+ */
+
+static char *sysdev[] = {
+        "/dev/cuaa0",
+        "/dev/cuaa1",
+        "/dev/cuaa2",
+        "/dev/cuaa3",
+};
+
+#include <sys/ioctl.h>
+#include "deveia-posix.c"
+#include "deveia-bsd.c"
+
+
+static struct tcdef_t bps[] = {
+	{0,		B0},
+	{50,		B50},
+	{75,		B75},
+	{110,		B110},
+	{134,		B134},
+	{150,		B150},
+	{200,		B200},
+	{300,		B300},
+	{600,		B600},
+	{1200,	B1200},
+	{1800,	B1800},
+	{2400,	B2400},
+	{4800,	B4800},
+	{9600,	B9600},
+	{19200,	B19200},
+	{38400,	B38400},
+	{57600,	B57600},
+	{115200,	B115200},
+	{230400,	B230400},
+	{-1,		-1}
+};
+
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/OpenBSD/devfs.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,7 @@
+#include "devfs-posix.c"
+
+static vlong
+osdisksize(int fd)
+{
+	return 0;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/OpenBSD/emu	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,111 @@
+dev
+	root
+	cons
+	env
+	mnt
+	pipe
+	prog
+	prof
+	srv
+	dup
+	ssl
+	cap
+	fs
+	cmd	cmd
+	indir
+
+	draw
+	pointer
+	snarf
+
+	ip	ipif-posix ipaux
+	eia
+	audio	audio
+	mem
+
+lib
+	interp
+	tk
+	freetype
+	math
+	draw
+
+	memlayer
+	memdraw
+	keyring
+	sec
+	mp
+
+	9
+
+link
+
+mod
+	sys
+	draw
+
+	tk
+	math
+	srv	srv
+	keyring
+	crypt
+	ipints
+	loader
+	freetype
+
+port
+	alloc
+	cache
+	chan
+	dev
+	devtab
+
+	dial
+	dis
+	discall
+	env
+	error
+	errstr
+	exception
+	exportfs
+	inferno
+	latin1
+	main
+	parse
+	pgrp
+	print
+	proc
+	qio
+	random
+	sysfile
+	uqid
+
+code
+
+init
+	emuinit
+
+root
+	/dev	/
+	/fd	/
+	/prog	/
+	/prof	/
+	/net	/
+	/net.alt	/
+	/chan	/
+	/nvfs	/
+	/env	/
+#	/chan
+#	/dev
+#	/dis
+#	/env
+#	/n
+#	/net
+#	/nvfs /
+#	/prog
+#	/icons
+#	/osinit.dis
+#	/dis/emuinit.dis
+#	/dis/lib/auth.dis
+#	/dis/lib/ssl.dis
+#	/n/local /
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/OpenBSD/mkfile	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,45 @@
+<../../mkconfig
+SYSTARG=OpenBSD
+OBJTYPE=386
+
+#Configurable parameters
+
+CONF=emu			#default configuration
+CONFLIST=emu
+CLEANCONFLIST=
+
+INSTALLDIR=$ROOT/$SYSTARG/$OBJTYPE/bin	#path of directory where kernel is installed
+
+#end configurable parameters
+
+<$ROOT/mkfiles/mkfile-$SYSTARG-$OBJTYPE	#set vars based on target system
+
+<| $SHELLNAME ../port/mkdevlist $CONF	#sets $IP, $DEVS, $PORT, $LIBS
+
+OBJ=\
+	asm-$OBJTYPE.$O\
+	os.$O\
+	win-x11a.$O\
+	$CONF.root.$O\
+	lock.$O\
+	$DEVS\
+	$PORT\
+
+HFILES=\
+
+CFLAGS='-DROOT="'$ROOT'"' -DEMU -I. -I../port -I$ROOT/$SYSTARG/$OBJTYPE/include -I$ROOT/include -I$ROOT/libinterp $CTHREADFLAGS $CFLAGS $EMUOPTIONS
+SYSLIBS= -lm -lX11 -lXext -lossaudio 
+KERNDATE=`{$NDATE}
+
+default:V:	$O.$CONF
+
+<../port/portmkfile
+
+$O.$CONF:	$OBJ $CONF.c $CONF.root.h $LIBFILES
+	$CC $CFLAGS '-DKERNDATE='$KERNDATE $CONF.c
+	$LD $LDFLAGS -o $target $OBJ $CONF.$O $LIBFILES $SYSLIBS
+
+install:V: $O.$CONF
+	cp $O.$CONF $INSTALLDIR/$CONF
+
+devfs.$O:	../port/devfs-posix.c
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/OpenBSD/mkfile-OpenBSD	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,17 @@
+#
+#	architecture-dependent files for OpenBSD
+#
+
+LDFLAGS=
+
+TARGFILES=devfs-posix.$O\
+	deveia-OpenBSD.$O\
+	devip.$O\
+	ipif-posix.$O\
+	os-OpenBSD.$O\
+	win-x11.$O\
+	srv.$O\
+	lock.$O\
+	asm-OpenBSD-$OBJTYPE.$O
+
+SYSLIBS=/usr/X11R6/lib/libX11.a -lm 
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/OpenBSD/os.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,533 @@
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+#undef getwd
+#include <sys/types.h>
+#include <sys/mman.h>
+#include	<sys/param.h>
+#include	<sys/resource.h>
+#include 	<sys/socket.h>
+#include	<sys/time.h>
+#include	<signal.h>
+#include	<time.h>
+#include	<termios.h>
+#include	<sched.h>
+#include	<pwd.h>
+#include	<errno.h>
+#include	<unistd.h>
+
+enum
+{
+	DELETE  = 0x7F,
+	NSTACKSPERALLOC = 16,
+	X11STACK=	256*1024
+};
+char *hosttype = "OpenBSD";
+
+int rfork_thread(int, void *, void (*)(void *), void *);
+
+extern void unlockandexit(int*);
+extern void executeonnewstack(void*, void (*f)(void*), void*);
+static void *stackalloc(Proc *p, void **tos);
+static void stackfreeandexit(void *stack);
+
+extern int dflag;
+
+void
+pexit(char *msg, int t)
+{
+	Osenv *e;
+	Proc *p;
+	void *kstack;
+
+	lock(&procs.l);
+	p = up;
+	if(p->prev)
+		p->prev->next = p->next;
+	else
+		procs.head = p->next;
+
+	if(up->next)
+		p->next->prev = p->prev;
+	else
+		procs.tail = p->prev;
+	unlock(&procs.l);
+
+	if(0)
+		print("pexit: %s: %s\n", up->text, msg);
+
+	e = up->env;
+	if(e != nil) {
+		closefgrp(e->fgrp);
+		closepgrp(e->pgrp);
+		closeegrp(e->egrp);
+		closesigs(e->sigs);
+	}
+	kstack = p->kstack;
+	free(p->prog);
+	free(p);
+	if(kstack != nil)
+		stackfreeandexit(kstack);
+}
+
+void
+trapBUS(int signo, siginfo_t *info, void *context)
+{
+	if(info)
+		print("trapBUS: signo: %d code: %d addr: %lx\n",
+		info->si_signo, info->si_code, info->si_addr);
+	else
+		print("trapBUS: no info\n"); 
+	disfault(nil, "Bus error");
+}
+
+static void
+trapUSR1(int signo)
+{
+	int intwait;
+
+	USED(signo);
+
+	intwait = up->intwait;
+	up->intwait = 0;	/* clear it to let proc continue in osleave */
+
+	if(up->type != Interp)		/* Used to unblock pending I/O */
+		return;
+	if(intwait == 0)		/* Not posted so its a sync error */
+		disfault(nil, Eintr);	/* Should never happen */
+}
+
+static void
+trapUSR2(int signo)
+{
+	USED(signo);
+	/* we've done our work of interrupting sigsuspend */
+}
+
+static void
+trapILL(int signo)
+{
+	disfault(nil, "Illegal instruction");
+}
+
+static void
+trapSEGV(int signo)
+{
+	disfault(nil, "Segmentation violation");
+}
+
+static void
+trapFPE(int signo)
+{
+	char buf[64];
+	USED(signo);
+	snprint(buf, sizeof(buf), "sys: fp: exception status=%.4lux", getfsr());
+	disfault(nil, buf);
+}
+
+static sigset_t initmask;
+
+static void
+setsigs(void)
+{
+	struct sigaction act;
+	sigset_t mask;
+
+	memset(&act, 0 , sizeof(act));
+	sigemptyset(&initmask);
+	
+	signal(SIGPIPE, SIG_IGN);	/* prevent signal when devcmd child exits */
+	if(signal(SIGTERM, SIG_IGN) != SIG_IGN)
+		signal(SIGTERM, cleanexit);
+
+	act.sa_handler = trapUSR1;
+	act.sa_mask = initmask;
+	sigaction(SIGUSR1, &act, nil);
+
+	act.sa_handler = trapUSR2;
+	sigaction(SIGUSR2, &act, nil);
+	sigemptyset(&mask);
+	sigaddset(&mask, SIGUSR2);
+	sigaddset(&initmask, SIGUSR2);
+	sigprocmask(SIG_BLOCK, &mask, NULL);
+
+	/*
+ 	 * prevent Zombies forming when any process terminates
+	 */
+	act.sa_sigaction = 0;
+	act.sa_flags |= SA_NOCLDWAIT;
+	if(sigaction(SIGCHLD, &act, nil))
+		panic("sigaction SIGCHLD");
+
+	if(sflag == 0) {
+		act.sa_sigaction = trapBUS;
+		act.sa_flags |= SA_SIGINFO;
+		if(sigaction(SIGBUS, &act, nil))
+			panic("sigaction SIGBUS");
+		act.sa_handler = trapILL;
+		if(sigaction(SIGILL, &act, nil))
+			panic("sigaction SIGBUS");
+		act.sa_handler = trapSEGV;
+		if(sigaction(SIGSEGV, &act, nil))
+			panic("sigaction SIGSEGV");
+		act.sa_handler = trapFPE;
+		if(sigaction(SIGFPE, &act, nil))
+			panic("sigaction SIGFPE");
+		if(sigaddset(&initmask, SIGINT) == -1)
+			panic("sigaddset");
+	}
+	if(sigprocmask(SIG_BLOCK, &initmask, nil)!= 0)
+		panic("sigprocmask");
+}
+
+static void
+tramp(void *arg)
+{
+	Proc *p;
+
+	p = arg;
+	p->pid = p->sigid = getpid();
+	sigprocmask(SIG_BLOCK, &initmask, nil);	/* in 5.3, rfork_thread doesn't copy from parent, contrary to docs? */
+	(*p->func)(p->arg);
+	pexit("{Tramp}", 0);
+	_exit(0);
+}
+
+void
+kproc(char *name, void (*func)(void*), void *arg, int flags)
+{
+	Proc *p;
+	Pgrp *pg;
+	Fgrp *fg;
+	Egrp *eg;
+	int pid;
+	void *tos;
+
+	p = newproc();
+
+	if(flags & KPDUPPG) {
+		pg = up->env->pgrp;
+		incref(&pg->r);
+		p->env->pgrp = pg;
+	}
+	if(flags & KPDUPFDG) {
+		fg = up->env->fgrp;
+		incref(&fg->r);
+		p->env->fgrp = fg;
+	}
+	if(flags & KPDUPENVG) {
+		eg = up->env->egrp;
+		incref(&eg->r);
+		p->env->egrp = eg;
+	}
+
+	p->env->uid = up->env->uid;
+	p->env->gid = up->env->gid;
+	kstrdup(&p->env->user, up->env->user);
+
+	strcpy(p->text, name);
+
+	p->func = func;
+	p->arg = arg;
+
+	lock(&procs.l);
+	if(procs.tail != nil) {
+		p->prev = procs.tail;
+		procs.tail->next = p;
+	}
+	else {
+		procs.head = p;
+		p->prev = nil;
+	}
+	procs.tail = p;
+	unlock(&procs.l);
+
+	if(flags & KPX11){
+		p->kstack = nil;	/* never freed; also up not defined */
+		tos = (char*)mallocz(X11STACK, 0) + X11STACK - sizeof(void*);
+	}else
+		p->kstack = stackalloc(p, &tos);
+	pid = rfork_thread(RFPROC|RFMEM|RFNOWAIT, tos, tramp, p);
+	if(pid < 0)
+		panic("rfork");
+}
+
+void
+oshostintr(Proc *p)
+{
+	kill(p->sigid, SIGUSR1);
+}
+
+void
+osblock(void)
+{
+	sigset_t mask;
+
+	sigprocmask(SIG_SETMASK, NULL, &mask);
+	sigdelset(&mask, SIGUSR2);
+	sigsuspend(&mask);
+}
+
+void
+osready(Proc *p)
+{
+	if(kill(p->sigid, SIGUSR2) < 0)
+		fprint(2, "emu: osready failed: pid %d: %s\n", p->sigid, strerror(errno));
+}
+
+void
+oslongjmp(void *regs, osjmpbuf env, int val)
+{
+	USED(regs);
+	siglongjmp(env, val);
+}
+
+struct termios tinit;
+
+static void
+termset(void)
+{
+	struct termios t;
+
+	tcgetattr(0, &t);
+	tinit = t;
+	t.c_lflag &= ~(ICANON|ECHO|ISIG);
+	t.c_cc[VMIN] = 1;
+	t.c_cc[VTIME] = 0;
+	tcsetattr(0, TCSANOW, &t);
+}
+
+static void
+termrestore(void)
+{
+	tcsetattr(0, TCSANOW, &tinit);
+}
+
+void
+cleanexit(int x)
+{
+	USED(x);
+
+	if(up->intwait) {
+		up->intwait = 0;
+		return;
+	}
+
+	if(dflag == 0)
+		termrestore();
+
+	kill(0, SIGKILL);
+	exit(0);
+}
+
+void
+osreboot(char *file, char **argv)
+{
+	if(dflag == 0)
+		termrestore();
+	execvp(file, argv);
+	panic("reboot failure");
+}
+
+int gidnobody= -1, uidnobody= -1;
+
+void
+getnobody()
+{
+	struct passwd *pwd;
+	
+	if(pwd = getpwnam("nobody")) {
+		uidnobody = pwd->pw_uid;
+		gidnobody = pwd->pw_gid;
+	}
+}
+
+void
+libinit(char *imod)
+{
+	struct passwd *pw;
+	Proc *p;
+	void *tos;
+	char sys[64];
+
+	setsid();
+
+	gethostname(sys, sizeof(sys));
+	kstrdup(&ossysname, sys);
+	getnobody();
+
+	if(dflag == 0)
+		termset();
+
+	setsigs();
+
+	p = newproc();
+	p->kstack = stackalloc(p, &tos);
+
+	pw = getpwuid(getuid());
+	if(pw != nil)
+		kstrdup(&eve, pw->pw_name);
+	else
+		print("cannot getpwuid\n");
+ 
+	p->env->uid = getuid();
+	p->env->gid = getgid();
+
+	executeonnewstack(tos, emuinit, imod);
+}
+
+int
+readkbd(void)
+{
+	int n;
+	char buf[1];
+
+	n = read(0, buf, sizeof(buf));
+	if(n < 0)
+		print("keyboard close (n=%d, %s)\n", n, strerror(errno));
+	if(n <= 0)
+		pexit("keyboard thread", 0);
+
+	switch(buf[0]) {
+	case '\r':
+		buf[0] = '\n';
+		break;
+	case DELETE:
+		cleanexit(0);
+		break;
+	}
+	return buf[0];
+}
+
+/*
+ * Return an abitrary millisecond clock time
+ */
+long
+osmillisec(void)
+{
+	static long sec0 = 0, usec0;
+	struct timeval t;
+
+	if(gettimeofday(&t,(struct timezone*)0)<0)
+		return 0;
+	if(sec0==0) {
+		sec0 = t.tv_sec;
+		usec0 = t.tv_usec;
+	}
+	return (t.tv_sec-sec0)*1000+(t.tv_usec-usec0+500)/1000;
+}
+
+int
+limbosleep(ulong milsec)
+{
+	return osmillisleep(milsec);
+}
+
+/*
+ * Return the time since the epoch in nanoseconds and microseconds
+ * The epoch is defined at 1 Jan 1970
+ */
+vlong
+osnsec(void)
+{
+	struct timeval t;
+
+	gettimeofday(&t, nil);
+	return (vlong)t.tv_sec*1000000000L + t.tv_usec*1000;
+}
+
+vlong
+osusectime(void)
+{
+	struct timeval t;
+ 
+	gettimeofday(&t, nil);
+	return (vlong)t.tv_sec * 1000000 + t.tv_usec;
+}
+
+int
+osmillisleep(ulong milsec)
+{
+	struct timespec time;
+
+	time.tv_sec = milsec / 1000;
+	time.tv_nsec = (milsec % 1000) * 1000000;
+	nanosleep(&time, 0);
+	return 0;
+}
+
+void
+osyield(void)
+{
+	sched_yield();
+}
+
+void
+ospause(void)
+{
+	for(;;)
+		pause();
+}
+
+void
+oslopri(void)
+{
+	setpriority(PRIO_PROCESS, 0, getpriority(PRIO_PROCESS,0)+4);
+}
+
+static struct {
+	Lock l;
+	void *free;
+} stacklist;
+
+static void
+_stackfree(void *stack)
+{
+	*((void **)stack) = stacklist.free;
+	stacklist.free = stack;
+}
+
+static void
+stackfreeandexit(void *stack)
+{
+	lock(&stacklist.l);
+	_stackfree(stack);
+	unlockandexit(&stacklist.l.val);
+}
+
+static void *
+stackalloc(Proc *p, void **tos)
+{
+	void *rv;
+	lock(&stacklist.l);
+	if (stacklist.free == 0) {
+		int x;
+		/*
+		 * obtain some more by using sbrk()
+		 */
+		void *more = sbrk(KSTACK * (NSTACKSPERALLOC + 1));
+		if (more == 0)
+			panic("stackalloc: no more stacks");
+		/*
+		 * align to KSTACK
+		 */
+		more = (void *)((((unsigned long)more) + (KSTACK - 1)) & ~(KSTACK - 1));
+		/*
+		 * free all the new stacks onto the freelist
+		 */
+		for (x = 0; x < NSTACKSPERALLOC; x++)
+			_stackfree((char *)more + KSTACK * x);
+	}
+	rv = stacklist.free;
+	stacklist.free = *(void **)rv;
+	unlock(&stacklist.l);
+	*tos = rv + KSTACK - sizeof(void*);
+	*(Proc **)rv = p;
+	return rv;
+}
+
+int
+segflush(void *p, ulong n)
+{
+	return mprotect(p, n, PROT_EXEC|PROT_READ|PROT_WRITE);
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/OpenBSD/rfork_thread.S	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,104 @@
+/*-
+ * Copyright (c) 2000 Peter Wemm <peter@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ *                8      12          16         20
+ * rfork_thread(flags, stack_addr, start_fnc, start_arg);
+ *
+ * flags:		Flags to rfork system call.  See rfork(2).
+ * stack_addr:		Top of stack for thread.
+ * start_fnc:		Address of thread function to call in child.
+ * start_arg:		Argument to pass to the thread function in child.
+ */
+
+ENTRY(rfork_thread)
+	pushl	%ebp
+	movl	%esp, %ebp
+	pushl	%esi
+
+	/*
+	 * Push thread info onto the new thread's stack
+	 */
+	movl	12(%ebp), %esi	# get stack addr
+
+	subl	$4, %esi
+	movl	20(%ebp), %eax	# get start argument
+	movl	%eax, (%esi)
+
+	subl	$4, %esi
+	movl	16(%ebp), %eax	# get start thread address
+	movl	%eax, (%esi)
+
+	/*
+	 * Prepare and execute the thread creation syscall
+	 */
+	pushl	8(%ebp)
+	pushl	$0
+	movl	$SYS_rfork, %eax
+	int	$0x80
+	jb 	2f
+
+	/*
+	 * Check to see if we are in the parent or child
+	 */
+	cmpl	$0, %edx
+	jnz	1f
+	addl	$8, %esp
+	popl	%esi
+	movl	%ebp, %esp
+	popl	%ebp
+	ret
+	.p2align 2
+
+	/*
+	 * If we are in the child (new thread), then
+	 * set-up the call to the internal subroutine.  If it
+	 * returns, then call __exit.
+	 */
+1:
+	movl	%esi,%esp
+	popl	%eax 
+	call	*%eax
+	addl	$4, %esp
+
+	/*
+	 * Exit system call
+	 */
+	pushl	%eax
+	pushl	$0
+	movl	$SYS_threxit, %eax
+	int	$0x80
+
+	/*
+	 * Branch here if the thread creation fails:
+	 */
+2:
+	addl	$8, %esp
+	popl	%esi
+	movl	%ebp, %esp
+	popl	%ebp
+	PIC_PROLOGUE
+	jmp     PIC_PLT(_C_LABEL(__cerror))
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Plan9/asm-386.s	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,33 @@
+
+TEXT	tramp(SB),$0
+	MOVL	nsp+0(FP), BX		/* new stack */
+	MOVL	fn+4(FP), CX		/* func to exec */
+	MOVL	arg+8(FP),DX
+
+	LEAL	-8(BX), SP		/* new stack */
+	PUSHL	DX
+	CALL	*CX
+	POPL	AX
+
+	PUSHL	$0
+	CALL	_exits(SB)
+	POPL	AX
+	RET
+
+TEXT	vstack(SB),$0
+	MOVL	arg+0(FP), AX
+	MOVL	ustack(SB), SP
+	PUSHL	AX
+	CALL	exectramp(SB)
+	POPL	AX			/* dammit ken! */
+	RET
+
+TEXT	FPsave(SB), 1, $0
+	MOVL	fpu+0(FP), AX
+	FSTENV	0(AX)
+	RET
+
+TEXT	FPrestore(SB), 1, $0
+	MOVL	fpu+0(FP), AX
+	FLDENV	0(AX)
+	RET
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Plan9/asm-arm.s	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,32 @@
+/* for VFP */
+#define VMRS(fp, cpu) WORD $(0xeef00a10 | (fp)<<16 | (cpu)<<12) /* FP → arm */
+#define VMSR(cpu, fp) WORD $(0xeee00a10 | (fp)<<16 | (cpu)<<12) /* arm → FP */
+
+#define Fpscr 1
+
+	TEXT	tramp(SB), 1, $0
+	MOVW	fn+4(FP), R1		/* func to exec */
+	MOVW	arg+8(FP), R2		/* argument */
+	SUB	$8, R0			/* new stack */
+	MOVW	R0, SP
+	MOVW	R2, R0
+	BL	(R1)
+
+	MOVW	$0, R0
+	BL	_exits(SB)
+	RET
+
+	TEXT	vstack(SB), 1, $0
+	MOVW	ustack(SB), SP
+	BL		exectramp(SB)
+	RET
+
+	TEXT	FPsave(SB), 1, $0
+	VMRS(Fpscr, 1)
+	MOVW	R1, 0(R0)
+	RET
+
+	TEXT	FPrestore(SB), 1, $0
+	MOVW	(R0), R0
+	VMSR(0, Fpscr)
+	RET
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Plan9/asm-mips.s	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,24 @@
+
+	TEXT	tramp(SB), 1, $0
+	ADDU	$-8, R1, R3		/* new stack */
+	MOVW	4(FP), R2		/* func to exec */
+	MOVW	8(FP), R1		/* arg to reg */
+	MOVW	R3, R29			/* new stack */
+	JAL	(R2)
+	MOVW	R0, R1
+	JMP	_exits(SB)
+
+	TEXT	vstack(SB), 1, $0	/* Passes &targ through R1 */
+	MOVW	ustack(SB), R29
+	JMP	exectramp(SB)
+	RET
+
+	TEXT	FPsave(SB), 1, $0
+	MOVW	FCR31, R2
+	MOVW	R2, 0(R1)
+	RET
+
+	TEXT	FPrestore(SB), 1, $0
+	MOVW	0(R1), R2
+	MOVW	R2, FCR31
+	RET
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Plan9/asm-power.s	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,28 @@
+	TEXT	tramp(SB), 1, $0
+	ADD	$-8, R3, R4		/* new stack */
+	MOVW	4(FP), R5		/* func to exec */
+	MOVW	R5, LR
+	MOVW	8(FP), R3		/* arg to reg */
+	MOVW	R4, R1			/* new stack */
+	BL	(LR)
+	MOVW	R0, R3
+	MOVW	$_exits(SB), R4
+	MOVW	R4, LR
+	BR	(LR)
+
+	TEXT	vstack(SB), 1, $0	/* Passes &targ through R3 */
+	MOVW	ustack(SB), R1
+	MOVW	$exectramp(SB), R4
+	MOVW	R4, CTR
+	BR	(CTR)
+	RETURN
+
+	TEXT	FPsave(SB), 1, $0
+	MOVFL	FPSCR, F0
+	FMOVD	F0, 0(R3)
+	RETURN
+
+	TEXT	FPrestore(SB), 1, $0
+	FMOVD	0(R3), F0
+	MOVFL	F0, FPSCR
+	RETURN
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Plan9/asm-sparc.s	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,22 @@
+	TEXT	tramp(SB), 1, $0
+	ADD	$-8, R7, R3		/* new stack */
+	MOVW	4(FP), R4		/* func to exec */
+	MOVW	8(FP), R7		/* arg to reg */
+	MOVW	R3, R1			/* new stack */
+	JMPL	(R4)
+	MOVW	R0, R7
+	JMPL	_exits(SB)		/* Leaks the stack in R29 */
+
+	TEXT	vstack(SB), 1, $0	/* Passes &targ through R7 */
+	MOVW	ustack(SB), R1
+	MOVW	$exectramp(SB), R3
+	JMP	(R3)
+	RETURN
+
+	TEXT	FPsave(SB), 1, $0
+	MOVW	FSR, 0(R7)
+	RETURN
+
+	TEXT	FPrestore(SB), 1, $0
+	MOVW	0(R7), FSR
+	RETURN
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Plan9/cmd.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,189 @@
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+extern	void	vstack(void*);
+
+/*
+ * all this is for the benefit of devcmd.
+ * i hope it's grateful.
+ */
+
+typedef struct Targ Targ;
+struct Targ
+{
+	int	fd[3];	/* standard input, output and error */
+	int	wfd;
+	int*	spin;
+	char**	args;
+	char*	dir;
+	int	pid;
+	int	nice;
+};
+
+/*
+ * called by vstack once it has moved to
+ * the unshared stack in the new process.
+ */
+void
+exectramp(Targ *t)
+{
+	int *fd, i, nfd;
+	char filename[128], err[ERRMAX], status[2*ERRMAX];
+
+	t->pid = getpid();
+	*t->spin = 0;	/* allow parent to proceed: can't just rendezvous: see below */
+	fd = t->fd;
+
+	snprint(filename, sizeof(filename), "#d/%d", t->wfd);
+	t->wfd = open(filename, OWRITE|OCEXEC);
+	/* if it failed, we'll manage */
+
+	nfd = MAXNFD;	/* TO DO: should read from /fd */
+	for(i = 0; i < nfd; i++)
+		if(i != fd[0] && i != fd[1] && i != fd[2] && i != t->wfd)
+			close(i);
+
+	if(fd[0] != 0){
+		dup(fd[0], 0);
+		close(fd[0]);
+	}
+	if(fd[1] != 1){
+		dup(fd[1], 1);
+		close(fd[1]);
+	}
+	if(fd[2] != 2){
+		dup(fd[2], 2);
+		close(fd[2]);
+	}
+
+	if(t->dir != nil && chdir(t->dir) < 0){
+		if(t->wfd > 0)
+			fprint(t->wfd, "chdir: %s: %r", t->dir);
+		_exits("bad dir");
+	}
+	if(t->nice)
+		oslopri();
+
+	exec(t->args[0], t->args);
+	err[0] = 0;
+	errstr(err, sizeof(err));
+	if(t->args[0][0] != '/' && t->args[0][0] != '#' &&
+	   strncmp(t->args[0], "../", 3) != 0 && strncmp(t->args[0], "./", 2) != 0 &&
+	   strlen(t->args[0])+5 < sizeof(filename)){
+		snprint(filename, sizeof(filename), "/bin/%s", t->args[0]);
+		exec(filename, t->args);
+		errstr(err, sizeof(err));
+	}
+	snprint(status, sizeof(status), "%s: can't exec: %s", t->args[0], err);
+	if(t->wfd > 0)
+		write(t->wfd, status, strlen(status));
+	_exits(status);
+}
+
+void*
+oscmd(char **args, int nice, char *dir, int *fd)
+{
+	Targ *t;
+	int spin, *spinptr, fd0[2], fd1[2], fd2[2], wfd[2], n;
+	Dir *d;
+
+	up->genbuf[0] = 0;
+	t = mallocz(sizeof(*t), 1);
+	if(t == nil)
+		return nil;
+	t->args = args;
+	t->dir = dir;
+	t->nice = nice;
+	fd0[0] = fd0[1] = -1;
+	fd1[0] = fd1[1] = -1;
+	fd2[0] = fd2[1] = -1;
+	wfd[0] = wfd[1] = -1;
+	if(dir != nil){
+		d = dirstat(dir);
+		if(d == nil)
+			goto Error;
+		free(d);
+	}
+	if(pipe(fd0) < 0 || pipe(fd1) < 0 || pipe(fd2) < 0 || pipe(wfd) < 0)
+		goto Error;
+
+	spinptr = &spin;
+	spin = 1;
+
+	t->fd[0] = fd0[0];
+	t->fd[1] = fd1[1];
+	t->fd[2] = fd2[1];
+	t->wfd = wfd[1];
+	t->spin = spinptr;
+	switch(rfork(RFPROC|RFMEM|RFREND|RFNOTEG|RFFDG|RFNAMEG|RFENVG)) {
+	case -1:
+		goto Error;
+	case 0:
+		/* if child returns first from rfork, its call to vstack replaces ... */
+		vstack(t);
+		/* ... parent's return address from rfork and parent returns here */
+	default:
+		/* if parent returns first from rfork, it comes here */
+		/* can't call anything: on shared stack until child releases spin in exectramp */
+		while(*spinptr)
+			;
+		break;
+	}
+
+	close(fd0[0]);
+	close(fd1[1]);
+	close(fd2[1]);
+	close(wfd[1]);
+
+	n = read(wfd[0], up->genbuf, sizeof(up->genbuf)-1);
+	close(wfd[0]);
+	if(n > 0){
+		close(fd0[1]);
+		close(fd1[0]);
+		close(fd2[0]);
+		up->genbuf[n] = 0;
+		errstr(up->genbuf, sizeof(up->genbuf));
+		free(t);
+		return nil;
+	}
+
+	fd[0] = fd0[1];
+	fd[1] = fd1[0];
+	fd[2] = fd2[0];
+	return t;
+
+Error:
+	errstr(up->genbuf, sizeof(up->genbuf));	/* save the message before close */
+	close(fd0[0]);
+	close(fd0[1]);
+	close(fd1[0]);
+	close(fd1[1]);
+	close(fd2[0]);
+	close(fd2[1]);
+	close(wfd[0]);
+	close(wfd[1]);
+	free(t);
+	errstr(up->genbuf, sizeof(up->genbuf));
+	return nil;
+}
+
+int
+oscmdkill(void *a)
+{
+	Targ *t = a;
+
+	return postnote(PNGROUP, t->pid, "kill");
+}
+
+int
+oscmdwait(void*, char *buf, int n)
+{
+	return await(buf, n);
+}
+
+void
+oscmdfree(void *a)
+{
+	free(a);
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Plan9/devfs.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,365 @@
+/*
+ * Plan 9 file system interface
+ */
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+typedef struct Fsinfo Fsinfo;
+struct Fsinfo
+{
+	int	fd;
+	QLock;		/* serialise access to offset */
+	ulong	offset;	/* offset used only for directory reads */
+	Cname*	name;	/* Plan 9's name for file */
+	Qid	rootqid;		/* Plan 9's qid for Inferno's root */
+	char*	root;		/* prefix to strip from all names in diagnostics */
+};
+#define	FS(c)	((Fsinfo*)((c)->aux))
+
+char	rootdir[MAXROOT] = ROOT;
+
+static void
+fserr(Fsinfo *f)
+{
+	int n;
+	char *p;
+
+	oserrstr(up->env->errstr, ERRMAX);
+	if(f != nil && *up->env->errstr == '\'' && (n = strlen(f->root)) > 1){
+		/* don't reveal full names */
+		if(strncmp(up->env->errstr+1, f->root, n-1) == 0){
+			p = up->env->errstr+1+n;
+			memmove(up->env->errstr+1, p, strlen(p)+1);
+		}
+	}
+	error(up->env->errstr);
+}
+
+static void
+fsfree(Chan *c)
+{
+	cnameclose(FS(c)->name);
+	free(c->aux);
+}
+
+Chan*
+fsattach(char *spec)
+{
+	Chan *c;
+	Dir *d;
+	char *root;
+	Qid rootqid;
+	static int devno;
+	static Lock l;
+
+	if(!emptystr(spec)){
+		if(strcmp(spec, "*") != 0)
+			error(Ebadspec);
+		root = "/";
+	}else
+		root = rootdir;
+
+	d = dirstat(root);
+	if(d == nil)
+		fserr(nil);
+	rootqid = d->qid;
+	free(d);
+
+	c = devattach('U', spec);
+	lock(&l);
+	c->dev = devno++;
+	c->qid = rootqid;
+	unlock(&l);
+	c->aux = smalloc(sizeof(Fsinfo));
+	FS(c)->name = newcname(root);
+	FS(c)->rootqid = rootqid;
+	FS(c)->fd = -1;
+	FS(c)->root = root;
+
+	return c;
+}
+
+Walkqid*
+fswalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	int j, alloc;
+	Walkqid *wq;
+	Dir *dir;
+	char *n;
+	Cname *current, *next;
+	Qid rootqid;
+
+	if(nname > 0)
+		isdir(c);	/* do we need this? */
+
+	alloc = 0;
+	current = nil;
+	wq = smalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid));
+	if(waserror()){
+		if(alloc && wq->clone!=nil)
+			cclose(wq->clone);
+		cnameclose(current);
+		free(wq);
+		return nil;
+	}
+	if(nc == nil){
+		nc = devclone(c);
+		nc->type = 0;
+		alloc = 1;
+	}
+	wq->clone = nc;
+
+	rootqid = FS(c)->rootqid;
+	current = FS(c)->name;
+	if(current != nil)
+		incref(&current->r);
+	for(j=0; j<nname; j++){
+		if(!(nc->qid.type&QTDIR)){
+			if(j==0)
+				error(Enotdir);
+			break;
+		}
+		n = name[j];
+		if(strcmp(n, ".") != 0 && !(isdotdot(n) && nc->qid.path == rootqid.path)){	/* TO DO: underlying qids aliased */
+			//print("** ufs walk '%s' -> %s\n", current->s, n);
+			next = current;
+			incref(&next->r);
+			next = addelem(current, n);
+			dir = dirstat(next->s);
+			if(dir == nil){
+				cnameclose(next);
+				if(j == 0)
+					error(Enonexist);
+				strcpy(up->env->errstr, Enonexist);
+				break;
+			}
+			nc->qid = dir->qid;
+			free(dir);
+			cnameclose(current);
+			current = next;
+		}
+		wq->qid[wq->nqid++] = nc->qid;
+	}
+//	print("** ufs walk '%s'\n", current->s);
+
+	poperror();
+	if(wq->nqid < nname){
+		cnameclose(current);
+		if(alloc)
+			cclose(wq->clone);
+		wq->clone = nil;
+	}else if(wq->clone){
+		/* now attach to our device */
+		nc->aux = smalloc(sizeof(Fsinfo));
+		nc->type = c->type;
+		FS(nc)->rootqid = FS(c)->rootqid;
+		FS(nc)->name = current;
+		FS(nc)->fd = -1;
+		FS(nc)->root = FS(c)->root;
+	}else
+		panic("fswalk: can't happen");
+	return wq;
+}
+
+int
+fsstat(Chan *c, uchar *dp, int n)
+{
+	if(FS(c)->fd >= 0)
+		n = fstat(FS(c)->fd, dp, n);
+	else
+		n = stat(FS(c)->name->s, dp, n);
+	if(n < 0)
+		fserr(FS(c));
+	/* TO DO: change name to / if rootqid */
+	return n;
+}
+
+Chan*
+fsopen(Chan *c, int mode)
+{
+	osenter();
+	FS(c)->fd = open(FS(c)->name->s, mode);
+	osleave();
+	if(FS(c)->fd < 0)
+		fserr(FS(c));
+	c->mode = openmode(mode);
+	c->offset = 0;
+	FS(c)->offset = 0;
+	c->flag |= COPEN;
+	return c;
+}
+
+void
+fscreate(Chan *c, char *name, int mode, ulong perm)
+{
+	Dir *d;
+	Cname *n;
+
+	if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
+		error(Efilename);
+	n = addelem(newcname(FS(c)->name->s), name);
+	osenter();
+	FS(c)->fd = create(n->s, mode, perm);
+	osleave();
+	if(FS(c)->fd < 0) {
+		cnameclose(n);
+		fserr(FS(c));
+	}
+	d = dirfstat(FS(c)->fd);
+	if(d == nil) {
+		cnameclose(n);
+		close(FS(c)->fd);
+		FS(c)->fd = -1;
+		fserr(FS(c));
+	}
+	c->qid = d->qid;
+	free(d);
+
+	cnameclose(FS(c)->name);
+	FS(c)->name = n;
+
+	c->mode = openmode(mode);
+	c->offset = 0;
+	FS(c)->offset = 0;
+	c->flag |= COPEN;
+}
+
+void
+fsclose(Chan *c)
+{
+	if(c->flag & COPEN){
+		osenter();
+		close(FS(c)->fd);
+		osleave();
+	}
+	/* don't need to check for CRCLOSE, because Plan 9 itself implements ORCLOSE */
+	fsfree(c);
+}
+
+static long
+fsdirread(Chan *c, void *va, long count, vlong offset)
+{
+	long n, r;
+	static char slop[16384];
+
+	if(FS(c)->offset != offset){
+		seek(FS(c)->fd, 0, 0);
+		for(n=0; n<offset;) {
+			r = offset - n;
+			if(r > sizeof(slop))
+				r = sizeof(slop);
+			osenter();
+			r = read(FS(c)->fd, slop, r);
+			osleave();
+			if(r <= 0){
+				FS(c)->offset = n;
+				return 0;
+			}
+			n += r;
+		}
+		FS(c)->offset = offset;
+	}
+	osenter();
+	r = read(FS(c)->fd, va, count);
+	osleave();
+	if(r < 0)
+		return r;
+	FS(c)->offset = offset+r;
+	return r;
+}
+
+long
+fsread(Chan *c, void *va, long n, vlong offset)
+{
+	int r;
+
+	if(c->qid.type & QTDIR){	/* need to maintain offset only for directories */
+		qlock(FS(c));
+		if(waserror()){
+			qunlock(FS(c));
+			nexterror();
+		}
+		r = fsdirread(c, va, n, offset);
+		poperror();
+		qunlock(FS(c));
+	}else{
+		osenter();
+		r = pread(FS(c)->fd, va, n, offset);
+		osleave();
+	}
+	if(r < 0)
+		fserr(FS(c));
+	return r;
+}
+
+long
+fswrite(Chan *c, void *va, long n, vlong offset)
+{
+	int r;
+
+	osenter();
+	r = pwrite(FS(c)->fd, va, n, offset);
+	osleave();
+	if(r < 0)
+		fserr(FS(c));
+	return r;
+}
+
+void
+fsremove(Chan *c)
+{
+	int r;
+
+	if(waserror()){
+		fsfree(c);
+		nexterror();
+	}
+	osenter();
+	r = remove(FS(c)->name->s);
+	osleave();
+	if(r < 0)
+		fserr(FS(c));
+	poperror();
+	fsfree(c);
+}
+
+int
+fswstat(Chan *c, uchar *dp, int n)
+{
+	osenter();
+	if(FS(c)->fd >= 0)
+		n = fwstat(FS(c)->fd, dp, n);
+	else
+		n = wstat(FS(c)->name->s, dp, n);
+	osleave();
+	if(n < 0)
+		fserr(FS(c));
+	return n;
+}
+
+void
+setid(char *name, int owner)
+{
+	if(!owner || iseve())
+		kstrdup(&up->env->user, name);
+}
+
+Dev fsdevtab = {
+	'U',
+	"fs",
+
+	devinit,
+	fsattach,
+	fswalk,
+	fsstat,
+	fsopen,
+	fscreate,
+	fsclose,
+	fsread,
+	devbread,
+	fswrite,
+	devbwrite,
+	fsremove,
+	fswstat
+};
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Plan9/devsrv9.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,395 @@
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+typedef struct Srv Srv;
+struct Srv
+{
+	Ref;
+	int	fd;	/* fd for opened /srv or /srv/X, or -1 */
+	int	sfd;	/* fd for created /srv entry or -1 */
+	uvlong	path;
+	Srv	*next;
+};
+
+static QLock	srv9lk;
+static Srv	*srv9;
+static Srv	*srvroot;
+
+static char*
+srvname(Chan *c)
+{
+	char *p;
+
+	p = strrchr(c->name->s, '/');
+	if(p == nil)
+		return "";
+	return p+1;
+}
+
+static Srv*
+srvget(uvlong path)
+{
+	Srv *sv;
+
+	qlock(&srv9lk);
+	for(sv = srv9; sv != nil; sv = sv->next)
+		if(sv->path == path){
+			incref(sv);
+			qunlock(&srv9lk);
+			return sv;
+		}
+	sv = smalloc(sizeof(*sv));
+	sv->path = path;
+	sv->fd = -1;
+	sv->sfd = -1;
+	sv->ref = 1;
+	sv->next = srv9;
+	srv9 = sv;
+	qunlock(&srv9lk);
+	return sv;
+}
+
+static void
+srvput(Srv *sv)
+{
+	Srv **l;
+	int fd, sfd;
+
+	if(sv != nil && decref(sv) == 0){
+		qlock(&srv9lk);
+		for(l = &srv9; *l != nil; l = &(*l)->next)
+			if(*l == sv){
+				*l = sv->next;
+				break;
+			}
+		qunlock(&srv9lk);
+		fd = sv->fd;
+		sfd = sv->sfd;
+		free(sv);
+		if(sfd >= 0){
+			osenter();
+			close(sfd);
+			osleave();
+		}
+		if(fd >= 0){
+			osenter();
+			close(fd);
+			osleave();
+		}
+	}
+}
+
+static void
+srv9init(void)
+{
+	Srv *sv;
+
+	sv = mallocz(sizeof(*srvroot), 1);
+	sv->path = 0;
+	sv->fd = -1;
+	sv->ref = 1;	/* subsequently never reaches zero */
+	srvroot = srv9 = sv;
+}
+
+static Chan*
+srv9attach(char *spec)
+{
+	Chan *c;
+
+	if(*spec)
+		error(Ebadspec);
+	c = devattach(L'₪', spec);
+	if(c != nil){
+		incref(srvroot);
+		c->aux = srvroot;
+	}
+	return c;
+}
+
+static Walkqid*
+srv9walk(Chan *c, Chan *nc, char **name, int nname)
+{
+	int j, alloc;
+	Walkqid *wq;
+	char *n;
+	Dir *d;
+
+	if(nname > 0)
+		isdir(c);
+
+	alloc = 0;
+	wq = smalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid));
+	if(waserror()){
+		if(alloc)
+			cclose(wq->clone);
+		free(wq);
+		return nil;
+	}
+	if(nc == nil){
+		nc = devclone(c);
+		nc->type = 0;	/* device doesn't know about this channel yet */
+		alloc = 1;
+	}
+	wq->clone = nc;
+
+	for(j=0; j<nname; j++){
+		if(!(nc->qid.type&QTDIR)){
+			if(j==0)
+				error(Enotdir);
+			break;
+		}
+		n = name[j];
+		if(strcmp(n, ".") != 0 && strcmp(n, "..") != 0){
+			snprint(up->genbuf, sizeof(up->genbuf), "/srv/%s", n);
+			d = dirstat(up->genbuf);
+			if(d == nil){
+				if(j == 0)
+					error(Enonexist);
+				kstrcpy(up->env->errstr, Enonexist, ERRMAX);
+				break;
+			}
+			nc->qid = d->qid;
+			free(d);
+		}
+		wq->qid[wq->nqid++] = nc->qid;
+	}
+	poperror();
+	if(wq->nqid < nname){
+		if(alloc)
+			cclose(wq->clone);
+		wq->clone = nil;
+	}else{
+		/* attach cloned channel to device */
+		wq->clone->type = c->type;
+		if(wq->clone != c)
+			nc->aux = srvget(nc->qid.path);
+	}
+	return wq;
+}
+
+static int
+srv9stat(Chan *c, uchar *db, int n)
+{
+	Srv *sv;
+	Dir d;
+
+	if(c->qid.type & QTDIR){
+		devdir(c, c->qid, "#₪", 0, eve, 0775, &d);
+		n = convD2M(&d, db, n);
+		if(n == 0)
+			error(Eshortstat);
+		return n;
+	}
+	sv = c->aux;
+	if(sv->fd >= 0){
+		osenter();
+		n = fstat(sv->fd, db, n);
+		osleave();
+	}else{
+		osenter();
+		n = stat(srvname(c), db, n);
+		osleave();
+	}
+	return n;
+}
+
+static Chan*
+srv9open(Chan *c, int omode)
+{
+	Srv *sv;
+	char *args[10];
+	int fd[2], i, ifd, is9p;
+	Dir *d;
+
+	sv = c->aux;
+	if(c->qid.type == QTDIR){
+		osenter();
+		sv->fd = open("/srv", omode);
+		osleave();
+		if(sv->fd < 0)
+			oserror();
+		c->mode = omode;
+		c->flag |= COPEN;
+		c->offset = 0;
+		return c;
+	}
+
+	if(omode&OTRUNC || openmode(omode) != ORDWR)
+		error(Eperm);
+	if(sv->fd < 0){
+		snprint(up->genbuf, sizeof(up->genbuf), "/srv/%s", srvname(c));
+
+		/* check permission */
+		osenter();
+		ifd = open(up->genbuf, omode);
+		osleave();
+		if(ifd < 0)
+			oserror();
+		osenter();
+		d = dirfstat(ifd);
+		is9p = d != nil && d->qid.type & QTMOUNT;
+		free(d);
+		osleave();
+
+		if(is9p){
+			close(ifd);
+
+			/* spawn exportfs */
+			args[0] = "exportfs";
+			args[1] = "-S";
+			args[2] = up->genbuf;
+			args[3] = nil;
+			if(pipe(fd) < 0)
+				oserror();
+			/* TO DO: without RFMEM there's a copy made of each page touched by any kproc until the exec */
+			switch(rfork(RFPROC|RFNOWAIT|RFREND|RFFDG|RFNAMEG|RFENVG)){	/* no sharing except NOTEG */
+			case -1:
+				oserrstr(up->genbuf, sizeof(up->genbuf));
+				close(fd[0]);
+				close(fd[1]);
+				error(up->genbuf);
+			case 0:
+				for(i=3; i<MAXNFD; i++)
+					if(i != fd[1])
+						close(i);
+				dup(fd[1], 0);
+				if(fd[0] != 0)
+					close(fd[0]);
+				dup(0, 1);
+				exec("/bin/exportfs", args);
+				exits("exportfs failed");
+			default:
+				sv->fd = fd[0];
+				close(fd[1]);
+				break;
+			}
+		}else
+			sv->fd = ifd;
+	}
+
+	c->mode = ORDWR;
+	c->offset = 0;
+	c->flag |= COPEN;
+	return c;
+}
+
+static void
+srv9close(Chan *c)
+{
+	srvput(c->aux);
+}
+
+static long
+srv9read(Chan *c, void *va, long n, vlong off)
+{
+	Srv *sv;
+
+	sv = c->aux;
+	osenter();
+	n = pread(sv->fd, va, n, off);
+	osleave();
+	if(n < 0)
+		oserror();
+	return n;
+}
+
+static long
+srv9write(Chan *c, void *va, long n, vlong off)
+{
+	Srv *sv;
+
+	sv = c->aux;
+	osenter();
+	n = pwrite(sv->fd, va, n, off);
+	osleave();
+	if(n == 0)
+		error(Ehungup);
+	if(n < 0)
+		oserror();
+	return n;
+}
+
+static void
+srv9create(Chan *c, char *name, int omode, ulong perm)
+{
+	Srv *sv;
+	int sfd, fd[2];
+	vlong path;
+	Dir *d;
+
+	if(openmode(omode) != ORDWR)
+		error(Eperm);
+
+	if(pipe(fd) < 0)
+		oserror();
+	if(waserror()){
+		close(fd[0]);
+		close(fd[1]);
+		nexterror();
+	}
+
+	snprint(up->genbuf, sizeof(up->genbuf), "/srv/%s", name);
+	osenter();
+	sfd = create(up->genbuf, OWRITE|ORCLOSE, perm);
+	osleave();
+	if(sfd < 0)
+		oserror();
+	if(waserror()){
+		close(sfd);
+		nexterror();
+	}
+	osenter();
+	if(fprint(sfd, "%d", fd[1]) < 0){
+		osleave();
+		oserror();
+	}
+	d = dirfstat(sfd);
+	osleave();
+	if(d != nil){
+		path = d->qid.path;
+		free(d);
+	}else
+		oserror();
+
+	poperror();
+	poperror();
+	close(fd[1]);
+
+	if(waserror()){
+		close(sfd);
+		close(fd[0]);
+		nexterror();
+	}
+	sv = srvget(path);
+	sv->fd = fd[0];
+	sv->sfd = sfd;
+	poperror();
+
+	srvput((Srv*)c->aux);
+	c->qid.type = QTFILE;
+	c->qid.path = path;
+	c->aux = sv;
+	c->flag |= COPEN;
+	c->mode = ORDWR;
+	c->offset = 0;
+}
+
+Dev srv9devtab = {
+	L'₪',
+	"srv9",
+
+	srv9init,	
+	srv9attach,
+	srv9walk,
+	srv9stat,
+	srv9open,
+	srv9create,	/* TO DO */
+	srv9close,
+	srv9read,
+	devbread,
+	srv9write,
+	devbwrite,
+	devremove,	/* TO DO */
+	devwstat,	/* TO DO */
+};
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Plan9/emu	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,114 @@
+dev
+	root
+	cons
+	env
+	mnt
+	pipe
+	prog
+	prof
+	srv
+	dup
+	ssl
+	cap
+	fs
+	cmd	cmd
+	indir
+#	sign
+
+	draw	win
+	pointer
+
+	dynld
+	mem
+	srv9
+
+#	ip and eia are simply bound in from Plan 9
+
+lib
+	interp
+	tk
+	freetype
+	math
+	draw
+
+	memlayer
+	memdraw
+	keyring
+	sec
+	mp
+	dynld
+	9
+
+link
+
+mod
+	sys
+	draw
+
+	tk
+	math
+#	srv		not used on Plan 9
+	keyring
+	crypt
+	ipints
+	loader
+	freetype
+
+port
+	alloc
+	cache
+	chan
+	dev
+	devtab
+
+	dial
+	dis
+	discall
+	env
+	error
+	errstr
+	exception
+	exportfs
+	exptab
+	inferno
+	latin1
+	main
+	parse
+	pgrp
+	print
+	proc
+	qio
+	random
+	sysfile
+	uqid
+
+code
+
+init
+	emuinit
+
+root
+	/chan	/
+	/dev	/
+	/fd	/
+	/prog	/
+	/prof	/
+	/net	/
+	/net.alt	/
+	/nvfs	/
+	/env	/
+	/root	/
+	/srv	/
+#	/tmp	/
+#	/dis
+#	/env
+#	/n
+#	/net
+#	/nvfs /
+#	/prog
+#	/icons
+#	/osinit.dis
+#	/dis/emuinit.dis
+#	/dis/lib/auth.dis
+#	/dis/lib/ssl.dis
+#	/n/local /
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Plan9/emusig	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,100 @@
+dev
+	root
+	cons
+	env
+	mnt
+	pipe
+	prog
+	prof
+	srv
+	ssl
+	cap
+	fs
+	cmd	cmd
+	indir
+	sign
+
+	draw	win
+
+#	ip and eia are simply bound in from Plan 9
+
+lib
+	interp
+	tk
+	freetype
+	math
+	draw
+	memlayer
+	memdraw
+	keyring
+	crypt
+	9
+
+link
+
+mod
+	sys
+	draw
+	tk
+	math
+#	srv		not used on Plan 9
+	keyring
+	crypt
+	ipints
+	loader
+	freetype
+
+port
+	alloc
+	cache
+	chan
+	dev
+	devtab
+
+	dial
+	dis
+	discall
+	env
+	error
+	errstr
+	exception
+	exportfs
+	inferno
+	latin1
+	main
+	parse
+	pgrp
+	print
+	proc
+	qio
+	sysfile
+	uqid
+
+code
+
+init
+	emuinit
+
+root
+	/dev	/
+	/prog	/
+	/prof	/
+	/net	/
+	/net.alt	/
+	/chan	/
+	/nvfs	/
+	/env	/
+#	/chan
+#	/dev
+#	/dis
+#	/env
+#	/n
+#	/net
+#	/nvfs /
+#	/prog
+#	/icons
+#	/osinit.dis
+#	/dis/emuinit.dis
+#	/dis/lib/auth.dis
+#	/dis/lib/ssl.dis
+#	/n/local /
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Plan9/mkfile	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,45 @@
+SYSTARG=Plan9
+OBJTYPE=$objtype
+<../../mkconfig
+
+#Configurable parameters
+
+CONF=emu			#default configuration
+CONFLIST=emu
+CLEANCONFLIST=
+
+INSTALLDIR=$ROOT/$SYSTARG/$OBJTYPE/bin	#path of directory where kernel is installed
+
+#end configurable parameters
+
+<$ROOT/mkfiles/mkfile-$SYSTARG-$OBJTYPE	#set vars based on target system
+
+<| $SHELLNAME ../port/mkdevlist $CONF	#sets $IP, $DEVS, $PORT, $LIBS
+
+OBJ=\
+	asm-$OBJTYPE.$O\
+	os.$O\
+	$CONF.root.$O\
+	$DEVS\
+	$PORT\
+
+
+HFILES=\
+
+CFLAGS='-DROOT="'$ROOT'"' -DEMU -I. -I../port -I$ROOT/$SYSTARG/$OBJTYPE/include -I$ROOT/include -I$ROOT/libinterp $CTHREADFLAGS $CFLAGS $EMUOPTIONS
+KERNDATE=`{$NDATE}
+
+default:V:	$O.$CONF
+
+<../port/portmkfile
+
+$O.$CONF:	$OBJ $CONF.c $CONF.root.h $LIBFILES
+	$CC $CFLAGS '-DKERNDATE='$KERNDATE $CONF.c
+	$LD  -o $target $OBJ $CONF.$O $LIBFILES $SYSLIBS
+
+safeinstall:V:	$O.$CONF
+	mv $INSTALLDIR/$CONF $INSTALLDIR/$CONF.`{date -n}
+	cp $O.$CONF $INSTALLDIR/$CONF
+
+install:V: $O.$CONF
+	cp $O.$CONF $INSTALLDIR/$CONF
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Plan9/os.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,428 @@
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+enum
+{
+	KSTACK	= 16*1024,
+	DELETE	= 0x7F,
+};
+
+Proc	**Xup;
+
+extern	void	killrefresh(void);
+extern	void	tramp(char*, void (*)(void*), void*);
+
+extern	int	usenewwin;
+
+int	*ustack;	/* address on unshared stack: see vstack in asm*.s */
+extern	int	dflag;
+char *hosttype = "Plan9";
+char *cputype;
+
+void
+osblock(void)
+{
+	rendezvous(up, nil);
+}
+
+void
+osready(Proc *p)
+{
+	rendezvous(p, nil);
+}
+
+void
+pexit(char *msg, int)
+{
+	Osenv *e;
+
+	USED(msg);
+
+	lock(&procs.l);
+	if(up->prev) 
+		up->prev->next = up->next;
+	else
+		procs.head = up->next;
+
+	if(up->next)
+		up->next->prev = up->prev;
+	else
+		procs.tail = up->prev;
+	unlock(&procs.l);
+
+/*	print("pexit: %s: %s\n", up->text, msg);	/**/
+	e = up->env;
+	if(e != nil) {
+		closefgrp(e->fgrp);
+		closepgrp(e->pgrp);
+		closeegrp(e->egrp);
+		closesigs(e->sigs);
+	}
+	free(e->user);
+	free(up->prog);
+	up->prog = nil;
+	up->type = Moribund;
+	longjmp(up->privstack, 1);
+}
+
+int
+kproc1(char *name, void (*func)(void*), void *arg, int flags)
+{
+	int pid;
+	Proc *p;
+	Pgrp *pg;
+	Fgrp *fg;
+	Egrp *eg;
+
+	p = newproc();
+	if(p == nil)
+		panic("kproc: no memory");
+	p->kstack = mallocz(KSTACK, 0);
+	if(p->kstack == nil)
+		panic("kproc: no memory");
+
+	if(flags & KPDUPPG) {
+		pg = up->env->pgrp;
+		incref(&pg->r);
+		p->env->pgrp = pg;
+	}
+	if(flags & KPDUPFDG) {
+		fg = up->env->fgrp;
+		incref(&fg->r);
+		p->env->fgrp = fg;
+	}
+	if(flags & KPDUPENVG) {
+		eg = up->env->egrp;
+		incref(&eg->r);
+		p->env->egrp = eg;
+	}
+
+	p->env->uid = up->env->uid;
+	p->env->gid = up->env->gid;
+	kstrdup(&p->env->user, up->env->user);
+
+	strcpy(p->text, name);
+
+	p->func = func;
+	p->arg = arg;
+
+	lock(&procs.l);
+	if(procs.tail != nil) {
+		p->prev = procs.tail;
+		procs.tail->next = p;
+	}
+	else {
+		procs.head = p;
+		p->prev = nil;
+	}
+	procs.tail = p;
+	unlock(&procs.l);
+
+	/*
+	 * switch back to the unshared stack to do the fork
+	 * only the parent returns from kproc
+	 */
+	up->kid = p;
+	up->kidsp = p->kstack;
+	pid = setjmp(up->sharestack);
+	if(pid == 0)
+		longjmp(up->privstack, 1);
+	return pid;
+}
+
+void
+kproc(char *name, void (*func)(void*), void *arg, int flags)
+{
+	kproc1(name, func, arg, flags);
+}
+
+void
+traphandler(void *reg, char *msg)
+{
+	int intwait;
+
+	intwait = up->intwait;
+	up->intwait = 0;
+	/* Ignore pipe writes from devcmd */
+	if(strstr(msg, "write on closed pipe") != nil)
+		noted(NCONT);
+
+	if(sflag) {
+		if(intwait && strcmp(msg, Eintr) == 0)
+			noted(NCONT);
+		else
+			noted(NDFLT);
+	}
+	if(intwait == 0)
+		disfault(reg, msg);
+	noted(NCONT);
+}
+
+int
+readfile(char *path, char *buf, int n)
+{
+	int fd;
+
+	fd = open(path, OREAD);
+	if(fd >= 0) {
+		n = read(fd, buf, n-1);
+		if(n > 0)			/* both calls to readfile() have a ``default'' */
+			buf[n] = '\0';
+		close(fd);
+		return n;
+	}
+	return 0;
+}
+
+static void
+dobinds(void)
+{
+	char dir[MAXROOT+9];
+
+	snprint(dir, sizeof(dir), "%s/net", rootdir);
+	bind("/net", dir, MREPL);
+
+	snprint(dir, sizeof(dir), "%s/net.alt", rootdir);
+	bind("/net.alt", dir, MREPL);
+
+	snprint(dir, sizeof(dir), "%s/dev", rootdir);
+	bind("#t", dir, MAFTER);
+	bind("#A", dir, MAFTER);
+}
+
+void
+libinit(char *imod)
+{
+	char *sp;
+	Proc *xup, *p;
+	int fd, n, pid;
+	char nbuf[64];
+
+	xup = nil;
+	Xup = &xup;
+
+	/*
+	 * setup personality
+	 */
+	if(readfile("/dev/user", nbuf, sizeof nbuf))
+		kstrdup(&eve, nbuf);
+	if(readfile("/dev/sysname", nbuf, sizeof nbuf))
+		kstrdup(&ossysname, nbuf);
+	if(readfile("/env/cputype", nbuf, sizeof nbuf))
+		kstrdup(&cputype, nbuf);
+
+	/*
+	 * guess at a safe stack for vstack
+	 */
+	ustack = &fd;
+
+	rfork(RFNAMEG|RFREND);
+
+	if(!dflag){
+		fd = open("/dev/consctl", OWRITE);
+		if(fd < 0)
+			fprint(2, "libinit: open /dev/consctl: %r\n");
+		n = write(fd, "rawon", 5);
+		if(n != 5)
+			fprint(2, "keyboard rawon (n=%d, %r)\n", n);
+	}
+
+	osmillisec();	/* set the epoch */
+	dobinds();
+
+	notify(traphandler);
+
+	/*
+	 * dummy up a up and stack so the first proc
+	 * calls emuinit after setting up his private jmp_buf
+	 */
+	p = newproc();
+	p->kstack = mallocz(KSTACK, 0);
+	if(p == nil || p->kstack == nil)
+		panic("libinit: no memory");
+	sp = p->kstack;
+	p->func = emuinit;
+	p->arg = imod;
+
+	/*
+	 * set up a stack for forking kids on separate stacks.
+	 * longjmp back here from kproc.
+	 */
+	while(setjmp(p->privstack)){
+		if(up->type == Moribund){
+			free(up->kstack);
+			free(up);
+			_exits("");
+		}
+		p = up->kid;
+		sp = up->kidsp;
+		up->kid = nil;
+		switch(pid = rfork(RFPROC|RFMEM|RFNOWAIT)){
+		case 0:
+			/*
+			 * send the kid around the loop to set up his private jmp_buf
+			 */
+			break;
+		default:
+			/*
+			 * parent just returns to his shared stack in kproc
+			 */
+			longjmp(up->sharestack, pid);
+			panic("longjmp failed");
+		}
+	}
+
+	/*
+	 * you get here only once per Proc
+	 * go to the shared memory stack
+	 */
+	up = p;
+	up->pid = up->sigid = getpid();
+	tramp(sp+KSTACK, up->func, up->arg);
+	panic("tramp returned");
+}
+
+void
+oshostintr(Proc *p)
+{
+	postnote(PNPROC, p->sigid, Eintr);
+}
+
+void
+oslongjmp(void *regs, osjmpbuf env, int val)
+{
+	if(regs != nil)
+		notejmp(regs, env, val);
+	else
+		longjmp(env, val);
+}
+
+void
+osreboot(char*, char**)
+{
+}
+
+void
+cleanexit(int x)
+{
+	USED(x);
+	killrefresh();
+	postnote(PNGROUP, getpid(), "interrupt");
+	exits("interrupt");
+}
+
+int
+readkbd(void)
+{
+	int n;
+	char buf[1];
+
+	n = read(0, buf, sizeof(buf));
+	if(n < 0)
+		fprint(2, "emu: keyboard read error: %r\n");
+	if(n <= 0)
+		pexit("keyboard", 0);
+	switch(buf[0]) {
+	case DELETE:
+		cleanexit(0);
+	case '\r':
+		buf[0] = '\n';
+	}
+	return buf[0];
+}
+
+static vlong
+b2v(uchar *p)
+{
+	int i;
+	vlong v;
+
+	v = 0;
+	for(i=0; i<sizeof(uvlong); i++)
+		v = (v<<8)|p[i];
+	return v;
+}
+
+vlong
+nsec(void)
+{
+	int n;
+	static int nsecfd = -1;
+	uchar buf[sizeof(uvlong)];
+
+	if(nsecfd < 0){
+		nsecfd = open("/dev/bintime", OREAD|OCEXEC);  /* never closed */
+		if(nsecfd<0){
+			fprint(2,"can't open /dev/bintime: %r\n");
+			return 0;
+		}
+	}
+	n = read(nsecfd, buf, sizeof(buf));
+	if(n!=sizeof(buf)) {
+		fprint(2,"read err on /dev/bintime: %r\n");
+		return 0;
+	}
+	return b2v(buf);
+}
+
+long
+osmillisec(void)
+{
+	static vlong nsec0 = 0;
+
+	if(nsec0 == 0){
+		nsec0 = nsec();
+		return 0;
+	}
+	return (nsec()-nsec0)/1000000;
+}
+
+/*
+ * Return the time since the epoch in microseconds
+ * The epoch is defined at 1 Jan 1970
+ */
+vlong
+osusectime(void)
+{
+	return nsec()/1000;
+}
+
+int
+osmillisleep(ulong milsec)
+{
+	sleep(milsec);
+	return 0;
+}
+
+int
+limbosleep(ulong milsec)
+{
+	return osmillisleep(milsec);
+}
+
+void
+osyield(void)
+{
+	sleep(0);
+}
+
+void
+ospause(void)
+{
+	for(;;)
+		sleep(1000000);
+}
+
+void
+oslopri(void)
+{
+	int fd;
+	char buf[32];
+
+	snprint(buf, sizeof(buf), "/proc/%d/ctl", getpid());
+	if((fd = open(buf, OWRITE)) >= 0){
+		fprint(fd, "pri 8");
+		close(fd);
+	}
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Plan9/win.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,566 @@
+#include	"dat.h"
+#include	"fns.h"
+#include	"kernel.h"
+#include	"error.h"
+
+#include	<draw.h>
+#include	<memdraw.h>
+#include	<cursor.h>
+#include	"keyboard.h"
+
+enum
+{
+	Margin	= 4,
+	Lsize		= 100,
+};
+
+extern Memimage *screenimage;
+
+extern int kproc1(char*, void (*)(void*), void*, int);
+
+static	ulong*	attachwindow(Rectangle*, ulong*, int*, int*);
+
+static void	plan9readmouse(void*);
+static void	plan9readkeybd(void*);
+static int	mapspecials(char *s1, char *s2, int *n);
+
+int	usenewwin = 1;
+int	kbdiscons;
+static int	truedepth;
+
+static int		datafd;
+static int		ctlfd;
+static int		mousefd = -1;
+static int		keybdfd;
+static int		mousepid = -1;
+static int		keybdpid = -1;
+static int		cursfd;
+static char	winname[64];
+
+/* Following updated by attachwindow() asynchronously */
+static QLock		ql;
+static Rectangle	tiler;
+static ulong*		data;
+static uchar*		loadbuf;
+static int		cursfd;
+static int		imageid;
+static Rectangle	imager;
+static uchar	*chunk;
+static int	chunksize;
+static	int	dispbufsize;
+
+#define	NINFO	12*12
+#define	HDR		21
+
+
+void
+killrefresh(void)
+{
+	if(mousepid < 0)
+		return;
+	close(mousefd);
+	close(ctlfd);
+	close(datafd);
+	postnote(PNPROC, mousepid, Eintr);
+	postnote(PNPROC, keybdpid, Eintr);
+}
+
+uchar*
+attachscreen(Rectangle *r, ulong *chan, int *d, int *width, int *softscreen)
+{
+	int fd;
+	char *p, buf[128], info[NINFO+1];
+
+	if(usenewwin){
+		p = getenv("wsys");
+		if(p == nil)
+			return nil;
+
+		fd = open(p, ORDWR);
+		if(fd < 0) {
+			fprint(2, "attachscreen: can't open window manager: %r\n");
+			return nil;
+		}
+		sprint(buf, "new -dx %d -dy %d", Xsize+2*Margin, Ysize+2*Margin);
+		if(mount(fd, -1, "/mnt/wsys", MREPL, buf) < 0) {
+			fprint(2, "attachscreen: can't mount window manager: %r\n");
+			return nil;
+		}
+		if(bind("/mnt/wsys", "/dev", MBEFORE) < 0){
+			fprint(2, "attachscreen: can't bind /mnt/wsys before /dev: %r\n");
+			return nil;
+		}
+	}
+
+	cursfd = open("/dev/cursor", OWRITE);
+	if(cursfd < 0) {
+		fprint(2, "attachscreen: open cursor: %r\n");
+		return nil;
+	}
+	
+	/* Set up graphics window console (chars->gkbdq) */
+	keybdfd = open("/dev/cons", OREAD);
+	if(keybdfd < 0) {
+		fprint(2, "attachscreen: open keyboard: %r\n");
+		return nil;
+	}
+	mousefd = open("/dev/mouse", ORDWR);
+	if(mousefd < 0){
+		fprint(2, "attachscreen: can't open mouse: %r\n");
+		return nil;
+	}
+	if(usenewwin || 1){
+		fd = open("/dev/consctl", OWRITE);
+		if(fd < 0)
+			fprint(2, "attachscreen: open /dev/consctl: %r\n");
+		if(write(fd, "rawon", 5) != 5)
+			fprint(2, "attachscreen: write /dev/consctl: %r\n");
+	}
+
+	/* Set up graphics files */
+	ctlfd = open("/dev/draw/new", ORDWR);
+	if(ctlfd < 0){
+		fprint(2, "attachscreen: can't open graphics control file: %r\n");
+		return nil;
+	}
+	if(read(ctlfd, info, sizeof info) < NINFO){
+		close(ctlfd);
+		fprint(2, "attachscreen: can't read graphics control file: %r\n");
+		return nil;
+	}
+	sprint(buf, "/dev/draw/%d/data", atoi(info+0*12));
+	datafd = open(buf, ORDWR|OCEXEC);
+	if(datafd < 0){
+		close(ctlfd);
+		fprint(2, "attachscreen: can't read graphics data file: %r\n");
+		return nil;
+	}
+	dispbufsize = iounit(datafd);
+	if(dispbufsize <= 0)
+		dispbufsize = 8000;
+	if(dispbufsize < 512){
+		close(ctlfd);
+		close(datafd);
+		fprint(2, "attachscreen: iounit %d too small\n", dispbufsize);
+		return nil;
+	}
+	chunksize = dispbufsize - 64;
+
+	if(attachwindow(r, chan, d, width) == nil){
+		close(ctlfd);
+		close(datafd);
+		return nil;
+	}
+	
+	mousepid = kproc1("readmouse", plan9readmouse, nil, 0);
+	keybdpid = kproc1("readkbd", plan9readkeybd, nil, 0);
+
+	fd = open("/dev/label", OWRITE);
+	if(fd >= 0){
+		snprint(buf, sizeof(buf), "inferno %d", getpid());
+		write(fd, buf, strlen(buf));
+		close(fd);
+	}
+
+	*softscreen = 1;
+	return (uchar*)data;
+}
+
+static ulong*
+attachwindow(Rectangle *r, ulong *chan, int *d, int *width)
+{
+	int n, fd, nb;
+	char buf[256];
+	uchar ubuf[128];
+	ulong imagechan;
+
+	/*
+	 * Discover name of window
+	 */
+	fd = open("/mnt/wsys/winname", OREAD);
+	if(fd<0 || (n=read(fd, winname, sizeof winname))<=0){
+		fprint(2, "attachwindow: can only run inferno under rio, not stand-alone\n");
+		return nil;
+	}
+	close(fd);
+	/*
+	 * If had previous window, release it
+	 */
+	if(imageid > 0){
+		ubuf[0] = 'f';
+		BPLONG(ubuf+1, imageid);
+		if(write(datafd, ubuf, 1+4) != 1+4)
+			fprint(2, "attachwindow: cannot free old window: %r\n");
+	}
+	/*
+	 * Allocate image pointing to window, and discover its ID
+	 */
+	ubuf[0] = 'n';
+	++imageid;
+	BPLONG(ubuf+1, imageid);
+	ubuf[5] = n;
+	memmove(ubuf+6, winname, n);
+	if(write(datafd, ubuf, 6+n) != 6+n){
+		fprint(2, "attachwindow: cannot bind %d to window id '%s': %r\n", imageid, winname);
+		return nil;
+	}
+	if(read(ctlfd, buf, sizeof buf) < 12*12){
+		fprint(2, "attachwindow: cannot read window id: %r\n");
+		return nil;
+	}
+	imagechan = strtochan(buf+2*12);
+	truedepth = chantodepth(imagechan);
+	if(truedepth == 0){
+		fprint(2, "attachwindow: cannot handle window depth specifier %.12s\n", buf+2*12);
+		return nil;
+	}
+
+	/*
+	 * Report back
+	 */
+	if(chan != nil)
+		*chan = imagechan;
+	if(d != nil)
+		*d = chantodepth(imagechan);
+	nb = 0;
+	if(r != nil){
+		Xsize = atoi(buf+6*12)-atoi(buf+4*12)-2*Margin;
+		Ysize = atoi(buf+7*12)-atoi(buf+5*12)-2*Margin;
+		r->min.x = 0;
+		r->min.y = 0;
+		r->max.x = Xsize;
+		r->max.y = Ysize;
+		nb = bytesperline(*r, truedepth);
+		data = malloc(nb*Ysize);
+		loadbuf = malloc(nb*Lsize+1);
+		chunk = malloc(HDR+chunksize+5);	/* +5 for flush (1 old, 5 new) */
+	}
+	imager.min.x = atoi(buf+4*12);
+	imager.min.y = atoi(buf+5*12);
+	imager.max.x = atoi(buf+6*12);
+	imager.max.y = atoi(buf+7*12);
+
+	if(width != nil)
+		*width = nb/4;
+
+	tiler.min.x = atoi(buf+4*12)+Margin;
+	tiler.min.y = atoi(buf+5*12)+Margin;
+	tiler.max.x = atoi(buf+6*12)-Margin;
+	tiler.max.y = atoi(buf+7*12)-Margin;
+
+	return data;
+}
+
+static int
+plan9loadimage(Rectangle r, uchar *data, int ndata)
+{
+	long dy;
+	int n, bpl;
+
+	if(!rectinrect(r, imager)){
+		werrstr("loadimage: bad rectangle");
+		return -1;
+	}
+	bpl = bytesperline(r, truedepth);
+	n = bpl*Dy(r);
+	if(n > ndata){
+		werrstr("loadimage: insufficient data");
+		return -1;
+	}
+	ndata = 0;
+	while(r.max.y > r.min.y){
+		dy = r.max.y - r.min.y;
+		if(dy*bpl> chunksize)
+			dy = chunksize/bpl;
+		n = dy*bpl;
+		chunk[0] = 'y';
+		BPLONG(chunk+1, imageid);
+		BPLONG(chunk+5, r.min.x);
+		BPLONG(chunk+9, r.min.y);
+		BPLONG(chunk+13, r.max.x);
+		BPLONG(chunk+17, r.min.y+dy);
+		memmove(chunk+21, data, n);
+		ndata += n;
+		data += n;
+		r.min.y += dy;
+		n += 21;
+		if(r.min.y >= r.max.y)	/* flush to screen */
+			chunk[n++] = 'v';
+		if(write(datafd, chunk, n) != n)
+			return -1;
+	}
+	return ndata;
+}
+
+static void
+_flushmemscreen(Rectangle r)
+{
+	int n, dy, l;
+	Rectangle rr;
+
+	if(data == nil || loadbuf == nil || chunk==nil)
+		return;
+	if(!rectclip(&r, Rect(0, 0, Xsize, Ysize)))
+		return;
+	if(!rectclip(&r, Rect(0, 0, Dx(tiler), Dy(tiler))))
+		return;
+	if(Dx(r)<=0 || Dy(r)<=0)
+		return;
+	l = bytesperline(r, truedepth);
+	while(r.min.y < r.max.y){
+		dy = Dy(r);
+		if(dy > Lsize)
+			dy = Lsize;
+		rr = r;
+		rr.max.y = rr.min.y+dy;
+		n = unloadmemimage(screenimage, rr, loadbuf, l*dy);
+		/* offset from (0,0) to window */
+		rr.min.x += tiler.min.x;
+		rr.min.y += tiler.min.y;
+		rr.max.x += tiler.min.x;
+		rr.max.y += tiler.min.y;
+		if(plan9loadimage(rr, loadbuf, n) != n)
+			fprint(2, "flushmemscreen: %d bytes: %r\n", n);
+		r.min.y += dy;
+	}
+}
+
+void
+flushmemscreen(Rectangle r)
+{
+	qlock(&ql);
+	_flushmemscreen(r);
+	qunlock(&ql);
+}
+
+void
+drawcursor(Drawcursor *c)
+{
+	int j, i, h, w, bpl;
+	uchar *bc, *bs, *cclr, *cset, curs[2*4+2*2*16];
+
+	/* Set the default system cursor */
+	if(c->data == nil) {
+		write(cursfd, curs, 0);
+		return;
+	}
+
+	BPLONG(curs+0*4, c->hotx);
+	BPLONG(curs+1*4, c->hoty);
+
+	w = (c->maxx-c->minx);
+	h = (c->maxy-c->miny)/2;
+
+	cclr = curs+2*4;
+	cset = curs+2*4+2*16;
+	bpl = bytesperline(Rect(c->minx, c->miny, c->maxx, c->maxy), 1);
+	bc = c->data;
+	bs = c->data + h*bpl;
+
+	if(h > 16)
+		h = 16;
+	if(w > 16)
+		w = 16;
+	w /= 8;
+	for(i = 0; i < h; i++) {
+		for(j = 0; j < w; j++) {
+			cclr[j] = bc[j];
+			cset[j] = bs[j];
+		}
+		bc += bpl;
+		bs += bpl;
+		cclr += 2;
+		cset += 2;
+	}
+	write(cursfd, curs, sizeof curs);
+}
+
+static int
+checkmouse(char *buf, int n)
+{
+	int x, y, tick, b;
+	static int lastb, lastt, lastx, lasty, lastclick;
+
+	switch(n){
+	default:
+		kwerrstr("atomouse: bad count");
+		return -1;
+
+	case 1+4*12:
+		if(buf[0] == 'r'){
+			qlock(&ql);
+			if(attachwindow(nil, nil, nil, nil) == nil) {
+				qunlock(&ql);
+				return -1;
+			}
+			_flushmemscreen(Rect(0, 0, Xsize, Ysize));
+			qunlock(&ql);
+		}
+		x = atoi(buf+1+0*12) - tiler.min.x;
+		y = atoi(buf+1+1*12) - tiler.min.y;
+		b = atoi(buf+1+2*12);
+		tick = atoi(buf+1+3*12);
+		if(b && lastb == 0){	/* button newly pressed */
+			if(b==lastclick && tick-lastt<400
+			   && abs(x-lastx)<10 && abs(y-lasty)<10)
+				b |= (1<<8);
+			lastt = tick;
+			lastclick = b&0xff;
+			lastx = x;
+			lasty = y;
+		}
+		lastb = b&0xff;
+		//mouse.msec = tick;
+		mousetrack(b, x, y, 0);
+		return n;
+	}
+}
+
+static void
+plan9readmouse(void *v)
+{
+	int n;
+	char buf[128];
+
+	USED(v);
+	for(;;){
+		n = read(mousefd, buf, sizeof(buf));
+		if(n < 0)	/* probably interrupted */
+			_exits(0);
+		checkmouse(buf, n);
+	}
+}
+
+static void
+plan9readkeybd(void*)
+{
+	int n, partial;
+	char buf[32];
+	char dbuf[32 * 3];		/* overestimate but safe */
+
+	partial = 0;
+	for(;;){
+		n = read(keybdfd, buf + partial, sizeof(buf) - partial);
+		if(n < 0)	/* probably interrupted */
+			_exits(0);
+		partial += n;
+		n = mapspecials(dbuf, buf, &partial);
+		qproduce(gkbdq, dbuf, n);
+	}
+}
+
+void
+setpointer(int x, int y)
+{
+	char buf[50];
+	int n;
+
+	if(mousefd < 0)
+		return;
+	x += tiler.min.x;
+	y += tiler.min.y;
+	n = snprint(buf, sizeof buf, "m%11d %11d ", x, y);
+	write(mousefd, buf, n);
+}
+
+/*
+ * plan9 keyboard codes; from /sys/include/keyboard.h; can't include directly
+ * because constant names clash.
+ */
+enum {
+	P9KF=	0xF000,	/* Rune: beginning of private Unicode space */
+	P9Spec=	0xF800,
+	/* KF|1, KF|2, ..., KF|0xC is F1, F2, ..., F12 */
+	Khome=	P9KF|0x0D,
+	Kup=	P9KF|0x0E,
+	Kpgup=	P9KF|0x0F,
+	Kprint=	P9KF|0x10,
+	Kleft=	P9KF|0x11,
+	Kright=	P9KF|0x12,
+	Kdown=	P9Spec|0x00,
+	Kview=	P9Spec|0x00,
+	Kpgdown=	P9KF|0x13,
+	Kins=	P9KF|0x14,
+	Kend=	KF|0x18,
+
+	Kalt=		P9KF|0x15,
+	Kshift=	P9KF|0x16,
+	Kctl=		P9KF|0x17,
+};
+
+/*
+ * translate plan 9 special characters from s2 (of length *n) into s1;
+ * return number of chars placed into s1.
+ * any trailing incomplete chars are moved to the beginning of s2,
+ * and *n set to the number moved there.
+ */
+static int
+mapspecials(char *s1, char *s2, int *n)
+{
+	char *s, *d, *es2;
+	Rune r;
+	d = s1;
+	s = s2;
+	es2 = s2 + *n;
+	while (fullrune(s, es2 - s)) {
+		s += chartorune(&r, s);
+		switch (r) {
+		case Kshift:
+			r = LShift;
+			break;
+		case Kctl:
+			r = LCtrl;
+			break;
+		case Kalt:
+			r = LAlt;
+			break;
+		case Khome:
+			r = Home;
+			break;
+		case Kend:
+			r = End;
+			break;
+		case Kup:
+			r = Up;
+			break;
+		case Kdown:
+			r = Down;
+			break;
+		case Kleft:
+			r = Left;
+			break;
+		case Kright:
+			r = Right;
+			break;
+		case Kpgup:
+			r = Pgup;
+			break;
+		case Kpgdown:
+			r = Pgdown;
+			break;
+		case Kins:
+			r = Ins;
+			break;
+		/*
+		 * function keys
+		 */
+		case P9KF|1:
+		case P9KF|2:
+		case P9KF|3:
+		case P9KF|4:
+		case P9KF|5:
+		case P9KF|6:
+		case P9KF|7:
+		case P9KF|8:
+		case P9KF|9:
+		case P9KF|10:
+		case P9KF|11:
+		case P9KF|12:
+			r = (r - P9KF) + KF;
+		}
+		d += runetochar(d, &r);
+	}
+	*n = es2 - s;
+	memmove(s2, s, *n);
+	return d - s1;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Solaris/asm-386.s	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,72 @@
+	.section	.bss
+	.align	4
+.L4_.bss:
+	.align	4
+Solaris_Asm_IntP: / Offset 0
+	.type	Solaris_Asm_IntP,@object
+	.size	Solaris_Asm_IntP,4
+	.set	.,.+4
+Solaris_Asm_VoidP: / Offset 4
+	.type	Solaris_Asm_VoidP,@object
+	.size	Solaris_Asm_VoidP,4
+	.set	.,.+4
+	.section	.text
+	.align	4
+.L1_.text:
+
+/====================
+/ FPsave
+/--------------------
+	.align	4
+	.align	4
+	.globl	FPsave
+FPsave:
+	pushl	%ebp
+	movl	%esp,%ebp
+	movl	8(%ebp),%eax
+	movl	%eax,Solaris_Asm_VoidP
+	fstenv	(%eax)
+	leave	
+	ret	
+	.align	4
+	.type	FPsave,@function
+	.size	FPsave,.-FPsave
+
+/====================
+/ FPrestore
+/--------------------
+	.align	4
+	.globl	FPrestore
+FPrestore:
+	pushl	%ebp
+	movl	%esp,%ebp
+	movl	8(%ebp),%eax
+	movl	%eax,Solaris_Asm_VoidP
+	fldenv	(%eax)
+	leave	
+	ret	
+	.align	4
+	.type	FPrestore,@function
+	.size	FPrestore,.-FPrestore
+
+
+/====================
+/ getcallerpc
+/--------------------
+	.align	4
+	.globl	getcallerpc
+getcallerpc:
+	movl	4(%ebp),%eax
+	ret	
+	.align	4
+	.type	getcallerpc,@function
+	.size	getcallerpc,.-getcallerpc
+
+/ test-and-set
+	.align	4
+	.globl	_tas
+_tas:
+	movl	$1, %eax
+	movl	4(%esp), %ecx
+	xchgl	%eax, 0(%ecx)
+	ret
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Solaris/asm-sparc.s	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,76 @@
+
+	.section	".text", #alloc, #execinstr
+	.align		8
+	.skip		16
+	.global		segflush
+	.type		segflush, #function
+
+	!	The flush instruction works on 8-byte chunks.
+	!	We truncate the pointer and increase the count
+	!	to make sure we flush the right range.
+
+	!	SPARC requires 5 instructions after flush to
+	!	let the caches settle.  The loop code supplies
+	!	the delay instructions.
+
+segflush:				! int segflush(void *p, ulong len)
+
+	and	%o0,-8,%o0		! clear low 3 bits of p
+	add	%o1, 7, %o1		! len += 7
+1:
+	flush	%o0			! synchronize cache
+	sub	%o1, 8, %o1		! len -= 8
+	cmp	%o1, 0			! if len > 0, repeat
+	bg	1b
+	add	%o0, 8, %o0		! p += 8 in delay slot
+
+	retl
+	add	%g0, %g0, %o0		! return 0
+	.size	segflush,(.-segflush)
+
+
+        .section        ".text", #alloc, #execinstr
+        .align          8
+        .skip           16
+        .global FPsave
+        .type   FPsave, #function
+FPsave:
+	retl
+	st	%fsr,[%o0]
+        .size   FPsave,(.-FPsave)
+
+
+        .section        ".text", #alloc, #execinstr
+        .align          8
+        .skip           16
+        .global FPrestore
+        .type   FPrestore, #function
+FPrestore:
+	retl
+	ld	[%o0],%fsr
+        .size   FPrestore,(.-FPrestore)
+
+
+	.section	".text", #alloc, #execinstr
+	.align		8
+	.skip		16
+	.global getcallerpc
+	.type getcallerpc, #function
+getcallerpc:                  ! ignore argument
+	retl                    
+	add %i7,0,%o0
+
+	.size   getcallerpc,(.-getcallerpc)
+
+
+	.section	".text", #alloc, #execinstr
+	.align	8
+	.skip	16
+	.global	_tas
+	.type	_tas, #function
+_tas:
+	or	%g0,1,%o1
+	swap	[%o0],%o1
+	retl
+	or	%g0,%o1,%o0
+	.size	_tas,(.-_tas)
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Solaris/audio.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,593 @@
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+#define __EXTENSIONS__
+#include <sys/time.h>
+#include <time.h>
+#include <fcntl.h>
+#include <stropts.h>
+#include <sys/audioio.h>
+#include <sys/ioctl.h>
+#include <sys/filio.h>
+#include "audio.h"
+#include <sys/audioio.h>
+
+#define 	Audio_Mic_Val		AUDIO_MICROPHONE
+#define 	Audio_Linein_Val	AUDIO_LINE_IN
+
+#define	Audio_Speaker_Val	AUDIO_SPEAKER
+#define	Audio_Headphone_Val	AUDIO_HEADPHONE
+#define	Audio_Lineout_Val	AUDIO_LINE_OUT
+
+#define 	Audio_Pcm_Val		AUDIO_ENCODING_LINEAR
+#define 	Audio_Ulaw_Val		AUDIO_ENCODING_ULAW
+#define 	Audio_Alaw_Val		AUDIO_ENCODING_ALAW
+
+#include "audio-tbls.c"
+
+#define	min(a,b)	((a) < (b) ? (a) : (b))
+static int debug = 0;
+
+extern int nanosleep(const struct timespec *, struct timespec *);
+
+#define AUDIO_FILE_STRING	"/dev/audio"
+
+enum {
+	A_Pause,
+	A_UnPause
+};
+
+enum {
+	A_In,
+	A_Out
+};
+
+static QLock inlock;
+static QLock outlock;
+
+static	int	audio_file_in  = -1;	/* file in */
+static	int	audio_file_out = -1;	/* file out */
+
+static	int	audio_swap_flag = 0;	/* endian swap */
+
+static	int	audio_in_pause = A_UnPause;
+
+static Audio_t av;
+
+static int audio_enforce(Audio_t*);
+static int audio_open_in(void);
+static int audio_open_out(void);
+static int audio_pause_in(int, int);
+static int audio_flush(int, int);
+static int audio_pause_out(int);
+static int audio_set_blocking(int);
+static int audio_set_info(int, Audio_d*, int);
+static void audio_swap_endian(char*, int);
+
+void
+audio_file_init(void)
+{
+	static ushort flag = 1;
+	audio_swap_flag = *((uchar*)&flag) == 0;	/* big-endian? */
+	audio_info_init(&av);
+}
+
+void
+audio_file_open(Chan *c, int omode)
+{
+	switch(omode){
+	case OREAD:
+		qlock(&inlock);
+		if(waserror()){
+			qunlock(&inlock);
+			nexterror();
+		}
+
+		if(audio_file_in >= 0)
+			error(Einuse);
+		if((audio_file_in = audio_open_in()) < 0)
+			oserror();
+
+		poperror();
+		qunlock(&inlock);
+		break;
+	case OWRITE:
+		qlock(&outlock);
+		if(waserror()){
+			qunlock(&outlock);
+			nexterror();
+		}
+		if(audio_file_out >= 0)
+			error(Einuse);
+		if((audio_file_out = audio_open_out() ) < 0)
+			oserror();
+		poperror();
+		qunlock(&outlock);
+		break;
+	case ORDWR:
+		qlock(&inlock);
+		qlock(&outlock);
+		if(waserror()){
+			qunlock(&inlock);
+			qunlock(&outlock);
+			nexterror();
+		}
+		if(audio_file_in >= 0 || audio_file_out >= 0)
+			error(Einuse);
+
+		if((audio_file_in = audio_open_in()) < 0)
+			oserror();
+		if(waserror()){
+			close(audio_file_in);
+			audio_file_in = -1;
+			nexterror();
+		}
+		if((audio_file_out = audio_open_out()) < 0)
+			oserror();
+		poperror();
+
+		poperror();
+		qunlock(&inlock);
+		qunlock(&outlock);
+		break;
+	}
+}
+
+void
+audio_file_close(Chan *c)
+{
+	switch(c->mode){
+	case OREAD:
+		qlock(&inlock);
+		close(audio_file_in);
+		audio_file_in = -1;
+		qunlock(&inlock);
+		break;
+	case OWRITE:
+		qlock(&outlock);
+		close(audio_file_out);
+		audio_file_out = -1;
+		qunlock(&outlock);
+		break;
+	case ORDWR:
+		qlock(&inlock);
+		close(audio_file_in);
+		audio_file_in = -1;
+		qunlock(&inlock);
+		qlock(&outlock);
+		close(audio_file_out);
+		audio_file_out = -1;
+		qunlock(&outlock);
+		break;
+	}
+}
+
+long
+audio_file_read(Chan *c, void *va, long count, vlong offset)
+{
+	struct  timespec time;
+	long ba, status, chunk, total;
+	char *pva = (char *) va;
+
+	qlock(&inlock);
+	if(waserror()){
+		qunlock(&inlock);
+		nexterror();
+	}
+
+	if(audio_file_in < 0)
+		error(Eperm);
+
+	/* check block alignment */
+	ba = av.in.bits * av.in.chan / Bits_Per_Byte;
+
+	if(count % ba)
+		error(Ebadarg);
+
+	if(!audio_pause_in(audio_file_in, A_UnPause))
+		error(Eio);
+	
+	total = 0;
+	while(total < count) {
+		chunk = count - total;
+		osenter();
+		status = read(audio_file_in, pva + total, chunk);
+		osleave(); 
+		if(status < 0)
+			error(Eio);
+		total += status;
+	}
+
+	if(total != count)
+		error(Eio);
+
+	if(audio_swap_flag && av.out.bits == 16)
+		audio_swap_endian(pva, count); 
+
+	poperror();
+	qunlock(&inlock);
+
+	time.tv_sec = 0; /* hack around broken thread scheduler in Solaris */
+	time.tv_nsec= 1;
+	nanosleep(&time,nil);
+
+	return count;
+}
+
+long
+audio_file_write(Chan *c, void *va, long count, vlong offset)
+{
+	struct  timespec time;
+	long status = -1;
+	long ba, total, chunk, bufsz;
+
+	qlock(&outlock);
+	if(waserror()){
+		qunlock(&outlock);
+		nexterror();
+	}
+
+	if(audio_file_out < 0)
+		error(Eperm);
+
+	/* check block alignment */
+	ba = av.out.bits * av.out.chan / Bits_Per_Byte;
+
+	if(count % ba)
+		error(Ebadarg);
+
+	if(audio_swap_flag && av.out.bits == 16)
+		audio_swap_endian(va, count); 
+
+	total = 0;
+	bufsz = av.out.buf * Audio_Max_Buf / Audio_Max_Val;
+
+	if(bufsz == 0)
+		error(Ebadarg);
+
+	while(total < count) {
+		chunk = min(bufsz, count - total);
+		osenter();
+		status = write(audio_file_out, va, chunk);
+		osleave();
+		if(status <= 0)
+			error(Eio);
+		total += status;
+	}
+
+	poperror();
+	qunlock(&outlock);
+
+	time.tv_sec = 0; /* hack around broken thread scheduler in Solaris */
+	time.tv_nsec= 1;
+	nanosleep(&time,nil);
+
+	return count;
+}
+
+int
+audio_open_in(void)
+{
+	int fd;
+
+	/* open non-blocking in case someone already has it open */
+	/* otherwise we would block until they close! */
+	fd = open(AUDIO_FILE_STRING, O_RDONLY|O_NONBLOCK);
+
+	if(fd < 0)
+		oserror();
+
+	/* change device to be blocking */
+	if(!audio_set_blocking(fd)) {
+		close(fd);
+		error(Eio);
+	}
+
+	if(!audio_pause_in(fd, A_Pause)) {
+		close(fd);
+		error(Eio);
+	}
+
+	if(!audio_flush(fd, A_In)) {
+		close(fd);
+		error(Eio);
+	}
+
+	/* set audio info */
+	av.in.flags = ~0;
+	av.out.flags = 0;
+
+	if(!audio_set_info(fd, &av.in, A_In)) {
+		close(fd);
+		error(Ebadarg);
+	}
+
+	av.in.flags = 0;
+
+	/* tada, we're open, blocking, paused and flushed */
+	return fd;
+}
+
+int
+audio_open_out(void)
+{
+	int fd;
+	struct audio_info	hdr;
+
+	/* open non-blocking in case someone already has it open */
+	/* otherwise we would block until they close! */
+	fd = open(AUDIO_FILE_STRING, O_WRONLY|O_NONBLOCK);
+
+	if(fd < 0)
+		oserror();
+
+	/* change device to be blocking */
+	if(!audio_set_blocking(fd)) {
+		close(fd);
+		error("cannot set blocking mode");
+	}
+
+	/* set audio info */
+	av.in.flags = 0;
+	av.out.flags = ~0;
+
+	if(!audio_set_info(fd, &av.out, A_Out)) {
+		close(fd);
+		error(Ebadarg);
+	}
+
+	av.out.flags = 0;
+
+	return fd;
+}
+
+long
+audio_ctl_write(Chan *c, void *va, long count, vlong offset)
+{
+	int	fd;
+	int	ff;
+	Audio_t tmpav = av;
+
+	tmpav.in.flags = 0;
+	tmpav.out.flags = 0;
+
+	if (!audioparse(va, count, &tmpav))
+		error(Ebadarg);
+
+	if (!audio_enforce(&tmpav))
+		error(Ebadarg);
+
+	qlock(&inlock);
+	if (waserror()) {
+		qunlock(&inlock);
+		nexterror();
+	}
+
+	if (audio_file_in >= 0 && (tmpav.in.flags & AUDIO_MOD_FLAG)) {
+		if (!audio_pause_in(audio_file_in, A_Pause))
+			error(Ebadarg);
+		if (!audio_flush(audio_file_in, A_In))
+			error(Ebadarg);
+		if (!audio_set_info(audio_file_in, &tmpav.in, A_In))
+			error(Ebadarg);
+	}
+	poperror();
+	qunlock(&inlock);
+
+	qlock(&outlock);
+	if (waserror()) {
+		qunlock(&outlock);
+		nexterror();
+	}
+	if (audio_file_out >= 0 && (tmpav.out.flags & AUDIO_MOD_FLAG)){
+		if (!audio_pause_out(audio_file_out))
+			error(Ebadarg);
+		if (!audio_set_info(audio_file_out, &tmpav.out, A_Out))
+			error(Ebadarg);
+	}
+	poperror();
+	qunlock(&outlock);
+
+	tmpav.in.flags = 0;
+	tmpav.out.flags = 0;
+
+	av = tmpav;
+
+	return count;
+}
+
+static int
+audio_set_blocking(int fd)
+{
+	int val;
+
+	if((val = fcntl(fd, F_GETFL, 0)) == -1)
+		return 0;
+	
+	val &= ~O_NONBLOCK;
+
+	if(fcntl(fd, F_SETFL, val) < 0)
+		return 0;
+
+	return 1;
+}
+
+static int
+audio_set_info(int fd, Audio_d *i, int d)
+{
+	int status;
+	int unequal_stereo = 0;
+	audio_info_t	info;
+	audio_prinfo_t  *dev;
+
+	if(fd < 0)
+		return 0;
+
+	/* devitialize header */
+	AUDIO_INITINFO(&info);
+
+	if(d == A_In)
+		dev = &info.record;
+	else
+		dev = &info.play;
+
+	/* sample rate */
+	if(i->flags & AUDIO_RATE_FLAG)
+		dev->sample_rate = i->rate;
+
+	/* channels */
+	if(i->flags & AUDIO_CHAN_FLAG)
+		dev->channels = i->chan;
+
+	/* precision */
+	if(i->flags & AUDIO_BITS_FLAG)
+		dev->precision = i->bits;
+
+	/* encoding */
+	if(i->flags & AUDIO_ENC_FLAG)
+		dev->encoding = i->enc;
+
+	/* devices */
+	if(i->flags & AUDIO_DEV_FLAG)
+		dev->port = i->dev;
+
+	/* dev volume */
+	if(i->flags & (AUDIO_LEFT_FLAG|AUDIO_VOL_FLAG)) {
+		dev->gain = (i->left * AUDIO_MAX_GAIN) / Audio_Max_Val;
+
+		/* do left first then right later */
+		if(i->left == i->right) 
+			dev->balance = AUDIO_MID_BALANCE;
+		else {
+			dev->balance = AUDIO_LEFT_BALANCE;
+			if(i->chan != 1)
+				unequal_stereo = 1;
+		}
+	}
+
+	osenter();
+	status = ioctl(fd, AUDIO_SETINFO, &info);  /* qlock and load general stuff */
+	osleave();
+
+	if(status == -1) {
+		if(debug) print("audio_set_info 1 failed: fd = %d errno = %d\n", fd, errno);
+		return 0;
+	}
+
+	/* check for different right and left for dev */
+	if(unequal_stereo) {
+
+		/* re-init header */
+		AUDIO_INITINFO(&info);
+
+		dev->gain = (i->right * AUDIO_MAX_GAIN) / Audio_Max_Val;
+		dev->balance == AUDIO_RIGHT_BALANCE;
+
+		osenter();
+		status = ioctl(fd, AUDIO_SETINFO, &info);
+		osleave();
+
+		if(status == -1) {
+			if(debug) print("audio_set_info 2 failed: fd = %d errno = %d\n",fd, errno);
+			return 0;
+		}
+	}
+
+	return 1;
+}
+
+void 
+audio_swap_endian(char *p, int n)
+{
+	int b;
+
+	while (n > 1) {
+		b = p[0];
+		p[0] = p[1];
+		p[1] = b;
+		p += 2;
+		n -= 2;
+	}
+}
+
+static int
+audio_pause_out(int fd)
+{
+	audio_info_t	info;
+	int	foo = 0;
+	int status;
+
+	osenter();
+	status = ioctl(fd, AUDIO_DRAIN, &foo);
+	osleave();
+
+	if(status == -1) 
+		return 0;
+	return 1;
+}
+
+static int
+audio_pause_in(int fd, int f)
+{
+	audio_info_t	info;
+	int status;
+
+	if(fd < 0)
+		return 0;
+
+	if(audio_in_pause == f)
+		return 1;
+	
+	/* initialize header */
+	AUDIO_INITINFO(&info);
+
+	/* unpause input */
+	if(f == A_Pause)
+		info.record.pause = 1;
+	else
+		info.record.pause = 0;
+
+	osenter();
+	status = ioctl(fd, AUDIO_SETINFO, &info);
+	osleave();
+
+	if(status == -1) 
+		return 0;
+
+	audio_in_pause = f;
+
+	return 1;
+}
+
+static int
+audio_flush(int fd, int d)
+{
+	int flag = d==A_In? FLUSHR: FLUSHW;
+	int status;
+
+	osenter();
+	status = ioctl(fd, I_FLUSH, flag); /* drain anything already put into buffer */
+	osleave();
+
+	if(status == -1) 
+		return 0;
+	return 1;
+}
+
+static int
+audio_enforce(Audio_t *t)
+{
+	if((t->in.enc == Audio_Ulaw_Val || t->in.enc == Audio_Alaw_Val) && 
+		(t->in.rate != 8000 || t->in.chan != 1))
+		 return 0;
+	if((t->out.enc == Audio_Ulaw_Val || t->out.enc == Audio_Alaw_Val) && 
+		(t->out.rate != 8000 || t->out.chan != 1))
+		 return 0;
+	return 1;
+}
+
+Audio_t*
+getaudiodev(void)
+{
+	return &av;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Solaris/cmd.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,213 @@
+#include	<sys/types.h>
+#include	<signal.h>
+#include 	<pwd.h>
+#include	<sys/resource.h>
+#include	<sys/wait.h>
+#include	<fcntl.h>
+
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+enum
+{
+	Debug = 0
+};
+
+/*
+ * os-specific devcmd support.
+ * this version should be reasonably portable across Unix systems.
+ */
+typedef struct Targ Targ;
+struct Targ
+{
+	int	fd[3];	/* fd[0] is standard input, fd[1] is standard output, fd[2] is standard error */
+	char**	args;
+	char*	dir;
+	int	pid;
+	int	wfd;	/* child writes errors that occur after the fork or on exec */
+	int	uid;
+	int	gid;
+};
+
+extern int gidnobody;
+extern int uidnobody;
+
+static int
+childproc(Targ *t)
+{
+	int i, nfd;
+
+	if(Debug)
+		print("devcmd: '%s'", t->args[0]);
+
+	nfd = getdtablesize();
+	for(i = 0; i < nfd; i++)
+		if(i != t->fd[0] && i != t->fd[1] && i != t->fd[2] && i != t->wfd)
+			close(i);
+
+	dup2(t->fd[0], 0);
+	dup2(t->fd[1], 1);
+	dup2(t->fd[2], 2);
+	close(t->fd[0]);
+	close(t->fd[1]);
+	close(t->fd[2]);
+
+	/* should have an auth file to do host-specific authorisation? */
+	if(t->gid != -1){
+		if(setgid(t->gid) < 0 && getegid() == 0){
+			fprint(t->wfd, "can't set gid %d: %s", t->gid, strerror(errno));
+			_exit(1);
+		}
+	}
+
+	if(t->uid != -1){
+		if(setuid(t->uid) < 0 && geteuid() == 0){
+			fprint(t->wfd, "can't set uid %d: %s", t->uid, strerror(errno));
+			_exit(1);
+		}
+	}
+
+	if(t->dir != nil && chdir(t->dir) < 0){
+		fprint(t->wfd, "can't chdir to %s: %s", t->dir, strerror(errno));
+		_exit(1);
+	}
+
+	signal(SIGPIPE, SIG_DFL);
+
+	execvp(t->args[0], t->args);
+	if(Debug)
+		print("execvp: %s\n",strerror(errno));
+	fprint(t->wfd, "exec failed: %s", strerror(errno));
+
+	_exit(1);
+}
+
+void*
+oscmd(char **args, int nice, char *dir, int *fd)
+{
+	Targ *t;
+	int r, fd0[2], fd1[2], fd2[2], wfd[2], n, pid;
+
+	t = mallocz(sizeof(*t), 1);
+	if(t == nil)
+		return nil;
+
+	fd0[0] = fd0[1] = -1;
+	fd1[0] = fd1[1] = -1;
+	fd2[0] = fd2[1] = -1;
+	wfd[0] = wfd[1] = -1;
+	if(pipe(fd0) < 0 || pipe(fd1) < 0 || pipe(fd2) < 0 || pipe(wfd) < 0)
+		goto Error;
+	if(fcntl(wfd[1], F_SETFD, FD_CLOEXEC) < 0)	/* close on exec to give end of file on success */
+		goto Error;
+
+	t->fd[0] = fd0[0];
+	t->fd[1] = fd1[1];
+	t->fd[2] = fd2[1];
+	t->wfd = wfd[1];
+	t->args = args;
+	t->dir = dir;
+	t->gid = up->env->gid;
+	if(t->gid == -1)
+		t->gid = gidnobody;
+	t->uid = up->env->uid;
+	if(t->uid == -1)
+		t->uid = uidnobody;
+
+	signal(SIGCHLD, SIG_DFL);
+	switch(pid = fork()) {
+	case -1:
+		goto Error;
+	case 0:
+		setpgrp();
+		if(nice)
+			oslopri();
+		childproc(t);
+		_exit(1);
+	default:
+		t->pid = pid;
+		if(Debug)
+			print("cmd pid %d\n", t->pid);
+		break;
+	}
+
+	close(fd0[0]);
+	close(fd1[1]);
+	close(fd2[1]);
+	close(wfd[1]);
+
+	n = read(wfd[0], up->genbuf, sizeof(up->genbuf)-1);
+	close(wfd[0]);
+	if(n > 0){
+		close(fd0[1]);
+		close(fd1[0]);
+		close(fd2[0]);
+		free(t);
+		up->genbuf[n] = 0;
+		if(Debug)
+			print("oscmd: bad exec: %q\n", up->genbuf);
+		error(up->genbuf);
+		return nil;
+	}
+
+	fd[0] = fd0[1];
+	fd[1] = fd1[0];
+	fd[2] = fd2[0];
+	return t;
+
+Error:
+	r = errno;
+	if(Debug)
+		print("oscmd: %q\n",strerror(r));
+	close(fd0[0]);
+	close(fd0[1]);
+	close(fd1[0]);
+	close(fd1[1]);
+	close(fd2[0]);
+	close(fd2[1]);
+	close(wfd[0]);
+	close(wfd[1]);
+	error(strerror(r));
+	return nil;
+}
+
+int
+oscmdkill(void *a)
+{
+	Targ *t = a;
+
+	if(Debug)
+		print("kill: %d\n", t->pid);
+	return kill(-t->pid, SIGTERM);
+}
+
+int
+oscmdwait(void *a, char *buf, int n)
+{
+	Targ *t = a;
+	int s;
+
+	if(waitpid(t->pid, &s, 0) == -1){
+		if(Debug)
+			print("wait error: %d [in %d] %q\n", t->pid, getpid(), strerror(errno));
+		return -1;
+	}
+	if(WIFEXITED(s)){
+		if(WEXITSTATUS(s) == 0)
+			return snprint(buf, n, "%d 0 0 0 ''", t->pid);
+		return snprint(buf, n, "%d 0 0 0 'exit: %d'", t->pid, WEXITSTATUS(s));
+	}
+	if(WIFSIGNALED(s)){
+		if(WTERMSIG(s) == SIGTERM || WTERMSIG(s) == SIGKILL)
+			return snprint(buf, n, "%d 0 0 0 killed", t->pid);
+		return snprint(buf, n, "%d 0 0 0 'signal: %d'", t->pid, WTERMSIG(s));
+	}
+	return snprint(buf, n, "%d 0 0 0 'odd status: 0x%x'", t->pid, s);
+}
+
+void
+oscmdfree(void *a)
+{
+	free(a);
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Solaris/deveia.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,38 @@
+/*
+ * Solaris serial port definitions
+ */
+
+static char *sysdev[] = {
+        "/dev/term/a",
+        "/dev/term/b"
+};
+
+#include "deveia-posix.c"
+#include "deveia-bsd.c"
+
+static struct tcdef_t bps[] = {
+	{0,		B0},
+	{50,		B50},
+	{75,		B75},
+	{110,		B110},
+	{134,		B134},
+	{150,		B150},
+	{200,		B200},
+	{300,		B300},
+	{600,		B600},
+	{1200,	B1200},
+	{1800,	B1800},
+	{2400,	B2400},
+	{4800,	B4800},
+	{9600,	B9600},
+	{19200,	B19200},
+	{38400,	B38400},
+	{57600,	B57600},
+	{76800,	B76800},
+	{115200,	B115200},
+	{153600,	B153600},
+	{230400,	B230400},
+	{307200,	B307200},
+	{460800,	B460800},
+	{-1,		-1}
+};
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Solaris/devfs.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,7 @@
+#include "devfs-posix.c"
+
+static vlong
+osdisksize(int fd)
+{
+	return 0;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Solaris/emu	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,111 @@
+dev
+	root
+	cons
+	env
+	mnt
+	pipe
+	prog
+	prof
+	srv
+	dup
+	ssl
+	cap
+	fs
+	cmd	cmd
+	indir
+
+	draw	win-x11a
+	pointer
+	snarf
+
+	ip	ipif-posix ipaux
+	eia
+	audio	audio
+	mem
+
+lib
+	interp
+	tk
+	freetype
+	math
+	draw
+
+	memlayer
+	memdraw
+	keyring
+	sec
+	mp
+
+	9
+
+link
+
+mod
+	sys
+	draw
+
+	tk
+	math
+	srv	srv
+	keyring
+	crypt
+	ipints
+	loader
+	freetype
+
+port
+	alloc
+	cache
+	chan
+	dev
+	devtab
+
+	dial
+	dis
+	discall
+	env
+	error
+	errstr
+	exception
+	exportfs
+	inferno
+	latin1
+	main
+	parse
+	pgrp
+	print
+	proc
+	qio
+	random
+	sysfile
+	uqid
+
+code
+
+init
+	emuinit
+
+root
+	/dev	/
+	/fd	/
+	/prog	/
+	/prof	/
+	/net	/
+	/net.alt	/
+	/chan	/
+	/nvfs	/
+	/env	/
+#	/chan
+#	/dev
+#	/dis
+#	/env
+#	/n
+#	/net
+#	/nvfs /
+#	/prog
+#	/icons
+#	/osinit.dis
+#	/dis/emuinit.dis
+#	/dis/lib/auth.dis
+#	/dis/lib/ssl.dis
+#	/n/local /
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Solaris/mkfile	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,46 @@
+SYSTARG=Solaris
+OBJTYPE=sparc
+<../../mkconfig
+SYSTARG=Solaris
+OBJTYPE=sparc
+
+#Configurable parameters
+
+CONF=emu			#default configuration
+CONFLIST=emu
+CLEANCONFLIST=
+
+INSTALLDIR=$ROOT/$SYSTARG/$OBJTYPE/bin	#path of directory where kernel is installed
+
+#end configurable parameters
+
+<$ROOT/mkfiles/mkfile-$SYSTARG-$OBJTYPE	#set vars based on target system
+
+<| $SHELLNAME ../port/mkdevlist $CONF	#sets $IP, $DEVS, $PORT, $LIBS
+
+OBJ=\
+	asm-$OBJTYPE.$O\
+	os.$O\
+	$CONF.root.$O\
+	lock.$O\
+	$DEVS\
+	$PORT\
+
+HFILES=\
+
+CFLAGS='-DROOT="'$ROOT'"' -DEMU -I. -I../port -I$ROOT/$SYSTARG/$OBJTYPE/include -I$ROOT/include -I$ROOT/libinterp $CTHREADFLAGS $CFLAGS $EMUOPTIONS
+SYSLIBS=$EMULIBS
+KERNDATE=`{$NDATE}
+
+default:V:	$O.$CONF
+
+<../port/portmkfile
+
+$O.$CONF:	$OBJ $CONF.c $CONF.root.h $LIBFILES
+	$CC $CFLAGS '-DKERNDATE='$KERNDATE $CONF.c
+	$LD $LDFLAGS -o $target $OBJ $CONF.$O $LIBFILES $SYSLIBS
+
+install:V: $O.$CONF
+	cp $O.$CONF $INSTALLDIR/$CONF
+
+devfs.$O:	../port/devfs-posix.c
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Solaris/os.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,436 @@
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+#undef _POSIX_C_SOURCE 
+#undef getwd
+#include	<unistd.h>
+#include	<thread.h>
+#include	<time.h>
+#include	<termios.h>
+#include	<signal.h>
+#include 	<pwd.h>
+#include	<sys/resource.h>
+#include	<sys/time.h>
+
+enum
+{
+	DELETE  = 0x7F
+};
+char *hosttype = "Solaris";
+
+static thread_key_t	prdakey;
+
+static siginfo_t siginfo;
+
+extern int dflag;
+
+Proc*
+getup(void)
+{
+	void *vp;
+
+	if (thr_getspecific(prdakey, &vp))
+		return nil;
+	return vp;
+}
+
+void
+pexit(char *msg, int t)
+{
+	Osenv *e;
+
+	lock(&procs.l);
+	if(up->prev)
+		up->prev->next = up->next;
+	else
+		procs.head = up->next;
+
+	if(up->next)
+		up->next->prev = up->prev;
+	else
+		procs.tail = up->prev;
+	unlock(&procs.l);
+
+	/*print("pexit: %s: %s\n", up->text, msg);*/
+	e = up->env;
+	if(e != nil) {
+		closefgrp(e->fgrp);
+		closepgrp(e->pgrp);
+		closeegrp(e->egrp);
+		closesigs(e->sigs);
+	}
+	free(up->prog);
+	sema_destroy(up->os);
+	free(up->os);
+	free(up);
+	thr_exit(0);
+}
+
+static void *
+tramp(void *v)
+{
+	struct Proc *Up;
+
+	if(thr_setspecific(prdakey, v)) {
+		print("set specific data failed in tramp\n");
+		thr_exit(0);
+	}
+	Up = v;
+	Up->sigid = thr_self();
+	Up->func(Up->arg);
+	pexit("", 0);
+}
+
+void
+kproc(char *name, void (*func)(void*), void *arg, int flags)
+{
+	thread_t thread;
+	Proc *p;
+	Pgrp *pg;
+	Fgrp *fg;
+	Egrp *eg;
+	sema_t *sem;
+
+	p = newproc();
+
+	sem = malloc(sizeof(*sem));
+	if(sem == nil)
+		panic("can't allocate semaphore");
+	sema_init(sem, 0, USYNC_THREAD, 0);
+	p->os = sem;
+
+	if(flags & KPDUPPG) {
+		pg = up->env->pgrp;
+		incref(&pg->r);
+		p->env->pgrp = pg;
+	}
+	if(flags & KPDUPFDG) {
+		fg = up->env->fgrp;
+		incref(&fg->r);
+		p->env->fgrp = fg;
+	}
+	if(flags & KPDUPENVG) {
+		eg = up->env->egrp;
+		incref(&eg->r);
+		p->env->egrp = eg;
+	}
+
+	p->env->uid = up->env->uid;
+	p->env->gid = up->env->gid;
+	kstrdup(&p->env->user, up->env->user);
+
+	strcpy(p->text, name);
+
+	p->func = func;
+	p->arg = arg;
+
+	lock(&procs.l);
+	if(procs.tail != nil) {
+		p->prev = procs.tail;
+		procs.tail->next = p;
+	}
+	else {
+		procs.head = p;
+		p->prev = nil;
+	}
+	procs.tail = p;
+	unlock(&procs.l);
+
+	if(thr_create(0, 0, &tramp, p, THR_BOUND|THR_DETACHED, &thread))
+		panic("thr_create failed\n");
+	thr_yield();
+}
+
+/* to get pc on trap use siginfo.si_pc field and define all trap handlers
+	as printILL - have to set sa_sigaction, sa_flags not sa_handler
+*/
+
+static void
+trapUSR1(void)
+{
+	int intwait;
+
+	intwait = up->intwait;
+	up->intwait = 0;	/* clear it to let proc continue in osleave */
+
+	if(up->type != Interp)		/* Used to unblock pending I/O */
+		return;
+	if(intwait == 0)		/* Not posted so its a sync error */
+		disfault(nil, Eintr);	/* Should never happen */
+}
+
+static void
+trapILL(void)
+{
+	disfault(nil, "Illegal instruction");
+}
+
+static void
+printILL(int sig, siginfo_t *siginfo, void *v)
+{
+	panic("Illegal instruction with code=%d at address=%x, opcode=%x.\n",
+		siginfo->si_code, siginfo->si_addr,*(char*)siginfo->si_addr);
+}
+
+static void
+trapBUS(void)
+{
+	disfault(nil, "Bus error");
+}
+
+static void
+trapSEGV(void)
+{
+	disfault(nil, "Segmentation violation");
+}
+
+static void
+trapFPE(void)
+{
+	disfault(nil, "Floating point exception");
+}
+
+void
+oshostintr(Proc *p)
+{
+	thr_kill(p->sigid, SIGUSR1);
+}
+
+void
+osblock(void)
+{
+	while(sema_wait(up->os))
+		;	/* retry on signals */
+}
+
+void
+osready(Proc *p)
+{
+	sema_post(p->os);
+}
+
+void
+oslongjmp(void *regs, osjmpbuf env, int val)
+{
+	USED(regs);
+	siglongjmp(env, val);
+}
+
+static struct termios tinit;
+
+static void
+termset(void)
+{
+	struct termios t;
+
+	tcgetattr(0, &t);
+	tinit = t;
+	t.c_lflag &= ~(ICANON|ECHO|ISIG);
+	t.c_cc[VMIN] = 1;
+	t.c_cc[VTIME] = 0;
+	tcsetattr(0, TCSANOW, &t);
+}
+
+static void
+termrestore(void)
+{
+	tcsetattr(0, TCSANOW, &tinit);
+}
+
+void
+cleanexit(int x)
+{
+	USED(x);
+
+	if(up->intwait) {
+		up->intwait = 0;
+		return;
+	}
+
+	if(dflag == 0)
+		termrestore();
+	exit(0);
+}
+
+int gidnobody= -1, uidnobody= -1;
+
+void
+getnobody(void)
+{
+	struct passwd *pwd;
+	
+	if (pwd=getpwnam("nobody")) {
+		uidnobody = pwd->pw_uid;
+		gidnobody = pwd->pw_gid;
+	}
+}
+
+void
+osreboot(char *file, char **argv)
+{
+	if(dflag == 0)
+		termrestore();
+	execvp(file, argv);
+	panic("reboot failure");
+}
+
+void
+libinit(char *imod)
+{
+	struct Proc *Up;
+	struct sigaction act;
+	struct passwd *pw;
+	char sys[64];
+
+	setsid();
+
+	if(dflag == 0)
+		termset();
+
+	gethostname(sys, sizeof(sys));
+	kstrdup(&ossysname, sys);
+	getnobody();
+
+	memset(&act, 0 , sizeof(act));
+	act.sa_handler=trapUSR1;
+	sigaction(SIGUSR1, &act, nil);
+	/*
+	 * For the correct functioning of devcmd in the
+	 * face of exiting slaves
+	 */
+	signal(SIGPIPE, SIG_IGN);
+	if(signal(SIGTERM, SIG_IGN) != SIG_IGN)
+		signal(SIGTERM, cleanexit);
+	if(sflag == 0) {
+		act.sa_handler = trapBUS;
+		sigaction(SIGBUS, &act, nil);
+		act.sa_handler = trapILL;
+		sigaction(SIGILL, &act, nil);
+		act.sa_handler = trapSEGV;
+		sigaction(SIGSEGV, &act, nil);
+		act.sa_handler = trapFPE;
+		sigaction(SIGFPE, &act, nil);
+		if(signal(SIGINT, SIG_IGN) != SIG_IGN)
+			signal(SIGINT, cleanexit);
+	} else{
+		act.sa_sigaction = printILL;
+		act.sa_flags=SA_SIGINFO;
+		sigaction(SIGILL, &act, nil);
+	}	
+
+	if(thr_keycreate(&prdakey,NULL))
+		print("keycreate failed\n");
+
+	Up = newproc();
+	if(thr_setspecific(prdakey,Up))
+		panic("set specific thread data failed\n");
+
+	pw = getpwuid(getuid());
+	if(pw != nil)
+		kstrdup(&eve, pw->pw_name);
+	else
+		print("cannot getpwuid\n");
+
+	up->env->uid = getuid();
+	up->env->gid = getgid();
+	emuinit(imod);
+}
+
+int
+readkbd(void)
+{
+	int n;
+	char buf[1];
+
+	n = read(0, buf, sizeof(buf));
+	if(n < 0)
+		fprint(2, "keyboard read: %s\n", strerror(errno));
+	if(n <= 0)
+		pexit("keyboard thread", 0);
+
+	switch(buf[0]) {
+	case '\r':
+		buf[0] = '\n';
+		break;
+	case DELETE:
+		cleanexit(0);
+		break;
+	}
+	return buf[0];
+}
+
+/*
+ * Return an abitrary millisecond clock time
+ */
+long
+osmillisec(void)
+{
+	static long sec0 = 0, usec0;
+	struct timeval t;
+
+	if(gettimeofday(&t, NULL)<0)
+		return(0);
+	if(sec0==0){
+		sec0 = t.tv_sec;
+		usec0 = t.tv_usec;
+	}
+	return((t.tv_sec-sec0)*1000+(t.tv_usec-usec0+500)/1000);
+}
+
+/*
+ * Return the time since the epoch in nanoseconds and microseconds
+ * The epoch is defined at 1 Jan 1970
+ */
+vlong
+osnsec(void)
+{
+	struct timeval t;
+
+	gettimeofday(&t, nil);
+	return (vlong)t.tv_sec*1000000000L + t.tv_usec*1000;
+}
+
+vlong
+osusectime(void)
+{
+	struct timeval t;
+
+	gettimeofday(&t, nil);
+	return (vlong)t.tv_sec * 1000000 + t.tv_usec;
+}
+
+int
+osmillisleep(ulong milsec)
+{
+	struct  timespec time;
+
+	time.tv_sec = milsec/1000;
+	time.tv_nsec= (milsec%1000)*1000000;
+	nanosleep(&time,nil);
+	return 0;
+}
+
+int
+limbosleep(ulong milsec)
+{
+	return osmillisleep(milsec);
+}
+
+void
+osyield(void)
+{
+	thr_yield();
+}
+
+void
+ospause(void)
+{
+	for(;;)
+		pause();
+}
+
+void
+oslopri(void)
+{
+	setpriority(PRIO_PROCESS, 0, getpriority(PRIO_PROCESS,0)+4);
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Unixware/asm-386.s	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,71 @@
+	.section	.bss
+	.align	4
+.L4_.bss:
+	.align	4
+Unixware_Asm_IntP: / Offset 0
+	.type	Unixware_Asm_IntP,@object
+	.size	Unixware_Asm_IntP,4
+	.set	.,.+4
+Unixware_Asm_VoidP: / Offset 4
+	.type	Unixware_Asm_VoidP,@object
+	.size	Unixware_Asm_VoidP,4
+	.set	.,.+4
+	.section	.text
+	.align	4
+.L1_.text:
+/====================
+/ FPsave
+/--------------------
+	.align	4
+	.align	4
+	.globl	FPsave
+FPsave:
+	pushl	%ebp
+	movl	%esp,%ebp
+	movl	8(%ebp),%eax
+	movl	%eax,Unixware_Asm_VoidP
+	fstenv	(%eax)
+	leave	
+	ret	
+	.align	4
+	.type	FPsave,@function
+	.size	FPsave,.-FPsave
+
+/====================
+/ FPrestore
+/--------------------
+	.align	4
+	.globl	FPrestore
+FPrestore:
+	pushl	%ebp
+	movl	%esp,%ebp
+	movl	8(%ebp),%eax
+	movl	%eax,Unixware_Asm_VoidP
+	fldenv	(%eax)
+	leave	
+	ret	
+	.align	4
+	.type	FPrestore,@function
+	.size	FPrestore,.-FPrestore
+
+
+/====================
+/ getcallerpc
+/--------------------
+	.align	4
+	.globl	getcallerpc
+getcallerpc:
+	movl	4(%ebp),%eax
+	ret	
+	.align	4
+	.type	getcallerpc,@function
+	.size	getcallerpc,.-getcallerpc
+
+/ test-and-set
+	.align	4
+	.globl	_tas
+_tas:
+	movl	$1, %eax
+	movl	4(%esp), %ecx
+	xchgl	%eax, 0(%ecx)
+	ret
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Unixware/cmd.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,213 @@
+#include	<sys/types.h>
+#include	<signal.h>
+#include 	<pwd.h>
+#include	<sys/resource.h>
+#include	<sys/wait.h>
+#include	<fcntl.h>
+
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+enum
+{
+	Debug = 0
+};
+
+/*
+ * os-specific devcmd support.
+ * this version should be reasonably portable across Unix systems.
+ */
+typedef struct Targ Targ;
+struct Targ
+{
+	int	fd[3];	/* fd[0] is standard input, fd[1] is standard output, fd[2] is standard error */
+	char**	args;
+	char*	dir;
+	int	pid;
+	int	wfd;	/* child writes errors that occur after the fork or on exec */
+	int	uid;
+	int	gid;
+};
+
+extern int gidnobody;
+extern int uidnobody;
+
+static int
+childproc(Targ *t)
+{
+	int i, nfd;
+
+	if(Debug)
+		print("devcmd: '%s'", t->args[0]);
+
+	nfd = getdtablesize();
+	for(i = 0; i < nfd; i++)
+		if(i != t->fd[0] && i != t->fd[1] && i != t->fd[2] && i != t->wfd)
+			close(i);
+
+	dup2(t->fd[0], 0);
+	dup2(t->fd[1], 1);
+	dup2(t->fd[2], 2);
+	close(t->fd[0]);
+	close(t->fd[1]);
+	close(t->fd[2]);
+
+	/* should have an auth file to do host-specific authorisation? */
+	if(t->gid != -1){
+		if(setgid(t->gid) < 0 && getegid() == 0){
+			fprint(t->wfd, "can't set gid %d: %s", t->gid, strerror(errno));
+			_exit(1);
+		}
+	}
+
+	if(t->uid != -1){
+		if(setuid(t->uid) < 0 && geteuid() == 0){
+			fprint(t->wfd, "can't set uid %d: %s", t->uid, strerror(errno));
+			_exit(1);
+		}
+	}
+
+	if(t->dir != nil && chdir(t->dir) < 0){
+		fprint(t->wfd, "can't chdir to %s: %s", t->dir, strerror(errno));
+		_exit(1);
+	}
+
+	signal(SIGPIPE, SIG_DFL);
+
+	execvp(t->args[0], t->args);
+	if(Debug)
+		print("execvp: %s\n",strerror(errno));
+	fprint(t->wfd, "exec failed: %s", strerror(errno));
+
+	_exit(1);
+}
+
+void*
+oscmd(char **args, int nice, char *dir, int *fd)
+{
+	Targ *t;
+	int r, fd0[2], fd1[2], fd2[2], wfd[2], n, pid;
+
+	t = mallocz(sizeof(*t), 1);
+	if(t == nil)
+		return nil;
+
+	fd0[0] = fd0[1] = -1;
+	fd1[0] = fd1[1] = -1;
+	fd2[0] = fd2[1] = -1;
+	wfd[0] = wfd[1] = -1;
+	if(pipe(fd0) < 0 || pipe(fd1) < 0 || pipe(fd2) < 0 || pipe(wfd) < 0)
+		goto Error;
+	if(fcntl(wfd[1], F_SETFD, FD_CLOEXEC) < 0)	/* close on exec to give end of file on success */
+		goto Error;
+
+	t->fd[0] = fd0[0];
+	t->fd[1] = fd1[1];
+	t->fd[2] = fd2[1];
+	t->wfd = wfd[1];
+	t->args = args;
+	t->dir = dir;
+	t->gid = up->env->gid;
+	if(t->gid == -1)
+		t->gid = gidnobody;
+	t->uid = up->env->uid;
+	if(t->uid == -1)
+		t->uid = uidnobody;
+
+	signal(SIGCHLD, SIG_DFL);
+	switch(pid = fork()) {
+	case -1:
+		goto Error;
+	case 0:
+		setpgrp();
+		if(nice)
+			oslopri();
+		childproc(t);
+		_exit(1);
+	default:
+		t->pid = pid;
+		if(Debug)
+			print("cmd pid %d\n", t->pid);
+		break;
+	}
+
+	close(fd0[0]);
+	close(fd1[1]);
+	close(fd2[1]);
+	close(wfd[1]);
+
+	n = read(wfd[0], up->genbuf, sizeof(up->genbuf)-1);
+	close(wfd[0]);
+	if(n > 0){
+		close(fd0[1]);
+		close(fd1[0]);
+		close(fd2[0]);
+		free(t);
+		up->genbuf[n] = 0;
+		if(Debug)
+			print("oscmd: bad exec: %q\n", up->genbuf);
+		error(up->genbuf);
+		return nil;
+	}
+
+	fd[0] = fd0[1];
+	fd[1] = fd1[0];
+	fd[2] = fd2[0];
+	return t;
+
+Error:
+	r = errno;
+	if(Debug)
+		print("oscmd: %q\n",strerror(r));
+	close(fd0[0]);
+	close(fd0[1]);
+	close(fd1[0]);
+	close(fd1[1]);
+	close(fd2[0]);
+	close(fd2[1]);
+	close(wfd[0]);
+	close(wfd[1]);
+	error(strerror(r));
+	return nil;
+}
+
+int
+oscmdkill(void *a)
+{
+	Targ *t = a;
+
+	if(Debug)
+		print("kill: %d\n", t->pid);
+	return kill(-t->pid, SIGTERM);
+}
+
+int
+oscmdwait(void *a, char *buf, int n)
+{
+	Targ *t = a;
+	int s;
+
+	if(waitpid(t->pid, &s, 0) == -1){
+		if(Debug)
+			print("wait error: %d [in %d] %q\n", t->pid, getpid(), strerror(errno));
+		return -1;
+	}
+	if(WIFEXITED(s)){
+		if(WEXITSTATUS(s) == 0)
+			return snprint(buf, n, "%d 0 0 0 ''", t->pid);
+		return snprint(buf, n, "%d 0 0 0 'exit: %d'", t->pid, WEXITSTATUS(s));
+	}
+	if(WIFSIGNALED(s)){
+		if(WTERMSIG(s) == SIGTERM || WTERMSIG(s) == SIGKILL)
+			return snprint(buf, n, "%d 0 0 0 killed", t->pid);
+		return snprint(buf, n, "%d 0 0 0 'signal: %d'", t->pid, WTERMSIG(s));
+	}
+	return snprint(buf, n, "%d 0 0 0 'odd status: 0x%x'", t->pid, s);
+}
+
+void
+oscmdfree(void *a)
+{
+	free(a);
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Unixware/deveia.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,11 @@
+/*
+ * Solaris serial port definitions
+ */
+
+static char *sysdev[] = {
+        "/dev/cua/a",
+        "/dev/cua/b"
+};
+
+#include "deveia-posix.c"
+#include "deveia-bsd.c"
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Unixware/devfs.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,7 @@
+#include "devfs-posix.c"
+
+static vlong
+osdisksize(int fd)
+{
+	return 0;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Unixware/emu	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,111 @@
+dev
+	root
+	cons
+	env
+	mnt
+	pipe
+	prog
+	prof
+	srv
+	dup
+	ssl
+	cap
+	fs
+	cmd	cmd
+	indir
+
+	draw	win-x11a
+	pointer
+	snarf
+
+	ip	ipif-posix ipaux
+	eia
+#	audio	audio
+	mem
+
+lib
+	interp
+	tk
+	freetype
+	math
+	draw
+
+	memlayer
+	memdraw
+	keyring
+	sec
+	mp
+
+	9
+
+link
+
+mod
+	sys
+	draw
+
+	tk
+	math
+	srv	srv
+	keyring
+	crypt
+	ipints
+	loader
+	freetype
+
+port
+	alloc
+	cache
+	chan
+	dev
+	devtab
+
+	dial
+	dis
+	discall
+	env
+	error
+	errstr
+	exception
+	exportfs
+	inferno
+	latin1
+	main
+	parse
+	pgrp
+	print
+	proc
+	qio
+	random
+	sysfile
+	uqid
+
+code
+
+init
+	emuinit
+
+root
+	/dev	/
+	/fd	/
+	/prog	/
+	/prof	/
+	/net	/
+	/net.alt	/
+	/chan	/
+	/nvfs	/
+	/env	/
+#	/chan
+#	/dev
+#	/dis
+#	/env
+#	/n
+#	/net
+#	/nvfs /
+#	/prog
+#	/icons
+#	/osinit.dis
+#	/dis/emuinit.dis
+#	/dis/lib/auth.dis
+#	/dis/lib/ssl.dis
+#	/n/local /
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Unixware/mkfile	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,44 @@
+SYSTARG=Unixware
+OBJTYPE=386
+<../../mkconfig
+SYSTARG=Unixware
+OBJTYPE=386
+
+#Configurable parameters
+
+CONF=emu			#default configuration
+CONFLIST=emu
+CLEANCONFLIST=
+
+INSTALLDIR=$ROOT/$SYSTARG/$OBJTYPE/bin	#path of directory where kernel is installed
+
+#end configurable parameters
+
+<$ROOT/mkfiles/mkfile-$SYSTARG-$OBJTYPE	#set vars based on target system
+
+<| $SHELLNAME ../port/mkdevlist $CONF	#sets $IP, $DEVS, $PORT, $LIBS
+
+OBJ=\
+	asm-$OBJTYPE.$O\
+	os.$O\
+	$CONF.root.$O\
+	lock.$O\
+	$DEVS\
+	$PORT\
+
+HFILES=\
+
+CFLAGS='-DROOT="'$ROOT'"' -DEMU -I. -I../port -I$ROOT/$SYSTARG/$OBJTYPE/include -I$ROOT/include -I$ROOT/libinterp $CTHREADFLAGS $CFLAGS $EMUOPTIONS
+SYSLIBS=	-lm -lX11 -lsocket -lnsl -lresolv -lxti -Kthread
+KERNDATE=`{$NDATE}
+
+default:V:	$O.$CONF
+
+<../port/portmkfile
+
+$O.$CONF:	$OBJ $CONF.c $CONF.root.h $LIBNAMES
+	$CC $CFLAGS '-DKERNDATE='$KERNDATE $CONF.c
+	$LD $LDFLAGS -o $target $OBJ $CONF.$O $LIBFILES $SYSLIBS
+
+install:V: $O.$CONF
+	cp $O.$CONF $INSTALLDIR/$CONF
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Unixware/mkfile-Unixware	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,15 @@
+#
+#	architecture-dependent files for Unixware
+#
+
+TARGFILES=asm-Unixware-$OBJTYPE.$O\
+	devfs-posix.$O\
+	devip.$O\
+	ipif-posix.$O\
+	os-Unixware.$O\
+	win-x11.$O\
+	deveia-Unixware.$O\
+	srv.$O\
+	lock.$O\
+
+SYSLIBS=	-lm -lX11 -lsocket -lnsl -lresolv -lxti -Kthread
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/Unixware/os.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,437 @@
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+#undef _POSIX_C_SOURCE 
+#undef getwd
+#include	<unistd.h>
+#include	<thread.h>
+#include	<time.h>
+#include	<termios.h>
+#include	<signal.h>
+#include 	<pwd.h>
+#include	<sys/resource.h>
+#include	<sys/time.h>
+
+enum
+{
+	DELETE  = 0x7F
+};
+char *hosttype = "Unixware";
+
+static thread_key_t	prdakey;
+
+static siginfo_t siginfo;
+
+extern int dflag;
+
+Proc*
+getup(void)
+{
+	void *vp;
+
+	if (thr_getspecific(prdakey, &vp))
+		return nil;
+	return vp;
+}
+
+void
+pexit(char *msg, int t)
+{
+	Osenv *e;
+
+	lock(&procs.l);
+	if(up->prev)
+		up->prev->next = up->next;
+	else
+		procs.head = up->next;
+
+	if(up->next)
+		up->next->prev = up->prev;
+	else
+		procs.tail = up->prev;
+	unlock(&procs.l);
+
+	/*print("pexit: %s: %s\n", up->text, msg);*/
+	e = up->env;
+	if(e != nil) {
+		closefgrp(e->fgrp);
+		closepgrp(e->pgrp);
+		closeegrp(e->egrp);
+		closesigs(e->sigs);
+	}
+	free(up->prog);
+	sema_destroy(up->os);
+	free(up->os);
+	free(up);
+	thr_exit(0);
+}
+
+static void *
+tramp(void *v)
+{
+	struct Proc *Up;
+
+	if(thr_setspecific(prdakey, v)) {
+		print("set specific data failed in tramp\n");
+		thr_exit(0);
+	}
+	Up = v;
+	Up->sigid = thr_self();
+	Up->func(Up->arg);
+	pexit("", 0);
+}
+
+void
+kproc(char *name, void (*func)(void*), void *arg, int flags)
+{
+	thread_t thread;
+	Proc *p;
+	Pgrp *pg;
+	Fgrp *fg;
+	Egrp *eg;
+	sema_t *sem;
+
+	p = newproc();
+
+	sem = malloc(sizeof(*sem));
+	if(sem == nil)
+		panic("can't allocate semaphore");
+	sema_init(sem, 0, USYNC_THREAD, 0);
+	p->os = sem;
+
+	if(flags & KPDUPPG) {
+		pg = up->env->pgrp;
+		incref(&pg->r);
+		p->env->pgrp = pg;
+	}
+	if(flags & KPDUPFDG) {
+		fg = up->env->fgrp;
+		incref(&fg->r);
+		p->env->fgrp = fg;
+	}
+	if(flags & KPDUPENVG) {
+		eg = up->env->egrp;
+		incref(&eg->r);
+		p->env->egrp = eg;
+	}
+
+	p->env->uid = up->env->uid;
+	p->env->gid = up->env->gid;
+	kstrdup(&p->env->user, up->env->user);
+
+	strcpy(p->text, name);
+
+	p->func = func;
+	p->arg = arg;
+
+	lock(&procs.l);
+	if(procs.tail != nil) {
+		p->prev = procs.tail;
+		procs.tail->next = p;
+	}
+	else {
+		procs.head = p;
+		p->prev = nil;
+	}
+	procs.tail = p;
+	unlock(&procs.l);
+
+	if(thr_create(0, 0, &tramp, p, THR_BOUND|THR_DETACHED, &thread))
+		panic("thr_create failed\n");
+	thr_yield();
+}
+
+/* to get pc on trap use siginfo.si_pc field and define all trap handlers
+	as printILL - have to set sa_sigaction, sa_flags not sa_handler
+*/
+
+static void
+trapUSR1(void)
+{
+	int intwait;
+
+	intwait = up->intwait;
+	up->intwait = 0;	/* clear it to let proc continue in osleave */
+
+	if(up->type != Interp)		/* Used to unblock pending I/O */
+		return;
+
+	if(intwait == 0)		/* Not posted so it's a sync error */
+		disfault(nil, Eintr);	/* Should never happen */
+}
+
+static void
+trapILL(void)
+{
+	disfault(nil, "Illegal instruction");
+}
+
+static void
+printILL(int sig, siginfo_t *siginfo, void *v)
+{
+	panic("Illegal instruction with code=%d at address=%x, opcode=%x.\n",
+		siginfo->si_code, siginfo->si_addr,*(char*)siginfo->si_addr);
+}
+
+static void
+trapBUS(void)
+{
+	disfault(nil, "Bus error");
+}
+
+static void
+trapSEGV(void)
+{
+	disfault(nil, "Segmentation violation");
+}
+
+static void
+trapFPE(void)
+{
+	disfault(nil, "Floating point exception");
+}
+
+void
+oshostintr(Proc *p)
+{
+	thr_kill(p->sigid, SIGUSR1);
+}
+
+void
+osblock(void)
+{
+	while(sema_wait(up->os))
+		;	/* retry on signals */
+}
+
+void
+osready(Proc *p)
+{
+	sema_post(p->os);
+}
+
+void
+oslongjmp(void *regs, osjmpbuf env, int val)
+{
+	USED(regs);
+	siglongjmp(env, val);
+}
+
+static struct termios tinit;
+
+static void
+termset(void)
+{
+	struct termios t;
+
+	tcgetattr(0, &t);
+	tinit = t;
+	t.c_lflag &= ~(ICANON|ECHO|ISIG);
+	t.c_cc[VMIN] = 1;
+	t.c_cc[VTIME] = 0;
+	tcsetattr(0, TCSANOW, &t);
+}
+
+static void
+termrestore(void)
+{
+	tcsetattr(0, TCSANOW, &tinit);
+}
+
+void
+cleanexit(int x)
+{
+	USED(x);
+
+	if(up->intwait) {
+		up->intwait = 0;
+		return;
+	}
+
+	if(dflag == 0)
+		termrestore();
+	exit(0);
+}
+
+int gidnobody= -1, uidnobody= -1;
+
+void
+getnobody(void)
+{
+	struct passwd *pwd;
+	
+	if (pwd=getpwnam("nobody")) {
+		uidnobody = pwd->pw_uid;
+		gidnobody = pwd->pw_gid;
+	}
+}
+
+void
+osreboot(char *file, char **argv)
+{
+	if(dflag == 0)
+		termrestore();
+	execvp(file, argv);
+	panic("reboot failure");
+}
+
+void
+libinit(char *imod)
+{
+	struct Proc *Up;
+	struct sigaction act;
+	struct passwd *pw;
+	char sys[64];
+
+	setsid();
+
+	if(dflag == 0)
+		termset();
+
+	gethostname(sys, sizeof(sys));
+	kstrdup(&ossysname, sys);
+	getnobody();
+
+	memset(&act, 0 , sizeof(act));
+	act.sa_handler=trapUSR1;
+	sigaction(SIGUSR1, &act, nil);
+	/*
+	 * For the correct functioning of devcmd in the
+	 * face of exiting slaves
+	 */
+	signal(SIGPIPE, SIG_IGN);
+	if(signal(SIGTERM, SIG_IGN) != SIG_IGN)
+		signal(SIGTERM, cleanexit);
+	if(sflag == 0) {
+		act.sa_handler = trapBUS;
+		sigaction(SIGBUS, &act, nil);
+		act.sa_handler = trapILL;
+		sigaction(SIGILL, &act, nil);
+		act.sa_handler = trapSEGV;
+		sigaction(SIGSEGV, &act, nil);
+		act.sa_handler = trapFPE;
+		sigaction(SIGFPE, &act, nil);
+		if(signal(SIGINT, SIG_IGN) != SIG_IGN)
+			signal(SIGINT, cleanexit);
+	} else{
+		act.sa_sigaction = printILL;
+		act.sa_flags=SA_SIGINFO;
+		sigaction(SIGILL, &act, nil);
+	}	
+
+	if(thr_keycreate(&prdakey,NULL))
+		print("keycreate failed\n");
+
+	Up = newproc();
+	if(thr_setspecific(prdakey,Up))
+		panic("set specific thread data failed\n");
+
+	pw = getpwuid(getuid());
+	if(pw != nil)
+		kstrdup(&eve, pw->pw_name);
+	else
+		print("cannot getpwuid\n");
+
+	up->env->uid = getuid();
+	up->env->gid = getgid();
+	emuinit(imod);
+}
+
+int
+readkbd(void)
+{
+	int n;
+	char buf[1];
+
+	n = read(0, buf, sizeof(buf));
+	if(n < 0)
+		fprint(2, "keyboard read: %s\n", strerror(errno));
+	if(n <= 0)
+		pexit("keyboard thread", 0);
+
+	switch(buf[0]) {
+	case '\r':
+		buf[0] = '\n';
+		break;
+	case DELETE:
+		cleanexit(0);
+		break;
+	}
+	return buf[0];
+}
+
+/*
+ * Return an abitrary millisecond clock time
+ */
+long
+osmillisec(void)
+{
+	static long sec0 = 0, usec0;
+	struct timeval t;
+
+	if(gettimeofday(&t, NULL)<0)
+		return(0);
+	if(sec0==0){
+		sec0 = t.tv_sec;
+		usec0 = t.tv_usec;
+	}
+	return((t.tv_sec-sec0)*1000+(t.tv_usec-usec0+500)/1000);
+}
+
+/*
+ * Return the time since the epoch in microseconds
+ * The epoch is defined at 1 Jan 1970
+ */
+vlong
+osnsec(void)
+{
+	struct timeval t;
+
+	gettimeofday(&t, nil);
+	return (vlong)t.tv_sec*1000000000L + t.tv_usec*1000;
+}
+
+vlong
+osusectime(void)
+{
+	struct timeval t;
+
+	gettimeofday(&t, nil);
+	return (vlong)t.tv_sec * 1000000 + t.tv_usec;
+}
+
+int
+osmillisleep(ulong milsec)
+{
+	struct  timespec time;
+
+	time.tv_sec = milsec/1000;
+	time.tv_nsec= (milsec%1000)*1000000;
+	nanosleep(&time,nil);
+	return 0;
+}
+
+int
+limbosleep(ulong milsec)
+{
+	return osmillisleep(milsec);
+}
+
+void
+osyield(void)
+{
+	thr_yield();
+}
+
+void
+ospause(void)
+{
+	for(;;)
+		pause();
+}
+
+void
+oslopri(void)
+{
+	setpriority(PRIO_PROCESS, 0, getpriority(PRIO_PROCESS,0)+4);
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/mkfile	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,19 @@
+<../mkconfig
+
+all:V:	all-$HOSTMODEL
+install:V:	install-$HOSTMODEL
+safeinstall:V:	safeinstall-$HOSTMODEL
+clean:V:	clean-$HOSTMODEL
+nuke:V:	nuke-$HOSTMODEL
+
+&-Posix:QV:
+	echo "(cd $SYSTARG; mk $MKFLAGS $stem)"
+	(cd $SYSTARG; mk $MKFLAGS $stem) || exit 1
+
+&-Nt:QV:
+	echo '@{builtin cd' $SYSTARG '; mk $MKFLAGS $stem}'
+	@{builtin cd $SYSTARG; mk $MKFLAGS $stem }
+
+&-Plan9:QV:
+	echo '@{builtin cd' $SYSTARG '; mk $MKFLAGS $stem}'
+	@{builtin cd $SYSTARG; mk $MKFLAGS $stem }
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/port/acme-offset	Sun Nov  4 12:33:14 2018
@@ -0,0 +1 @@
+X ,x/b?(read|write)\(.*ulong.*\)$/v/(bread|bwrite)/x/ulong offset/c/vlong offset
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/port/alloc.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,1021 @@
+#include "dat.h"
+#include "fns.h"
+#include "interp.h"
+#include "error.h"
+
+enum
+{
+	MAXPOOL		= 4
+};
+
+#define left	u.s.bhl
+#define right	u.s.bhr
+#define fwd	u.s.bhf
+#define prev	u.s.bhv
+#define parent	u.s.bhp
+
+#define RESERVED	512*1024
+
+struct Pool
+{
+	char*	name;
+	int	pnum;
+	ulong	maxsize;
+	int	quanta;
+	int	chunk;
+	int	monitor;
+	ulong	ressize;	/* restricted size */
+	ulong	cursize;
+	ulong	arenasize;
+	ulong	hw;
+	Lock	l;
+	Bhdr*	root;
+	Bhdr*	chain;
+	ulong	nalloc;
+	ulong	nfree;
+	int	nbrk;
+	int	lastfree;
+	void	(*move)(void*, void*);
+};
+
+void*	initbrk(ulong);
+
+struct
+{
+	int	n;
+	Pool	pool[MAXPOOL];
+	/* Lock l; */
+} table = {
+	3,
+	{
+		{ "main",  0, 	32*1024*1024, 31,  512*1024, 0, 31*1024*1024 },
+		{ "heap",  1, 	32*1024*1024, 31,  512*1024, 0, 31*1024*1024 },
+		{ "image", 2,   64*1024*1024+256, 31, 4*1024*1024, 1, 63*1024*1024 },
+	}
+};
+
+Pool*	mainmem = &table.pool[0];
+Pool*	heapmem = &table.pool[1];
+Pool*	imagmem = &table.pool[2];
+
+static void _auditmemloc(char *, void *);
+void (*auditmemloc)(char *, void *) = _auditmemloc;
+static void _poolfault(void *, char *, ulong);
+void (*poolfault)(void *, char *, ulong) = _poolfault;
+
+/*	non tracing
+ *
+enum {
+	Npadlong	= 0,
+	MallocOffset = 0,
+	ReallocOffset = 0
+};
+ *
+ */
+
+/* tracing */
+enum {
+	Npadlong	= 2,
+	MallocOffset = 0,
+	ReallocOffset = 1
+};
+
+enum {
+	Monitor = 1
+};
+
+void	(*memmonitor)(int, ulong, ulong, ulong) = nil;
+#define	MM(v,pc,base,size)	if(!Monitor || memmonitor==nil){} else memmonitor((v),(pc),(base),(size))
+
+#define CKLEAK	0
+int	ckleak;
+#define	ML(v, sz, pc)	if(CKLEAK && ckleak && v){ if(sz) fprint(2, "%lux %lux %lux\n", (ulong)v, (ulong)sz, (ulong)pc); else fprint(2, "%lux\n", (ulong)v); }
+
+int
+memusehigh(void)
+{
+	return 	mainmem->cursize > mainmem->ressize ||
+			heapmem->cursize > heapmem->ressize ||
+			0 && imagmem->cursize > imagmem->ressize;
+}
+
+int
+memlow(void)
+{
+	return heapmem->cursize > (heapmem->maxsize)/2;
+}
+
+int
+poolsetsize(char *s, int size)
+{
+	int i;
+
+	for(i = 0; i < table.n; i++) {
+		if(strcmp(table.pool[i].name, s) == 0) {
+			table.pool[i].maxsize = size;
+			table.pool[i].ressize = size-RESERVED;
+			if(size < RESERVED)
+				panic("not enough memory");
+			return 1;
+		}
+	}
+	return 0;
+}
+
+void
+poolimmutable(void *v)
+{
+	Bhdr *b;
+
+	D2B(b, v);
+	b->magic = MAGIC_I;
+}
+
+void
+poolmutable(void *v)
+{
+	Bhdr *b;
+
+	D2B(b, v);
+	b->magic = MAGIC_A;
+	((Heap*)v)->color = mutator;
+}
+
+char*
+poolname(Pool *p)
+{
+	return p->name;
+}
+
+Bhdr*
+poolchain(Pool *p)
+{
+	return p->chain;
+}
+
+void
+pooldel(Pool *p, Bhdr *t)
+{
+	Bhdr *s, *f, *rp, *q;
+
+	if(t->parent == nil && p->root != t) {
+		t->prev->fwd = t->fwd;
+		t->fwd->prev = t->prev;
+		return;
+	}
+
+	if(t->fwd != t) {
+		f = t->fwd;
+		s = t->parent;
+		f->parent = s;
+		if(s == nil)
+			p->root = f;
+		else {
+			if(s->left == t)
+				s->left = f;
+			else
+				s->right = f;
+		}
+
+		rp = t->left;
+		f->left = rp;
+		if(rp != nil)
+			rp->parent = f;
+		rp = t->right;
+		f->right = rp;
+		if(rp != nil)
+			rp->parent = f;
+
+		t->prev->fwd = t->fwd;
+		t->fwd->prev = t->prev;
+		return;
+	}
+
+	if(t->left == nil)
+		rp = t->right;
+	else {
+		if(t->right == nil)
+			rp = t->left;
+		else {
+			f = t;
+			rp = t->right;
+			s = rp->left;
+			while(s != nil) {
+				f = rp;
+				rp = s;
+				s = rp->left;
+			}
+			if(f != t) {
+				s = rp->right;
+				f->left = s;
+				if(s != nil)
+					s->parent = f;
+				s = t->right;
+				rp->right = s;
+				if(s != nil)
+					s->parent = rp;
+			}
+			s = t->left;
+			rp->left = s;
+			s->parent = rp;
+		}
+	}
+	q = t->parent;
+	if(q == nil)
+		p->root = rp;
+	else {
+		if(t == q->left)
+			q->left = rp;
+		else
+			q->right = rp;
+	}
+	if(rp != nil)
+		rp->parent = q;
+}
+
+void
+pooladd(Pool *p, Bhdr *q)
+{
+	int size;
+	Bhdr *tp, *t;
+
+	q->magic = MAGIC_F;
+
+	q->left = nil;
+	q->right = nil;
+	q->parent = nil;
+	q->fwd = q;
+	q->prev = q;
+
+	t = p->root;
+	if(t == nil) {
+		p->root = q;
+		return;
+	}
+
+	size = q->size;
+
+	tp = nil;
+	while(t != nil) {
+		if(size == t->size) {
+			q->prev = t->prev;
+			q->prev->fwd = q;
+			q->fwd = t;
+			t->prev = q;
+			return;
+		}
+		tp = t;
+		if(size < t->size)
+			t = t->left;
+		else
+			t = t->right;
+	}
+
+	q->parent = tp;
+	if(size < tp->size)
+		tp->left = q;
+	else
+		tp->right = q;
+}
+
+static void*
+dopoolalloc(Pool *p, ulong asize, ulong pc)
+{
+	Bhdr *q, *t;
+	int alloc, ldr, ns, frag;
+	int osize, size;
+
+	if(asize >= 1024*1024*1024)	/* for sanity and to avoid overflow */
+		return nil;
+	size = asize;
+	osize = size;
+	size = (size + BHDRSIZE + p->quanta) & ~(p->quanta);
+
+	lock(&p->l);
+	p->nalloc++;
+
+	t = p->root;
+	q = nil;
+	while(t) {
+		if(t->size == size) {
+			t = t->fwd;
+			pooldel(p, t);
+			t->magic = MAGIC_A;
+			p->cursize += t->size;
+			if(p->cursize > p->hw)
+				p->hw = p->cursize;
+			unlock(&p->l);
+			if(p->monitor)
+				MM(p->pnum, pc, (ulong)B2D(t), size);
+			return B2D(t);
+		}
+		if(size < t->size) {
+			q = t;
+			t = t->left;
+		}
+		else
+			t = t->right;
+	}
+	if(q != nil) {
+		pooldel(p, q);
+		q->magic = MAGIC_A;
+		frag = q->size - size;
+		if(frag < (size>>2) && frag < 0x8000) {
+			p->cursize += q->size;
+			if(p->cursize > p->hw)
+				p->hw = p->cursize;
+			unlock(&p->l);
+			if(p->monitor)
+				MM(p->pnum, pc, (ulong)B2D(q), size);
+			return B2D(q);
+		}
+		/* Split */
+		ns = q->size - size;
+		q->size = size;
+		B2T(q)->hdr = q;
+		t = B2NB(q);
+		t->size = ns;
+		B2T(t)->hdr = t;
+		pooladd(p, t);
+		p->cursize += q->size;
+		if(p->cursize > p->hw)
+			p->hw = p->cursize;
+		unlock(&p->l);
+		if(p->monitor)
+			MM(p->pnum, pc, (ulong)B2D(q), size);
+		return B2D(q);
+	}
+
+	ns = p->chunk;
+	if(size > ns)
+		ns = size;
+	ldr = p->quanta+1;
+
+	alloc = ns+ldr+ldr;
+	p->arenasize += alloc;
+	if(p->arenasize > p->maxsize) {
+		p->arenasize -= alloc;
+		ns = p->maxsize-p->arenasize-ldr-ldr;
+		ns &= ~p->quanta;
+		if (ns < size) {
+			if(poolcompact(p)) {
+				unlock(&p->l);
+				return poolalloc(p, osize);
+			}
+
+			unlock(&p->l);
+			print("arena %s too large: size %d cursize %lud arenasize %lud maxsize %lud\n",
+			 p->name, size, p->cursize, p->arenasize, p->maxsize);
+			return nil;
+		}
+		alloc = ns+ldr+ldr;
+		p->arenasize += alloc;
+	}
+
+	p->nbrk++;
+	t = (Bhdr *)sbrk(alloc);
+	if(t == (void*)-1) {
+		p->nbrk--;
+		unlock(&p->l);
+		return nil;
+	}
+	/* Double alignment */
+	t = (Bhdr *)(((ulong)t + 7) & ~7);
+
+	if(p->chain != nil && (char*)t-(char*)B2LIMIT(p->chain)-ldr == 0){
+		/* can merge chains */
+		if(0)print("merging chains %p and %p in %s\n", p->chain, t, p->name);
+		q = B2LIMIT(p->chain);
+		q->magic = MAGIC_A;
+		q->size = alloc;
+		B2T(q)->hdr = q;
+		t = B2NB(q);
+		t->magic = MAGIC_E;
+		p->chain->csize += alloc;
+		p->cursize += alloc;
+		unlock(&p->l);
+		poolfree(p, B2D(q));		/* for backward merge */
+		return poolalloc(p, osize);
+	}
+	
+	t->magic = MAGIC_E;		/* Make a leader */
+	t->size = ldr;
+	t->csize = ns+ldr;
+	t->clink = p->chain;
+	p->chain = t;
+	B2T(t)->hdr = t;
+	t = B2NB(t);
+
+	t->magic = MAGIC_A;		/* Make the block we are going to return */
+	t->size = size;
+	B2T(t)->hdr = t;
+	q = t;
+
+	ns -= size;			/* Free the rest */
+	if(ns > 0) {
+		q = B2NB(t);
+		q->size = ns;
+		B2T(q)->hdr = q;
+		pooladd(p, q);
+	}
+	B2NB(q)->magic = MAGIC_E;	/* Mark the end of the chunk */
+
+	p->cursize += t->size;
+	if(p->cursize > p->hw)
+		p->hw = p->cursize;
+	unlock(&p->l);
+	if(p->monitor)
+		MM(p->pnum, pc, (ulong)B2D(t), size);
+	return B2D(t);
+}
+
+void *
+poolalloc(Pool *p, ulong asize)
+{
+	Prog *prog;
+
+	if(p->cursize > p->ressize && (prog = currun()) != nil && prog->flags&Prestricted)
+		return nil;
+	return dopoolalloc(p, asize, getcallerpc(&p));
+}
+
+void
+poolfree(Pool *p, void *v)
+{
+	Bhdr *b, *c;
+	extern Bhdr *ptr;
+
+	D2B(b, v);
+	if(p->monitor)
+		MM(p->pnum|(1<<8), getcallerpc(&p), (ulong)v, b->size);
+
+	lock(&p->l);
+	p->nfree++;
+	p->cursize -= b->size;
+	c = B2NB(b);
+	if(c->magic == MAGIC_F) {	/* Join forward */
+		if(c == ptr)
+			ptr = b;
+		pooldel(p, c);
+		c->magic = 0;
+		b->size += c->size;
+		B2T(b)->hdr = b;
+	}
+
+	c = B2PT(b)->hdr;
+	if(c->magic == MAGIC_F) {	/* Join backward */
+		if(b == ptr)
+			ptr = c;
+		pooldel(p, c);
+		b->magic = 0;
+		c->size += b->size;
+		b = c;
+		B2T(b)->hdr = b;
+	}
+	pooladd(p, b);
+	unlock(&p->l);
+}
+
+void *
+poolrealloc(Pool *p, void *v, ulong size)
+{
+	Bhdr *b;
+	void *nv;
+	int osize;
+
+	if(size >= 1024*1024*1024)	/* for sanity and to avoid overflow */
+		return nil;
+	if(size == 0){
+		poolfree(p, v);
+		return nil;
+	}
+	SET(osize);
+	if(v != nil){
+		lock(&p->l);
+		D2B(b, v);
+		osize = b->size - BHDRSIZE;
+		unlock(&p->l);
+		if(osize >= size)
+			return v;
+	}
+	nv = poolalloc(p, size);
+	if(nv != nil && v != nil){
+		memmove(nv, v, osize);
+		poolfree(p, v);
+	}
+	return nv;
+}
+
+ulong
+poolmsize(Pool *p, void *v)
+{
+	Bhdr *b;
+	ulong size;
+
+	if(v == nil)
+		return 0;
+	lock(&p->l);
+	D2B(b, v);
+	size = b->size - BHDRSIZE;
+	unlock(&p->l);
+	return size;
+}
+
+static ulong
+poolmax(Pool *p)
+{
+	Bhdr *t;
+	ulong size;
+
+	lock(&p->l);
+	size = p->maxsize - p->cursize;
+	t = p->root;
+	if(t != nil) {
+		while(t->right != nil)
+			t = t->right;
+		if(size < t->size)
+			size = t->size;
+	}
+	if(size >= BHDRSIZE)
+		size -= BHDRSIZE;
+	unlock(&p->l);
+	return size;
+}
+
+ulong
+poolmaxsize(void)
+{
+	int i;
+	ulong total;
+
+	total = 0;
+	for(i = 0; i < nelem(table.pool); i++)
+		total += table.pool[i].maxsize;
+	return total;
+}
+
+int
+poolread(char *va, int count, ulong offset)
+{
+	Pool *p;
+	int n, i, signed_off;
+
+	n = 0;
+	signed_off = offset;
+	for(i = 0; i < table.n; i++) {
+		p = &table.pool[i];
+		n += snprint(va+n, count-n, "%11lud %11lud %11lud %11lud %11lud %11d %11lud %s\n",
+			p->cursize,
+			p->maxsize,
+			p->hw,
+			p->nalloc,
+			p->nfree,
+			p->nbrk,
+			poolmax(p),
+			p->name);
+
+		if(signed_off > 0) {
+			signed_off -= n;
+			if(signed_off < 0) {
+				memmove(va, va+n+signed_off, -signed_off);
+				n = -signed_off;
+			}
+			else
+				n = 0;
+		}
+
+	}
+	return n;
+}
+
+void*
+smalloc(size_t size)
+{
+	void *v;
+
+	for(;;){
+		v = malloc(size);
+		if(v != nil)
+			break;
+		if(0)
+			print("smalloc waiting from %lux\n", getcallerpc(&size));
+		osenter();
+		osmillisleep(100);
+		osleave();
+	}
+	setmalloctag(v, getcallerpc(&size));
+	setrealloctag(v, 0);
+	return v;
+}
+
+void*
+kmalloc(size_t size)
+{
+	void *v;
+
+	v = dopoolalloc(mainmem, size+Npadlong*sizeof(ulong), getcallerpc(&size));
+	if(v != nil){
+		ML(v, size, getcallerpc(&size));
+		if(Npadlong){
+			v = (ulong*)v+Npadlong;
+			setmalloctag(v, getcallerpc(&size));
+			setrealloctag(v, 0);
+		}
+		memset(v, 0, size);
+		MM(0, getcallerpc(&size), (ulong)v, size);
+	}
+	return v;
+}
+
+
+
+void*
+malloc(size_t size)
+{
+	void *v;
+
+	v = poolalloc(mainmem, size+Npadlong*sizeof(ulong));
+	if(v != nil){
+		ML(v, size, getcallerpc(&size));
+		if(Npadlong){
+			v = (ulong*)v+Npadlong;
+			setmalloctag(v, getcallerpc(&size));
+			setrealloctag(v, 0);
+		}
+		memset(v, 0, size);
+		MM(0, getcallerpc(&size), (ulong)v, size);
+	} else 
+		print("malloc failed from %lux\n", getcallerpc(&size));
+	return v;
+}
+
+void*
+mallocz(ulong size, int clr)
+{
+	void *v;
+
+	v = poolalloc(mainmem, size+Npadlong*sizeof(ulong));
+	if(v != nil){
+		ML(v, size, getcallerpc(&size));
+		if(Npadlong){
+			v = (ulong*)v+Npadlong;
+			setmalloctag(v, getcallerpc(&size));
+			setrealloctag(v, 0);
+		}
+		if(clr)
+			memset(v, 0, size);
+		MM(0, getcallerpc(&size), (ulong)v, size);
+	} else 
+		print("mallocz failed from %lux\n", getcallerpc(&size));
+	return v;
+}
+
+void
+free(void *v)
+{
+	Bhdr *b;
+
+	if(v != nil) {
+		if(Npadlong)
+			v = (ulong*)v-Npadlong;
+		D2B(b, v);
+		ML(v, 0, 0);
+		MM(1<<8|0, getcallerpc(&v), (ulong)((ulong*)v+Npadlong), b->size);
+		poolfree(mainmem, v);
+	}
+}
+
+void*
+realloc(void *v, size_t size)
+{
+	void *nv;
+
+	if(size == 0)
+		return malloc(size);	/* temporary change until realloc calls can be checked */
+	if(v != nil)
+		v = (ulong*)v-Npadlong;
+	if(Npadlong!=0 && size!=0)
+		size += Npadlong*sizeof(ulong);
+	nv = poolrealloc(mainmem, v, size);
+	ML(v, 0, 0);
+	ML(nv, size, getcallerpc(&v));
+	if(nv != nil) {
+		nv = (ulong*)nv+Npadlong;
+		setrealloctag(nv, getcallerpc(&v));
+		if(v == nil)
+			setmalloctag(v, getcallerpc(&v));
+	} else 
+		print("realloc failed from %lux\n", getcallerpc(&v));
+	return nv;
+}
+
+void
+setmalloctag(void *v, ulong pc)
+{
+	ulong *u;
+
+	USED(v);
+	USED(pc);
+	if(Npadlong <= MallocOffset || v == nil)
+		return;
+	u = v;
+	u[-Npadlong+MallocOffset] = pc;
+}
+
+ulong
+getmalloctag(void *v)
+{
+	USED(v);
+	if(Npadlong <= MallocOffset)
+		return ~0;
+	return ((ulong*)v)[-Npadlong+MallocOffset];
+}
+
+void
+setrealloctag(void *v, ulong pc)
+{
+	ulong *u;
+
+	USED(v);
+	USED(pc);
+	if(Npadlong <= ReallocOffset || v == nil)
+		return;
+	u = v;
+	u[-Npadlong+ReallocOffset] = pc;
+}
+
+ulong
+getrealloctag(void *v)
+{
+	USED(v);
+	if(Npadlong <= ReallocOffset)
+		return ((ulong*)v)[-Npadlong+ReallocOffset];
+	return ~0;
+}
+
+ulong
+msize(void *v)
+{
+	if(v == nil)
+		return 0;
+	return poolmsize(mainmem, (ulong*)v-Npadlong)-Npadlong*sizeof(ulong);
+}
+
+void*
+calloc(size_t n, size_t szelem)
+{
+	return malloc(n*szelem);
+}
+
+/*
+void
+pooldump(Pool *p)
+{
+	Bhdr *b, *base, *limit, *ptr;
+
+	b = p->chain;
+	if(b == nil)
+		return;
+	base = b;
+	ptr = b;
+	limit = B2LIMIT(b);
+
+	while(base != nil) {
+		print("\tbase #%.8lux ptr #%.8lux", base, ptr);
+		if(ptr->magic == MAGIC_A || ptr->magic == MAGIC_I)
+			print("\tA%.5d\n", ptr->size);
+		else if(ptr->magic == MAGIC_E)
+			print("\tE\tL#%.8lux\tS#%.8lux\n", ptr->clink, ptr->csize);
+		else
+			print("\tF%.5d\tL#%.8lux\tR#%.8lux\tF#%.8lux\tP#%.8lux\tT#%.8lux\n",
+				ptr->size, ptr->left, ptr->right, ptr->fwd, ptr->prev, ptr->parent);
+		ptr = B2NB(ptr);
+		if(ptr >= limit) {
+			print("link to #%.8lux\n", base->clink);
+			base = base->clink;
+			if(base == nil)
+				break;
+			ptr = base;
+			limit = B2LIMIT(base);
+		}
+	}
+}
+*/
+
+void
+poolsetcompact(Pool *p, void (*move)(void*, void*))
+{
+	p->move = move;
+}
+
+int
+poolcompact(Pool *pool)
+{
+	Bhdr *base, *limit, *ptr, *end, *next;
+	int compacted, nb;
+
+	if(pool->move == nil || pool->lastfree == pool->nfree)
+		return 0;
+
+	pool->lastfree = pool->nfree;
+
+	base = pool->chain;
+	ptr = B2NB(base);	/* First Block in arena has clink */
+	limit = B2LIMIT(base);
+	compacted = 0;
+
+	pool->root = nil;
+	end = ptr;
+	while(base != nil) {
+		next = B2NB(ptr);
+		if(ptr->magic == MAGIC_A || ptr->magic == MAGIC_I) {
+			if(ptr != end) {
+				memmove(end, ptr, ptr->size);
+				pool->move(B2D(ptr), B2D(end));
+				compacted = 1;
+			}
+			end = B2NB(end);
+		}
+		if(next >= limit) {
+			nb = (uchar*)limit - (uchar*)end;
+			if(nb > 0){
+				if(nb < pool->quanta+1){
+					print("poolcompact: leftover too small\n");
+					abort();
+				}
+				end->size = nb;
+				B2T(end)->hdr = end;
+				pooladd(pool, end);
+			}
+			base = base->clink;
+			if(base == nil)
+				break;
+			ptr = B2NB(base);
+			end = ptr;	/* could do better by copying between chains */
+			limit = B2LIMIT(base);
+		} else
+			ptr = next;
+	}
+
+	return compacted;
+}
+
+static void
+_poolfault(void *v, char *msg, ulong c)
+{
+	auditmemloc(msg, v);
+	panic("%s %lux (from %lux/%lux)", msg, v, getcallerpc(&v), c);
+}
+
+static void
+dumpvl(char *msg, ulong *v, int n)
+{
+	int i, l;
+
+	l = print("%s at %p: ", msg, v);
+	for (i = 0; i < n; i++) {
+		if (l >= 60) {
+			print("\n");
+			l = print("    %p: ", v);
+		}
+		l += print(" %lux", *v++);
+	}
+	print("\n");
+}
+
+static void
+corrupted(char *str, char *msg, Pool *p, Bhdr *b, void *v)
+{
+	print("%s(%p): pool %s CORRUPT: %s at %p'%lud(magic=%lux)\n",
+		str, v, p->name, msg, b, b->size, b->magic);
+	dumpvl("bad Bhdr", (ulong *)((ulong)b & ~3)-4, 10);
+}
+
+static void
+_auditmemloc(char *str, void *v)
+{
+	Pool *p;
+	Bhdr *bc, *ec, *b, *nb, *fb = nil;
+	char *fmsg, *msg;
+	ulong fsz;
+
+	SET(fsz);
+	SET(fmsg);
+	for (p = &table.pool[0]; p < &table.pool[nelem(table.pool)]; p++) {
+		lock(&p->l);
+		for (bc = p->chain; bc != nil; bc = bc->clink) {
+			if (bc->magic != MAGIC_E) {
+				unlock(&p->l);
+				corrupted(str, "chain hdr!=MAGIC_E", p, bc, v);
+				goto nextpool;
+			}
+			ec = B2LIMIT(bc);
+			if (((Bhdr*)v >= bc) && ((Bhdr*)v < ec))
+				goto found;
+		}
+		unlock(&p->l);
+nextpool:	;
+	}
+	print("%s: %p not in pools\n", str, v);
+	return;
+
+found:
+	for (b = bc; b < ec; b = nb) {
+		switch(b->magic) {
+		case MAGIC_F:
+			msg = "free blk";
+			break;
+		case MAGIC_I:
+			msg = "immutable block";
+			break;
+		case MAGIC_A:
+			msg = "block";
+			break;
+		default:
+			if (b == bc && b->magic == MAGIC_E) {
+				msg = "pool hdr";
+				break;
+			}
+			unlock(&p->l);
+			corrupted(str, "bad magic", p, b, v);
+			goto badchunk;
+		}
+		if (b->size <= 0 || (b->size & p->quanta)) {
+			unlock(&p->l);
+			corrupted(str, "bad size", p, b, v);
+			goto badchunk;
+		}
+		if (fb != nil)
+			break;
+		nb = B2NB(b);
+		if ((Bhdr*)v < nb) {
+			fb = b;
+			fsz = b->size;
+			fmsg = msg;
+		}
+	}
+	unlock(&p->l);
+	if (b >= ec) {
+		if (b > ec)
+			corrupted(str, "chain size mismatch", p, b, v);
+		else if (b->magic != MAGIC_E)
+			corrupted(str, "chain end!=MAGIC_E", p, b, v);
+	}
+badchunk:
+	if (fb != nil) {
+		print("%s: %p in %s:", str, v, p->name);
+		if (fb == v)
+			print(" is %s '%lux\n", fmsg, fsz);
+		else
+			print(" in %s at %p'%lux\n", fmsg, fb, fsz);
+		dumpvl("area", (ulong *)((ulong)v & ~3)-4, 20);
+	}
+}
+
+char *
+poolaudit(char*(*audit)(int, Bhdr *))
+{
+	Pool *p;
+	Bhdr *bc, *ec, *b;
+	char *r = nil;
+
+	for (p = &table.pool[0]; p < &table.pool[nelem(table.pool)]; p++) {
+		lock(&p->l);
+		for (bc = p->chain; bc != nil; bc = bc->clink) {
+			if (bc->magic != MAGIC_E) {
+				unlock(&p->l);
+				return "bad chain hdr";
+			}
+			ec = B2LIMIT(bc);
+			for (b = bc; b < ec; b = B2NB(b)) {
+				if (b->size <= 0 || (b->size & p->quanta))
+					r = "bad size in bhdr";
+				else
+					switch(b->magic) {
+					case MAGIC_E:
+						if (b != bc) {
+							r = "unexpected MAGIC_E";
+							break;
+						}
+					case MAGIC_F:
+					case MAGIC_A:
+					case MAGIC_I:
+						r = audit(p->pnum, b);
+						break;
+					default:
+						r = "bad magic";
+					}
+				if (r != nil) {
+					unlock(&p->l);
+					return r;
+				}
+			}
+			if (b != ec || b->magic != MAGIC_E) {
+				unlock(&p->l);
+				return "bad chain ending";
+			}
+		}
+		unlock(&p->l);
+	}
+	return r;
+}
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/port/audio-tbls.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,53 @@
+svp_t audio_bits_tbl[] = {
+	{ "8", 8 } ,	 /* 8 bits per sample */
+	{ "16", 16 },  /* 16 bits per sample */
+	{nil},
+};
+
+svp_t audio_chan_tbl[] = {
+	{ "1", 1 },		/* 1 channel */
+	{ "2", 2 },	/* 2 channels */
+	{nil},
+};
+
+svp_t audio_indev_tbl[] = {
+	{ "mic", Audio_Mic_Val }, 		/* input microphone */
+	{ "line", Audio_Linein_Val }, 	/* line in */
+	{nil},
+};
+
+svp_t audio_outdev_tbl[] = {
+	{ "spkr", Audio_Speaker_Val },	/* output speaker */
+	{ "hdph", Audio_Headphone_Val },/* head phones */
+	{ "line", Audio_Lineout_Val },	/* line out */
+	{nil},
+};
+
+svp_t audio_enc_tbl[] = {
+	{ "ulaw", Audio_Ulaw_Val },	/* u-law encoding */
+	{ "alaw", Audio_Alaw_Val },	/* A-law encoding */
+	{ "pcm", Audio_Pcm_Val },	/* Pulse Code Modulation */
+	{nil},
+};
+
+svp_t audio_rate_tbl[] = {
+	{ "8000", 8000 },	/* 8000 samples per second */
+	{ "11025", 11025 },	/* 11025 samples per second */
+	{ "22050", 22050 },	/* 22050 samples per second */
+	{ "44100", 44100 },	/* 44100 samples per second */
+	{nil},
+};
+
+Audio_d Default_Audio_Format =  {
+	0,
+	16,				/* bits per sample */
+	Audio_Max_Val,			/* buffer size (as percentage) */
+	2,				/* number of channels */
+	-1,				/* device */
+	Audio_Pcm_Val,			/* encoding format */
+	8000,				/* samples per second */
+	Audio_Max_Val,			/* left channel gain */
+	Audio_Max_Val,			/* right channel gain */
+};
+int Default_Audio_Input = Audio_Mic_Val;
+int Default_Audio_Output = Audio_Speaker_Val;
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/port/audio.h	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,81 @@
+#define	AUDIO_BITS_FLAG		0x00000001
+#define	AUDIO_BUF_FLAG		0x00000002
+#define	AUDIO_CHAN_FLAG		0x00000004
+#define	AUDIO_COUNT_FLAG	0x00000008
+#define	AUDIO_DEV_FLAG		0x00000010
+#define	AUDIO_ENC_FLAG		0x00000020
+#define	AUDIO_RATE_FLAG		0x00000040
+#define	AUDIO_VOL_FLAG		0x00000080
+#define	AUDIO_LEFT_FLAG		0x00000100
+#define	AUDIO_RIGHT_FLAG	0x00000200
+#define	AUDIO_IN_FLAG		0x00000400
+#define	AUDIO_OUT_FLAG		0x00000800
+#define	AUDIO_MOD_FLAG		0x10000000
+
+#define	Audio_Min_Val		0
+#define	Audio_Max_Val		100
+
+#define 	Audio_No_Val		0
+#define 	Audio_In_Val		1
+#define 	Audio_Out_Val		2
+
+#define	Audio_Max_Buf		32768
+#define	Bits_Per_Byte		8
+
+typedef struct Audio_d Audio_d;
+struct Audio_d {
+	ulong	flags;		/* bit flag for fields */
+	ulong	bits;		/* bits per sample */
+	ulong	buf;		/* buffer size */
+	ulong	chan;		/* number of channels */
+	ulong	dev;		/* device */
+	ulong	enc;		/* encoding format */
+	ulong	rate;		/* samples per second */
+	ulong	left;		/* left channel gain */
+	ulong	right;		/* right channel gain */
+};
+
+typedef struct Audio_t Audio_t;
+struct Audio_t {
+	Audio_d	in;		/* input device */
+	Audio_d	out;		/* output device */
+};
+
+#define AUDIO_CMD_MAXNUM 32
+
+void audio_info_init(Audio_t*);
+int audioparse(char*, int n, Audio_t*);
+
+enum
+{
+	Qdir = 0,		/* must start at 0 representing a directory */
+	Qaudio,
+	Qaudioctl
+};
+
+/* required external platform specific functions */
+void	audio_file_init(void);
+void	audio_file_open(Chan*, int);
+long	audio_file_read(Chan*, void*, long, vlong);
+long	audio_file_write(Chan*, void*, long, vlong);
+long	audio_ctl_write(Chan*, void*, long, vlong);
+void	audio_file_close(Chan*);
+
+typedef struct _svp_t {
+	char*		s;	/* string */
+	unsigned long	v;	/* value */
+} svp_t;
+
+/* string value pairs for default audio values */
+extern svp_t audio_chan_tbl[];
+extern svp_t audio_indev_tbl[];
+extern svp_t audio_outdev_tbl[];
+extern svp_t audio_enc_tbl[];
+extern svp_t audio_rate_tbl[];
+extern svp_t audio_val_tbl[];
+extern svp_t audio_bits_tbl[];
+
+extern Audio_d Default_Audio_Format;
+extern int Default_Audio_Input, Default_Audio_Output;
+
+extern Audio_t* getaudiodev(void);
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/port/cache.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,45 @@
+#include "dat.h"
+#include "fns.h"
+
+/*
+ * no cache in hosted mode
+ */
+void
+cinit(void)
+{
+}
+
+void
+copen(Chan *c)
+{
+	c->flag &= ~CCACHE;
+}
+
+int
+cread(Chan *c, uchar *b, int n, vlong off)
+{
+	USED(c);
+	USED(b);
+	USED(n);
+	USED(off);
+	return 0;
+}
+
+void
+cwrite(Chan *c, uchar *buf, int n, vlong off)
+{
+	USED(c);
+	USED(buf);
+	USED(n);
+	USED(off);
+}
+
+void
+cupdate(Chan *c, uchar *buf,  int n, vlong off)
+{
+	USED(c);
+	USED(buf);
+	USED(n);
+	USED(off);
+}
+
--- /dev/null	Sun Oct 31 09:33:26 2021
+++ b/emu/port/chan.c	Sun Nov  4 12:33:14 2018
@@ -0,0 +1,1403 @@
+#include	"dat.h"
+#include	"fns.h"
+#include	"error.h"
+
+char*
+c2name(Chan *c)		/* DEBUGGING */
+{
+	if(c == nil)
+		return "<nil chan>";
+	if(c->name == nil)
+		return "<nil name>";
+	if(c->name->s == nil)
+		return "<nil name.s>";
+	return c->name->s;
+}
+
+enum
+{
+	CNAMESLOP	= 20
+};
+
+struct
+{
+	Lock	l;
+	int	fid;
+	Chan	*free;
+	Chan	*list;
+}chanalloc;
+
+typedef struct Elemlist Elemlist;
+
+struct Elemlist
+{
+	char	*name;	/* copy of name, so '/' can be overwritten */
+	int	nelems;
+	char	**elems;
+	int	*off;
+	int	mustbedir;
+};
+
+#define SEP(c) ((c) == 0 || (c) == '/')
+void cleancname(Cname*);
+
+int
+isdotdot(char *p)
+{
+	return p[0]=='.' && p[1]=='.' && p[2]=='\0';
+}
+
+int
+incref(Ref *r)
+{
+	int x;
+
+	lock(&r->lk);
+	x = ++r->ref;
+	unlock(&r->lk);
+	return x;
+}
+
+int
+decref(Ref *r)
+{
+	int x;
+
+	lock(&r->lk);
+	x = --r->ref;
+	unlock(&r->lk);
+	if(x < 0) 
+		panic("decref, pc=0x%lux", getcallerpc(&r));
+
+	return x;
+}
+
+/*
+ * Rather than strncpy, which zeros the rest of the buffer, kstrcpy
+ * truncates if necessary, always zero terminates, does not zero fill,
+ * and puts ... at the end of the string if it's too long.  Usually used to
+ * save a string in up->genbuf;
+ */
+void
+kstrcpy(char *s, char *t, int ns)
+{
+	int nt;
+
+	nt = strlen(t);
+	if(nt+1 <= ns){
+		memmove(s, t, nt+1);
+		return;
+	}
+	/* too long */
+	if(ns < 4){
+		/* but very short! */
+		strncpy(s, t, ns);
+		return;
+	}
+	/* truncate with ... at character boundary (very rare case) */
+	memmove(s, t, ns-4);
+	ns -= 4;
+	s[ns] = '\0';
+	/* look for first byte of UTF-8 sequence by skipping continuation bytes */
+	while(ns>0 && (s[--ns]&0xC0)==0x80)
+		;
+	strcpy(s+ns, "...");
+}
+
+int
+emptystr(char *s)
+{
+	return s == nil || s[0] == '\0';
+}
+
+/*
+ * Atomically replace *p with copy of s
+ */
+void
+kstrdup(char **p, char *s)
+{
+	int n;
+	char *t, *prev;
+
+	n = strlen(s)+1;
+	t = kmalloc(n);
+	if(t == nil)
+		panic("kstrdup: no memory");
+	memmove(t, s, n);
+	prev = *p;
+	*p = t;
+	free(prev);
+}
+
+static char isfrog[256]=
+{
+	/*NUL*/	1, 1, 1, 1, 1, 1, 1, 1,
+	/*BKS*/	1, 1, 1, 1, 1, 1, 1, 1,
+	/*DLE*/	1, 1, 1, 1, 1, 1, 1, 1,
+	/*CAN*/	1, 1, 1, 1, 1, 1, 1, 1
+};
+
+void
+chandevinit(void)
+{
+	int i;
+
+
+/*	isfrog[' '] = 1; */	/* let's see what happens */
+	isfrog['/'] = 1;
+	isfrog[0x7f] = 1;
+
+	for(i=0; devtab[i] != nil; i++)
+		devtab[i]->init();
+}
+
+Chan*
+newchan(void)
+{
+	Chan *c;
+
+	lock(&chanalloc.l);
+	c = chanalloc.free;
+	if(c != 0)
+		chanalloc.free = c->next;
+	unlock(&chanalloc.l);
+
+	if(c == nil) {
+		c = malloc(sizeof(Chan));
+		if(c == nil)
+			error(Enomem);
+		lock(&chanalloc.l);
+		c->fid = ++chanalloc.fid;
+		c->link = chanalloc.list;
+		chanalloc.list = c;
+		unlock(&chanalloc.l);
+	}
+
+	/* if you get an error before associating with a dev,
+	   close calls rootclose, a nop */
+	c->type = 0;
+	c->flag = 0;
+	c->r.ref = 1;
+	c->dev = 0;
+	c->offset = 0;
+	c->iounit = 0;
+	c->umh = 0;
+	c->uri = 0;
+	c->dri = 0;
+	c->aux = 0;
+	c->mchan = 0;
+	c->mcp = 0;
+	c->mux = 0;
+	c->mqid.path = 0;
+	c->mqid.vers = 0;
+	c->mqid.type = 0;
+	c->name = 0;
+	return c;
+}
+
+static Ref ncname;
+
+Cname*
+newcname(char *s)
+{
+	Cname *n;
+	int i;
+
+	n = smalloc(sizeof(Cname));
+	i = strlen(s);
+	n->len = i;
+	n->alen = i+CNAMESLOP;
+	n->s = smalloc(n->alen);
+	memmove(n->s, s, i+1);
+	n->r.ref = 1;
+	incref(&ncname);
+	return n;
+}
+
+void
+cnameclose(Cname *n)
+{
+	if(n == nil)
+		return;
+	if(decref(&n->r))
+		return;
+	decref(&ncname);
+	free(n->s);
+	free(n);
+}
+
+Cname*
+addelem(Cname *n, char *s)
+{
+	int i, a;
+	char *t;
+	Cname *new;
+
+	if(s[0]=='.' && s[1]=='\0')
+		return n;
+
+	if(n->r.ref > 1){
+		/* copy on write */
+		new = newcname(n->s);
+		cnameclose(n);
+		n = new;
+	}
+
+	i = strlen(s);
+	if(n->len+1+i+1 > n->alen){
+		a = n->len+1+i+1 + CNAMESLOP;
+		t = smalloc(a);
+		memmove(t, n->s, n->len+1);
+		free(n->s);
+		n->s = t;
+		n->alen = a;
+	}
+	if(n->len>0 && n->s[n->len-1]!='/' && s[0]!='/')	/* don't insert extra slash if one is present */
+		n->s[n->len++] = '/';
+	memmove(n->s+n->len, s, i+1);
+	n->len += i;
+	if(isdotdot(s))
+		cleancname(n);
+	return n;
+}
+
+void
+chanfree(Chan *c)
+{
+	c->flag = CFREE;
+
+	if(c->umh != nil){
+		putmhead(c->umh);
+		c->umh = nil;
+	}
+	if(c->umc != nil){
+		cclose(c->umc);
+		c->umc = nil;
+	}
+	if(c->mux != nil){
+		muxclose(c->mux);
+		c->mux = nil;
+	}
+	if(c->mchan != nil){
+		cclose(c->mchan);
+		c->mchan = nil;
+	}
+
+	cnameclose(c->name);
+
+	lock(&chanalloc.l);
+	c->next = chanalloc.free;
+	chanalloc.free = c;
+	unlock(&chanalloc.l);
+}
+
+void
+cclose(Chan *c)
+{
+	if(c == 0)
+		return;
+
+	if(c->flag&CFREE)
+		panic("cclose %lux", getcallerpc(&c));
+
+	if(decref(&c->r))
+		return;
+
+	if(!waserror()){
+		devtab[c->type]->close(c);
+		poperror();
+	}
+
+	chanfree(c);
+}
+
+/*
+ * Make sure we have the only copy of c.  (Copy on write.)
+ */
+Chan*
+cunique(Chan *c)
+{
+	Chan *nc;
+
+	if(c->r.ref != 1) {
+		nc = cclone(c);
+		cclose(c);
+		c = nc;
+	}
+
+	return c;
+}
+
+int
+eqqid(Qid a, Qid b)
+{
+	return a.path==b.path && a.vers==b.vers;
+}
+
+int
+eqchan(Chan *a, Chan *b, int pathonly)
+{
+	if(a->qid.path != b->qid.path)
+		return 0;
+	if(!pathonly && a->qid.vers!=b->qid.vers)
+		return 0;
+	if(a->type != b->type)
+		return 0;
+	if(a->dev != b->dev)
+		return 0;
+	return 1;
+}
+
+int
+eqchantdqid(Chan *a, int type, int dev, Qid qid, int pathonly)
+{
+	if(a->qid.path != qid.path)
+		return 0;
+	if(!pathonly && a->qid.vers!=qid.vers)
+		return 0;
+	if(a->type != type)
+		return 0;
+	if(a->dev != dev)
+		return 0;
+	return 1;
+}
+
+Mhead*
+newmhead(Chan *from)
+{
+	Mhead *mh;
+
+	mh = smalloc(sizeof(Mhead));
+	mh->r.ref = 1;
+	mh->from = from;
+	incref(&from->r);
+
+/*
+	n = from->name->len;
+	if(n >= sizeof(mh->fromname))
+		n = sizeof(mh->fromname)-1;
+	memmove(mh->fromname, from->name->s, n);
+	mh->fromname[n] = 0;
+*/
+	return mh;
+}
+
+int
+cmount(Chan *new, Chan *old, int flag, char *spec)
+{
+	Pgrp *pg;
+	int order, flg;
+	Mhead *m, **l, *mh;
+	Mount *nm, *f, *um, **h;
+
+	if(QTDIR & (old->qid.type^new->qid.type))
+		error(Emount);
+
+if(old->umh)
+	print("cmount old extra umh\n");
+
+	order = flag&MORDER;
+
+	if((old->qid.type&QTDIR)==0 && order != MREPL)
+		error(Emount);
+
+	mh = new->umh;
+
+	/*
+	 * Not allowed to bind when the old directory
+	 * is itself a union.  (Maybe it should be allowed, but I don't see
+	 * what the semantics would be.)
+	 *
+	 * We need to check mh->mount->next to tell unions apart from
+	 * simple mount points, so that things like
+	 *	mount -c fd /root
+	 *	bind -c /root /
+	 * work.  The check of mount->mflag catches things like
+	 *	mount fd /root
+	 *	bind -c /root /
+	 * 
+	 * This is far more complicated than it should be, but I don't
+	 * see an easier way at the moment.		-rsc
+	 */
+	if((flag&MCREATE) && mh && mh->mount
+	&& (mh->mount->next || !(mh->mount->mflag&MCREATE)))
+		error(Emount);
+
+	pg = up->env->pgrp;
+	wlock(&pg->ns);
+
+	l = &MOUNTH(pg, old->qid);
+	for(m = *l; m; m = m->hash) {
+		if(eqchan(m->from, old, 1))
+			break;
+		l = &m->hash;
+	}