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