1 /* cdfs - CD, DVD and BD reader and writer file system */
2 #include <u.h>
3 #include <libc.h>
4 #include <auth.h>
5 #include <fcall.h>
6 #include <thread.h>
7 #include <9p.h>
8 #include <disk.h>
9 #include "dat.h"
10 #include "fns.h"
11
12 typedef struct Aux Aux;
13 struct Aux {
14 int doff;
15 Otrack *o;
16 };
17
18 ulong getnwa(Drive *);
19
20 static void checktoc(Drive*);
21
22 int vflag;
23
24 static Drive *drive;
25 static int nchange;
26
27 enum {
28 Qdir = 0,
29 Qctl = 1,
30 Qwa = 2,
31 Qwd = 3,
32 Qtrack = 4,
33 };
34
35 char*
geterrstr(void)36 geterrstr(void)
37 {
38 static char errbuf[ERRMAX];
39
40 rerrstr(errbuf, sizeof errbuf);
41 return errbuf;
42 }
43
44 void*
emalloc(ulong sz)45 emalloc(ulong sz)
46 {
47 void *v;
48
49 v = mallocz(sz, 1);
50 if(v == nil)
51 sysfatal("malloc %lud fails", sz);
52 return v;
53 }
54
55 static void
fsattach(Req * r)56 fsattach(Req *r)
57 {
58 char *spec;
59
60 spec = r->ifcall.aname;
61 if(spec && spec[0]) {
62 respond(r, "invalid attach specifier");
63 return;
64 }
65
66 checktoc(drive);
67 r->fid->qid = (Qid){Qdir, drive->nchange, QTDIR};
68 r->ofcall.qid = r->fid->qid;
69 r->fid->aux = emalloc(sizeof(Aux));
70 respond(r, nil);
71 }
72
73 static char*
fsclone(Fid * old,Fid * new)74 fsclone(Fid *old, Fid *new)
75 {
76 Aux *na;
77
78 na = emalloc(sizeof(Aux));
79 *na = *((Aux*)old->aux);
80 if(na->o)
81 na->o->nref++;
82 new->aux = na;
83 return nil;
84 }
85
86 static char*
fswalk1(Fid * fid,char * name,Qid * qid)87 fswalk1(Fid *fid, char *name, Qid *qid)
88 {
89 int i;
90
91 checktoc(drive);
92 switch((ulong)fid->qid.path) {
93 case Qdir:
94 if(strcmp(name, "..") == 0) {
95 *qid = (Qid){Qdir, drive->nchange, QTDIR};
96 return nil;
97 }
98 if(strcmp(name, "ctl") == 0) {
99 *qid = (Qid){Qctl, 0, 0};
100 return nil;
101 }
102 if(strcmp(name, "wa") == 0 && drive->writeok &&
103 (drive->mmctype == Mmcnone ||
104 drive->mmctype == Mmccd)) {
105 *qid = (Qid){Qwa, drive->nchange, QTDIR};
106 return nil;
107 }
108 if(strcmp(name, "wd") == 0 && drive->writeok) {
109 *qid = (Qid){Qwd, drive->nchange, QTDIR};
110 return nil;
111 }
112 for(i=0; i<drive->ntrack; i++)
113 if(strcmp(drive->track[i].name, name) == 0)
114 break;
115 if(i == drive->ntrack)
116 return "file not found";
117 *qid = (Qid){Qtrack+i, 0, 0};
118 return nil;
119
120 case Qwa:
121 case Qwd:
122 if(strcmp(name, "..") == 0) {
123 *qid = (Qid){Qdir, drive->nchange, QTDIR};
124 return nil;
125 }
126 return "file not found";
127 default: /* bug: lib9p could handle this */
128 return "walk in non-directory";
129 }
130 }
131
132 static void
fscreate(Req * r)133 fscreate(Req *r)
134 {
135 int omode, type;
136 Otrack *o;
137 Fid *fid;
138
139 fid = r->fid;
140 omode = r->ifcall.mode;
141
142 if(omode != OWRITE) {
143 respond(r, "bad mode (use OWRITE)");
144 return;
145 }
146
147 switch((ulong)fid->qid.path) {
148 case Qdir:
149 default:
150 respond(r, "permission denied");
151 return;
152
153 case Qwa:
154 if (drive->mmctype != Mmcnone &&
155 drive->mmctype != Mmccd) {
156 respond(r, "audio supported only on cd");
157 return;
158 }
159 type = TypeAudio;
160 break;
161
162 case Qwd:
163 type = TypeData;
164 break;
165 }
166
167 if((drive->cap & Cwrite) == 0) {
168 respond(r, "drive does not write");
169 return;
170 }
171
172 o = drive->create(drive, type);
173 if(o == nil) {
174 respond(r, geterrstr());
175 return;
176 }
177 drive->nchange = -1;
178 checktoc(drive); /* update directory info */
179 o->nref = 1;
180 ((Aux*)fid->aux)->o = o;
181
182 fid->qid = (Qid){Qtrack+(o->track - drive->track), drive->nchange, 0};
183 r->ofcall.qid = fid->qid;
184 respond(r, nil);
185 }
186
187 static void
fsremove(Req * r)188 fsremove(Req *r)
189 {
190 switch((ulong)r->fid->qid.path){
191 case Qwa:
192 case Qwd:
193 if(drive->fixate(drive) < 0)
194 respond(r, geterrstr());
195 // let us see if it can figure this out: drive->writeok = No;
196 else
197 respond(r, nil);
198 checktoc(drive);
199 break;
200 default:
201 respond(r, "permission denied");
202 break;
203 }
204 }
205
206 /* result is one word, so it can be used as a uid in Dir structs */
207 char *
disctype(Drive * drive)208 disctype(Drive *drive)
209 {
210 char *type, *rw, *laysfx;
211
212 rw = laysfx = "";
213 switch (drive->mmctype) {
214 case Mmccd:
215 type = "cd-";
216 break;
217 case Mmcdvdminus:
218 case Mmcdvdplus:
219 type = drive->dvdtype;
220 break;
221 case Mmcbd:
222 type = "bd-";
223 if (drive->laysfx)
224 laysfx = drive->laysfx;
225 break;
226 case Mmcnone:
227 type = "no-disc";
228 break;
229 default:
230 type = "**GOK**"; /* traditional */
231 break;
232 }
233 if (drive->mmctype != Mmcnone && drive->dvdtype == nil)
234 if (drive->erasable == Yes)
235 rw = drive->mmctype == Mmcbd? "re": "rw";
236 else if (drive->recordable == Yes)
237 rw = "r";
238 else
239 rw = "rom";
240 return smprint("%s%s%s", type, rw, laysfx);
241 }
242
243 int
fillstat(ulong qid,Dir * d)244 fillstat(ulong qid, Dir *d)
245 {
246 char *ty;
247 Track *t;
248 static char buf[32];
249
250 nulldir(d);
251 d->type = L'M';
252 d->dev = 1;
253 d->length = 0;
254 ty = disctype(drive);
255 strncpy(buf, ty, sizeof buf);
256 free(ty);
257 d->uid = d->gid = buf;
258 d->muid = "";
259 d->qid = (Qid){qid, drive->nchange, 0};
260 d->atime = time(0);
261 d->mtime = drive->changetime;
262
263 switch(qid){
264 case Qdir:
265 d->name = "/";
266 d->qid.type = QTDIR;
267 d->mode = DMDIR|0777;
268 break;
269
270 case Qctl:
271 d->name = "ctl";
272 d->mode = 0666;
273 break;
274
275 case Qwa:
276 if(drive->writeok == No ||
277 drive->mmctype != Mmcnone &&
278 drive->mmctype != Mmccd)
279 return 0;
280 d->name = "wa";
281 d->qid.type = QTDIR;
282 d->mode = DMDIR|0777;
283 break;
284
285 case Qwd:
286 if(drive->writeok == No)
287 return 0;
288 d->name = "wd";
289 d->qid.type = QTDIR;
290 d->mode = DMDIR|0777;
291 break;
292
293 default:
294 if(qid-Qtrack >= drive->ntrack)
295 return 0;
296 t = &drive->track[qid-Qtrack];
297 if(strcmp(t->name, "") == 0)
298 return 0;
299 d->name = t->name;
300 d->mode = t->mode;
301 d->length = t->size;
302 break;
303 }
304 return 1;
305 }
306
307 static ulong
cddb_sum(int n)308 cddb_sum(int n)
309 {
310 int ret;
311 ret = 0;
312 while(n > 0) {
313 ret += n%10;
314 n /= 10;
315 }
316 return ret;
317 }
318
319 static ulong
diskid(Drive * d)320 diskid(Drive *d)
321 {
322 int i, n;
323 ulong tmp;
324 Msf *ms, *me;
325
326 n = 0;
327 for(i=0; i < d->ntrack; i++)
328 n += cddb_sum(d->track[i].mbeg.m*60+d->track[i].mbeg.s);
329
330 ms = &d->track[0].mbeg;
331 me = &d->track[d->ntrack].mbeg;
332 tmp = (me->m*60+me->s) - (ms->m*60+ms->s);
333
334 /*
335 * the spec says n%0xFF rather than n&0xFF. it's unclear which is
336 * correct. most CDs are in the database under both entries.
337 */
338 return ((n % 0xFF) << 24 | (tmp << 8) | d->ntrack);
339 }
340
341 static void
readctl(Req * r)342 readctl(Req *r)
343 {
344 int i, isaudio;
345 ulong nwa;
346 char *p, *e, *ty;
347 char s[1024];
348 Msf *m;
349
350 isaudio = 0;
351 for(i=0; i<drive->ntrack; i++)
352 if(drive->track[i].type == TypeAudio)
353 isaudio = 1;
354
355 p = s;
356 e = s + sizeof s;
357 *p = '\0';
358 if(isaudio){
359 p = seprint(p, e, "aux/cddb query %8.8lux %d", diskid(drive),
360 drive->ntrack);
361 for(i=0; i<drive->ntrack; i++){
362 m = &drive->track[i].mbeg;
363 p = seprint(p, e, " %d", (m->m*60 + m->s)*75 + m->f);
364 }
365 m = &drive->track[drive->ntrack].mbeg;
366 p = seprint(p, e, " %d\n", m->m*60 + m->s);
367 }
368
369 if(drive->readspeed == drive->writespeed)
370 p = seprint(p, e, "speed %d\n", drive->readspeed);
371 else
372 p = seprint(p, e, "speed read %d write %d\n",
373 drive->readspeed, drive->writespeed);
374 p = seprint(p, e, "maxspeed read %d write %d\n",
375 drive->maxreadspeed, drive->maxwritespeed);
376
377 if (drive->Scsi.changetime != 0 && drive->ntrack != 0) { /* have disc? */
378 ty = disctype(drive);
379 p = seprint(p, e, "%s", ty);
380 free(ty);
381 if (drive->mmctype != Mmcnone) {
382 nwa = getnwa(drive);
383 p = seprint(p, e, " next writable sector ");
384 if (nwa == ~0ul)
385 p = seprint(p, e, "none; disc full");
386 else
387 p = seprint(p, e, "%lud", nwa);
388 }
389 seprint(p, e, "\n");
390 }
391 readstr(r, s);
392 }
393
394 static void
fsread(Req * r)395 fsread(Req *r)
396 {
397 int j, n, m;
398 uchar *p, *ep;
399 Dir d;
400 Fid *fid;
401 Otrack *o;
402 vlong offset;
403 void *buf;
404 long count;
405 Aux *a;
406
407 fid = r->fid;
408 offset = r->ifcall.offset;
409 buf = r->ofcall.data;
410 count = r->ifcall.count;
411
412 switch((ulong)fid->qid.path) {
413 case Qdir:
414 checktoc(drive);
415 p = buf;
416 ep = p+count;
417 m = Qtrack+drive->ntrack;
418 a = fid->aux;
419 if(offset == 0)
420 a->doff = 1; /* skip root */
421
422 for(j=a->doff; j<m; j++) {
423 if(fillstat(j, &d)) {
424 if((n = convD2M(&d, p, ep-p)) <= BIT16SZ)
425 break;
426 p += n;
427 }
428 }
429 a->doff = j;
430
431 r->ofcall.count = p - (uchar*)buf;
432 break;
433 case Qwa:
434 case Qwd:
435 r->ofcall.count = 0;
436 break;
437 case Qctl:
438 readctl(r);
439 break;
440 default:
441 /* a disk track; we can only call read for whole blocks */
442 o = ((Aux*)fid->aux)->o;
443 if((count = o->drive->read(o, buf, count, offset)) < 0) {
444 respond(r, geterrstr());
445 return;
446 }
447 r->ofcall.count = count;
448 break;
449 }
450 respond(r, nil);
451 }
452
453 static char Ebadmsg[] = "bad cdfs control message";
454
455 static char*
writectl(void * v,long count)456 writectl(void *v, long count)
457 {
458 char buf[256];
459 char *f[10], *p;
460 int i, nf, n, r, w, what;
461
462 if(count >= sizeof(buf))
463 count = sizeof(buf)-1;
464 memmove(buf, v, count);
465 buf[count] = '\0';
466
467 nf = tokenize(buf, f, nelem(f));
468 if(nf == 0)
469 return Ebadmsg;
470
471 if(strcmp(f[0], "speed") == 0){
472 what = 0;
473 r = w = -1;
474 if(nf == 1)
475 return Ebadmsg;
476 for(i=1; i<nf; i++){
477 if(strcmp(f[i], "read") == 0 || strcmp(f[i], "write") == 0){
478 if(what!=0 && what!='?')
479 return Ebadmsg;
480 what = f[i][0];
481 }else{
482 if (strcmp(f[i], "best") == 0)
483 n = (1<<16) - 1;
484 else {
485 n = strtol(f[i], &p, 0);
486 if(*p != '\0' || n <= 0)
487 return Ebadmsg;
488 }
489 switch(what){
490 case 0:
491 if(r >= 0 || w >= 0)
492 return Ebadmsg;
493 r = w = n;
494 break;
495 case 'r':
496 if(r >= 0)
497 return Ebadmsg;
498 r = n;
499 break;
500 case 'w':
501 if(w >= 0)
502 return Ebadmsg;
503 w = n;
504 break;
505 default:
506 return Ebadmsg;
507 }
508 what = '?';
509 }
510 }
511 if(what != '?')
512 return Ebadmsg;
513 return drive->setspeed(drive, r, w);
514 }
515 return drive->ctl(drive, nf, f);
516 }
517
518 static void
fswrite(Req * r)519 fswrite(Req *r)
520 {
521 Otrack *o;
522 Fid *fid;
523
524 fid = r->fid;
525 r->ofcall.count = r->ifcall.count;
526 if(fid->qid.path == Qctl) {
527 respond(r, writectl(r->ifcall.data, r->ifcall.count));
528 return;
529 }
530
531 if((o = ((Aux*)fid->aux)->o) == nil || o->omode != OWRITE) {
532 respond(r, "permission denied");
533 return;
534 }
535
536 if(o->drive->write(o, r->ifcall.data, r->ifcall.count) < 0)
537 respond(r, geterrstr());
538 else
539 respond(r, nil);
540 }
541
542 static void
fsstat(Req * r)543 fsstat(Req *r)
544 {
545 fillstat((ulong)r->fid->qid.path, &r->d);
546 r->d.name = estrdup9p(r->d.name);
547 r->d.uid = estrdup9p(r->d.uid);
548 r->d.gid = estrdup9p(r->d.gid);
549 r->d.muid = estrdup9p(r->d.muid);
550 respond(r, nil);
551 }
552
553 static void
fsopen(Req * r)554 fsopen(Req *r)
555 {
556 int omode;
557 Fid *fid;
558 Otrack *o;
559
560 fid = r->fid;
561 omode = r->ifcall.mode;
562 checktoc(drive);
563 r->ofcall.qid = (Qid){fid->qid.path, drive->nchange, fid->qid.vers};
564
565 switch((ulong)fid->qid.path){
566 case Qdir:
567 case Qwa:
568 case Qwd:
569 if(omode != OREAD) {
570 respond(r, "permission denied");
571 return;
572 }
573 break;
574 case Qctl:
575 if(omode & ~(OTRUNC|OREAD|OWRITE|ORDWR)) {
576 respond(r, "permission denied");
577 return;
578 }
579 break;
580 default:
581 if(fid->qid.path >= Qtrack+drive->ntrack) {
582 respond(r, "file no longer exists");
583 return;
584 }
585
586 /*
587 * allow the open with OWRITE or ORDWR if the
588 * drive and disc are both capable?
589 */
590 if(omode != OREAD ||
591 (o = drive->openrd(drive, fid->qid.path-Qtrack)) == nil) {
592 respond(r, "permission denied");
593 return;
594 }
595
596 o->nref = 1;
597 ((Aux*)fid->aux)->o = o;
598 break;
599 }
600 respond(r, nil);
601 }
602
603 static void
fsdestroyfid(Fid * fid)604 fsdestroyfid(Fid *fid)
605 {
606 Aux *aux;
607 Otrack *o;
608
609 aux = fid->aux;
610 if(aux == nil)
611 return;
612 o = aux->o;
613 if(o && --o->nref == 0) {
614 bterm(o->buf);
615 drive->close(o);
616 checktoc(drive);
617 }
618 }
619
620 static void
checktoc(Drive * drive)621 checktoc(Drive *drive)
622 {
623 int i;
624 Track *t;
625
626 drive->gettoc(drive);
627 if(drive->nameok)
628 return;
629
630 for(i=0; i<drive->ntrack; i++) {
631 t = &drive->track[i];
632 if(t->size == 0) /* being created */
633 t->mode = 0;
634 else
635 t->mode = 0444;
636 sprint(t->name, "?%.3d", i);
637 switch(t->type){
638 case TypeNone:
639 t->name[0] = 'u';
640 // t->mode = 0;
641 break;
642 case TypeData:
643 t->name[0] = 'd';
644 break;
645 case TypeAudio:
646 t->name[0] = 'a';
647 break;
648 case TypeBlank:
649 t->name[0] = '\0';
650 break;
651 default:
652 print("unknown track type %d\n", t->type);
653 break;
654 }
655 }
656
657 drive->nameok = 1;
658 }
659
660 long
bufread(Otrack * t,void * v,long n,vlong off)661 bufread(Otrack *t, void *v, long n, vlong off)
662 {
663 return bread(t->buf, v, n, off);
664 }
665
666 long
bufwrite(Otrack * t,void * v,long n)667 bufwrite(Otrack *t, void *v, long n)
668 {
669 return bwrite(t->buf, v, n);
670 }
671
672 Srv fs = {
673 .attach= fsattach,
674 .destroyfid= fsdestroyfid,
675 .clone= fsclone,
676 .walk1= fswalk1,
677 .open= fsopen,
678 .read= fsread,
679 .write= fswrite,
680 .create= fscreate,
681 .remove= fsremove,
682 .stat= fsstat,
683 };
684
685 void
usage(void)686 usage(void)
687 {
688 fprint(2, "usage: cdfs [-Dv] [-d /dev/sdC0] [-m mtpt]\n");
689 exits("usage");
690 }
691
692 void
main(int argc,char ** argv)693 main(int argc, char **argv)
694 {
695 Scsi *s;
696 int fd;
697 char *dev, *mtpt;
698
699 dev = "/dev/sdD0";
700 mtpt = "/mnt/cd";
701
702 ARGBEGIN{
703 case 'D':
704 chatty9p++;
705 break;
706 case 'd':
707 dev = EARGF(usage());
708 break;
709 case 'm':
710 mtpt = EARGF(usage());
711 break;
712 case 'v':
713 if((fd = create("/tmp/cdfs.log", OWRITE, 0666)) >= 0) {
714 dup(fd, 2);
715 dup(fd, 1);
716 if(fd != 1 && fd != 2)
717 close(fd);
718 vflag++;
719 scsiverbose = 2; /* verbose but no Readtoc errs */
720 } else
721 fprint(2, "%s: can't open /tmp/cdfs.log: %r\n", argv0);
722 break;
723 default:
724 usage();
725 }ARGEND
726
727 if(dev == nil || mtpt == nil || argc > 0)
728 usage();
729
730 werrstr("");
731 if((s = openscsi(dev)) == nil)
732 sysfatal("openscsi '%s': %r", dev);
733 if((drive = mmcprobe(s)) == nil)
734 sysfatal("mmcprobe '%s': %r", dev);
735 checktoc(drive);
736
737 postmountsrv(&fs, nil, mtpt, MREPL|MCREATE);
738 exits(nil);
739 }
740