conterm/kern/exportfs.c

822 lines
13 KiB
C

#include "u.h"
#include "lib.h"
#include "dat.h"
#include "fns.h"
#include "error.h"
typedef struct Fid Fid;
typedef struct Export Export;
typedef struct Exq Exq;
#define nil ((void*)0)
enum
{
Nfidhash = 1,
MAXRPC = MAXMSG+MAXFDATA,
MAXDIRREAD = (MAXFDATA/DIRLEN)*DIRLEN
};
struct Export
{
Ref r;
Exq* work;
Lock fidlock;
Fid* fid[Nfidhash];
Chan* root;
Chan* io;
Pgrp* pgrp;
int npart;
char part[MAXRPC];
};
struct Fid
{
Fid* next;
Fid** last;
Chan* chan;
long offset;
int fid;
int ref; /* fcalls using the fid; locked by Export.Lock */
int attached; /* fid attached or cloned but not clunked */
};
struct Exq
{
Lock lk;
int nointr;
int noresponse; /* don't respond to this one */
Exq* next;
int shut; /* has been noted for shutdown */
Export* export;
void* slave;
Fcall rpc;
char buf[MAXRPC];
};
struct
{
Lock l;
Qlock qwait;
Rendez rwait;
Exq *head; /* work waiting for a slave */
Exq *tail;
}exq;
static void exshutdown(Export*);
static void exflush(Export*, int, int);
static void exslave(void*);
static void exfree(Export*);
static void exportproc(Export*);
static char* Exauth(Export*, Fcall*);
static char* Exattach(Export*, Fcall*);
static char* Exclunk(Export*, Fcall*);
static char* Excreate(Export*, Fcall*);
static char* Exopen(Export*, Fcall*);
static char* Exread(Export*, Fcall*);
static char* Exremove(Export*, Fcall*);
static char* Exstat(Export*, Fcall*);
static char* Exwalk(Export*, Fcall*);
static char* Exwrite(Export*, Fcall*);
static char* Exwstat(Export*, Fcall*);
static char* Exversion(Export*, Fcall*);
static char *(*fcalls[Tmax])(Export*, Fcall*);
static char Enofid[] = "no such fid";
static char Eseekdir[] = "can't seek on a directory";
static char Ereaddir[] = "unaligned read of a directory";
static int exdebug = 0;
int
sysexport(int fd)
{
Chan *c;
Export *fs;
if(waserror())
return -1;
c = fdtochan(fd, ORDWR, 1, 1);
poperror();
c->flag |= CMSG;
fs = mallocz(sizeof(Export));
fs->r.ref = 1;
fs->pgrp = up->pgrp;
refinc(&fs->pgrp->r);
refinc(&up->slash->r);
fs->root = up->slash;
refinc(&fs->root->r);
fs->root = domount(fs->root);
fs->io = c;
exportproc(fs);
return 0;
}
static void
exportinit(void)
{
lock(&exq.l);
if(fcalls[Tversion] != nil) {
unlock(&exq.l);
return;
}
fmtinstall('F', fcallfmt);
fmtinstall('D', dirfmt);
fmtinstall('M', dirmodefmt);
fcalls[Tversion] = Exversion;
fcalls[Tauth] = Exauth;
fcalls[Tattach] = Exattach;
fcalls[Twalk] = Exwalk;
fcalls[Topen] = Exopen;
fcalls[Tcreate] = Excreate;
fcalls[Tread] = Exread;
fcalls[Twrite] = Exwrite;
fcalls[Tclunk] = Exclunk;
fcalls[Tremove] = Exremove;
fcalls[Tstat] = Exstat;
fcalls[Twstat] = Exwstat;
unlock(&exq.l);
}
void
exportproc(Export *fs)
{
Exq *q;
char *buf;
int n, cn, len;
exportinit();
for(;;){
q = mallocz(sizeof(Exq));
if(q == 0)
panic("no memory");
q->rpc.data = q->buf + MAXMSG;
buf = q->buf;
len = MAXRPC;
if(fs->npart) {
memmove(buf, fs->part, fs->npart);
buf += fs->npart;
len -= fs->npart;
goto chk;
}
for(;;) {
if(waserror())
goto bad;
n = (*devtab[fs->io->type].read)(fs->io, buf, len, 0);
poperror();
if(n <= 0)
goto bad;
buf += n;
len -= n;
chk:
n = buf - q->buf;
/* convM2S returns size of correctly decoded message */
cn = convM2S(q->buf, &q->rpc, n);
if(cn < 0){
iprint("bad message type in devmnt\n");
goto bad;
}
if(cn > 0) {
n -= cn;
if(n < 0){
iprint("negative size in devmnt");
goto bad;
}
fs->npart = n;
if(n != 0)
memmove(fs->part, q->buf+cn, n);
break;
}
}
if(exdebug)
iprint("export %d <- %F\n", getpid(), &q->rpc);
if(q->rpc.type == Tflush){
exflush(fs, q->rpc.tag, q->rpc.oldtag);
free(q);
continue;
}
q->export = fs;
refinc(&fs->r);
lock(&exq.l);
if(exq.head == nil)
exq.head = q;
else
exq.tail->next = q;
q->next = nil;
exq.tail = q;
unlock(&exq.l);
if(exq.qwait.first == nil) {
n = thread("exportfs", exslave, nil);
/* iprint("launch export (pid=%ux)\n", n); */
}
rendwakeup(&exq.rwait);
}
bad:
free(q);
exshutdown(fs);
exfree(fs);
}
void
exflush(Export *fs, int flushtag, int tag)
{
Exq *q, **last;
int n;
Fcall fc;
char buf[MAXMSG];
/* hasn't been started? */
lock(&exq.l);
last = &exq.head;
for(q = exq.head; q != nil; q = q->next){
if(q->export == fs && q->rpc.tag == tag){
*last = q->next;
unlock(&exq.l);
exfree(fs);
free(q);
goto Respond;
}
last = &q->next;
}
unlock(&exq.l);
/* in progress? */
lock(&fs->r.l);
for(q = fs->work; q != nil; q = q->next){
if(q->rpc.tag == tag && !q->noresponse){
lock(&q->lk);
q->noresponse = 1;
if(!q->nointr)
intr(q->slave);
unlock(&q->lk);
unlock(&fs->r.l);
goto Respond;
return;
}
}
unlock(&fs->r.l);
if(exdebug) iprint("exflush: did not find rpc: %d\n", tag);
Respond:
fc.type = Rflush;
fc.tag = flushtag;
n = convS2M(&fc, buf);
if(exdebug) iprint("exflush -> %F\n", &fc);
if(!waserror()){
(*devtab[fs->io->type].write)(fs->io, buf, n, 0);
poperror();
}
}
void
exshutdown(Export *fs)
{
Exq *q, **last;
lock(&exq.l);
last = &exq.head;
for(q = exq.head; q != nil; q = *last){
if(q->export == fs){
*last = q->next;
exfree(fs);
free(q);
continue;
}
last = &q->next;
}
unlock(&exq.l);
lock(&fs->r.l);
q = fs->work;
while(q != nil){
if(q->shut){
q = q->next;
continue;
}
q->shut = 1;
unlock(&fs->r.l);
/* postnote(q->slave, 1, "interrupted", NUser); */
iprint("postnote 2!\n");
lock(&fs->r.l);
q = fs->work;
}
unlock(&fs->r.l);
}
void
exfree(Export *fs)
{
Fid *f, *n;
int i;
if(refdec(&fs->r) != 0)
return;
closepgrp(fs->pgrp);
cclose(fs->root);
cclose(fs->io);
for(i = 0; i < Nfidhash; i++){
for(f = fs->fid[i]; f != nil; f = n){
if(f->chan != nil)
cclose(f->chan);
n = f->next;
free(f);
}
}
free(fs);
}
int
exwork(void *a)
{
return exq.head != nil;
}
void
exslave(void *a)
{
Export *fs;
Exq *q, *t, **last;
char *err;
int n;
/*
closepgrp(up->pgrp);
up->pgrp = nil;
*/
for(;;){
qlock(&exq.qwait);
rendsleep(&exq.rwait, exwork, nil);
lock(&exq.l);
q = exq.head;
if(q == nil) {
unlock(&exq.l);
qunlock(&exq.qwait);
continue;
}
exq.head = q->next;
q->slave = curthread();
unlock(&exq.l);
qunlock(&exq.qwait);
q->noresponse = 0;
q->nointr = 0;
fs = q->export;
lock(&fs->r.l);
q->next = fs->work;
fs->work = q;
unlock(&fs->r.l);
up->pgrp = q->export->pgrp;
if(exdebug > 1)
iprint("exslave dispatch %d %F\n", getpid(), &q->rpc);
if(waserror()){
iprint("exslave err %r\n");
err = up->errstr;
goto Err;
}
if(q->rpc.type >= Tmax || !fcalls[q->rpc.type])
err = "bad fcall type";
else
err = (*fcalls[q->rpc.type])(fs, &q->rpc);
poperror();
Err:;
q->rpc.type++;
if(err){
q->rpc.type = Rerror;
strncpy(q->rpc.ename, err, ERRLEN);
}
n = convS2M(&q->rpc, q->buf);
if(exdebug)
iprint("exslve %d -> %F\n", getpid(), &q->rpc);
lock(&q->lk);
if(q->noresponse == 0){
q->nointr = 1;
clearintr();
if(!waserror()){
(*devtab[fs->io->type].write)(fs->io, q->buf, n, 0);
poperror();
}
}
unlock(&q->lk);
/*
* exflush might set noresponse at this point, but
* setting noresponse means don't send a response now;
* it's okay that we sent a response already.
*/
if(exdebug > 1)
iprint("exslave %d written %d\n", getpid(), q->rpc.tag);
lock(&fs->r.l);
last = &fs->work;
for(t = fs->work; t != nil; t = t->next){
if(t == q){
*last = q->next;
break;
}
last = &t->next;
}
unlock(&fs->r.l);
exfree(q->export);
free(q);
}
iprint("exslave shut down");
threadexit();
}
Fid*
Exmkfid(Export *fs, int fid)
{
ulong h;
Fid *f, *nf;
nf = mallocz(sizeof(Fid));
if(nf == nil)
return nil;
lock(&fs->fidlock);
h = fid % Nfidhash;
for(f = fs->fid[h]; f != nil; f = f->next){
if(f->fid == fid){
unlock(&fs->fidlock);
free(nf);
return nil;
}
}
nf->next = fs->fid[h];
if(nf->next != nil)
nf->next->last = &nf->next;
nf->last = &fs->fid[h];
fs->fid[h] = nf;
nf->fid = fid;
nf->ref = 1;
nf->attached = 1;
nf->offset = 0;
nf->chan = nil;
unlock(&fs->fidlock);
return nf;
}
Fid*
Exgetfid(Export *fs, int fid)
{
Fid *f;
ulong h;
lock(&fs->fidlock);
h = fid % Nfidhash;
for(f = fs->fid[h]; f; f = f->next) {
if(f->fid == fid){
if(f->attached == 0)
break;
f->ref++;
unlock(&fs->fidlock);
return f;
}
}
unlock(&fs->fidlock);
return nil;
}
void
Exputfid(Export *fs, Fid *f)
{
lock(&fs->fidlock);
f->ref--;
if(f->ref == 0 && f->attached == 0){
if(f->chan != nil)
cclose(f->chan);
f->chan = nil;
*f->last = f->next;
if(f->next != nil)
f->next->last = f->last;
unlock(&fs->fidlock);
free(f);
return;
}
unlock(&fs->fidlock);
}
char*
Exsession(Export *e, Fcall *rpc)
{
memset(rpc->authid, 0, sizeof(rpc->authid));
memset(rpc->authdom, 0, sizeof(rpc->authdom));
memset(rpc->chal, 0, sizeof(rpc->chal));
return nil;
}
char*
Exauth(Export *e, Fcall *f)
{
return "authentication not required";
}
char*
Exattach(Export *fs, Fcall *rpc)
{
Fid *f;
f = Exmkfid(fs, rpc->fid);
if(f == nil)
return Einuse;
if(waserror()){
f->attached = 0;
Exputfid(fs, f);
return up->errstr;
}
f->chan = clone(fs->root, nil);
poperror();
rpc->qid = f->chan->qid;
Exputfid(fs, f);
return nil;
}
char*
Exclone(Export *fs, Fcall *rpc)
{
Fid *f, *nf;
if(rpc->fid == rpc->newfid)
return Einuse;
f = Exgetfid(fs, rpc->fid);
if(f == nil)
return Enofid;
nf = Exmkfid(fs, rpc->newfid);
if(nf == nil){
Exputfid(fs, f);
return Einuse;
}
if(waserror()){
Exputfid(fs, f);
Exputfid(fs, nf);
return up->errstr;
}
nf->chan = clone(f->chan, nil);
poperror();
Exputfid(fs, f);
Exputfid(fs, nf);
return nil;
}
char*
Exclunk(Export *fs, Fcall *rpc)
{
Fid *f;
f = Exgetfid(fs, rpc->fid);
if(f != nil){
f->attached = 0;
Exputfid(fs, f);
}
return nil;
}
char*
Exwalk(Export *fs, Fcall *rpc)
{
Fid *f;
Chan *c;
f = Exgetfid(fs, rpc->fid);
if(f == nil)
return Enofid;
if(waserror()){
Exputfid(fs, f);
return up->errstr;
}
c = walk(f->chan, rpc->name, 1);
if(c == nil)
error(Enonexist);
poperror();
f->chan = c;
rpc->qid = c->qid;
Exputfid(fs, f);
return nil;
}
char*
Exopen(Export *fs, Fcall *rpc)
{
Fid *f;
Chan *c;
f = Exgetfid(fs, rpc->fid);
if(f == nil)
return Enofid;
if(waserror()){
Exputfid(fs, f);
return up->errstr;
}
c = f->chan;
c = (*devtab[c->type].open)(c, rpc->mode);
poperror();
f->chan = c;
f->offset = 0;
rpc->qid = f->chan->qid;
Exputfid(fs, f);
return nil;
}
char*
Excreate(Export *fs, Fcall *rpc)
{
Fid *f;
Chan *c;
f = Exgetfid(fs, rpc->fid);
if(f == nil)
return Enofid;
if(waserror()){
Exputfid(fs, f);
return up->errstr;
}
c = f->chan;
if(c->mnt && !(c->flag&CCREATE))
c = createdir(c);
(*devtab[c->type].create)(c, rpc->name, rpc->mode, rpc->perm);
poperror();
f->chan = c;
rpc->qid = f->chan->qid;
Exputfid(fs, f);
return nil;
}
char*
Exread(Export *fs, Fcall *rpc)
{
Fid *f;
Chan *c;
long off;
int dir, n, seek;
f = Exgetfid(fs, rpc->fid);
if(f == nil)
return Enofid;
c = f->chan;
dir = c->qid.path & CHDIR;
if(dir){
rpc->count -= rpc->count%DIRLEN;
if(rpc->offset%DIRLEN || rpc->count==0){
Exputfid(fs, f);
return Ereaddir;
}
if(f->offset > rpc->offset){
Exputfid(fs, f);
return Eseekdir;
}
}
if(waserror()) {
Exputfid(fs, f);
return up->errstr;
}
for(;;){
n = rpc->count;
seek = 0;
off = rpc->offset;
if(dir && f->offset != off){
off = f->offset;
n = rpc->offset - off;
if(n > MAXDIRREAD)
n = MAXDIRREAD;
seek = 1;
}
if(dir && c->mnt != nil)
n = unionread(c, rpc->data, n);
else{
c->offset = off;
n = (*devtab[c->type].read)(c, rpc->data, n, off);
}
if(n == 0 || !seek)
break;
f->offset = off + n;
c->offset += n;
}
rpc->count = n;
poperror();
Exputfid(fs, f);
return nil;
}
char*
Exwrite(Export *fs, Fcall *rpc)
{
Fid *f;
Chan *c;
f = Exgetfid(fs, rpc->fid);
if(f == nil)
return Enofid;
if(waserror()){
Exputfid(fs, f);
return up->errstr;
}
c = f->chan;
if(c->qid.path & CHDIR)
error(Eisdir);
rpc->count = (*devtab[c->type].write)(c, rpc->data, rpc->count, rpc->offset);
poperror();
Exputfid(fs, f);
return nil;
}
char*
Exstat(Export *fs, Fcall *rpc)
{
Fid *f;
Chan *c;
f = Exgetfid(fs, rpc->fid);
if(f == nil)
return Enofid;
if(waserror()){
Exputfid(fs, f);
return up->errstr;
}
c = f->chan;
(*devtab[c->type].stat)(c, rpc->stat);
poperror();
Exputfid(fs, f);
return nil;
}
char*
Exwstat(Export *fs, Fcall *rpc)
{
Fid *f;
Chan *c;
f = Exgetfid(fs, rpc->fid);
if(f == nil)
return Enofid;
if(waserror()){
Exputfid(fs, f);
return up->errstr;
}
c = f->chan;
(*devtab[c->type].wstat)(c, rpc->stat);
poperror();
Exputfid(fs, f);
return nil;
}
char*
Exremove(Export *fs, Fcall *rpc)
{
Fid *f;
Chan *c;
f = Exgetfid(fs, rpc->fid);
if(f == nil)
return Enofid;
if(waserror()){
Exputfid(fs, f);
return up->errstr;
}
c = f->chan;
(*devtab[c->type].remove)(c);
poperror();
/*
* chan is already clunked by remove.
* however, we need to recover the chan,
* and follow sysremove's lead in making to point to root.
*/
c->type = 0;
f->attached = 0;
Exputfid(fs, f);
return nil;
}