xref: /plan9/sys/src/cmd/cdfs/main.c (revision 39e9a3f16a02917facc249a68c87044e5601acab)
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