shithub: fork

Download patch

ref: 59fe51f5cbb81a135734c693ab51a5b243534c92
parent: b2d62e0881e44484015c726a220c7aaa61024aae
author: qwx <qwx@sciops.net>
date: Sat Aug 19 05:18:27 EDT 2023

kbdfs: emergency fshalt command

diff: cannot open b/sys/src/cmd/aux/kbdfs//null: file does not exist: 'b/sys/src/cmd/aux/kbdfs//null'
--- /dev/null
+++ b/sys/src/cmd/aux/kbdfs/kbdfs.c
@@ -1,0 +1,1706 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include <keyboard.h>
+#include <9p.h>
+
+enum {
+	Nscan=	128,
+
+	Qroot=	0,
+	Qkbd,
+	Qkbdin,
+	Qkbin,
+	Qkbmap,
+	Qcons,
+	Qconsctl,
+	Nqid,
+
+	Lnone = 0,
+	Lshift,
+	Lesc1,
+	Laltgr,
+	Lctl,
+	Lctrlesc1,
+	Lshiftesc1,
+	Lshiftaltgr,
+	Lmod4,
+	Laltgrmod4,
+	Nlayers,
+
+	Rawon=	0,
+	Rawoff,
+
+	STACK = 8*1024,
+};
+
+typedef struct Key Key;
+typedef struct Scan Scan;
+
+struct Key {
+	int	down;
+	Rune	b;	/* button, unshifted key */
+	Rune	r;	/* rune, shifted key */
+};
+
+struct Scan {
+	int	esc1;
+	int	esc2;
+	int	caps;
+	int	num;
+	int	shift;
+	int	ctl;
+	int	alt;
+	int	altgr;
+	int	mod4;
+	int	leds;
+};
+
+struct Qtab {
+	char *name;
+	int mode;
+	int type;
+} qtab[Nqid] = {
+	"/",
+		DMDIR|0500,
+		QTDIR,
+
+	"kbd",
+		0600,
+		0,
+
+	"kbdin",
+		0200,
+		0,
+
+	"kbin",
+		0200,	
+		0,
+
+	"kbmap",
+		0600,	
+		0,
+
+	"cons",
+		0600,	
+		0,
+
+	"consctl",
+		0600,
+		0,
+};
+
+char Eshort[] = "read count too small";
+char Ebadarg[] = "invalid argument";
+char Eperm[] = "permission denied";
+char Einuse[] = "file in use";
+char Enonexist[] = "file does not exist";
+char Ebadspec[] = "bad attach specifier";
+char Ewalk[] = "walk in non directory";
+char Ephase[] = "the front fell off";
+char Eintr[] = "interrupted";
+
+int kbdifd = -1;
+int scanfd = -1;
+int ledsfd = -1;
+int consfd = -1;
+int mctlfd = -1;
+int msinfd = -1;
+int notefd = -1;
+int killfd = -1;
+
+int kbdopen;
+int consctlopen;
+int quiet = 0;
+char *sname = nil;
+char *mntpt = "/dev";
+char *user;
+
+int debug;
+
+Channel *keychan;	/* chan(Key) */
+Channel *mctlchan;	/* chan(Key) */
+
+Channel *kbdreqchan;	/* chan(Req*) */
+Channel *consreqchan;	/* chan(Req*) */
+
+Channel *ctlchan;	/* chan(int) */
+
+Channel *rawchan;	/* chan(Rune) */
+Channel *runechan;	/* chan(Rune) */
+
+Channel *conschan;	/* chan(char*) */
+Channel *kbdchan;	/* chan(char*) */
+Channel *intchan;	/* chan(int) */
+
+char *layertab[Nlayers] = {
+	[Lnone]		"none",
+	[Lshift]	"shift",
+	[Lesc1]		"esc",
+	[Laltgr]	"altgr",
+	[Lctl]		"ctl",
+	[Lctrlesc1]	"ctlesc",
+	[Lshiftesc1]	"shiftesc",
+	[Lshiftaltgr]	"shiftaltgr",
+	[Lmod4]		"mod4",
+	[Laltgrmod4]	"altgrmod4",
+};
+
+/* /sys/lib/kbmap/ascii defaults */
+Rune ascii[Nlayers][Nscan] = {
+
+	/*
+	 * The codes at 0x79 and 0x7b are for the (無)変換 "(Mu)henkan" keys
+	 * used by OADG 109(A) keyboards. The PFU Happy Hacking keyboard
+	 * has only one layout and will produce these for otherwise unmarked
+	 * keys. The default mappings for the HHKB on other systems map
+	 * these to Kdown and Kup. The jp kbmap will instead map these
+	 * (along with 0x70) to control characters that ktrans understands.
+	 */
+	[Lnone]
+	{
+	[0x00]	0,	Kesc,	'1',	'2',	'3',	'4',	'5',	'6',
+	[0x08]	'7',	'8',	'9',	'0',	'-',	'=',	'\b',	'\t',
+	[0x10]	'q',	'w',	'e',	'r',	't',	'y',	'u',	'i',
+	[0x18]	'o',	'p',	'[',	']',	'\n',	Kctl,	'a',	's',
+	[0x20]	'd',	'f',	'g',	'h',	'j',	'k',	'l',	';',
+	[0x28]	'\'',	'`',	Kshift,	'\\',	'z',	'x',	'c',	'v',
+	[0x30]	'b',	'n',	'm',	',',	'.',	'/',	Kshift,	'*',
+	[0x38]	Kalt,	' ',	Kctl,	KF|1,	KF|2,	KF|3,	KF|4,	KF|5,
+	[0x40]	KF|6,	KF|7,	KF|8,	KF|9,	KF|10,	Knum,	Kscroll,'7',
+	[0x48]	'8',	'9',	'-',	'4',	'5',	'6',	'+',	'1',
+	[0x50]	'2',	'3',	'0',	'.',	0,	0,	0,	KF|11,
+	[0x58]	KF|12,	0,	0,	0,	0,	0,	0,	0,
+	[0x60]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x68]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x70]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x78]	0,	Kdown,	0,	Kup,	0,	0,	0,	0,
+	},
+
+	[Lshift]
+	{
+	[0x00]	0,	Kesc,	'!',	'@',	'#',	'$',	'%',	'^',
+	[0x08]	'&',	'*',	'(',	')',	'_',	'+',	'\b',	'\t',
+	[0x10]	'Q',	'W',	'E',	'R',	'T',	'Y',	'U',	'I',
+	[0x18]	'O',	'P',	'{',	'}',	'\n',	Kctl,	'A',	'S',
+	[0x20]	'D',	'F',	'G',	'H',	'J',	'K',	'L',	':',
+	[0x28]	'"',	'~',	Kshift,	'|',	'Z',	'X',	'C',	'V',
+	[0x30]	'B',	'N',	'M',	'<',	'>',	'?',	Kshift,	'*',
+	[0x38]	Kalt,	' ',	Kctl,	KF|1,	KF|2,	KF|3,	KF|4,	KF|5,
+	[0x40]	KF|6,	KF|7,	KF|8,	KF|9,	KF|10,	Knum,	Kscroll,'7',
+	[0x48]	'8',	'9',	'-',	'4',	'5',	'6',	'+',	'1',
+	[0x50]	'2',	'3',	'0',	'.',	0,	0,	0,	KF|11,
+	[0x58]	KF|12,	0,	0,	0,	0,	0,	0,	0,
+	[0x60]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x68]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x70]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x78]	0,	Kdown,	0,	Kup,	0,	0,	0,	0,
+	},
+
+	[Lesc1]
+	{
+	[0x00]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x08]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x10]	Ksbwd,	Kbrtdn,	0,	0,	0,	0,	0,	0,
+	[0x18]	0,	Ksfwd,	Kbrtup,	0,	'\n',	Kctl,	0,	0,
+	[0x20]	Kmute,	0,	Kpause,	0,	0,	0,	0,	0,
+	[0x28]	0,	0,	0,	0,	0,	0,	Kvoldn,	0,
+	[0x30]	Kvolup,	0,	0,	0,	0,	'/',	0,	Kprint,
+	[0x38]	Kaltgr,	0,	0,	0,	0,	0,	0,	0,
+	[0x40]	0,	0,	0,	0,	0,	0,	Kbreak,	Khome,
+	[0x48]	Kup,	Kpgup,	0,	Kleft,	0,	Kright,	0,	Kend,
+	[0x50]	Kdown,	Kpgdown,Kins,	Kdel,	0,	0,	0,	0,
+	[0x58]	0,	0,	0,	Kmod4,	0,	0,	0,	0,
+	[0x60]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x68]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x70]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x78]	0,	Kup,	0,	0,	0,	0,	0,	0,
+	},
+
+	[Lshiftesc1]
+	{
+	[0x00]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x08]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x10]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x18]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x20]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x28]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x30]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x38]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x40]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x48]	Kup,	0,	0,	0,	0,	0,	0,	0,
+	[0x50]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x58]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x60]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x68]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x70]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x78]	0,	Kup,	0,	0,	0,	0,	0,	0,
+	},
+
+	[Lctrlesc1]
+	{
+	[0x00]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x08]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x10]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x18]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x20]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x28]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x30]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x38]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x40]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x48]	Kup,	0,	0,	0,	0,	0,	0,	0,
+	[0x50]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x58]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x60]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x68]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x70]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x78]	0,	Kup,	0,	0,	0,	0,	0,	0,
+	},
+
+	[Laltgr]
+	{
+	[0x00]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x08]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x10]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x18]	0,	0,	0,	0,	'\n',	Kctl,	0,	0,
+	[0x20]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x28]	0,	0,	Kshift,	0,	0,	0,	0,	0,
+	[0x30]	0,	0,	0,	0,	0,	'/',	0,	Kprint,
+	[0x38]	Kaltgr,	0,	0,	0,	0,	0,	0,	0,
+	[0x40]	0,	0,	0,	0,	0,	0,	Kbreak,	Khome,
+	[0x48]	Kup,	Kpgup,	0,	Kleft,	0,	Kright,	0,	Kend,
+	[0x50]	Kdown,	Kpgdown,Kins,	Kdel,	0,	0,	0,	0,
+	[0x58]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x60]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x68]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x70]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x78]	0,	Kup,	0,	0,	0,	0,	0,	0,
+	},
+
+	[Lctl]
+	{
+	[0x00]	0,	'', 	'', 	'', 	'', 	'', 	'', 	'', 
+	[0x08]	'', 	'', 	'', 	'', 	'
', 	'', 	'\b',	'\t',
+	[0x10]	'', 	'', 	'', 	'', 	'', 	'', 	'', 	'\t',
+	[0x18]	'', 	'', 	'', 	'', 	'\n',	Kctl,	'', 	'', 
+	[0x20]	'', 	'', 	'', 	'\b',	'\n',	'', 	'', 	'', 
+	[0x28]	'', 	0, 	Kshift,	'', 	'', 	'', 	'', 	'', 
+	[0x30]	'', 	'', 	'
', 	'', 	'', 	'', 	Kshift,	'\n',
+	[0x38]	Kalt,	0, 	Kctl,	'', 	'', 	'', 	'', 	'', 
+	[0x40]	'', 	'', 	'', 	'
', 	'', 	'', 	'', 	'', 
+	[0x48]	'', 	'', 	'
', 	'', 	'', 	'', 	'', 	'', 
+	[0x50]	'', 	'', 	'', 	'', 	0,	0,	0,	'', 
+	[0x58]	'', 	0,	0,	0,	0,	0,	0,	0,
+	[0x60]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x68]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x70]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x78]	0,	'', 	0,	'\b',	0,	0,	0,	0,
+	},
+
+	[Lshiftaltgr]
+	{
+	[0x00]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x08]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x10]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x18]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x20]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x28]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x30]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x38]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x40]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x48]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x50]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x58]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x60]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x68]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x70]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x78]	0,	0,	0,	0,	0,	0,	0,	0,
+	},
+
+	[Lmod4]
+	{
+	[0x00]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x08]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x10]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x18]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x20]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x28]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x30]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x38]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x40]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x48]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x50]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x58]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x60]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x68]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x70]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x78]	0,	0,	0,	0,	0,	0,	0,	0,
+	},
+
+	[Laltgrmod4]
+	{
+	[0x00]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x08]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x10]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x18]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x20]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x28]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x30]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x38]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x40]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x48]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x50]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x58]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x60]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x68]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x70]	0,	0,	0,	0,	0,	0,	0,	0,
+	[0x78]	0,	0,	0,	0,	0,	0,	0,	0,
+	},
+};
+
+Rune kbtabs[Nlayers][Nscan];
+
+char*
+dev(char *file)
+{
+	static char *buf = nil;
+	free(buf);
+	buf = smprint("%s/%s", mntpt, file);
+	return buf;
+}
+
+int
+eopen(char *name, int mode)
+{
+	int fd;
+
+	fd = open(name, mode);
+	if(fd < 0 && !quiet)
+		fprint(2, "%s: warning: can't open %s: %r\n", argv0, name);
+	return fd;
+}
+
+void
+reboot(void)
+{
+	int fd;
+
+	if(debug)
+		return;
+
+	if((fd = eopen(dev("reboot"), OWRITE)) < 0)
+		return;
+	fprint(fd, "reboot\n");
+	close(fd);
+}
+
+void
+emergencywarp(void)
+{
+	int fd;
+
+	if(debug)
+		return;
+
+	if(access("/srv/cwfs.cmd", AEXIST) == 0 && (fd = eopen("/srv/cwfs.cmd", OWRITE)) >= 0){
+		fprint(fd, "halt\n");
+		close(fd);
+	}
+	if(access("/srv/hjfs.cmd", AEXIST) == 0 && (fd = eopen("/srv/hjfs.cmd", OWRITE)) >= 0){
+		fprint(fd, "halt\n");
+		close(fd);
+	}
+	reboot();
+}
+
+void
+shutdown(void)
+{
+	if(notefd >= 0)
+		write(notefd, "hangup", 6);
+	if(killfd >= 0)
+		write(killfd, "hangup", 6);
+	threadexitsall(nil);
+}
+
+void
+shiftup(void)
+{
+	Key key = { .down = 0, .r = Kshift, .b = Kshift };
+	send(keychan, &key);
+}
+
+/*
+ * Scan code processing
+ */
+void
+kbdputsc(Scan *scan, int c)
+{
+	Key key;
+
+	/*
+	 *  e0's is the first of a 2 character sequence, e1 and e2 the first
+	 *  of a 3 character sequence (on the safari)
+	 */
+	if(scan->esc2){
+		scan->esc2--;
+		return;
+	} else if(c == 0xe1 || c == 0xe2){
+		scan->esc2 = 2;
+		return;
+	} else if(c == 0xe0){
+		scan->esc1 = 1;
+		return;
+	}
+
+	key.down = (c & 0x80) == 0;
+	c &= 0x7f;
+
+	if(c >= Nscan)
+		return;
+
+	/* qemu workarround: emulate e0 for numpad */
+	if(c != 0 && strchr("GHIKMOPQRS", c) != nil)
+		scan->esc1 |= !scan->num;
+
+	if(scan->esc1 && scan->ctl && kbtabs[Lctrlesc1][c] != 0)
+		key.r = kbtabs[Lctrlesc1][c];
+	else if(scan->esc1 && scan->shift && kbtabs[Lshiftesc1][c] != 0)
+		key.r = kbtabs[Lshiftesc1][c];
+	else if(scan->esc1)
+		key.r = kbtabs[Lesc1][c];
+	else if(scan->altgr && scan->mod4 && kbtabs[Laltgrmod4][c] != 0)
+		key.r = kbtabs[Laltgrmod4][c];
+	else if(scan->mod4 && kbtabs[Lmod4][c] != 0)
+		key.r = kbtabs[Lmod4][c];
+	else if(scan->shift && scan->altgr && kbtabs[Lshiftaltgr][c] != 0)
+		key.r = kbtabs[Lshiftaltgr][c];
+	else if(scan->shift)
+		key.r = kbtabs[Lshift][c];
+	else if(scan->altgr)
+		key.r = kbtabs[Laltgr][c];
+	else if(scan->ctl)
+		key.r = kbtabs[Lctl][c];
+	else
+		key.r = kbtabs[Lnone][c];
+
+	if(scan->esc1 || kbtabs[Lnone][c] == 0)
+		key.b = key.r;
+	else
+		key.b = kbtabs[Lnone][c];
+
+	if(scan->caps && key.r<='z' && key.r>='a')
+		key.r += 'A' - 'a';
+
+	if(scan->ctl && scan->altgr && key.r == Kdel)
+		emergencywarp();
+
+	if(scan->ctl && scan->alt && key.r == Kdel){
+		if(scan->shift)
+			shiftup();
+		else
+			reboot();
+	}
+
+	if(key.b)
+		send(keychan, &key);
+
+	switch(key.r){
+	case Kshift:
+		scan->shift = key.down;
+		break;
+	case Kctl:
+		scan->ctl = key.down;
+		break;
+	case Kaltgr:
+		scan->altgr = key.down;
+		break;
+	case Kmod4:
+		scan->mod4 = key.down;
+		break;
+	case Kalt:
+		scan->alt = key.down;
+		break;
+	case Knum:
+		scan->num ^= key.down;
+		break;
+	case Kcaps:
+		scan->caps ^= key.down;
+		break;
+	}
+	scan->esc1 = 0;
+}
+
+static void
+kbdin(Scan *a, char *p, int n)
+{
+	char *s;
+	Key k;
+	int i;
+
+	if(n > 0 && p[n-1] != 0){
+		/*
+		 * old format as used by bitsy keyboard:
+		 * just a string of characters, no keyup
+		 * information.
+		 */
+		s = emalloc9p(n+1);
+		memmove(s, p, n);
+		s[n] = 0;
+		p = s;
+		while(*p){
+			p += chartorune(&k.r, p);
+			if(k.r)
+				send(rawchan, &k.r);
+		}
+		free(s);
+		return;
+	}
+Nextmsg:
+	if(n < 2)
+		return;
+	switch(p[0]){
+	case 'R':
+	case 'r':
+		/* rune up/down */
+		chartorune(&k.r, p+1);
+		if(k.r == 0)
+			break;
+		k.b = 0;
+		k.down = (p[0] == 'r');
+		for(i=0; i<Nscan; i++){
+			if(kbtabs[Lnone][i] == k.r || kbtabs[Lshift][i] == k.r || (i >= 16 && kbtabs[Lctl][i] == k.r)){
+				/* assign button from kbtab */
+				k.b = kbtabs[Lnone][i];
+				/* handle ^X forms */
+				if(k.r == kbtabs[Lnone][i] && kbtabs[Lctl][i] && !a->shift && !a->altgr && a->ctl)
+					k.r = kbtabs[Lctl][i];
+				break;
+			}
+		}
+		/* button unknown to kbtab, use rune if no modifier keys are active */
+		if(k.b == 0 && !a->shift && !a->altgr && !a->ctl)
+			k.b = k.r;
+		if(k.r == Kshift)
+			a->shift = k.down;
+		else if(k.r == Kaltgr)
+			a->altgr = k.down;
+		else if(k.r == Kmod4)
+			a->mod4 = k.down;
+		else if(k.r == Kctl)
+			a->ctl = k.down;
+		send(keychan, &k);
+		break;
+
+	case 'c':
+		chartorune(&k.r, p+1);
+		nbsend(runechan, &k.r);
+		break;
+
+	default:
+		if(!kbdopen)
+			break;
+		i = strlen(p)+1;
+		s = emalloc9p(i);
+		memmove(s, p, i);
+		if(nbsendp(kbdchan, s) <= 0)
+			free(s);
+	}
+	i = strlen(p)+1;
+	n -= i, p += i;
+	goto Nextmsg;
+}
+
+void
+setleds(Scan *scan, int leds)
+{
+	char buf[8];
+
+	if(ledsfd < 0 || scan->leds == leds)
+		return;
+	leds &= 7;
+	snprint(buf, sizeof(buf), "%d", leds);
+	pwrite(ledsfd, buf, strlen(buf), 0);
+	scan->leds = leds;
+}
+
+/*
+ * Read scan codes from scanfd
+ */ 
+void
+scanproc(void *)
+{
+	uchar buf[64];
+	Scan scan;
+	int i, n;
+
+	threadsetname("scanproc");
+
+	memset(&scan, 0, sizeof scan);
+	while((n = read(scanfd, buf, sizeof buf)) > 0){
+		for(i=0; i<n; i++)
+			kbdputsc(&scan, buf[i]);
+		setleds(&scan, (scan.num<<1) | (scan.caps<<2));
+	}
+
+	shutdown();
+}
+
+void
+kbdiproc(void *)
+{
+	char buf[1024];
+	Scan a;
+	int n;
+
+	threadsetname("kbdiproc");
+
+	memset(&a, 0, sizeof(a));
+	while((n = read(kbdifd, buf, sizeof buf)) > 0)
+		kbdin(&a, buf, n);
+
+	shutdown();
+}
+
+char*
+utfconv(Rune *r, int n)
+{
+	char *s, *p;
+	int l;
+
+	l = runenlen(r, n) + 1;
+	s = emalloc9p(l);
+	for(p = s; n > 0; r++, n--)
+		p += runetochar(p, r);
+	*p = 0;
+	return s;
+}
+
+/*
+ * Read key events from keychan and produce characters to
+ * rawchan and keystate in kbdchan.
+ */
+void
+keyproc(void *)
+{
+	Rune rb[Nscan+1];
+	Key key;
+	int i, nb;
+	char *s;
+
+	threadsetname("keyproc");
+
+	nb = 0;
+	while(recv(keychan, &key) > 0){
+		if(key.r >= Kmouse+1 && key.r <= Kmouse+5){
+			if(msinfd >= 0)
+				send(mctlchan, &key);
+			continue;
+		}
+		rb[0] = 0;
+		if(key.b){
+			for(i=0; i<nb && rb[i+1] != key.b; i++)
+				;
+			if(!key.down){
+				while(i < nb && rb[i+1] == key.b){
+					memmove(rb+i+1, rb+i+2, (nb-i+1) * sizeof(rb[0]));
+					nb--;
+					rb[0] = 'K';
+				}
+			} else if(i == nb && nb < nelem(rb)-1 && key.b){
+				rb[++nb] = key.b;
+				rb[0] = 'k';
+			}
+		}
+		if(rb[0]){
+			if(kbdopen){
+				s = utfconv(rb, nb+1);
+				if(nbsendp(kbdchan, s) <= 0)
+					free(s);
+			}
+			if(mctlfd >= 0)
+				send(mctlchan, &key);
+		}
+		if(key.down && key.r)
+			send(rawchan, &key.r);
+	}
+}
+
+/*
+ * Read characters from consfd (serial console)
+ */ 
+void
+consproc(void *)
+{
+	char *p, *e, *x, buf[64];
+	int n, cr;
+	Rune r;
+
+	threadsetname("consproc");
+
+	cr = 0;
+	p = buf;
+	e = buf + sizeof(buf);
+	while((n = read(consfd, p, e - p)) > 0){
+		x = p + n;
+		p = buf;
+		while((n = x - p) > 0){
+			if(!fullrune(p, n)){
+				memmove(buf, p, n);
+				break;
+			}
+			p += chartorune(&r, p);
+			if(r == 021 || r == 023)	/* XON/XOFF */
+				continue;
+			if(r == 0 || r == Runeerror){
+				cr = 0;
+				continue;
+			}
+			if(r == '\n' && cr){
+				cr = 0;
+				continue;
+			}
+			if(cr = (r == '\r'))
+				r = '\n';
+			send(runechan, &r);
+		}
+		if(n < 0) n = 0;
+		p = buf + n;
+	}
+
+	shutdown();
+}
+
+static int
+nextrune(Channel *ch, Rune *r)
+{
+	while(recv(ch, r) > 0){
+		switch(*r){
+		case 0:
+		case Kcaps:
+		case Knum:
+		case Kshift:
+		case Kaltgr:
+		case Kmod4:
+			/* ignore modifiers */
+			continue;
+
+		case Kctl:
+		case Kalt:
+			/* composing escapes */
+			return 1;
+		}
+		return 0;
+	}
+	return -1;
+}
+
+/*
+ * Read runes from rawchan, possibly compose special characters
+ * and output the new runes to runechan
+ */
+void
+runeproc(void *)
+{
+	static struct {
+		char	*ld;	/* must be seen before using this conversion */
+		char	*si;	/* options for last input characters */
+		Rune	*so;	/* the corresponding Rune for each si entry */
+	} tab[] = {
+#include "latin1.h"
+	};
+	Rune r, rr;
+	int i, j;
+	int ctl;
+
+	threadsetname("runeproc");
+
+	ctl = 0;
+	while((i = nextrune(rawchan, &r)) >= 0){
+		if(i == 0){
+			ctl = 0;
+Forward:
+			send(runechan, &r);
+			continue;
+		}
+
+		if(r == Kctl){
+			ctl = 1;
+			continue;
+		}
+
+		/*
+		 * emulators like qemu and vmware use Ctrl+Alt to lock
+		 * keyboard input so dont confuse them for a compose
+		 * sequence.
+		 */
+		if(r != Kalt || ctl)
+			continue;
+
+		if(nextrune(rawchan, &r))
+			continue;
+
+		if(r == 'x' || r == 'X'){
+			i = (r == 'X') ? 4 : 6;
+			j = i;
+			r = 0;
+			do {
+				if(nextrune(rawchan, &rr))
+					break;
+				if(rr >= '0' && rr <= '9')
+					r = (r << 4) | (rr - '0');
+				else if(rr >= 'a' && rr <= 'f')
+					r = (r << 4) | (10 + (rr - 'a'));
+				else if(rr >= 'A' && rr <= 'F')
+					r = (r << 4) | (10 + (rr - 'A'));
+				else{
+					if(i == j && rr != ';'){
+						r = j == 6 ? 'x' : 'X';
+						goto Nothex;
+					}
+					break;
+				}
+			} while(--i > 0);
+			if((i == 0 || rr == ';') && r != 0 && r <= Runemax)
+				goto Forward;
+		} else {
+			if(nextrune(rawchan, &rr))
+				continue;
+Nothex:
+			for(i = 0; i<nelem(tab); i++){
+				if(tab[i].ld[0] != r)
+					continue;
+				if(tab[i].ld[1] == 0)
+					break;	
+				if(tab[i].ld[1] == rr){
+					nextrune(rawchan, &rr);
+					break;
+				}
+			}
+			if(i == nelem(tab) || rr == 0)
+				continue;
+			for(j = 0; tab[i].si[j]; j++){
+				if(tab[i].si[j] != rr)
+					continue;
+				r = tab[i].so[j];
+				goto Forward;
+			}
+		}
+	}
+}
+
+/*
+ * Need to do this in a separate proc because if process we're interrupting
+ * is dying and trying to print tombstone, kernel is blocked holding p->debug lock.
+ */
+void
+intrproc(void *)
+{
+	threadsetname("intrproc");
+
+	while(recv(intchan, nil) > 0)
+		write(notefd, "interrupt", 9);
+}
+
+/*
+ * Process Kmouse keys and mouse button swap on shift,
+ * unblank screen by twiching.
+ */
+void
+mctlproc(void *)
+{
+	Key key;
+	int i, mouseb = 0;
+
+	threadsetname("mctlproc");
+
+	for(;;){
+		if(nbrecv(mctlchan, &key) <= 0){
+			if(mctlfd >= 0)
+				fprint(mctlfd, "twitch");
+			if(recv(mctlchan, &key) <= 0)
+				break;
+		}
+
+		if(mctlfd >= 0 && key.r == Kshift){
+			if(key.down){
+				fprint(mctlfd, "buttonmap 132");
+			} else {
+				fprint(mctlfd, "swap");
+				fprint(mctlfd, "swap");
+			}
+			continue;
+		}
+
+		if(msinfd >= 0 && key.r >= Kmouse+1 && key.r <= Kmouse+5){
+			i = 1<<(key.r-(Kmouse+1));
+			if(key.down)
+				mouseb |= i;
+			else
+				mouseb &= ~i;
+			fprint(msinfd, "m%11d %11d %11d", 0, 0, mouseb);
+			continue;
+		}
+	}
+}
+
+/*
+ * Cook lines for cons
+ */
+void
+lineproc(void *aux)
+{
+	Rune rb[256], r;
+	Channel *cook;
+	int nr, done;
+	char *s;
+
+	cook = aux;
+
+	threadsetname("lineproc");
+
+	for(;;){
+		nr = 0;
+		done = 0;
+		do {
+			recv(cook, &r);
+			switch(r){
+			case Kdel:
+				if(nbsend(intchan, &notefd) <= 0)
+					continue;
+				/* no break */
+			case '\0':	/* flush */
+				nr = 0;
+				continue;
+			case Kbs:	/* ^H: erase character */
+			case Knack:	/* ^U: erase line */
+			case Ketb:	/* ^W: erase word */
+				while(nr > 0){
+					nr--;
+					fprint(1, "\b");
+					if(r == Kbs)
+						break;
+					if(r == Ketb && utfrune(" \t", rb[nr]))
+						break;
+				}
+				continue;
+			case Keof:	/* ^D: eof */
+				done = 1;
+				break;
+			case '\n':
+				done = 1;
+				/* no break */
+			default:
+				rb[nr++] = r;
+				fprint(1, "%C", r);
+			}
+		} while(!done && nr < nelem(rb));
+		s = utfconv(rb, nr);
+		if(nbsendp(conschan, s) <= 0)
+			free(s);
+	}
+}
+
+/*
+ * Reads Tread and Tflush requests from reqchan and responds
+ * to them with data received on the string channel.
+ */
+void
+reqproc(void *aux)
+{
+	enum { AREQ, ASTR, AEND };
+	Alt a[AEND+1];
+	Channel **ac;
+	Req *r, *q, **qq;
+	char *s, *p, *e;
+	int n, m;
+
+	threadsetname("reqproc");
+
+	e = nil;
+	s = nil;
+	p = nil;
+
+	q = nil;
+	qq = &q;
+
+	ac = aux;
+	a[AREQ].op = CHANRCV;
+	a[AREQ].c = ac[0];	/* chan(Req*) */
+	a[AREQ].v = &r;
+
+	a[ASTR].c = ac[1];	/* chan(char*) */
+	a[ASTR].v = &s;
+
+	a[AEND].op = CHANEND;
+
+	for(;;){
+		a[ASTR].op = s != nil ? CHANNOP : CHANRCV;
+
+		switch(alt(a)){
+		case AREQ:
+			if(r->ifcall.type == Tflush){
+				Req **rr, **xx;
+
+				for(rr = &q; *rr; rr=xx){
+					xx = &((*rr)->aux);
+					if(*rr == r->oldreq){
+						if((*rr = *xx) == nil)
+							qq = rr;
+						respond(r->oldreq, Eintr);
+						break;
+					}
+				}
+				respond(r, nil);
+				continue;
+			} else if(r->ifcall.type != Tread){
+				respond(r, Ephase);
+				continue;
+			}
+			r->aux = nil;
+			*qq = r;
+			qq = &r->aux;
+
+			if(0){
+		case ASTR:
+				p = s;
+			}
+
+			while(s != nil && q != nil){
+				r = q;
+				if((q = q->aux) == nil)
+					qq = &q;
+				r->ofcall.count = 0;
+				if(s == p){
+				More:
+					e = s + strlen(s);
+					if(r->fid->qid.path == Qkbd)
+						e++; /* send terminating \0 if its kbd file */
+				}
+				n = e - p;
+				m = r->ifcall.count - r->ofcall.count;
+				if(n > m){
+					if(r->ofcall.count > 0){
+						respond(r, nil);
+						continue;
+					}
+					n = m;
+				}
+				memmove((char*)r->ofcall.data + r->ofcall.count, p, n);
+				r->ofcall.count += n;
+				p += n;
+				if(p >= e){
+					free(s);
+					s = nbrecvp(a[ASTR].c);
+					if(s != nil){
+						p = s;
+						goto More;
+					}
+				}
+				respond(r, nil);
+			}
+		}
+	}
+}
+
+/*
+ * Keep track of rawing state and distribute the runes from
+ * runechan to the right channels depending on the state.
+ */
+void
+ctlproc(void *)
+{
+	Channel *cook, *aconsr[2], *akbdr[2];
+	enum { ACTL, ARUNE, AEND };
+	Alt a[AEND+1];
+	Rune r;
+	int c, raw;
+	char *s;
+
+	threadsetname("ctlproc");
+
+	if(kbdifd >= 0)
+		proccreate(kbdiproc, nil, STACK);	/* kbdifd -> kbdin() */
+	if(mctlfd >= 0 || msinfd >= 0)
+		proccreate(mctlproc, nil, STACK);	/* mctlchan -> mctlfd, msinfd */
+	if(scanfd >= 0)
+		proccreate(scanproc, nil, STACK);	/* scanfd -> keychan */
+	if(consfd >= 0)
+		proccreate(consproc, nil, STACK);	/* consfd -> runechan */
+	if(notefd >= 0)
+		proccreate(intrproc, nil, STACK);	/* intchan -> notefd */
+
+	threadcreate(keyproc, nil, STACK);		/* keychan -> mctlchan, rawchan, kbdchan */
+	threadcreate(runeproc, nil, STACK);		/* rawchan -> runechan */
+
+	aconsr[0] = consreqchan;
+	aconsr[1] = conschan;
+	threadcreate(reqproc, aconsr, STACK);		/* consreqchan,conschan -> respond */
+
+	akbdr[0] = kbdreqchan;
+	akbdr[1] = kbdchan;
+	threadcreate(reqproc, akbdr, STACK);		/* kbdreqchan,kbdchan -> respond */
+
+	cook = chancreate(sizeof(Rune), 0);
+	threadcreate(lineproc, cook, STACK);		/* cook -> conschan */
+
+	raw = 0;
+
+	a[ACTL].c = ctlchan;
+	a[ACTL].v = &c;
+	a[ACTL].op = CHANRCV;
+
+	a[ARUNE].c = runechan;
+	a[ARUNE].v = &r;
+	a[ARUNE].op = CHANRCV;
+
+	a[AEND].op = CHANEND;
+
+	for(;;){
+		switch(alt(a)){
+		case ACTL:
+			switch(c){
+			case Rawoff:
+			case Rawon:
+				if(raw = (c == Rawon)){
+					r = 0;
+					nbsend(cook, &r);
+				}
+			}
+			break;
+		case ARUNE:
+			if(kbdopen){
+				s = emalloc9p(UTFmax+2);
+				s[0] = 'c';
+				s[1+runetochar(s+1, &r)] = 0;
+				if(nbsendp(kbdchan, s) <= 0)
+					free(s);
+				break;
+			}
+			if(raw){
+				s = emalloc9p(UTFmax+1);
+				s[runetochar(s, &r)] = 0;
+				if(nbsendp(conschan, s) <= 0)
+					free(s);
+				break;
+			}
+			nbsend(cook, &r);
+			break;
+		}
+	}
+}
+
+/*
+ * Keyboard layout maps
+ */
+
+Rune*
+kbmapent(int t, int sc)
+{
+	if(t >= 0 && t < nelem(kbtabs) && sc >= 0 && sc < Nscan)
+		return &kbtabs[t][sc];
+	return nil;
+}
+
+void
+kbmapread(Req *req)
+{
+	char tmp[3*12+1];
+	int t, sc, soff, off, n;
+	Rune *rp;
+
+	off = req->ifcall.offset/(sizeof(tmp)-1);
+	soff = req->ifcall.offset%(sizeof(tmp)-1);
+	t = off/Nscan;
+	sc = off%Nscan;
+	if(rp = kbmapent(t, sc)){
+		sprint(tmp, "%11s %11d %11d\n", layertab[t], sc, *rp);
+		n = strlen(&tmp[soff]);
+		if(req->ifcall.count < n)
+			n = req->ifcall.count;
+		req->ofcall.count = n;
+		memmove(req->ofcall.data, &tmp[soff], n);
+	}else
+		req->ofcall.count = 0;
+	respond(req, nil);
+}
+
+Rune
+kbcompat(Rune r)
+{
+	static Rune o = Spec|0x60, tab[] = {
+		Kshift, Kbreak, Kctl, Kalt,
+		Kcaps, Knum, Kmiddle, Kaltgr,
+		Kmod4,
+	};
+	if(r >= o && r < o+nelem(tab))
+		return tab[r - o];
+	return r;
+}
+
+void
+kbmapwrite(Req *req)
+{
+	char line[100], *lp, *b, *e;
+	Rune r, *rp;
+	int sc, t, l;
+	Fid *f;
+
+	f = req->fid;
+	b = req->ifcall.data;
+	l = req->ifcall.count;
+	lp = line;
+	if(f->aux){
+		strcpy(line, f->aux);
+		lp = line+strlen(line);
+		free(f->aux);
+		f->aux = nil;
+	}
+	while(--l >= 0) {
+		*lp++  = *b++;
+		if(lp[-1] == '\n' || lp == &line[sizeof(line)-1]) {
+			*lp = 0;
+			if(*line == 0){
+			Badarg:
+				respond(req, Ebadarg);
+				return;
+			}
+			lp = line;
+			if(*line == '\n' || *line == '#')
+				continue;
+			while(*lp == ' ' || *lp == '\t')
+				lp++;
+			for(e = lp; strchr("\t ", *e) == nil; e++)
+				;
+			if(e == lp || *e == '\0')
+				goto Badarg;
+			*e = '\0';
+			for(t = 0; t < nelem(layertab); t++){
+				if(strcmp(lp, layertab[t]) != 0)
+					continue;
+				break;
+			}
+			if(t == nelem(layertab)){
+				t = strtoul(lp, &e, 0);
+				if(e == lp)
+					goto Badarg;
+			}
+			e++;
+			sc = strtoul(e, &lp, 0);
+			if(e == lp)
+				goto Badarg;
+			while(*lp == ' ' || *lp == '\t')
+				lp++;
+			if((rp = kbmapent(t, sc)) == nil)
+				goto Badarg;
+			r = 0;
+			if(*lp == '\'' && lp[1])
+				chartorune(&r, lp+1);
+			else if(*lp == '^' && lp[1]){
+				chartorune(&r, lp+1);
+				if(0x40 <= r && r < 0x60)
+					r -= 0x40;
+				else
+					goto Badarg;
+			}else if(*lp == 'M' && ('1' <= lp[1] && lp[1] <= '5'))
+				r = Kmouse+lp[1]-'0';
+			else if(*lp>='0' && *lp<='9') /* includes 0x... */
+				r = strtoul(lp, &lp, 0);
+			else
+				goto Badarg;
+			*rp = kbcompat(r);
+			lp = line;
+		}
+	}
+	if(lp != line){
+		l = lp-line;
+		f->aux = lp = emalloc9p(l+1);
+		memmove(lp, line, l);
+		lp[l] = 0;
+	}
+	req->ofcall.count = req->ifcall.count;
+	respond(req, nil);
+}
+
+/*
+ * Filesystem
+ */
+
+static int
+fillstat(ulong qid, Dir *d)
+{
+	struct Qtab *t;
+
+	memset(d, 0, sizeof *d);
+	d->uid = d->gid = user;
+	d->muid = "";
+	d->qid = (Qid){qid, 0, 0};
+	d->atime = time(0);
+	t = qtab + qid;
+	d->name = t->name;
+	d->qid.type = t->type;
+	d->mode = t->mode;
+	return 1;
+}
+
+static void
+fsattach(Req *r)
+{
+	char *spec;
+
+	spec = r->ifcall.aname;
+	if(spec && spec[0]){
+		respond(r, Ebadspec);
+		return;
+	}
+	r->fid->qid = (Qid){Qroot, 0, QTDIR};
+	r->ofcall.qid = r->fid->qid;
+	respond(r, nil);
+}
+
+static void
+fsstat(Req *r)
+{
+	fillstat((ulong)r->fid->qid.path, &r->d);
+	r->d.name = estrdup9p(r->d.name);
+	r->d.uid = estrdup9p(r->d.uid);
+	r->d.gid = estrdup9p(r->d.gid);
+	r->d.muid = estrdup9p(r->d.muid);
+	respond(r, nil);
+}
+
+static char*
+fswalk1(Fid *fid, char *name, Qid *qid)
+{
+	int i;
+	ulong path;
+
+	path = fid->qid.path;
+	switch(path){
+	case Qroot:
+		if (strcmp(name, "..") == 0) {
+			*qid = (Qid){Qroot, 0, QTDIR};
+			fid->qid = *qid;
+			return nil;
+		}
+		for(i = fid->qid.path; i<Nqid; i++){
+			if(strcmp(name, qtab[i].name) != 0)
+				continue;
+			*qid = (Qid){i, 0, 0};
+			fid->qid = *qid;
+			return nil;
+		}
+		return Enonexist;
+		
+	default:
+		return Ewalk;
+	}
+}
+
+static void
+fsopen(Req *r)
+{
+	Fid *f;
+	static int need[4] = { 4, 2, 6, 1 };
+	struct Qtab *t;
+	int n;
+
+	f = r->fid;
+	t = qtab + f->qid.path;
+	n = need[r->ifcall.mode & 3]<<6;
+	if((n & t->mode) != n)
+		respond(r, Eperm);
+	else{
+		f->aux = nil;
+		switch((ulong)f->qid.path){
+		case Qkbmap:
+			if(r->ifcall.mode & OTRUNC)
+				memcpy(kbtabs, ascii, sizeof kbtabs);
+			break;
+		case Qkbd:
+			if(kbdopen){
+				respond(r, Einuse);
+				return;
+			}
+			kbdopen++;
+			break;
+		case Qconsctl:
+			consctlopen++;
+			break;
+		}
+		respond(r, nil);
+	}
+}
+
+static int
+readtopdir(Fid*, uchar *buf, long off, int cnt, int blen)
+{
+	int i, m, n;
+	long pos;
+	Dir d;
+
+	n = 0;
+	pos = 0;
+	for (i = 1; i < Nqid; i++){
+		fillstat(i, &d);
+		m = convD2M(&d, &buf[n], blen-n);
+		if(off <= pos){
+			if(m <= BIT16SZ || m > cnt)
+				break;
+			n += m;
+			cnt -= m;
+		}
+		pos += m;
+	}
+	return n;
+}
+
+static void
+fsread(Req *r)
+{
+	Fid *f;
+
+	f = r->fid;
+	switch((ulong)f->qid.path){
+	default:
+		respond(r, Ephase);
+		return;
+	case Qroot:
+		r->ofcall.count = readtopdir(f, (void*)r->ofcall.data, r->ifcall.offset,
+			r->ifcall.count, r->ifcall.count);
+		break;
+	case Qkbd:
+		sendp(kbdreqchan, r);
+		return;
+	case Qcons:
+		sendp(consreqchan, r);
+		return;
+	case Qkbmap:
+		kbmapread(r);
+		return;
+	}
+	respond(r, nil);
+}
+
+static void
+fswrite(Req *r)
+{
+	Fid *f;
+	char *p;
+	int n, i;
+
+	f = r->fid;
+	p = r->ifcall.data;
+	n = r->ifcall.count;
+	switch((ulong)f->qid.path){
+	default:
+		respond(r, Ephase);
+		return;
+
+	case Qcons:
+		if(write(1, p, n) != n){
+			responderror(r);
+			return;
+		}
+		break;
+
+	case Qconsctl:
+		if(n >= 5 && memcmp(p, "rawon", 5) == 0)
+			sendul(ctlchan, Rawon);
+		else if(n >= 6 && memcmp(p, "rawoff", 6) == 0)
+			sendul(ctlchan, Rawoff);
+		else {
+			respond(r, Ebadarg);
+			return;
+		}
+		break;
+
+	case Qkbdin:
+	case Qkbin:
+		if(f->aux == nil){
+			f->aux = emalloc9p(sizeof(Scan));
+			memset(f->aux, 0, sizeof(Scan));
+		}
+		if(f->qid.path == Qkbin){
+			for(i=0; i<n; i++)
+				kbdputsc((Scan*)f->aux, (uchar)p[i]);
+		} else {
+			kbdin((Scan*)f->aux, p, n);
+		}
+		break;
+
+	case Qkbmap:
+		kbmapwrite(r);
+		return;
+
+	}
+	r->ofcall.count = n;
+	respond(r, nil);
+}
+
+static void
+fsflush(Req *r)
+{
+	switch((ulong)r->oldreq->fid->qid.path) {
+	case Qkbd:
+		sendp(kbdreqchan, r);
+		return;
+	case Qcons:
+		sendp(consreqchan, r);
+		return;
+	}
+	respond(r, nil);
+}
+
+static void
+fsdestroyfid(Fid *f)
+{
+	void *p;
+
+	if(f->omode != -1)
+		switch((ulong)f->qid.path){
+		case Qkbdin:
+		case Qkbin:
+		case Qkbmap:
+			if(p = f->aux){
+				f->aux = nil;
+				free(p);
+			}
+			break;
+		case Qkbd:
+			kbdopen--;
+			break;
+		case Qconsctl:
+			if(--consctlopen == 0)
+				sendul(ctlchan, Rawoff);
+			break;
+		}
+}
+
+static int
+procopen(int pid, char *name, int mode)
+{
+	char buf[128];
+
+	snprint(buf, sizeof(buf), "/proc/%d/%s", pid, name);
+	return eopen(buf, mode);
+}
+
+static void
+elevate(void)
+{
+	Dir *d, nd;
+	int fd;
+
+	if(debug)
+		return;
+
+	if((fd = procopen(getpid(), "ctl", OWRITE)) < 0)
+		return;
+
+	/* get higher than normal priority */
+	fprint(fd, "pri 16\n");
+
+	/* always present in physical memory */
+	fprint(fd, "noswap\n");
+
+	/* dont let anybody kill us */
+	if(d = dirfstat(fd)){
+		nulldir(&nd);
+		nd.mode = d->mode & ~0222;
+		dirfwstat(fd, &nd);
+		free(d);
+	}
+
+	close(fd);
+}
+
+static void
+fsstart(Srv*)
+{
+	killfd = procopen(getpid(), "notepg", OWRITE);
+	elevate();
+	memcpy(kbtabs, ascii, sizeof kbtabs);
+	proccreate(ctlproc, nil, STACK);
+}
+
+static void
+fsend(Srv*)
+{
+	shutdown();
+}
+
+Srv fs = {
+	.start=			fsstart,
+	.attach=		fsattach,
+	.walk1=			fswalk1,
+	.open=			fsopen,
+	.read=			fsread,
+	.write=			fswrite,
+	.stat=			fsstat,
+	.flush=			fsflush,
+	.destroyfid=		fsdestroyfid,
+	.end=			fsend,
+};
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [ -qdD ] [ -s sname ] [ -m mntpnt ] [ file ]\n", argv0);
+	exits("usage");
+}
+
+void
+threadmain(int argc, char** argv)
+{
+	ARGBEGIN{
+	case 'd':
+		debug++;
+		break;
+	case 'D':
+		chatty9p++;
+		break;
+	case 's':
+		sname = EARGF(usage());
+		break;
+	case 'm':
+		mntpt = EARGF(usage());
+		break;
+	case 'q':
+		quiet++;
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	if(*argv)
+		consfd = eopen(*argv, OREAD);
+
+	kbdifd = open(dev("kbd"), OREAD);
+	if(kbdifd < 0){
+		scanfd = eopen(dev("scancode"), OREAD);
+		ledsfd = eopen(dev("leds"), OWRITE);
+	}
+	mctlfd = eopen(dev("mousectl"), OWRITE);
+	msinfd = eopen(dev("mousein"), OWRITE);
+
+	notefd = procopen(getpid(), "notepg", OWRITE);
+
+	consreqchan = chancreate(sizeof(Req*), 0);
+	kbdreqchan = chancreate(sizeof(Req*), 0);
+
+	keychan = chancreate(sizeof(Key), 64);
+	mctlchan = chancreate(sizeof(Key), 64);
+	ctlchan = chancreate(sizeof(int), 0);
+	rawchan = chancreate(sizeof(Rune), 0);
+	runechan = chancreate(sizeof(Rune), 256);
+	conschan = chancreate(sizeof(char*), 128);
+	kbdchan = chancreate(sizeof(char*), 128);
+	intchan = chancreate(sizeof(int), 0);
+
+	user = getuser();
+	threadpostmountsrv(&fs, sname, mntpt, MBEFORE);
+	threadexits(0);
+}