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* 36 geterrstr(void) 37 { 38 static char errbuf[ERRMAX]; 39 40 rerrstr(errbuf, sizeof errbuf); 41 return errbuf; 42 } 43 44 void* 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 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* 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* 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 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 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 * 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 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 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 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 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 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* 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 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 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 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 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 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 661 bufread(Otrack *t, void *v, long n, vlong off) 662 { 663 return bread(t->buf, v, n, off); 664 } 665 666 long 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 686 usage(void) 687 { 688 fprint(2, "usage: cdfs [-Dv] [-d /dev/sdC0] [-m mtpt]\n"); 689 exits("usage"); 690 } 691 692 void 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