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 = 0; 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 static 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) 233 rw = drive->mmctype == Mmcbd? "re": "rw"; 234 else if (drive->recordable) 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 == 0 || 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 == 0) 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 char *p, *e, *ty; 344 char s[1024]; 345 Msf *m; 346 347 isaudio = 0; 348 for(i=0; i<drive->ntrack; i++) 349 if(drive->track[i].type == TypeAudio) 350 isaudio = 1; 351 352 p = s; 353 e = s + sizeof s; 354 *p = '\0'; 355 if(isaudio){ 356 p = seprint(p, e, "aux/cddb query %8.8lux %d", diskid(drive), 357 drive->ntrack); 358 for(i=0; i<drive->ntrack; i++){ 359 m = &drive->track[i].mbeg; 360 p = seprint(p, e, " %d", (m->m*60 + m->s)*75 + m->f); 361 } 362 m = &drive->track[drive->ntrack].mbeg; 363 p = seprint(p, e, " %d\n", m->m*60 + m->s); 364 } 365 366 if(drive->readspeed == drive->writespeed) 367 p = seprint(p, e, "speed %d\n", drive->readspeed); 368 else 369 p = seprint(p, e, "speed read %d write %d\n", 370 drive->readspeed, drive->writespeed); 371 p = seprint(p, e, "maxspeed read %d write %d\n", 372 drive->maxreadspeed, drive->maxwritespeed); 373 374 if (drive->Scsi.changetime != 0 && drive->ntrack != 0) { /* have disc? */ 375 ty = disctype(drive); 376 p = seprint(p, e, "%s", ty); 377 free(ty); 378 if (drive->mmctype != Mmcnone) 379 p = seprint(p, e, " next writable sector %lud", 380 getnwa(drive)); 381 seprint(p, e, "\n"); 382 } 383 readstr(r, s); 384 } 385 386 static void 387 fsread(Req *r) 388 { 389 int j, n, m; 390 uchar *p, *ep; 391 Dir d; 392 Fid *fid; 393 Otrack *o; 394 vlong offset; 395 void *buf; 396 long count; 397 Aux *a; 398 399 fid = r->fid; 400 offset = r->ifcall.offset; 401 buf = r->ofcall.data; 402 count = r->ifcall.count; 403 404 switch((ulong)fid->qid.path) { 405 case Qdir: 406 checktoc(drive); 407 p = buf; 408 ep = p+count; 409 m = Qtrack+drive->ntrack; 410 a = fid->aux; 411 if(offset == 0) 412 a->doff = 1; /* skip root */ 413 414 for(j=a->doff; j<m; j++) { 415 if(fillstat(j, &d)) { 416 if((n = convD2M(&d, p, ep-p)) <= BIT16SZ) 417 break; 418 p += n; 419 } 420 } 421 a->doff = j; 422 423 r->ofcall.count = p - (uchar*)buf; 424 break; 425 case Qwa: 426 case Qwd: 427 r->ofcall.count = 0; 428 break; 429 case Qctl: 430 readctl(r); 431 break; 432 default: 433 /* a disk track; we can only call read for whole blocks */ 434 o = ((Aux*)fid->aux)->o; 435 if((count = o->drive->read(o, buf, count, offset)) < 0) { 436 respond(r, geterrstr()); 437 return; 438 } 439 r->ofcall.count = count; 440 break; 441 } 442 respond(r, nil); 443 } 444 445 static char Ebadmsg[] = "bad cdfs control message"; 446 447 static char* 448 writectl(void *v, long count) 449 { 450 char buf[256]; 451 char *f[10], *p; 452 int i, nf, n, r, w, what; 453 454 if(count >= sizeof(buf)) 455 count = sizeof(buf)-1; 456 memmove(buf, v, count); 457 buf[count] = '\0'; 458 459 nf = tokenize(buf, f, nelem(f)); 460 if(nf == 0) 461 return Ebadmsg; 462 463 if(strcmp(f[0], "speed") == 0){ 464 what = 0; 465 r = w = -1; 466 if(nf == 1) 467 return Ebadmsg; 468 for(i=1; i<nf; i++){ 469 if(strcmp(f[i], "read") == 0 || strcmp(f[i], "write") == 0){ 470 if(what!=0 && what!='?') 471 return Ebadmsg; 472 what = f[i][0]; 473 }else{ 474 if (strcmp(f[i], "best") == 0) 475 n = (1<<16) - 1; 476 else { 477 n = strtol(f[i], &p, 0); 478 if(*p != '\0' || n <= 0) 479 return Ebadmsg; 480 } 481 switch(what){ 482 case 0: 483 if(r >= 0 || w >= 0) 484 return Ebadmsg; 485 r = w = n; 486 break; 487 case 'r': 488 if(r >= 0) 489 return Ebadmsg; 490 r = n; 491 break; 492 case 'w': 493 if(w >= 0) 494 return Ebadmsg; 495 w = n; 496 break; 497 default: 498 return Ebadmsg; 499 } 500 what = '?'; 501 } 502 } 503 if(what != '?') 504 return Ebadmsg; 505 return drive->setspeed(drive, r, w); 506 } 507 return drive->ctl(drive, nf, f); 508 } 509 510 static void 511 fswrite(Req *r) 512 { 513 Otrack *o; 514 Fid *fid; 515 516 fid = r->fid; 517 r->ofcall.count = r->ifcall.count; 518 if(fid->qid.path == Qctl) { 519 respond(r, writectl(r->ifcall.data, r->ifcall.count)); 520 return; 521 } 522 523 if((o = ((Aux*)fid->aux)->o) == nil || o->omode != OWRITE) { 524 respond(r, "permission denied"); 525 return; 526 } 527 528 if(o->drive->write(o, r->ifcall.data, r->ifcall.count) < 0) 529 respond(r, geterrstr()); 530 else 531 respond(r, nil); 532 } 533 534 static void 535 fsstat(Req *r) 536 { 537 fillstat((ulong)r->fid->qid.path, &r->d); 538 r->d.name = estrdup9p(r->d.name); 539 r->d.uid = estrdup9p(r->d.uid); 540 r->d.gid = estrdup9p(r->d.gid); 541 r->d.muid = estrdup9p(r->d.muid); 542 respond(r, nil); 543 } 544 545 static void 546 fsopen(Req *r) 547 { 548 int omode; 549 Fid *fid; 550 Otrack *o; 551 552 fid = r->fid; 553 omode = r->ifcall.mode; 554 checktoc(drive); 555 r->ofcall.qid = (Qid){fid->qid.path, drive->nchange, fid->qid.vers}; 556 557 switch((ulong)fid->qid.path){ 558 case Qdir: 559 case Qwa: 560 case Qwd: 561 if(omode != OREAD) { 562 respond(r, "permission denied"); 563 return; 564 } 565 break; 566 case Qctl: 567 if(omode & ~(OTRUNC|OREAD|OWRITE|ORDWR)) { 568 respond(r, "permission denied"); 569 return; 570 } 571 break; 572 default: 573 if(fid->qid.path >= Qtrack+drive->ntrack) { 574 respond(r, "file no longer exists"); 575 return; 576 } 577 578 /* 579 * allow the open with OWRITE or ORDWR if the 580 * drive and disc are both capable? 581 */ 582 if(omode != OREAD || 583 (o = drive->openrd(drive, fid->qid.path-Qtrack)) == nil) { 584 respond(r, "permission denied"); 585 return; 586 } 587 588 o->nref = 1; 589 ((Aux*)fid->aux)->o = o; 590 break; 591 } 592 respond(r, nil); 593 } 594 595 static void 596 fsdestroyfid(Fid *fid) 597 { 598 Aux *aux; 599 Otrack *o; 600 601 aux = fid->aux; 602 if(aux == nil) 603 return; 604 o = aux->o; 605 if(o && --o->nref == 0) { 606 bterm(o->buf); 607 drive->close(o); 608 checktoc(drive); 609 } 610 } 611 612 static void 613 checktoc(Drive *drive) 614 { 615 int i; 616 Track *t; 617 618 drive->gettoc(drive); 619 if(drive->nameok) 620 return; 621 622 for(i=0; i<drive->ntrack; i++) { 623 t = &drive->track[i]; 624 if(t->size == 0) /* being created */ 625 t->mode = 0; 626 else 627 t->mode = 0444; 628 sprint(t->name, "?%.3d", i); 629 switch(t->type){ 630 case TypeNone: 631 t->name[0] = 'u'; 632 // t->mode = 0; 633 break; 634 case TypeData: 635 t->name[0] = 'd'; 636 break; 637 case TypeAudio: 638 t->name[0] = 'a'; 639 break; 640 case TypeBlank: 641 t->name[0] = '\0'; 642 break; 643 default: 644 print("unknown track type %d\n", t->type); 645 break; 646 } 647 } 648 649 drive->nameok = 1; 650 } 651 652 long 653 bufread(Otrack *t, void *v, long n, vlong off) 654 { 655 return bread(t->buf, v, n, off); 656 } 657 658 long 659 bufwrite(Otrack *t, void *v, long n) 660 { 661 return bwrite(t->buf, v, n); 662 } 663 664 Srv fs = { 665 .attach= fsattach, 666 .destroyfid= fsdestroyfid, 667 .clone= fsclone, 668 .walk1= fswalk1, 669 .open= fsopen, 670 .read= fsread, 671 .write= fswrite, 672 .create= fscreate, 673 .remove= fsremove, 674 .stat= fsstat, 675 }; 676 677 void 678 usage(void) 679 { 680 fprint(2, "usage: cdfs [-Dv] [-d /dev/sdC0] [-m mtpt]\n"); 681 exits("usage"); 682 } 683 684 void 685 main(int argc, char **argv) 686 { 687 Scsi *s; 688 int fd; 689 char *dev, *mtpt; 690 691 dev = "/dev/sdD0"; 692 mtpt = "/mnt/cd"; 693 694 ARGBEGIN{ 695 case 'D': 696 chatty9p++; 697 break; 698 case 'd': 699 dev = EARGF(usage()); 700 break; 701 case 'm': 702 mtpt = EARGF(usage()); 703 break; 704 case 'v': 705 if((fd = create("/tmp/cdfs.log", OWRITE, 0666)) >= 0) { 706 dup(fd, 2); 707 dup(fd, 1); 708 if(fd != 1 && fd != 2) 709 close(fd); 710 vflag++; 711 scsiverbose = 2; /* verbose but no Readtoc errs */ 712 } 713 break; 714 default: 715 usage(); 716 }ARGEND 717 718 if(dev == nil || mtpt == nil || argc > 0) 719 usage(); 720 721 if((s = openscsi(dev)) == nil) 722 sysfatal("openscsi '%s': %r", dev); 723 if((drive = mmcprobe(s)) == nil) 724 sysfatal("mmcprobe '%s': %r", dev); 725 checktoc(drive); 726 727 postmountsrv(&fs, nil, mtpt, MREPL|MCREATE); 728 exits(nil); 729 } 730