xref: /plan9/sys/src/cmd/cdfs/main.c (revision 889a2622ddfec1cddd31ce9ec338e8fabdc678ea)
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*
geterrstr(void)36 geterrstr(void)
37 {
38 	static char errbuf[ERRMAX];
39 
40 	rerrstr(errbuf, sizeof errbuf);
41 	return errbuf;
42 }
43 
44 void*
emalloc(ulong sz)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
fsattach(Req * r)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*
fsclone(Fid * old,Fid * new)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*
fswalk1(Fid * fid,char * name,Qid * qid)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
fscreate(Req * r)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
fsremove(Req * r)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 *
disctype(Drive * drive)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
fillstat(ulong qid,Dir * d)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
cddb_sum(int n)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
diskid(Drive * d)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
readctl(Req * r)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
fsread(Req * r)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*
writectl(void * v,long count)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
fswrite(Req * r)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
fsstat(Req * r)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
fsopen(Req * r)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
fsdestroyfid(Fid * fid)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
checktoc(Drive * drive)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
bufread(Otrack * t,void * v,long n,vlong off)661 bufread(Otrack *t, void *v, long n, vlong off)
662 {
663 	return bread(t->buf, v, n, off);
664 }
665 
666 long
bufwrite(Otrack * t,void * v,long n)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
usage(void)686 usage(void)
687 {
688 	fprint(2, "usage: cdfs [-Dv] [-d /dev/sdC0] [-m mtpt]\n");
689 	exits("usage");
690 }
691 
692 void
main(int argc,char ** argv)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