Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

/* cdfs - CD, DVD and BD reader and writer file system */
#include <u.h>
#include <libc.h>
#include <auth.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include <disk.h>
#include "dat.h"
#include "fns.h"

typedef struct Aux Aux;
struct Aux {
        int     doff;
        Otrack  *o;
};

ulong   getnwa(Drive *);

static void checktoc(Drive*);

int vflag;

static Drive *drive;
static int nchange;

enum {
        Qdir = 0,
        Qctl = 1,
        Qwa = 2,
        Qwd = 3,
        Qtrack = 4,
};

char*
geterrstr(void)
{
        static char errbuf[ERRMAX];

        rerrstr(errbuf, sizeof errbuf);
        return errbuf;
}

void*
emalloc(ulong sz)
{
        void *v;

        v = mallocz(sz, 1);
        if(v == nil)
                sysfatal("malloc %lud fails", sz);
        return v;
}

static void
fsattach(Req *r)
{
        char *spec;

        spec = r->ifcall.aname;
        if(spec && spec[0]) {
                respond(r, "invalid attach specifier");
                return;
        }

        checktoc(drive);
        r->fid->qid = (Qid){Qdir, drive->nchange, QTDIR};
        r->ofcall.qid = r->fid->qid;
        r->fid->aux = emalloc(sizeof(Aux));
        respond(r, nil);
}

static char*
fsclone(Fid *old, Fid *new)
{
        Aux *na;

        na = emalloc(sizeof(Aux));
        *na = *((Aux*)old->aux);
        if(na->o)
                na->o->nref++;
        new->aux = na;
        return nil;
}

static char*
fswalk1(Fid *fid, char *name, Qid *qid)
{
        int i;

        checktoc(drive);
        switch((ulong)fid->qid.path) {
        case Qdir:
                if(strcmp(name, "..") == 0) {
                        *qid = (Qid){Qdir, drive->nchange, QTDIR};
                        return nil;
                }
                if(strcmp(name, "ctl") == 0) {
                        *qid = (Qid){Qctl, 0, 0};
                        return nil;
                }
                if(strcmp(name, "wa") == 0 && drive->writeok &&
                    (drive->mmctype == Mmcnone ||
                     drive->mmctype == Mmccd)) {
                        *qid = (Qid){Qwa, drive->nchange, QTDIR};
                        return nil;
                }
                if(strcmp(name, "wd") == 0 && drive->writeok) {
                        *qid = (Qid){Qwd, drive->nchange, QTDIR};
                        return nil;
                }
                for(i=0; i<drive->ntrack; i++)
                        if(strcmp(drive->track[i].name, name) == 0)
                                break;
                if(i == drive->ntrack)
                        return "file not found";
                *qid = (Qid){Qtrack+i, 0, 0};
                return nil;

        case Qwa:
        case Qwd:
                if(strcmp(name, "..") == 0) {
                        *qid = (Qid){Qdir, drive->nchange, QTDIR};
                        return nil;
                }
                return "file not found";
        default:        /* bug: lib9p could handle this */
                return "walk in non-directory";
        }
}

static void
fscreate(Req *r)
{
        int omode, type;
        Otrack *o;
        Fid *fid;

        fid = r->fid;
        omode = r->ifcall.mode;
        
        if(omode != OWRITE) {
                respond(r, "bad mode (use OWRITE)");
                return;
        }

        switch((ulong)fid->qid.path) {
        case Qdir:
        default:
                respond(r, "permission denied");
                return;

        case Qwa:
                if (drive->mmctype != Mmcnone &&
                    drive->mmctype != Mmccd) {
                        respond(r, "audio supported only on cd");
                        return;
                }
                type = TypeAudio;
                break;

        case Qwd:
                type = TypeData;
                break;
        }

        if((drive->cap & Cwrite) == 0) {
                respond(r, "drive does not write");
                return;
        }

        o = drive->create(drive, type);
        if(o == nil) {
                respond(r, geterrstr());
                return;
        }
        drive->nchange = -1;
        checktoc(drive);        /* update directory info */
        o->nref = 1;
        ((Aux*)fid->aux)->o = o;

        fid->qid = (Qid){Qtrack+(o->track - drive->track), drive->nchange, 0};
        r->ofcall.qid = fid->qid;
        respond(r, nil);
}

static void
fsremove(Req *r)
{
        switch((ulong)r->fid->qid.path){
        case Qwa:
        case Qwd:
                if(drive->fixate(drive) < 0)
                        respond(r, geterrstr());
// let us see if it can figure this out:        drive->writeok = No;    
                else
                        respond(r, nil);
                checktoc(drive);
                break;
        default:
                respond(r, "permission denied");
                break;
        }
}

/* result is one word, so it can be used as a uid in Dir structs */
char *
disctype(Drive *drive)
{
        char *type, *rw, *laysfx;

        rw = laysfx = "";
        switch (drive->mmctype) {
        case Mmccd:
                type = "cd-";
                break;
        case Mmcdvdminus:
        case Mmcdvdplus:
                type = drive->dvdtype;
                break;
        case Mmcbd:
                type = "bd-";
                if (drive->laysfx)
                        laysfx = drive->laysfx;
                break;
        case Mmcnone:
                type = "no-disc";
                break;
        default:
                type = "**GOK**";               /* traditional */
                break;
        }
        if (drive->mmctype != Mmcnone && drive->dvdtype == nil)
                if (drive->erasable == Yes)
                        rw = drive->mmctype == Mmcbd? "re": "rw";
                else if (drive->recordable == Yes)
                        rw = "r";
                else
                        rw = "rom";
        return smprint("%s%s%s", type, rw, laysfx);
}

int
fillstat(ulong qid, Dir *d)
{
        char *ty;
        Track *t;
        static char buf[32];

        nulldir(d);
        d->type = L'M';
        d->dev = 1;
        d->length = 0;
        ty = disctype(drive);
        strncpy(buf, ty, sizeof buf);
        free(ty);
        d->uid = d->gid = buf;
        d->muid = "";
        d->qid = (Qid){qid, drive->nchange, 0};
        d->atime = time(0);
        d->mtime = drive->changetime;

        switch(qid){
        case Qdir:
                d->name = "/";
                d->qid.type = QTDIR;
                d->mode = DMDIR|0777;
                break;

        case Qctl:
                d->name = "ctl";
                d->mode = 0666;
                break;

        case Qwa:
                if(drive->writeok == No ||
                    drive->mmctype != Mmcnone &&
                    drive->mmctype != Mmccd)
                        return 0;
                d->name = "wa";
                d->qid.type = QTDIR;
                d->mode = DMDIR|0777;
                break;

        case Qwd:
                if(drive->writeok == No)
                        return 0;
                d->name = "wd";
                d->qid.type = QTDIR;
                d->mode = DMDIR|0777;
                break;

        default:
                if(qid-Qtrack >= drive->ntrack)
                        return 0;
                t = &drive->track[qid-Qtrack];
                if(strcmp(t->name, "") == 0)
                        return 0;
                d->name = t->name;
                d->mode = t->mode;
                d->length = t->size;
                break;
        }
        return 1;
}

static ulong 
cddb_sum(int n)
{
        int ret;
        ret = 0;
        while(n > 0) {
                ret += n%10;
                n /= 10;
        }
        return ret;
}

static ulong
diskid(Drive *d)
{
        int i, n;
        ulong tmp;
        Msf *ms, *me;

        n = 0;
        for(i=0; i < d->ntrack; i++)
                n += cddb_sum(d->track[i].mbeg.m*60+d->track[i].mbeg.s);

        ms = &d->track[0].mbeg;
        me = &d->track[d->ntrack].mbeg;
        tmp = (me->m*60+me->s) - (ms->m*60+ms->s);

        /*
         * the spec says n%0xFF rather than n&0xFF.  it's unclear which is
         * correct.  most CDs are in the database under both entries.
         */
        return ((n % 0xFF) << 24 | (tmp << 8) | d->ntrack);
}

static void
readctl(Req *r)
{
        int i, isaudio;
        ulong nwa;
        char *p, *e, *ty;
        char s[1024];
        Msf *m;

        isaudio = 0;
        for(i=0; i<drive->ntrack; i++)
                if(drive->track[i].type == TypeAudio)
                        isaudio = 1;

        p = s;
        e = s + sizeof s;
        *p = '\0';
        if(isaudio){
                p = seprint(p, e, "aux/cddb query %8.8lux %d", diskid(drive),
                        drive->ntrack);
                for(i=0; i<drive->ntrack; i++){
                        m = &drive->track[i].mbeg;
                        p = seprint(p, e, " %d", (m->m*60 + m->s)*75 + m->f);
                }
                m = &drive->track[drive->ntrack].mbeg;
                p = seprint(p, e, " %d\n", m->m*60 + m->s);
        }

        if(drive->readspeed == drive->writespeed)
                p = seprint(p, e, "speed %d\n", drive->readspeed);
        else
                p = seprint(p, e, "speed read %d write %d\n",
                        drive->readspeed, drive->writespeed);
        p = seprint(p, e, "maxspeed read %d write %d\n",
                drive->maxreadspeed, drive->maxwritespeed);

        if (drive->Scsi.changetime != 0 && drive->ntrack != 0) { /* have disc? */
                ty = disctype(drive);
                p = seprint(p, e, "%s", ty);
                free(ty);
                if (drive->mmctype != Mmcnone) {
                        nwa = getnwa(drive);
                        p = seprint(p, e, " next writable sector ");
                        if (nwa == ~0ul)
                                p = seprint(p, e, "none; disc full");
                        else
                                p = seprint(p, e, "%lud", nwa);
                }
                seprint(p, e, "\n");
        }
        readstr(r, s);
}

static void
fsread(Req *r)
{
        int j, n, m;
        uchar *p, *ep;
        Dir d;
        Fid *fid;
        Otrack *o;
        vlong offset;
        void *buf;
        long count;
        Aux *a;

        fid = r->fid;
        offset = r->ifcall.offset;
        buf = r->ofcall.data;
        count = r->ifcall.count;

        switch((ulong)fid->qid.path) {
        case Qdir:
                checktoc(drive);
                p = buf;
                ep = p+count;
                m = Qtrack+drive->ntrack;
                a = fid->aux;
                if(offset == 0)
                        a->doff = 1;    /* skip root */

                for(j=a->doff; j<m; j++) {
                        if(fillstat(j, &d)) {
                                if((n = convD2M(&d, p, ep-p)) <= BIT16SZ)
                                        break;
                                p += n;
                        }
                }
                a->doff = j;

                r->ofcall.count = p - (uchar*)buf;
                break;
        case Qwa:
        case Qwd:
                r->ofcall.count = 0;
                break;
        case Qctl:
                readctl(r);
                break;
        default:
                /* a disk track; we can only call read for whole blocks */
                o = ((Aux*)fid->aux)->o;
                if((count = o->drive->read(o, buf, count, offset)) < 0) {
                        respond(r, geterrstr());
                        return;
                }
                r->ofcall.count = count;
                break;
        }
        respond(r, nil);
}

static char Ebadmsg[] = "bad cdfs control message";

static char*
writectl(void *v, long count)
{
        char buf[256];
        char *f[10], *p;
        int i, nf, n, r, w, what;

        if(count >= sizeof(buf))
                count = sizeof(buf)-1;
        memmove(buf, v, count);
        buf[count] = '\0';

        nf = tokenize(buf, f, nelem(f));
        if(nf == 0)
                return Ebadmsg;

        if(strcmp(f[0], "speed") == 0){
                what = 0;
                r = w = -1;
                if(nf == 1)
                        return Ebadmsg;
                for(i=1; i<nf; i++){
                        if(strcmp(f[i], "read") == 0 || strcmp(f[i], "write") == 0){
                                if(what!=0 && what!='?')
                                        return Ebadmsg;
                                what = f[i][0];
                        }else{
                                if (strcmp(f[i], "best") == 0)
                                        n = (1<<16) - 1;
                                else {
                                        n = strtol(f[i], &p, 0);
                                        if(*p != '\0' || n <= 0)
                                                return Ebadmsg;
                                }
                                switch(what){
                                case 0:
                                        if(r >= 0 || w >= 0)
                                                return Ebadmsg;
                                        r = w = n;
                                        break;
                                case 'r':
                                        if(r >= 0)
                                                return Ebadmsg;
                                        r = n;
                                        break;
                                case 'w':
                                        if(w >= 0)
                                                return Ebadmsg;
                                        w = n;
                                        break;
                                default:
                                        return Ebadmsg;
                                }
                                what = '?';
                        }
                }
                if(what != '?')
                        return Ebadmsg;
                return drive->setspeed(drive, r, w);
        }
        return drive->ctl(drive, nf, f);
}

static void
fswrite(Req *r)
{
        Otrack *o;
        Fid *fid;

        fid = r->fid;
        r->ofcall.count = r->ifcall.count;
        if(fid->qid.path == Qctl) {
                respond(r, writectl(r->ifcall.data, r->ifcall.count));
                return;
        }

        if((o = ((Aux*)fid->aux)->o) == nil || o->omode != OWRITE) {
                respond(r, "permission denied");
                return;
        }

        if(o->drive->write(o, r->ifcall.data, r->ifcall.count) < 0)
                respond(r, geterrstr());
        else
                respond(r, nil);
}

static void
fsstat(Req *r)
{
        fillstat((ulong)r->fid->qid.path, &r->d);
        r->d.name = estrdup9p(r->d.name);
        r->d.uid = estrdup9p(r->d.uid);
        r->d.gid = estrdup9p(r->d.gid);
        r->d.muid = estrdup9p(r->d.muid);
        respond(r, nil);
}

static void
fsopen(Req *r)
{
        int omode;
        Fid *fid;
        Otrack *o;

        fid = r->fid;
        omode = r->ifcall.mode;
        checktoc(drive);
        r->ofcall.qid = (Qid){fid->qid.path, drive->nchange, fid->qid.vers};

        switch((ulong)fid->qid.path){
        case Qdir:
        case Qwa:
        case Qwd:
                if(omode != OREAD) {
                        respond(r, "permission denied");
                        return;
                }
                break;
        case Qctl:
                if(omode & ~(OTRUNC|OREAD|OWRITE|ORDWR)) {
                        respond(r, "permission denied");
                        return;
                }
                break;
        default:
                if(fid->qid.path >= Qtrack+drive->ntrack) {
                        respond(r, "file no longer exists");
                        return;
                }

                /*
                 * allow the open with OWRITE or ORDWR if the
                 * drive and disc are both capable?
                 */
                if(omode != OREAD ||
                    (o = drive->openrd(drive, fid->qid.path-Qtrack)) == nil) {
                        respond(r, "permission denied");
                        return;
                }

                o->nref = 1;
                ((Aux*)fid->aux)->o = o;
                break;
        }
        respond(r, nil);
}

static void
fsdestroyfid(Fid *fid)
{
        Aux *aux;
        Otrack *o;

        aux = fid->aux;
        if(aux == nil)
                return;
        o = aux->o;
        if(o && --o->nref == 0) {
                bterm(o->buf);
                drive->close(o);
                checktoc(drive);
        }
}

static void
checktoc(Drive *drive)
{
        int i;
        Track *t;

        drive->gettoc(drive);
        if(drive->nameok)
                return;

        for(i=0; i<drive->ntrack; i++) {
                t = &drive->track[i];
                if(t->size == 0)        /* being created */
                        t->mode = 0;
                else
                        t->mode = 0444;
                sprint(t->name, "?%.3d", i);
                switch(t->type){
                case TypeNone:
                        t->name[0] = 'u';
//                      t->mode = 0;
                        break;
                case TypeData:
                        t->name[0] = 'd';
                        break;
                case TypeAudio:
                        t->name[0] = 'a';
                        break;
                case TypeBlank:
                        t->name[0] = '\0';
                        break;
                default:
                        print("unknown track type %d\n", t->type);
                        break;
                }
        }

        drive->nameok = 1;
}

long
bufread(Otrack *t, void *v, long n, vlong off)
{
        return bread(t->buf, v, n, off);
}

long
bufwrite(Otrack *t, void *v, long n)
{
        return bwrite(t->buf, v, n);
}

Srv fs = {
.attach=        fsattach,
.destroyfid=    fsdestroyfid,
.clone=         fsclone,
.walk1=         fswalk1,
.open=          fsopen,
.read=          fsread,
.write=         fswrite,
.create=        fscreate,
.remove=        fsremove,
.stat=          fsstat,
};

void
usage(void)
{
        fprint(2, "usage: cdfs [-Dv] [-d /dev/sdC0] [-m mtpt]\n");
        exits("usage");
}

void
main(int argc, char **argv)
{
        Scsi *s;
        int fd;
        char *dev, *mtpt;

        dev = "/dev/sdD0";
        mtpt = "/mnt/cd";

        ARGBEGIN{
        case 'D':
                chatty9p++;
                break;
        case 'd':
                dev = EARGF(usage());
                break;
        case 'm':
                mtpt = EARGF(usage());
                break;
        case 'v':
                if((fd = create("/tmp/cdfs.log", OWRITE, 0666)) >= 0) {
                        dup(fd, 2);
                        dup(fd, 1);
                        if(fd != 1 && fd != 2)
                                close(fd);
                        vflag++;
                        scsiverbose = 2; /* verbose but no Readtoc errs */
                } else
                        fprint(2, "%s: can't open /tmp/cdfs.log: %r\n", argv0);
                break;
        default:
                usage();
        }ARGEND

        if(dev == nil || mtpt == nil || argc > 0)
                usage();

        werrstr("");
        if((s = openscsi(dev)) == nil)
                sysfatal("openscsi '%s': %r", dev);
        if((drive = mmcprobe(s)) == nil)
                sysfatal("mmcprobe '%s': %r", dev);
        checktoc(drive);

        postmountsrv(&fs, nil, mtpt, MREPL|MCREATE);
        exits(nil);
}