/* cdfs - CD, DVD and BD reader and writer file system */ #include #include #include #include #include #include <9p.h> #include #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; intrack; 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; intrack; 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; intrack; 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; jdoff = 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= 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; intrack; 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); }