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

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


#include <u.h>
#include <libc.h>
#include <bio.h>
#include <regexp.h>
#include <thread.h>
#include <auth.h>
#include <fcall.h>
#include <plumb.h>
#include "plumber.h"

enum
{
	Stack = 8*1024
};

typedef struct Dirtab Dirtab;
typedef struct Fid Fid;
typedef struct Sendreq Sendreq;

struct Dirtab
{
	char	*name;
	uchar	type;
	uint	qid;
	uint	perm;
};

struct Fid
{
	int	fid;
	int	busy;
	int	open;
	int	mode;
	Qid	qid;
	Dirtab	*dir;
	long	offset;		/* zeroed at beginning of each message, read or write */
	char	*writebuf;	/* partial message written so far; offset tells how much */
	Fid	*next;
};


struct	/* needed because incref() doesn't return value */
{
	Lock;
	int			ref;
} rulesref;

enum
{
	DEBUG	= 0,
	NDIR	= 4,
	Nhash	= 16,

	Qdir		= 0,
	Qrules	= 1,
	Qsend	= 2,
};

static Dirtab dir[NDIR] =
{
	{ ".",			QTDIR,	Qdir,			0500|DMDIR },
	{ "rules",		QTFILE,	Qrules,		0600 },
	{ "send",		QTFILE,	Qsend,		0200 },
	{ nil,	0, 0, 0 },
};
static int	ndir = 3;

static int	srvfd;
static int	srvclosefd;			/* rock for end of pipe to close */
static int	clockfd;
static int	clock;
static Fid	*fids[Nhash];
static QLock	readlock;
static QLock	queue;
static char	srvfile[128];
static int	messagesize = 8192+IOHDRSZ;	/* good start */

static void	fsysproc(void*);
static void 	fsysrespond(Fcall*, uchar*, char*);
static Fid*	newfid(int);

static Fcall* fsysflush(Fcall*, uchar*, Fid*);
static Fcall* fsysversion(Fcall*, uchar*, Fid*);
static Fcall* fsysauth(Fcall*, uchar*, Fid*);
static Fcall* fsysattach(Fcall*, uchar*, Fid*);
static Fcall* fsyswalk(Fcall*, uchar*, Fid*);
static Fcall* fsysopen(Fcall*, uchar*, Fid*);
static Fcall* fsyscreate(Fcall*, uchar*, Fid*);
static Fcall* fsysread(Fcall*, uchar*, Fid*);
static Fcall* fsyswrite(Fcall*, uchar*, Fid*);
static Fcall* fsysclunk(Fcall*, uchar*, Fid*);
static Fcall* fsysremove(Fcall*, uchar*, Fid*);
static Fcall* fsysstat(Fcall*, uchar*, Fid*);
static Fcall* fsyswstat(Fcall*, uchar*, Fid*);

Fcall* 	(*fcall[Tmax])(Fcall*, uchar*, Fid*) =
{
	[Tflush]	= fsysflush,
	[Tversion]	= fsysversion,
	[Tauth]		= fsysauth,
	[Tattach]	= fsysattach,
	[Twalk]		= fsyswalk,
	[Topen]		= fsysopen,
	[Tcreate]	= fsyscreate,
	[Tread]		= fsysread,
	[Twrite]	= fsyswrite,
	[Tclunk]	= fsysclunk,
	[Tremove]	= fsysremove,
	[Tstat]		= fsysstat,
	[Twstat]	= fsyswstat,
};

char	Ebadfcall[] =	"bad fcall type";
char	Eperm[] = 	"permission denied";
char	Enomem[] =	"malloc failed for buffer";
char	Enotdir[] =	"not a directory";
char	Enoexist[] =	"plumb file does not exist";
char	Eisdir[] =		"file is a directory";
char	Ebadmsg[] =	"bad plumb message format";
char Enosuchport[] ="no such plumb port";
char Enoport[] =	"couldn't find destination for message";
char	Einuse[] = 	"file already open";


static ulong
getclock(void)
{
	char buf[32];

	seek(clockfd, 0, 0);
	read(clockfd, buf, sizeof buf);
	return atoi(buf);
}

void
startfsys(void)
{
	int p[2], fd;

	fmtinstall('F', fcallfmt);
	clockfd = open("/dev/time", OREAD|OCEXEC);
	clock = getclock();
	if(pipe(p) < 0)
		error("can't create pipe: %r");
	/* 0 will be server end, 1 will be client end */
	srvfd = p[0];
	srvclosefd = p[1];
	sprint(srvfile, "/srv/plumb.%s.%d", user, getpid());
	if(putenv("plumbsrv", srvfile) < 0)
		error("can't write $plumbsrv: %r");
	fd = create(srvfile, OWRITE|OCEXEC|ORCLOSE, 0600);
	if(fd < 0)
		error("can't create /srv file: %r");
	if(fprint(fd, "%d", p[1]) <= 0)
		error("can't write /srv/file: %r");
	/* leave fd open; ORCLOSE will take care of it */


	/* fsys proc runs at another NS, that does not
	 * have /mnt/plumb/send mounted on it, to avoid
	 * deadlock
	 */
	procrfork(fsysproc, nil, Stack, RFFDG|RFNAMEG);

	close(p[0]);
	if(mount(p[1], -1, "/mnt/plumb", MBEFORE, "") < 0)
		error("can't mount /mnt/plumb: %r");
	close(p[1]);
}

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

static void
fsysproc(void*)
{
	int n;
	Fcall *t;
	Fid *f;
	uchar *buf;

	threadnotify(alarmed, 1);
	close(srvclosefd);
	srvclosefd = -1;
	t = nil;
	for(;;){
		buf = malloc(messagesize);	/* avoid memset of emalloc */
		if(buf == nil)
			error("malloc failed: %r");
		qlock(&readlock);
		n = read9pmsg(srvfd, buf, messagesize);
		if(n <= 0){
			if(n < 0)
				error("i/o error on server channel");
			threadexitsall("unmounted");
		}
		if(readlock.head == nil)	/* no other processes waiting to read; start one */
			proccreate(fsysproc, nil, Stack);
		qunlock(&readlock);
		if(t == nil)
			t = emalloc(sizeof(Fcall));
		if(convM2S(buf, n, t) != n)
			error("convert error in convM2S");
		if(DEBUG)
			fprint(2, "<= %F\n", t);
		if(fcall[t->type] == nil)
			fsysrespond(t, buf, Ebadfcall);
		else{
			if(t->type==Tversion || t->type==Tauth)
				f = nil;
			else
				f = newfid(t->fid);
			t = (*fcall[t->type])(t, buf, f);
		}
	}
}

static void
fsysrespond(Fcall *t, uchar *buf, char *err)
{
	int n;

	if(err){
		t->type = Rerror;
		t->ename = err;
	}else
		t->type++;
	if(buf == nil)
		buf = emalloc(messagesize);
	n = convS2M(t, buf, messagesize);
	if(n < 0)
		error("convert error in convS2M");
	if(write(srvfd, buf, n) != n)
		error("write error in respond");
	if(DEBUG)
		fprint(2, "=> %F\n", t);
	free(buf);
}

static
Fid*
newfid(int fid)
{
	Fid *f, *ff, **fh;

	qlock(&queue);
	ff = nil;
	fh = &fids[fid&(Nhash-1)];
	for(f=*fh; f; f=f->next)
		if(f->fid == fid)
			goto Return;
		else if(ff==nil && !f->busy)
			ff = f;
	if(ff){
		ff->fid = fid;
		f = ff;
		goto Return;
	}
	f = emalloc(sizeof *f);
	f->fid = fid;
	f->next = *fh;
	*fh = f;
    Return:
	qunlock(&queue);
	return f;
}

static uint
dostat(Dirtab *dir, uchar *buf, uint nbuf, uint clock)
{
	Dir d;

	d.qid.type = dir->type;
	d.qid.path = dir->qid;
	d.qid.vers = 0;
	d.mode = dir->perm;
	d.length = 0;	/* would be nice to do better */
	d.name = dir->name;
	d.uid = user;
	d.gid = user;
	d.muid = user;
	d.atime = clock;
	d.mtime = clock;
	return convD2M(&d, buf, nbuf);
}



static char*
sendreq(Plumbmsg* msg)
{
	char	*pack;	/* plumbpack()ed message */
	int	npack;	/* length of pack */
	char*	e = nil;
	int	fd, nw;
	char	pf[80];

	pack = plumbpack(msg, &npack);
	if (debug)
		fprint(2, "netplumb: to %s [%.*s]\n\n", msg->dst, npack, pack);
	seprint(pf, pf+sizeof(pf), "/mnt/plumb/%s", msg->dst);
	fd = open(pf, OWRITE);
	if (fd < 0){
		e = "can't open port";
		goto Ret;
	}
	nw = write(fd, pack, npack);
	close(fd);
	if (nw != npack)
		e = "write to port failed";
Ret:
	free(pack);
	return e;
}

/* We don't have the ports. Therefore, we don't know for how long
 * to hold a message for a client that is still starting up.
 * We try once soon after the client process has been started, and
 * another time in a while. Then we discard the message if we fail.
 */

static void
holdproc(void* a)
{
	Plumbmsg* m = a;
	threadsetname("holdproc");
	sleep(100);
	if (sendreq(m) != nil){
		sleep(1000);
		sendreq(m);
	}
	plumbfree(m);
	threadexits(nil);
}

char*
mdispose(Plumbmsg *m, Ruleset *rs, Exec *e)
{
	char *err;

	qlock(&queue);
	if(m->dst==nil || m->dst[0]=='\0'){
		err = Enoport;
		//fprint(2, "mdispose rs %p\n", rs);
		if(rs != nil)
			err = startup(rs, e);
	} else {
		if(err = sendreq(m))
			err = startup(rs, e);
	}
	if (e != nil && e->holdforclient)
		proccreate(holdproc, m, 8*1024);
	else
		plumbfree(m);
	freeexec(e);
	qunlock(&queue);
	return err;
}

static void
dispose(Fcall *t, uchar *buf, Plumbmsg *m, Ruleset *rs, Exec *e)
{
	char *err;

	err = mdispose(m, rs, e);
	fsysrespond(t, buf, err);
	free(t);
}

static Fcall*
fsysversion(Fcall *t, uchar *buf, Fid*)
{
	if(t->msize < 256){
		fsysrespond(t, buf, "version: message size too small");
		return t;
	}
	if(t->msize < messagesize)
		messagesize = t->msize;
	t->msize = messagesize;
	if(strncmp(t->version, "9P2000", 6) != 0){
		fsysrespond(t, buf, "unrecognized 9P version");
		return t;
	}
	t->version = "9P2000";
	fsysrespond(t, buf, nil);
	return t;
}

static Fcall*
fsysauth(Fcall *t, uchar *buf, Fid*)
{
	fsysrespond(t, buf, "plumber: authentication not required");
	return t;
}

static Fcall*
fsysattach(Fcall *t, uchar *buf, Fid *f)
{
	Fcall out;

	if(strcmp(t->uname, user) != 0){
		fsysrespond(&out, buf, Eperm);
		return t;
	}
	f->busy = 1;
	f->open = 0;
	f->qid.type = QTDIR;
	f->qid.path = Qdir;
	f->qid.vers = 0;
	f->dir = dir;
	memset(&out, 0, sizeof(Fcall));
	out.type = t->type;
	out.tag = t->tag;
	out.fid = f->fid;
	out.qid = f->qid;
	fsysrespond(&out, buf, nil);
	return t;
}

static Fcall*
fsysflush(Fcall *t, uchar *buf, Fid*)
{
	fsysrespond(t, buf, nil);
	return t;
}

static Fcall*
fsyswalk(Fcall *t, uchar *buf, Fid *f)
{
	Fcall out;
	Fid *nf;
	ulong path;
	Dirtab *d, *dir;
	Qid q;
	int i;
	uchar type;
	char *err;

	if(f->open){
		fsysrespond(t, buf, "clone of an open fid");
		return t;
	}

	nf = nil;
	if(t->fid  != t->newfid){
		nf = newfid(t->newfid);
		if(nf->busy){
			fsysrespond(t, buf, "clone to a busy fid");
			return t;
		}
		nf->busy = 1;
		nf->open = 0;
		nf->dir = f->dir;
		nf->qid = f->qid;
		f = nf;	/* walk f */
	}

	out.nwqid = 0;
	err = nil;
	dir = f->dir;
	q = f->qid;

	if(t->nwname > 0){
		for(i=0; i<t->nwname; i++){
			if((q.type & QTDIR) == 0){
				err = Enotdir;
				break;
			}
			if(strcmp(t->wname[i], "..") == 0){
				type = QTDIR;
				path = Qdir;
	Accept:
				q.type = type;
				q.vers = 0;
				q.path = path;
				out.wqid[out.nwqid++] = q;
				continue;
			}
			d = dir;
			d++;	/* skip '.' */
			for(; d->name; d++)
				if(strcmp(t->wname[i], d->name) == 0){
					type = d->type;
					path = d->qid;
					dir = d;
					goto Accept;
				}
			err = Enoexist;
			break;
		}
	}

	out.type = t->type;
	out.tag = t->tag;
	if(err!=nil || out.nwqid<t->nwname){
		if(nf)
			nf->busy = 0;
	}else if(out.nwqid == t->nwname){
		f->qid = q;
		f->dir = dir;
	}

	fsysrespond(&out, buf, err);
	return t;
}

static Fcall*
fsysopen(Fcall *t, uchar *buf, Fid *f)
{
	int m, clearrules, mode;

	clearrules = 0;
	if(t->mode & OTRUNC){
		if(f->qid.path != Qrules)
			goto Deny;
		clearrules = 1;
	}
	/* can't truncate anything, so just disregard */
	mode = t->mode & ~(OTRUNC|OCEXEC);
	/* can't execute or remove anything */
	if(mode==OEXEC || (mode&ORCLOSE))
		goto Deny;
	switch(mode){
	default:
		goto Deny;
	case OREAD:
		m = 0400;
		break;
	case OWRITE:
		m = 0200;
		break;
	case ORDWR:
		m = 0600;
		break;
	}
	if(((f->dir->perm&~(DMDIR|DMAPPEND))&m) != m)
		goto Deny;
	if(f->qid.path==Qrules && (mode==OWRITE || mode==ORDWR)){
		lock(&rulesref);
		if(rulesref.ref++ != 0){
			rulesref.ref--;
			unlock(&rulesref);
			fsysrespond(t, buf, Einuse);
			return t;
		}
		unlock(&rulesref);
	}
	if(clearrules){
		writerules(nil, 0);
		rules[0] = nil;
	}
	t->qid = f->qid;
	t->iounit = 0;
	qlock(&queue);
	f->mode = mode;
	f->open = 1;
	qunlock(&queue);
	fsysrespond(t, buf, nil);
	return t;

    Deny:
	fsysrespond(t, buf, Eperm);
	return t;
}

static Fcall*
fsyscreate(Fcall *t, uchar *buf, Fid*)
{
	fsysrespond(t, buf, Eperm);
	return t;
}

static Fcall*
fsysreadrules(Fcall *t, uchar *buf)
{
	char *p;
	int n;

	p = printrules();
	n = strlen(p);
	t->data = p;
	if(t->offset >= n)
		t->count = 0;
	else{
		t->data = p+t->offset;
		if(t->offset+t->count > n)
			t->count = n-t->offset;
	}
	fsysrespond(t, buf, nil);
	free(p);
	return t;
}

static Fcall*
fsysread(Fcall *t, uchar *buf, Fid *f)
{
	uchar *b;
	int i, n, o, e;
	uint len;
	Dirtab *d;
	uint clock;

	if(f->qid.path != Qdir){
		if(f->qid.path == Qrules)
			return fsysreadrules(t, buf);
		fsysrespond(t, buf, "internal error: unknown read port");
		return t;
	}
	o = t->offset;
	e = t->offset+t->count;
	clock = getclock();
	b = malloc(messagesize-IOHDRSZ);
	if(b == nil){
		fsysrespond(t, buf, Enomem);
		return t;
	}
	n = 0;
	d = dir;
	d++;	/* first entry is '.' */
	for(i=0; d->name!=nil && i<e; i+=len){
		len = dostat(d, b+n, messagesize-IOHDRSZ-n, clock);
		if(len <= BIT16SZ)
			break;
		if(i >= o)
			n += len;
		d++;
	}
	t->data = (char*)b;
	t->count = n;
	fsysrespond(t, buf, nil);
	free(b);
	return t;
}

static Fcall*
fsyswrite(Fcall *t, uchar *buf, Fid *f)
{
	Plumbmsg *m;
	int i, n;
	long count;
	char *data;
	Exec *e;

	switch((int)f->qid.path){
	case Qdir:
		fsysrespond(t, buf, Eisdir);
		return t;
	case Qrules:
		clock = getclock();
		fsysrespond(t, buf, writerules(t->data, t->count));
		return t;
	case Qsend:
		if(f->offset == 0){
			data = t->data;
			count = t->count;
		}else{
			/* partial message already assembled */
			f->writebuf = erealloc(f->writebuf, f->offset + t->count);
			memmove(f->writebuf+f->offset, t->data, t->count);
			data = f->writebuf;
			count = f->offset+t->count;
		}
		m = plumbunpackpartial(data, count, &n);
		if(m == nil){
			if(n == 0){
				f->offset = 0;
				free(f->writebuf);
				f->writebuf = nil;
				fsysrespond(t, buf, Ebadmsg);
				return t;
			}
			/* can read more... */
			if(f->offset == 0){
				f->writebuf = emalloc(t->count);
				memmove(f->writebuf, t->data, t->count);
			}
			/* else buffer has already been grown */
			f->offset += t->count;
			fsysrespond(t, buf, nil);
			return t;
		}
		/* release partial buffer */
		f->offset = 0;
		free(f->writebuf);
		f->writebuf = nil;
		for(i=0; rules[i]; i++)
			if((e=matchruleset(m, rules[i])) != nil){
				dispose(t, buf, m, rules[i], e);
				return nil;
			}
		if(m->dst != nil){
			dispose(t, buf, m, nil, nil);
			return nil;
		}
		fsysrespond(t, buf, "no matching plumb rule");
		return t;
	}
	fsysrespond(t, buf, "internal error: write to unknown file");
	return t;
}

static Fcall*
fsysstat(Fcall *t, uchar *buf, Fid *f)
{
	t->stat = emalloc(messagesize-IOHDRSZ);
	t->nstat = dostat(f->dir, t->stat, messagesize-IOHDRSZ, clock);
	fsysrespond(t, buf, nil);
	free(t->stat);
	t->stat = nil;
	return t;
}

static Fcall*
fsyswstat(Fcall *t, uchar *buf, Fid*)
{
	fsysrespond(t, buf, Eperm);
	return t;
}

static Fcall*
fsysremove(Fcall *t, uchar *buf, Fid*)
{
	fsysrespond(t, buf, Eperm);
	return t;
}

static Fcall*
fsysclunk(Fcall *t, uchar *buf, Fid *f)
{
	Dirtab *d;

	qlock(&queue);
	if(f->open){
		d = f->dir;
		if(d->qid==Qrules && (f->mode==OWRITE || f->mode==ORDWR)){
			/*
			 * just to be sure last rule is parsed; error messages will be lost, though,
			 * unless last write ended with a blank line
			 */
			writerules(nil, 0);
			lock(&rulesref);
			rulesref.ref--;
			unlock(&rulesref);
		}
	}
	f->busy = 0;
	f->open = 0;
	f->offset = 0;
	if(f->writebuf != nil){
		free(f->writebuf);
		f->writebuf = nil;
	}
	qunlock(&queue);
	fsysrespond(t, buf, nil);
	return t;
}

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.