#include #include #include #include #include #include #include #include #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; inwname; 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.nwqidnwname){ 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= 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; }