Plan 9 from Bell Labs’s /usr/web/sources/extra/9hist/port/devmux.c

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


## diffname port/devmux.c 1991/1114
## diff -e /dev/null /n/bootesdump/1991/1114/sys/src/9/port/devmux.c
0a
#include	"u.h"
#include	"lib.h"
#include	"mem.h"
#include	"dat.h"
#include	"fns.h"
#include	"errno.h"
#include	"fcall.h"

#include	"devtab.h"

typedef struct Mux Mux;
typedef struct Con Con;
typedef struct Dtq Dtq;

enum
{
	Qdir	= 0,
	Qhead,
	Qclone,
};

enum
{
	Nmux	=	20,
};

struct Dtq
{
	QLock	rd;
	Rendez	r;
	Lock	listlk;
	Block	*list;
	int	ndelim;
};

struct Con
{
	int	ref;
	char	user[NAMELEN];
	ulong	perm;
	Dtq	conq;
};

struct Mux
{
	Ref;
	char	name[NAMELEN];
	char	user[NAMELEN];
	ulong	perm;
	int	headopen;
	Dtq	headq;
	Con	connects[Nmux];
};

Mux	*muxes;

ulong	muxreadq(Mux *m, Dtq*, char*, ulong);
void	muxwriteq(Dtq*, char*, long, int, int);

#define NMUX(c)		(((c->qid.path>>8)&0xffff)-1)
#define NQID(m, c)	(Qid){(m)<<8|(c)&0xff, 0}
#define NCON(c)		(c->qid.path&0xff)

int
muxgen(Chan *c, Dirtab *tab, int ntab, int s, Dir *dp)
{
	Mux *m;
	int mux;
	Con *cm;
	char buf[10];

	if(c->qid.path == CHDIR) {
		if(s >= conf.nmux)
			return -1;

		m = &muxes[s];
		if(m->name[0] == '\0')
			return 0;
		devdir(c, (Qid){CHDIR|((s+1)<<8), 0}, m->name, 0, m->user, m->perm, dp);
		return 1;
	}

	if(s >= Nmux+2)
		return -1;

	mux = NMUX(c);
	m = &muxes[mux];
	switch(s) {
	case Qhead:
		devdir(c, NQID(mux, Qhead), "head", m->headq.ndelim, m->user, m->perm, dp);
		break;
	case Qclone:
		devdir(c, NQID(mux, Qclone), "clone", 0, m->user, m->perm, dp);
		break;
	default:
		cm = &m->connects[s-Qclone];
		if(cm->ref == 0)
			return 0;
		sprint(buf, "%d", s-Qclone);
		devdir(c, NQID(mux, Qclone+s), buf, cm->conq.ndelim, cm->user, cm->perm, dp);
		break;
	}
	return 1;
}

void
muxinit(void)
{
}

void
muxreset(void)
{
	muxes = ialloc(conf.nmux*sizeof(Mux), 0);
}

Chan *
muxattach(char *spec)
{
	Chan *c;

	c = devattach('m', spec);

	c->qid.path = CHDIR|Qdir;
	return c;
}

Chan *
muxclone(Chan *c, Chan *nc)
{
	int ncon;
	Mux *m;

	if(c->qid.path == CHDIR)
		return devclone(c, nc);;

	m = &muxes[NMUX(c)];
	ncon = NCON(c);

	c = devclone(c, nc);
	switch(ncon) {
	case Qhead:
		incref(m);
		break;
	case Qclone:
		break;
	default:
		lock(m);
		m->connects[ncon].ref++;
		m->ref++;
		unlock(m);
	}
	return c;
}

int
muxwalk(Chan *c, char *name)
{
	if(strcmp(name, "..") == 0) {
		c->qid.path = CHDIR|Qdir;
		return 1;
	}

	return devwalk(c, name, 0, 0, muxgen);
}

void
muxstat(Chan *c, char *db)
{
	devstat(c, db, 0, 0, muxgen);
}

Chan *
muxopen(Chan *c, int omode)
{
	Mux *m;
	Con *cm, *e;

	if(c->qid.path & CHDIR)
		return devopen(c, omode, 0, 0, muxgen);

	m = &muxes[NMUX(c)];
	switch(NCON(c)) {
	case Qhead:
		if(m->headopen)
			errors("server channel busy");

		c = devopen(c, omode, 0, 0,muxgen);
		m->headopen = 1;
		incref(m);
		break;
	case Qclone:
		if(m->headopen == 0)
			errors("server shutdown");

		c = devopen(c, omode, 0, 0, muxgen);
		lock(m);
		cm = m->connects;
		for(e = &cm[Nmux]; cm < e; cm++)
			if(cm->ref == 0)
				break;
		if(cm == e) {
			unlock(m);
			errors("all cannels busy");
		}
		cm->ref++;
		m->ref++;
		unlock(m);
		strncpy(cm->user, u->p->user, NAMELEN);
		cm->perm = 0600;
		c->qid = NQID(NMUX(c), cm-m->connects);
		break;
	default:
		c = devopen(c, omode, 0, 0,muxgen);
		cm = &m->connects[NCON(c)];
		cm->ref++;
		incref(m);
		break;
	}

	return c;
}

void
muxcreate(Chan *c, char *name, int omode, ulong perm)
{
	int n;
	Mux *m, *e;

	if(c->qid.path != CHDIR)
		error(Eperm);

	omode = openmode(omode);

	m = muxes;
	for(e = &m[conf.nmux]; m < e; m++) {
		if(m->ref == 0 && canlock(m)) {
			if(m->ref != 0) {
				unlock(m);
				continue;
			}
			m->ref++;
			break;
		}	
	}

	if(m == e)
		errors("no multiplexors");

	strncpy(m->name, name, NAMELEN);
	strncpy(m->user, u->p->user, NAMELEN);
	m->perm = perm&~CHDIR;
	unlock(m);

	n = m - muxes;
	c->qid = (Qid){CHDIR|(n+1)<<8, 0};
	c->flag |= COPEN;
	c->mode = omode;
}

void
muxremove(Chan *c)
{
	Mux *m;

	if(c->qid.path == CHDIR || (c->qid.path&CHDIR) == 0)
		error(Eperm);

	m = &muxes[NMUX(c)];
	if(strcmp(u->p->user, m->user) != 0)
		errors("not owner");

	m->name[0] = '\0';
}

void
muxwstat(Chan *c, char *db)
{
	Mux *m;
	Dir d;
	int nc;

	if(c->qid.path == CHDIR)
		error(Eperm);

	m = &muxes[NMUX(c)];
	if(strcmp(u->p->user, m->user) != 0)
		errors("not owner");

	convM2D(db, &d);
	d.mode &= 0777;
	if(c->qid.path&CHDIR) {
		strcpy(m->name, d.name);
		strcpy(m->user, d.uid);
		m->perm = d.mode;
		return;
	}
	nc = NCON(c);
	switch(nc) {
	case Qclone:
		error(Eperm);
	case Qhead:
		m->perm = d.mode;
		break;
	default:
		m->connects[nc].perm = d.mode;
		break;
	}
}

void
muxclose(Chan *c)
{
	Block *f1, *f2;
	Con *cm, *e;
	Mux *m;
	int nc;

	if(c->qid.path == CHDIR)
		return;

	m = &muxes[NMUX(c)];
	nc = NCON(c);
	f1 = 0;
	f2 = 0;
	switch(nc) {
	case Qhead:
		m->headopen = 0;
		cm = m->connects;
		for(e = &cm[Nmux]; cm < e; cm++)
			if(cm->ref)
				wakeup(&cm->conq.r);
		lock(m);
		if(--m->ref == 0) {
			f1 = m->headq.list;
			m->headq.list = 0;
		}
		unlock(m);
		break;
	case Qclone:
		panic("muxclose");
	default:
		lock(m);
		cm = &m->connects[nc];
		if(--cm->ref == 0) {
			f1 = cm->conq.list;
			cm->conq.list = 0;		
		}
		if(--m->ref == 0) {
			m->name[0] = '\0';
			f2 = m->headq.list;
			m->headq.list = 0;
		}
		unlock(m);
	}
	if(f1)
		freeb(f1);
	if(f2)
		freeb(f2);
}

long
muxread(Chan *c, void *va, long n, ulong offset)
{
	Mux *m;
	Con *cm;
	int bread;

	if(c->qid.path & CHDIR)
		return devdirread(c, va, n, 0, 0, muxgen);

	m = &muxes[NMUX(c)];
	switch(NCON(c)) {
	case Qhead:
		bread = muxreadq(m, &m->headq, va, n);
		break;
	case Qclone:
		error(Eperm);
	default:
		cm = &m->connects[NCON(c)];
		bread = muxreadq(m, &cm->conq, va, n);
		break;
	}

	return bread;
}

Con *
muxhdr(Mux *m, char *h)
{
	Con *c;

	if(h[0] != Tmux)
		error(Ebadmsg);

	c = &m->connects[h[1]];
	if(c < m->connects || c > &m->connects[Nmux])	
		error(Ebadmsg);

	if(c->ref == 0)
		return 0;

	return c;
}

long
muxwrite(Chan *c, void *va, long n, ulong offset)
{
	Mux *m;
	Con *cm;
	int muxid;
	Block *f, *bp;
	char *a, hdr[2];

	if(c->qid.path & CHDIR)
		error(Eisdir);

	m = &muxes[NMUX(c)];
	switch(NCON(c)) {
	case Qclone:
		error(Eperm);
	case Qhead:
		if(n < 2)
			error(Ebadmsg);

		a = (char*)va;
		memmove(hdr, a, sizeof(hdr));
		cm = muxhdr(m, hdr);
		if(cm == 0)
			error(Ehungup);

		muxwriteq(&cm->conq, a+sizeof(hdr), n-sizeof(hdr), 0, 0);
		break;
	default:
		if(m->headopen == 0)
			error(Ehungup);

		muxid = NCON(c);
		muxwriteq(&m->headq, va, n, 1, muxid);
		break;
	}

	return n;
}

void
muxwriteq(Dtq *q, char *va, long n, int addid, int muxid)
{
	Block *head, *tail, *bp;
	ulong l;

	head = 0;
	SET(tail);
	if(waserror()) {
		if(head)
			freeb(head);
		nexterror();
	}

	while(n) {
		bp = allocb(n);
		bp->type = M_DATA;
		l = bp->lim - bp->wptr;
		memmove(bp->wptr, va, l);	/* Interruptable thru fault */
		va += l;
		bp->wptr += l;
		n -= l;
		if(head == 0)
			head = bp;
		else
			tail->next = bp;
		tail = bp;
	}
	poperror();
	tail->flags |= S_DELIM;
	lock(&q->listlk);
	for(tail = q->list; tail->next; tail = tail->next)
		;
	tail->next = head;
	q->ndelim++;
	unlock(&q->listlk);
}

int
nodata(Dtq *q)
{
	int n;

	lock(&q->listlk);
	n = q->ndelim;
	unlock(&q->listlk);
	return n;
}

ulong
muxreadq(Mux *m, Dtq *q, char *va, ulong n)
{
	int l, nread, gotdelim;
	Block *bp;

	qlock(&q->rd);
	bp = 0;
	if(waserror()) {
		qunlock(&q->rd);
		lock(&q->listlk);
		if(bp) {
			bp->next = q->list;
			q->list = bp;
		}
		unlock(&q->listlk);
		nexterror();
	}
	while(nodata(q))
		sleep(&q->r, nodata, q);

	if(m->headopen == 0)
		errors("server shutdown");

	nread = 0;
	while(n) {
		lock(&q->listlk);
		bp = q->list;
		q->list = bp->next;
		bp->next = 0;
		unlock(&q->listlk);

		l = BLEN(bp);
		if(n < l)
			n = l;
		memmove(va, bp->rptr, l);	/* Interruptable thru fault */
		va += l;
		bp->rptr += l;
		n -= l;
		gotdelim = bp->flags&S_DELIM;
		lock(&q->listlk);
		if(bp->rptr != bp->wptr) {
			bp->next = q->list;
			q->list = bp;
		}
		else if(gotdelim)
			q->ndelim--;
		unlock(&q->listlk);
		if(bp->rptr == bp->wptr)
			freeb(bp);
		if(gotdelim)
			break;
	}
	qunlock(&q->rd);
	return nread;
}
.
## diffname port/devmux.c 1991/1115
## diff -e /n/bootesdump/1991/1114/sys/src/9/port/devmux.c /n/bootesdump/1991/1115/sys/src/9/port/devmux.c
549a
}

Block *
muxclq(Dtq *q)
{
	Block *f;

	f = q->list;
	q->list = 0;
	q->nb = 0;
	q->ndelim = 0;
	return f;
.
548a
	poperror();
	if(q->nb < Flowctl)
		wakeup(&q->flowr);
.
547a
	q->nb -= nread;
	unlock(&q->listlk);
	if(f1)
		freeb(f1);
.
546a
		}
.
542,545d
540c
		if(bp->flags&S_DELIM) {
.
536c
		if(bp->rptr == bp->wptr)
			f1 = bp;
		else {
.
534c
		nread += l;
.
528,529c
		if(l > n)
			l = n;
.
526c
		if(f1) {
			freeb(f1);
			f1 = 0;
		}
.
521d
519a
	f1 = 0;
	lock(&q->listlk);
.
516,518d
513,514c
	while(!havedata(q)) {
		sleep(&q->r, havedata, q);
		if(m->headopen == 0)
			errors("server shutdown");
	}
.
499c
	Block *bp, *f1;
.
486a
	return q->nb < Flowctl;
}

void
muxflow(Dtq *q)
{
	qlock(&q->flow);
	if(waserror()) {
		qunlock(&q->flow);
		nexterror();
	}
	sleep(&q->flowr, muxflw, q);
	poperror();
	qunlock(&q->flow);
}

int
havedata(Dtq *q)
{
.
485c
muxflw(Dtq *q)
.
481a
	wakeup(&q->r);
.
480a
	q->nb += bwrite;
.
477,479c
	if(q->list == 0)
		q->list = head;
	else {
		for(tail = q->list; tail->next; tail = tail->next)
			;
		tail->next = head;
	}
.
475a

	if(q->nb > Flowctl)
		muxflow(q);

.
466a
		bwrite += l;
.
463a
		if(l > n)
			l = n;
.
461,462c
		if(addid) {
			bp = allocb(n+3);
			bp->wptr[0] = Tmux;
			bp->wptr[1] = muxid;
			bp->wptr[2] = 0;
			bp->wptr += 3;
			bwrite += 3;
			addid = 0;
		}
		else
			bp = allocb(n);
.
459a
	bwrite = 0;
.
450c
	ulong l, bwrite;
.
438c
		muxid = NCON(c)-Qoffset;
.
417a
	if(n > Maxmsg)
		error(Etoobig);

.
415c
	if(c->qid.path&CHDIR)
.
413c
	char *a, hdr[3];
.
393c
	if(h[0] != Tmux || h[2] != 0)
.
380c
		cm = &m->connects[NCON(c)-Qoffset];
.
344,353c
		cm = &m->connects[nc-Qoffset];
		if(--cm->ref == 0)
			f1 = muxclq(&cm->conq);
		if(--m->ref == 0)
			f1 = muxclq(&m->headq);
.
341c
		break;
.
334,337c
		if(--m->ref == 0)
			f1 = muxclq(&m->headq);
.
321a
	if((c->flag&COPEN) == 0)
		return;

.
319c
	if(c->qid.path&CHDIR)
.
306c
		m->connects[nc-Qoffset].perm = d.mode;
.
294d
237c
		if(m->name[0] == '\0' && m->ref == 0 && canlock(m)) {
.
217c
		m->ref++;
		unlock(m);
.
214,215c
		c = devopen(c, omode, 0, 0, muxgen);
		cm = &m->connects[NCON(c)-Qoffset];
		lock(m);
.
211c
		c->qid = NQID(mux, (cm-m->connects)+Qoffset);
.
206c
		cm->ref = 1;
.
187,190c
		}
.
185a
			ok = 0;
		else {
			ok = 1;
			m->headopen = 1;
			m->ref++;
		}
		unlock(m);
		if(!ok) {
			c->flag &= ~COPEN;
.
184a
		c = devopen(c, omode, 0, 0, muxgen);
		lock(m);
.
182c
	mux = NMUX(c);
	m = &muxes[mux];
.
177a
	int mux, ok;
.
149c
		m->connects[ncon-Qoffset].ref++;
.
140a

	if((c->flag&COPEN) == 0)
		return c;

.
139d
123d
99,100c
		sprint(buf, "%d", nq);
		devdir(c, NQID(mux, Qoffset+s), buf, cm->conq.nb, cm->user, cm->perm, dp);
.
96c
		nq = s-Qoffset;
		cm = &m->connects[nq];
.
90c
		devdir(c, NQID(mux, Qhead), "head", m->headq.nb, m->user, m->perm, dp);
.
87a

.
79c
		devdir(c, DQID(s), m->name, 0, m->user, m->perm, dp);
.
70a
	int nq;
.
61c
#define NQID(m, c)	(Qid){(m+1)<<8|(c)&0xff, 0}
#define DQID(m)		(Qid){(m+1)<<8|CHDIR, 0}
.
58a
void	muxflow(Dtq*);
Block  *muxclq(Dtq *q);
.
46a
	int	type;
.
33a
	int	nb;
	QLock	flow;
	Rendez	flowr;
.
24a
	Maxmsg	=	(32*1024),
	Flowctl	=	Maxmsg/2,
.
19a
	Qoffset,
.
18c
	Qhead	= 0,
.
## diffname port/devmux.c 1991/1117
## diff -e /n/bootesdump/1991/1115/sys/src/9/port/devmux.c /n/bootesdump/1991/1117/sys/src/9/port/devmux.c
577a
		qunlock(&q->rd);
.
571d
478c
	tail = 0;
.
443c
	if(m->srv) {
		memmove(buf, va, n);		/* so we can NUL-terminate */
		buf[n] = 0;
		fd = strtoul(buf, 0, 0);
		fdtochan(fd, -1, 0);		/* error check */
		m->c = u->p->fgrp->fd[fd];
		incref(m->c);
		return n;
	}
.
440c
	m = &muxes[NMUX(c)];
	if(n > Maxmsg || (m->srv && n >= sizeof(buf)))
.
435c
	char *a, hdr[3], buf[10];
.
433c
	int muxid, fd;
.
351d
348c
	m = &muxes[NMUX(c)];
	if(!(c->flag&COPEN) || m->srv)
.
319c
	if(c->qid.path&CHDIR || m->srv) {
.
300a
	unlock(m);
	if(srv)
		close(srv);

	muxclose(c);
.
299a
	srv = 0;
	lock(m);
	if(m->srv) {
		srv = m->c;
		m->c = 0;
	}
.
296a
	if((c->qid.path&CHDIR) == 0 && m->srv == 0)
		error(Eperm);
		
.
293c
	if(c->qid.path == CHDIR) 
.
291a
	Chan *srv;
.
283c
	c->qid = (Qid){(CHDIR&perm)|(n+1)<<8, 0};
.
279a
	m->srv = 1;
	if(perm&CHDIR)
		m->srv = 0;
.
269d
247c
	unlock(m);
	poperror();
.
244d
241d
239d
233d
230d
227,228c
		if(cm == e)
.
221,222d
215c
		m->headopen = 1;
		m->ref++;
.
205,213d
202,203d
199a
	lock(m);
	if(waserror()) {
		c->flag &= ~COPEN;
		unlock(m);
		nexterror();
	}
	if(m->srv) {
		if(m->c == 0)
			error(Eshutdown);
		new = m->c;
		incref(new);
		unlock(m);
		poperror();
		close(c);
		return new;
	}
.
196c
		return c;
.
194a
	c = devopen(c, omode, 0, 0, muxgen);
.
193a
	Chan *new;
.
90c
		if(m->srv)
			devdir(c, NQID(s, 0), m->name, 0, m->user, m->perm, dp);
		else
			devdir(c, DQID(s), m->name, 0, m->user, m->perm, dp);
.
63d
59a
	Chan	*c;
.
53c
	int	srv;
.
25c
	Nmux	=	32,
.
## diffname port/devmux.c 1992/0111
## diff -e /n/bootesdump/1991/1117/sys/src/9/port/devmux.c /n/bootesdump/1992/0111/sys/src/9/port/devmux.c
6c
#include	"../port/error.h"
.
## diffname port/devmux.c 1992/0113
## diff -e /n/bootesdump/1992/0111/sys/src/9/port/devmux.c /n/bootesdump/1992/0113/sys/src/9/port/devmux.c
613c
			errors(Emuxshutdown);
.
479c
			error(Emuxmsg);
.
441c
		error(Emuxmsg);
.
437c
		error(Emuxmsg);
.
335,336c
	if(strncmp(u->p->user, m->user, NAMELEN))
		error(Eperm);
.
307,308c
	if(strncmp(u->p->user, m->user, NAMELEN))
		error(Eperm);
.
278c
		error(Enomux);
.
237c
			error(Emuxbusy);
.
230c
			error(Emuxshutdown);
.
224c
			error(Einuse);
.
## diffname port/devmux.c 1992/0114
## diff -e /n/bootesdump/1992/0113/sys/src/9/port/devmux.c /n/bootesdump/1992/0114/sys/src/9/port/devmux.c
613c
			error(Emuxshutdown);
.
278c
		exhausted("multiplexers");
.
## diffname port/devmux.c 1992/0201
## diff -e /n/bootesdump/1992/0114/sys/src/9/port/devmux.c /n/bootesdump/1992/0201/sys/src/9/port/devmux.c
138c
	c = devattach('s', spec);
.
## diffname port/devmux.c 1992/0303
## diff -e /n/bootesdump/1992/0201/sys/src/9/port/devmux.c /n/bootesdump/1992/0303/sys/src/9/port/devmux.c
356,402d
294a
muxclose(Chan *c)
{
	Block *f1, *f2;
	Con *cm, *e;
	Mux *m;
	int nc;

	if(c->qid.path&CHDIR)
		return;

	m = &muxes[NMUX(c)];
	if(!(c->flag&COPEN) || m->srv)
		return;

	nc = NCON(c);
	f1 = 0;
	f2 = 0;
	switch(nc) {
	case Qhead:
		m->headopen = 0;
		cm = m->connects;
		for(e = &cm[Nmux]; cm < e; cm++)
			if(cm->ref)
				wakeup(&cm->conq.r);
		lock(m);
		if(--m->ref == 0)
			f1 = muxclq(&m->headq);
		unlock(m);
		break;
	case Qclone:
		break;
	default:
		lock(m);
		cm = &m->connects[nc-Qoffset];
		if(--cm->ref == 0)
			f1 = muxclq(&cm->conq);
		if(--m->ref == 0)
			f1 = muxclq(&m->headq);
		unlock(m);
	}
	if(f1)
		freeb(f1);
	if(f2)
		freeb(f2);
}

void
.
## diffname port/devmux.c 1992/0321
## diff -e /n/bootesdump/1992/0303/sys/src/9/port/devmux.c /n/bootesdump/1992/0321/sys/src/9/port/devmux.c
2c
#include	"../port/lib.h"
.
## diffname port/devmux.c 1992/0622
## diff -e /n/bootesdump/1992/0321/sys/src/9/port/devmux.c /n/bootesdump/1992/0622/sys/src/9/port/devmux.c
130c
	muxes = xalloc(conf.nmux*sizeof(Mux));
.
## diffname port/devmux.c 1992/0630
## diff -e /n/bootesdump/1992/0622/sys/src/9/port/devmux.c /n/bootesdump/1992/0630/sys/src/9/port/devmux.c
267c
	for(e = &m[Nmuxchan]; m < e; m++) {
.
130c
	muxes = xalloc(Nmuxchan*sizeof(Mux));
.
84c
		if(s >= Nmuxchan)
.
23,24c
	Nmuxchan=	64,
.
21d
7d
## diffname port/devmux.c 1992/0711
## diff -e /n/bootesdump/1992/0630/sys/src/9/port/devmux.c /n/bootesdump/1992/0711/sys/src/9/port/devmux.c
592c
	int l, nread;
.
454a
	USED(offset);
.
452d
408a
	USED(offset);
.
193c
	int mux;
.
79a
	USED(tab, ntab);
.
## diffname port/devmux.c 1992/0825
## diff -e /n/bootesdump/1992/0711/sys/src/9/port/devmux.c /n/bootesdump/1992/0825/sys/src/9/port/devmux.c
468c
		fdtochan(fd, -1, 0, 0);		/* error check */
.
## diffname port/devmux.c 1993/0501 # deleted
## diff -e /n/bootesdump/1992/0825/sys/src/9/port/devmux.c /n/fornaxdump/1993/0501/sys/src/brazil/port/devmux.c
1,668d

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.