Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

#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;
}