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; 211 212 switch (drive->mmctype) { 213 case Mmccd: 214 type = "cd-"; 215 break; 216 case Mmcdvdminus: 217 case Mmcdvdplus: 218 type = drive->dvdtype; 219 break; 220 case Mmcbd: 221 type = "bd-"; 222 break; 223 case Mmcnone: 224 type = "no-disc"; 225 break; 226 default: 227 type = "**GOK**"; /* traditional */ 228 break; 229 } 230 rw = ""; 231 if (drive->mmctype != Mmcnone && drive->dvdtype == nil) 232 if (drive->erasable == Yes) 233 rw = drive->mmctype == Mmcbd? "re": "rw"; 234 else if (drive->recordable == Yes) 235 rw = "r"; 236 else 237 rw = "rom"; 238 return smprint("%s%s", type, rw); 239 } 240 241 int 242 fillstat(ulong qid, Dir *d) 243 { 244 char *ty; 245 Track *t; 246 static char buf[32]; 247 248 nulldir(d); 249 d->type = L'M'; 250 d->dev = 1; 251 d->length = 0; 252 ty = disctype(drive); 253 strncpy(buf, ty, sizeof buf); 254 free(ty); 255 d->uid = d->gid = buf; 256 d->muid = ""; 257 d->qid = (Qid){qid, drive->nchange, 0}; 258 d->atime = time(0); 259 d->mtime = drive->changetime; 260 261 switch(qid){ 262 case Qdir: 263 d->name = "/"; 264 d->qid.type = QTDIR; 265 d->mode = DMDIR|0777; 266 break; 267 268 case Qctl: 269 d->name = "ctl"; 270 d->mode = 0666; 271 break; 272 273 case Qwa: 274 if(drive->writeok == No || 275 drive->mmctype != Mmcnone && 276 drive->mmctype != Mmccd) 277 return 0; 278 d->name = "wa"; 279 d->qid.type = QTDIR; 280 d->mode = DMDIR|0777; 281 break; 282 283 case Qwd: 284 if(drive->writeok == No) 285 return 0; 286 d->name = "wd"; 287 d->qid.type = QTDIR; 288 d->mode = DMDIR|0777; 289 break; 290 291 default: 292 if(qid-Qtrack >= drive->ntrack) 293 return 0; 294 t = &drive->track[qid-Qtrack]; 295 if(strcmp(t->name, "") == 0) 296 return 0; 297 d->name = t->name; 298 d->mode = t->mode; 299 d->length = t->size; 300 break; 301 } 302 return 1; 303 } 304 305 static ulong 306 cddb_sum(int n) 307 { 308 int ret; 309 ret = 0; 310 while(n > 0) { 311 ret += n%10; 312 n /= 10; 313 } 314 return ret; 315 } 316 317 static ulong 318 diskid(Drive *d) 319 { 320 int i, n; 321 ulong tmp; 322 Msf *ms, *me; 323 324 n = 0; 325 for(i=0; i < d->ntrack; i++) 326 n += cddb_sum(d->track[i].mbeg.m*60+d->track[i].mbeg.s); 327 328 ms = &d->track[0].mbeg; 329 me = &d->track[d->ntrack].mbeg; 330 tmp = (me->m*60+me->s) - (ms->m*60+ms->s); 331 332 /* 333 * the spec says n%0xFF rather than n&0xFF. it's unclear which is 334 * correct. most CDs are in the database under both entries. 335 */ 336 return ((n % 0xFF) << 24 | (tmp << 8) | d->ntrack); 337 } 338 339 static void 340 readctl(Req *r) 341 { 342 int i, isaudio; 343 ulong nwa; 344 char *p, *e, *ty; 345 char s[1024]; 346 Msf *m; 347 348 isaudio = 0; 349 for(i=0; i<drive->ntrack; i++) 350 if(drive->track[i].type == TypeAudio) 351 isaudio = 1; 352 353 p = s; 354 e = s + sizeof s; 355 *p = '\0'; 356 if(isaudio){ 357 p = seprint(p, e, "aux/cddb query %8.8lux %d", diskid(drive), 358 drive->ntrack); 359 for(i=0; i<drive->ntrack; i++){ 360 m = &drive->track[i].mbeg; 361 p = seprint(p, e, " %d", (m->m*60 + m->s)*75 + m->f); 362 } 363 m = &drive->track[drive->ntrack].mbeg; 364 p = seprint(p, e, " %d\n", m->m*60 + m->s); 365 } 366 367 if(drive->readspeed == drive->writespeed) 368 p = seprint(p, e, "speed %d\n", drive->readspeed); 369 else 370 p = seprint(p, e, "speed read %d write %d\n", 371 drive->readspeed, drive->writespeed); 372 p = seprint(p, e, "maxspeed read %d write %d\n", 373 drive->maxreadspeed, drive->maxwritespeed); 374 375 if (drive->Scsi.changetime != 0 && drive->ntrack != 0) { /* have disc? */ 376 ty = disctype(drive); 377 p = seprint(p, e, "%s", ty); 378 free(ty); 379 if (drive->mmctype != Mmcnone) { 380 nwa = getnwa(drive); 381 p = seprint(p, e, " next writable sector "); 382 if (nwa == ~0ul) 383 p = seprint(p, e, "none; disc full"); 384 else 385 p = seprint(p, e, "%lud", nwa); 386 } 387 seprint(p, e, "\n"); 388 } 389 readstr(r, s); 390 } 391 392 static void 393 fsread(Req *r) 394 { 395 int j, n, m; 396 uchar *p, *ep; 397 Dir d; 398 Fid *fid; 399 Otrack *o; 400 vlong offset; 401 void *buf; 402 long count; 403 Aux *a; 404 405 fid = r->fid; 406 offset = r->ifcall.offset; 407 buf = r->ofcall.data; 408 count = r->ifcall.count; 409 410 switch((ulong)fid->qid.path) { 411 case Qdir: 412 checktoc(drive); 413 p = buf; 414 ep = p+count; 415 m = Qtrack+drive->ntrack; 416 a = fid->aux; 417 if(offset == 0) 418 a->doff = 1; /* skip root */ 419 420 for(j=a->doff; j<m; j++) { 421 if(fillstat(j, &d)) { 422 if((n = convD2M(&d, p, ep-p)) <= BIT16SZ) 423 break; 424 p += n; 425 } 426 } 427 a->doff = j; 428 429 r->ofcall.count = p - (uchar*)buf; 430 break; 431 case Qwa: 432 case Qwd: 433 r->ofcall.count = 0; 434 break; 435 case Qctl: 436 readctl(r); 437 break; 438 default: 439 /* a disk track; we can only call read for whole blocks */ 440 o = ((Aux*)fid->aux)->o; 441 if((count = o->drive->read(o, buf, count, offset)) < 0) { 442 respond(r, geterrstr()); 443 return; 444 } 445 r->ofcall.count = count; 446 break; 447 } 448 respond(r, nil); 449 } 450 451 static char Ebadmsg[] = "bad cdfs control message"; 452 453 static char* 454 writectl(void *v, long count) 455 { 456 char buf[256]; 457 char *f[10], *p; 458 int i, nf, n, r, w, what; 459 460 if(count >= sizeof(buf)) 461 count = sizeof(buf)-1; 462 memmove(buf, v, count); 463 buf[count] = '\0'; 464 465 nf = tokenize(buf, f, nelem(f)); 466 if(nf == 0) 467 return Ebadmsg; 468 469 if(strcmp(f[0], "speed") == 0){ 470 what = 0; 471 r = w = -1; 472 if(nf == 1) 473 return Ebadmsg; 474 for(i=1; i<nf; i++){ 475 if(strcmp(f[i], "read") == 0 || strcmp(f[i], "write") == 0){ 476 if(what!=0 && what!='?') 477 return Ebadmsg; 478 what = f[i][0]; 479 }else{ 480 if (strcmp(f[i], "best") == 0) 481 n = (1<<16) - 1; 482 else { 483 n = strtol(f[i], &p, 0); 484 if(*p != '\0' || n <= 0) 485 return Ebadmsg; 486 } 487 switch(what){ 488 case 0: 489 if(r >= 0 || w >= 0) 490 return Ebadmsg; 491 r = w = n; 492 break; 493 case 'r': 494 if(r >= 0) 495 return Ebadmsg; 496 r = n; 497 break; 498 case 'w': 499 if(w >= 0) 500 return Ebadmsg; 501 w = n; 502 break; 503 default: 504 return Ebadmsg; 505 } 506 what = '?'; 507 } 508 } 509 if(what != '?') 510 return Ebadmsg; 511 return drive->setspeed(drive, r, w); 512 } 513 return drive->ctl(drive, nf, f); 514 } 515 516 static void 517 fswrite(Req *r) 518 { 519 Otrack *o; 520 Fid *fid; 521 522 fid = r->fid; 523 r->ofcall.count = r->ifcall.count; 524 if(fid->qid.path == Qctl) { 525 respond(r, writectl(r->ifcall.data, r->ifcall.count)); 526 return; 527 } 528 529 if((o = ((Aux*)fid->aux)->o) == nil || o->omode != OWRITE) { 530 respond(r, "permission denied"); 531 return; 532 } 533 534 if(o->drive->write(o, r->ifcall.data, r->ifcall.count) < 0) 535 respond(r, geterrstr()); 536 else 537 respond(r, nil); 538 } 539 540 static void 541 fsstat(Req *r) 542 { 543 fillstat((ulong)r->fid->qid.path, &r->d); 544 r->d.name = estrdup9p(r->d.name); 545 r->d.uid = estrdup9p(r->d.uid); 546 r->d.gid = estrdup9p(r->d.gid); 547 r->d.muid = estrdup9p(r->d.muid); 548 respond(r, nil); 549 } 550 551 static void 552 fsopen(Req *r) 553 { 554 int omode; 555 Fid *fid; 556 Otrack *o; 557 558 fid = r->fid; 559 omode = r->ifcall.mode; 560 checktoc(drive); 561 r->ofcall.qid = (Qid){fid->qid.path, drive->nchange, fid->qid.vers}; 562 563 switch((ulong)fid->qid.path){ 564 case Qdir: 565 case Qwa: 566 case Qwd: 567 if(omode != OREAD) { 568 respond(r, "permission denied"); 569 return; 570 } 571 break; 572 case Qctl: 573 if(omode & ~(OTRUNC|OREAD|OWRITE|ORDWR)) { 574 respond(r, "permission denied"); 575 return; 576 } 577 break; 578 default: 579 if(fid->qid.path >= Qtrack+drive->ntrack) { 580 respond(r, "file no longer exists"); 581 return; 582 } 583 584 /* 585 * allow the open with OWRITE or ORDWR if the 586 * drive and disc are both capable? 587 */ 588 if(omode != OREAD || 589 (o = drive->openrd(drive, fid->qid.path-Qtrack)) == nil) { 590 respond(r, "permission denied"); 591 return; 592 } 593 594 o->nref = 1; 595 ((Aux*)fid->aux)->o = o; 596 break; 597 } 598 respond(r, nil); 599 } 600 601 static void 602 fsdestroyfid(Fid *fid) 603 { 604 Aux *aux; 605 Otrack *o; 606 607 aux = fid->aux; 608 if(aux == nil) 609 return; 610 o = aux->o; 611 if(o && --o->nref == 0) { 612 bterm(o->buf); 613 drive->close(o); 614 checktoc(drive); 615 } 616 } 617 618 static void 619 checktoc(Drive *drive) 620 { 621 int i; 622 Track *t; 623 624 drive->gettoc(drive); 625 if(drive->nameok) 626 return; 627 628 for(i=0; i<drive->ntrack; i++) { 629 t = &drive->track[i]; 630 if(t->size == 0) /* being created */ 631 t->mode = 0; 632 else 633 t->mode = 0444; 634 sprint(t->name, "?%.3d", i); 635 switch(t->type){ 636 case TypeNone: 637 t->name[0] = 'u'; 638 // t->mode = 0; 639 break; 640 case TypeData: 641 t->name[0] = 'd'; 642 break; 643 case TypeAudio: 644 t->name[0] = 'a'; 645 break; 646 case TypeBlank: 647 t->name[0] = '\0'; 648 break; 649 default: 650 print("unknown track type %d\n", t->type); 651 break; 652 } 653 } 654 655 drive->nameok = 1; 656 } 657 658 long 659 bufread(Otrack *t, void *v, long n, vlong off) 660 { 661 return bread(t->buf, v, n, off); 662 } 663 664 long 665 bufwrite(Otrack *t, void *v, long n) 666 { 667 return bwrite(t->buf, v, n); 668 } 669 670 Srv fs = { 671 .attach= fsattach, 672 .destroyfid= fsdestroyfid, 673 .clone= fsclone, 674 .walk1= fswalk1, 675 .open= fsopen, 676 .read= fsread, 677 .write= fswrite, 678 .create= fscreate, 679 .remove= fsremove, 680 .stat= fsstat, 681 }; 682 683 void 684 usage(void) 685 { 686 fprint(2, "usage: cdfs [-Dv] [-d /dev/sdC0] [-m mtpt]\n"); 687 exits("usage"); 688 } 689 690 void 691 main(int argc, char **argv) 692 { 693 Scsi *s; 694 int fd; 695 char *dev, *mtpt; 696 697 dev = "/dev/sdD0"; 698 mtpt = "/mnt/cd"; 699 700 ARGBEGIN{ 701 case 'D': 702 chatty9p++; 703 break; 704 case 'd': 705 dev = EARGF(usage()); 706 break; 707 case 'm': 708 mtpt = EARGF(usage()); 709 break; 710 case 'v': 711 if((fd = create("/tmp/cdfs.log", OWRITE, 0666)) >= 0) { 712 dup(fd, 2); 713 dup(fd, 1); 714 if(fd != 1 && fd != 2) 715 close(fd); 716 vflag++; 717 scsiverbose = 2; /* verbose but no Readtoc errs */ 718 } 719 break; 720 default: 721 usage(); 722 }ARGEND 723 724 if(dev == nil || mtpt == nil || argc > 0) 725 usage(); 726 727 werrstr(""); 728 if((s = openscsi(dev)) == nil) 729 sysfatal("openscsi '%s': %r", dev); 730 if((drive = mmcprobe(s)) == nil) 731 sysfatal("mmcprobe '%s': %r", dev); 732 checktoc(drive); 733 734 postmountsrv(&fs, nil, mtpt, MREPL|MCREATE); 735 exits(nil); 736 } 737