Plan 9 from Bell Labs’s /usr/web/sources/contrib/nemo/sys/src/cmd/kbdfs.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <bio.h>
#include <thread.h>
#include <fcall.h>
#include <auth.h>
#include <draw.h>
#include <mouse.h>
#include <cursor.h>
#include <9p.h>
#include <b.h>

/* Pending:
 *	1. Needs a rewrite, along the lines of mousefs.c
 *	2. Should not send runes in the clear.
 */

typedef struct Dev	Dev;

enum {
	Qdir = 0,
	Qcons,
	Qctl,
	Qmax,

	Ndevs	= 64,
	Nevs	= 32,
	Stack	= 16 * 1024,

	Rreclaim	= L''	// rune to reclaim kbd (F1)
};

struct Dev {
	char	name;	// char to stampt events
	char*	addr;	// IP for the machine servicing this
	int	gone;
	int	con;	// to peer.
	Channel*evs;	// of char*; sent to peer.
};

int mainstacksize = Stack;

static char	Ebad[] = "something bad happened";
static char	Eperm[] =	"permission denied";
static char	Enotexist[] =	"file not found";
static char	Eattach[] = 	"unknown specifier in attach";
static char	Ebadctl[] =	"bad control request";
static Lock	devslock;
static Dev*	devs[Ndevs];
static int	ndevs;
static int	debug;
static Channel*	kbdc;	// devs[i].evs: where to send our events.
static char*	addr;
static char*	vname;

static char*	usr;
static char*	localaddr;
static int	doauth = 1;

#define dprint	if(debug)fprint
#define Dprint	if(debug>1)fprint


static int
alarmed(void *, char *s)
{
	if(strcmp(s, "alarm") == 0)
		return 1;
	return 0;
}

static char*
csquery(char *addr)
{
	static char buf[128];
	char* p;
	char* e;
	int fd, n;

	if (!addr || !strcmp(addr, "local"))
		return estrdup9p(localaddr);
	fd = open("/net/cs", ORDWR);
	if(fd < 0){
		fprint(2, "cannot open /net/cs: %r");
		return nil;
	}
	addr = netmkaddr(addr, "tcp", "11001");
	if(write(fd, addr, strlen(addr)) <= 0){
		fprint(2, "translating %s: %r\n", addr);
		close(fd);
		return nil;
	}
	seek(fd, 0, 0);
	buf[0] = 0;
	n = read(fd, buf, sizeof(buf)-1);
	close(fd);
	if (n < 0){
		fprint(2, "cannot read /net/cs: %r");
		return nil;
	}
	buf[n] = 0;
	p = strchr(buf, ' ');
	if (!p)
		return nil;
	p++;
	e = strchr(p, '!');
	if (e){
		// If it's the std service (port kbd), take just
		// the machine name.
		// For kbdfs's started at ports other than kbd,
		// take all the address, including the port.
		if (!strncmp(e+1, "11001", 5))
			*e = 0;
		else
			return p ? smprint("tcp!%s", p) : nil;
	}
	return p ? estrdup9p(p) : nil;
}

static int
islocal(char* addr)
{
	if (!addr || !strcmp(addr, "local"))
		return 1;
	if (!strcmp(addr, sysname()) || !strcmp(addr, localaddr))
		return 1;
	return 0;
}

static int
_setdest(char* addr)
{
	int	i;

	if (islocal(addr)){
		i = 0;
	} else {
		for (i = 1; i < ndevs; i++)
			if (devs[i]->addr != nil && !strcmp(addr, devs[i]->addr))
				break;
		if (i == ndevs)
			return 0;
	}
	kbdc = devs[i]->evs;	// send our events to it.
	dprint(2, "kbdfs: setdest %s\n", addr);
	return 1;
}

static int
setdest(char* addr)
{
	int	r;

	lock(&devslock);
	r = _setdest(addr);
	unlock(&devslock);
	return r;
}


static void
sendproc(void* a)
{
	Dev*	dev;
	Rune	r;
	char	s[UTFmax];

	dev = a;
	threadsetname("sendproc");
	while(recv(dev->evs, &r)>= 0 && r && !dev->gone){
		Dprint(2, "kbd sent: %C\n", r);
		runetochar(s, &r);
		if (write(dev->con, s, UTFmax) != UTFmax)
			break;
	}
	lock(&devslock);
	dprint(2, "kbdfs: %s sendproc exiting\n", dev->addr);
	free(dev->addr);
	dev->addr = nil;
	unlock(&devslock);
	r = 0;
	runetochar(s, &r);
	write(dev->con, s, UTFmax);
	close(dev->con);
	dev->con = -1;
	if (dev->evs == kbdc)
		setdest("local");
	threadexits(nil);
}

static void
hangupdev(Dev* d)
{
	Rune	z;

	if (d == devs[0])	// local never goes
		return;
	d->gone = 1;
	z = 0;
	send(d->evs, &z);
}

static Dev*
outgoing(void)
{
	int	i;

	lock(&devslock);
	for (i = 1; i < ndevs; i++)
		if (devs[i]->addr != nil && kbdc == devs[i]->evs){
			unlock(&devslock);
			return devs[i];
		}
	unlock(&devslock);
	return nil;
}

static int
gone(char* addr)
{
	int	i;

	/* We skip devs[0]. Local is never gone */

	lock(&devslock);
	for (i = 1; i < ndevs; i++)
		if (devs[i]->addr != nil && strstr(devs[i]->addr, addr)){
			if (kbdc == devs[i]->evs){
				dprint(2, "kbdfs: current gone\n");
				_setdest("local");
			}
			hangupdev(devs[i]);
			break;
		}
	unlock(&devslock);
	return i < ndevs;
}

static void
recvproc(void* a)
{
	char	buf[UTFmax];
	Rune	r;
	Dev*	d;
	int	fd;
	int	n;
	Dev*	outd;

	d = a;
	threadsetname("recvproc");
	fd = d->con;
	for(;;){
		n = read(d->con, buf, UTFmax);
		if (n < 0 || d->gone){
			dprint(2, "kbdfs: %s gone %d %d\n", d->addr, n, d->gone);
			break;
		}
		if (n == 0)
			continue;
		chartorune(&r, buf);
		if (!r){
			if (d == devs[0]){
				outd = outgoing();
				setdest("local");
				if (outd)
					hangupdev(outd);
				continue;
			} else {
				dprint(2, "kbdfs: %s end\n", d->addr);
				break;
			}
		}
		buf[n] = 0;
		Dprint(2, "kbd read: %C\n", r);
		if (d != devs[0]){
			if (kbdc == d->evs){
				// Got events from there.
				// cannot use d as the current output.
				gone(d->addr);
			}
		}
		if (kbdc != nil){
			buf[1] = d->name;
			send(kbdc, &r);
		} else
			fprint(2, "kbdfs: no kbdc\n");
	}
	dprint(2, "kbdfs: %s recvproc exiting\n", d->addr);
	if (d->evs == kbdc)
		setdest("local");
	r = 0;
	if (d != devs[0])
		send(d->evs, &r);
	d->con = -1;
	runetochar(buf, &r);
	write(fd, buf, UTFmax);
	close(fd);
	threadexits(nil);

}

static Dev*
newdev(char* addr, int fd)
{
	Dev*	dev;
	int	i;
	Rune	r;

	if (0 && strchr(addr, '!')){
		// that's ok with more than one kbdfs per machine.
		fprint(2, "kbdfs: newdev: bad address %s\n", addr);
		return nil;
	}
	if (islocal(addr))
		addr = localaddr;
	dprint(2, "kbdfs: newdev %s\n", addr);
	lock(&devslock);
	dev = nil;
	for (i = 0; i < ndevs; i++){
		if (devs[i]->addr == nil){
			dev = devs[i];
			while(nbrecv(devs[i]->evs, &r) > 0)
				;
			break;
		}
	}
	if (dev == nil){
		dev = emalloc9p(sizeof(Dev));
		dev->evs = chancreate(sizeof(Rune), Nevs);
		assert(ndevs < Ndevs);
		devs[ndevs++] = dev;
	}
	dev->gone = 0;
	dev->addr = estrdup9p(addr);
	dev->con = fd;
	unlock(&devslock);
	return dev;
}


static void
fsattach(Req *r)
{
	char *spec;

	spec = r->ifcall.aname;
	if(spec != nil && spec[0] != 0){
		respond(r, Eattach);
		return;
	}

	r->ofcall.qid = (Qid){Qdir, 0, QTDIR};
	r->fid->qid = r->ofcall.qid;
	respond(r, nil);
}

static void
fsopen(Req *r)
{
	int	p;

	r->ifcall.mode &= 3;
	if (r->fid->qid.type == QTAUTH || r->ifcall.mode == OREAD){
		r->fid->omode = r->ifcall.mode;
	} else {
		p = r->fid->qid.path;
		switch(p){
		case Qdir:
			respond(r, Eperm);
			return;
		case Qcons:
		case Qctl:
			r->fid->omode = r->ifcall.mode;
			break;
		default:
			respond(r, Ebad);
			return;
		}
	}
	respond(r, nil);
}

static int
dirgen(int i, Dir *d, void*)
{
	if(i > 1)
		return -1;
	memset(d, 0, sizeof *d);
	d->uid = estrdup9p("sys");
	d->gid = estrdup9p("sys");
	d->length = 0;
	switch(i){
	case -1:
		d->qid.type = QTDIR;
		d->qid.path = Qdir;
		d->mode = 0555|DMDIR;
		d->name = estrdup9p("/");
		break;
	case 0:
		d->mode = 0660;
		d->qid.path = Qcons;
		d->name = estrdup9p("cons");
		break;
	case 1:
		d->mode = 0220;
		d->qid.path = Qctl;
		d->name = estrdup9p("kbdctl");
		break;
	default:
		return -1;
	}
	return 0;
}

void
kreadproc(void* a)
{
	static char	locked = 0;
	Rune	ev;
	Req*	r;
	Channel* kreadreq = a;
	char	s[UTFmax+1];
	int	n;

	threadsetname("kreadproc");
	for(;;){
		r = recvp(kreadreq);

		if (recv(devs[0]->evs, &ev) < 0){
			fprint(2, "kbdfs: nil event?");
			break;
		}
		n = runetochar(s, &ev);
		r->ofcall.count = n;
		if (r->ifcall.count < r->ofcall.count)
			r->ofcall.count = r->ifcall.count;
		memmove(r->ofcall.data, s, r->ofcall.count);
		Dprint(2, "fsread: %s\n", s);
		respond(r, nil);
	}
}

static void
fsread(Req *r)
{
	static Channel* kreadreqc = nil;
	static char	locked = 0;
	char	m[200];
	char*	e;
	char*	c;
	int	p;
	int	i;


	p = r->fid->qid.path;
	switch(p){
	case Qdir:
		dirread9p(r, dirgen, nil);
		break;
	case Qcons:
		if (kreadreqc == nil){
			kreadreqc = chancreate(sizeof(Req*), Nevs);
			proccreate(kreadproc, kreadreqc, Stack);
		}
		sendp(kreadreqc, r);
		return;
	case Qctl:
		e = m;
		lock(&devslock);
		for (i = 0; i < ndevs; i++)
			if (devs[i]->addr){
				c =  (kbdc == devs[i]->evs) ? "*" : "";
				e = seprint(e, m+sizeof(m), "%s %s\n", devs[i]->addr, c);
			}
		unlock(&devslock);
		readstr(r, m);
		break;
	default:
		respond(r, Ebad);
		return;
	}
	respond(r, nil);
}

static int
authfd(int fd, char* role)
{
	AuthInfo*i;

	i = nil;
	alarm(5*1000);
	USED(i);
	i = auth_proxy(fd, nil, "proto=p9any user=%s role=%s", usr, role);
	alarm(0);
	if (i == nil)
		return 0;
	auth_freeAI(i);
	return 1;
}

static int
remote(char* addr)
{
	int	fd;
	Dev*	dev;

	if (islocal(addr)){
		dprint(2, "remote for local addr\n");
		return 1;
	}
	fd = dial(netmkaddr(addr, "tcp", "kbd"), 0, 0, 0);
	if (fd < 0){
		fprint(2, "kbdfs: %s: %r\n", addr);
		return 0;
	}
	if (doauth && !authfd(fd, "client")){
		fprint(2, "kbdfs: can't auth to %s: %r\n", addr);
		close(fd);
		return 0;
	}
	dev = newdev(addr, fd);
	assert(dev);
	proccreate(recvproc, dev, Stack);
	proccreate(sendproc, dev, Stack);
	return 1;
}

static char*
ctl(char* msg)
{
	char*	args[4];
	int	nargs;
	char*	addr;

	/* call address
	 * close address
	 */
	nargs = tokenize(msg, args, nelem(args));
	if (nargs != 2)
		return Ebadctl;
	addr = csquery(args[1]);
	if (strcmp(args[0], "call") == 0){
		if (!setdest(addr)){
			remote(addr);
			if (!setdest(addr)){
				free(addr);
				return Enotexist;
			}
		}
	} else if (strcmp(args[0], "close") == 0){
		if (!gone(addr)){
			free(addr);
			return Enotexist;
		}
	} else {
		free(addr);
		return Ebadctl;
	}
	free(addr);
	return nil;
}

static void
fswrite(Req *r)
{
	static int cfd = -1;
	int	p;
	char	msg[80];
	int	n;
	char*	e;

	p = r->fid->qid.path;
	switch(p){
	case Qcons:
		if (cfd < 0)
			cfd = open("#c/cons", OWRITE|OCEXEC);
		n = r->ifcall.count;
		r->ofcall.count = write(cfd, r->ifcall.data, n);
		break;
	case Qdir:
		respond(r, Ebad);
		return;
	case Qctl:
		n = r->ofcall.count = r->ifcall.count;
		if (n > sizeof(msg)-1)
			n = sizeof(msg)-1;
		memmove(msg, r->ifcall.data, n);
		msg[n] = 0;
		e = ctl(msg);
		if (e != nil){
			respond(r, e);
			return;
		}
		break;
	default:
		respond(r, Ebad);
		return;
	}
	respond(r, nil);
}

static char*
fswalk1(Fid *fid, char *name, Qid* q)
{

	if (fid->qid.path != Qdir){
		if(strcmp(name, "..") == 0){
			fid->qid.path = Qdir;
			fid->qid.type = QTDIR;
			*q = fid->qid;
			return nil;
		}
		return Enotexist;
	}
	if (strcmp(name, "..") == 0){
		fid->qid.path = Qdir;
		fid->qid.type = QTDIR;
	} else if (strcmp(name, "cons") == 0){
		fid->qid.path = Qcons;
		fid->qid.type = 0;
	} else if (strcmp(name, "kbdctl") == 0){
		fid->qid.path = Qctl;
		fid->qid.type = 0;
	} else
		return Enotexist;
	*q = fid->qid;
	return nil;
}

static void
fsstat(Req *r)
{
	int q;

	q = r->fid->qid.path;
	switch(q){
	case Qdir:
		dirgen(-1, &r->d, nil);
		break;
	case Qcons:
		dirgen(0, &r->d, nil);
		break;
	case Qctl:
		dirgen(1, &r->d, nil);
		break;
	}
	respond(r, nil);
}

static Srv mfs=
{
.attach=fsattach,
.open=	fsopen,
.read=	fsread,
.stat=	fsstat,
.write= fswrite,
.walk1= fswalk1,
};

static void
listener(void *a)
{
	int	afd, lfd;
	char	adir[40];
	char	ldir[40];
	char*	addr = a;
	int	dfd;
	NetConnInfo*i;
	Dev*	dev;

	threadsetname("listener");
	threadnotify(alarmed, 1);
	afd = announce(netmkaddr(addr, 0, "kbd"), adir);
	if (afd < 0)
		sysfatal("can't announce: %r");
	for(;;){
		lfd = listen(adir, ldir);
		if (lfd < 0)
			sysfatal("can't listen: %r");
		dfd = accept(lfd, ldir);
		i = getnetconninfo(ldir, dfd);
		close(lfd);
		if (doauth && !authfd(dfd, "server")){
			fprint(2, "auth failed for %s\n", i->rsys);
			close(dfd);
		} else {
			dev = newdev(i->rsys, dfd);
			dprint(2, "kbdfs: call from %s\n", dev->addr);
			setdest("local");
			proccreate(recvproc, dev, Stack);
			proccreate(sendproc, dev, Stack);
		}
		freenetconninfo(i);
	}
}

void
srvproc(void* a)
{
	int*	p = a;

	threadsetname("srvproc");
	threadnotify(alarmed, 1);
	close(p[1]);
	mfs.infd = p[0];
	mfs.outfd= p[0];
	mfs.nopipe= 1;
	mfs.srvfd = -1;
	srv(&mfs);
	fprint(2, "kbdfs: srvproc exiting\n");
	threadexits(nil);
}

void
setpri(int pri)
{
	int	pid;
	char*	s;
	int	fd;

	pid = getpid();
	s = smprint("/proc/%d/ctl", pid);
	fd = open(s, OWRITE);
	free(s);
	s = smprint("pri %d", pri);
	write(fd, s, strlen(s));
	free(s);
	close(fd);
}

static void
announceproc(void*)
{
	int	afd = -1;
	char*	cnstr;

	cnstr = strchr(vname, ' ');
	if (cnstr)
		*cnstr++ = 0;

	for(;;){
		afd = announcevol(afd, addr, vname, cnstr);
		sleep(10 * 1000);
	}
}

void
usage(void)
{
	fprint(2, "usage: %s [-AdD] [-m mnt] [-a addr]\n", argv0);
	exits("usage");
}

void
threadmain(int argc, char **argv)
{
	char*	mnt;
	int	p[2];
	int	kbdfd;

	mnt = "/dev";
	addr= "tcp!*!11001";
	ARGBEGIN{
	case 'A':
		doauth = 0;
		break;
	case 'D':
		chatty9p++;
	case 'd':
		debug++;
		break;
	case 'a':
	case 'n':
		addr = EARGF(usage());
		break;
	case 'm':
		mnt = EARGF(usage());
		break;
	case 'V':
		vname = EARGF(usage());
		break;
	default:
		usage();
	}ARGEND;

	if(argc!= 0)
		usage();

	chdir("/");
	localaddr=csquery(sysname());
	usr = getuser();
	if (usr == nil)
		usr = "none";
	setpri(13);
	procrfork(listener, estrdup9p(addr), Stack, RFNAMEG|RFFDG|RFNOTEG);

	kbdfd = 0; // open("/dev/cons", ORDWR|OCEXEC);
	if (kbdfd < 0)
		sysfatal("can't open cons: %r");
	newdev("local", kbdfd);
	setdest("local");
	procrfork(recvproc, devs[0], Stack, RFNAMEG|RFFDG|RFNOTEG);
	if(pipe(p) < 0)
		sysfatal("pipe: %r");
	procrfork(srvproc, p, Stack, RFNAMEG|RFFDG|RFNOTEG);
	close(p[0]);
	dprint(2, "kbdfs started\n");
	if (mount(p[1], -1, mnt, MBEFORE, "") < 0)
		sysfatal("mount: %r");
	dprint(2, "kbdfs mounted\n");
	if (addr != nil && vname != nil)
		proccreate(announceproc, 0, 8*1024);
	threadexits(nil);
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.