]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/cdfs/main.c
awk: make empty FS unicodely-correct.
[plan9front.git] / sys / src / cmd / cdfs / main.c
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
26 enum {
27         Qdir = 0,
28         Qctl = 1,
29         Qwa = 2,
30         Qwd = 3,
31         Qtrack = 4,
32 };
33
34 char*
35 geterrstr(void)
36 {
37         static char errbuf[ERRMAX];
38
39         rerrstr(errbuf, sizeof errbuf);
40         return errbuf;
41 }
42
43 void*
44 emalloc(ulong sz)
45 {
46         void *v;
47
48         v = mallocz(sz, 1);
49         if(v == nil)
50                 sysfatal("malloc %lud fails", sz);
51         return v;
52 }
53
54 static void
55 fsattach(Req *r)
56 {
57         char *spec;
58
59         spec = r->ifcall.aname;
60         if(spec && spec[0]) {
61                 respond(r, "invalid attach specifier");
62                 return;
63         }
64
65         checktoc(drive);
66         r->fid->qid = (Qid){Qdir, drive->nchange, QTDIR};
67         r->ofcall.qid = r->fid->qid;
68         r->fid->aux = emalloc(sizeof(Aux));
69         respond(r, nil);
70 }
71
72 static char*
73 fsclone(Fid *old, Fid *new)
74 {
75         Aux *na;
76
77         na = emalloc(sizeof(Aux));
78         *na = *((Aux*)old->aux);
79         if(na->o)
80                 na->o->nref++;
81         new->aux = na;
82         return nil;
83 }
84
85 static char*
86 fswalk1(Fid *fid, char *name, Qid *qid)
87 {
88         int i;
89
90         checktoc(drive);
91         switch((ulong)fid->qid.path) {
92         case Qdir:
93                 if(strcmp(name, "..") == 0) {
94                         *qid = (Qid){Qdir, drive->nchange, QTDIR};
95                         return nil;
96                 }
97                 if(strcmp(name, "ctl") == 0) {
98                         *qid = (Qid){Qctl, 0, 0};
99                         return nil;
100                 }
101                 if(strcmp(name, "wa") == 0 && drive->writeok &&
102                     (drive->mmctype == Mmcnone ||
103                      drive->mmctype == Mmccd)) {
104                         *qid = (Qid){Qwa, drive->nchange, QTDIR};
105                         return nil;
106                 }
107                 if(strcmp(name, "wd") == 0 && drive->writeok) {
108                         *qid = (Qid){Qwd, drive->nchange, QTDIR};
109                         return nil;
110                 }
111                 for(i=0; i<drive->ntrack; i++)
112                         if(strcmp(drive->track[i].name, name) == 0)
113                                 break;
114                 if(i == drive->ntrack)
115                         return "file not found";
116                 *qid = (Qid){Qtrack+i, 0, 0};
117                 return nil;
118
119         case Qwa:
120         case Qwd:
121                 if(strcmp(name, "..") == 0) {
122                         *qid = (Qid){Qdir, drive->nchange, QTDIR};
123                         return nil;
124                 }
125                 return "file not found";
126         default:        /* bug: lib9p could handle this */
127                 return "walk in non-directory";
128         }
129 }
130
131 static void
132 fscreate(Req *r)
133 {
134         int omode, type;
135         Otrack *o;
136         Fid *fid;
137
138         fid = r->fid;
139         omode = r->ifcall.mode;
140         
141         if(omode != OWRITE) {
142                 respond(r, "bad mode (use OWRITE)");
143                 return;
144         }
145
146         switch((ulong)fid->qid.path) {
147         case Qdir:
148         default:
149                 respond(r, "permission denied");
150                 return;
151
152         case Qwa:
153                 if (drive->mmctype != Mmcnone &&
154                     drive->mmctype != Mmccd) {
155                         respond(r, "audio supported only on cd");
156                         return;
157                 }
158                 type = TypeAudio;
159                 break;
160
161         case Qwd:
162                 type = TypeData;
163                 break;
164         }
165
166         if((drive->cap & Cwrite) == 0) {
167                 respond(r, "drive does not write");
168                 return;
169         }
170
171         o = drive->create(drive, type);
172         if(o == nil) {
173                 respond(r, geterrstr());
174                 return;
175         }
176         drive->nchange = -1;
177         checktoc(drive);        /* update directory info */
178         o->nref = 1;
179         ((Aux*)fid->aux)->o = o;
180
181         fid->qid = (Qid){Qtrack+(o->track - drive->track), drive->nchange, 0};
182         r->ofcall.qid = fid->qid;
183         respond(r, nil);
184 }
185
186 static void
187 fsremove(Req *r)
188 {
189         switch((ulong)r->fid->qid.path){
190         case Qwa:
191         case Qwd:
192                 if(drive->fixate(drive) < 0)
193                         respond(r, geterrstr());
194 // let us see if it can figure this out:        drive->writeok = No;    
195                 else
196                         respond(r, nil);
197                 checktoc(drive);
198                 break;
199         default:
200                 respond(r, "permission denied");
201                 break;
202         }
203 }
204
205 /* result is one word, so it can be used as a uid in Dir structs */
206 char *
207 disctype(Drive *drive)
208 {
209         char *type, *rw;
210
211         switch (drive->mmctype) {
212         case Mmccd:
213                 type = "cd-";
214                 break;
215         case Mmcdvdminus:
216         case Mmcdvdplus:
217                 type = drive->dvdtype;
218                 break;
219         case Mmcbd:
220                 type = "bd-";
221                 break;
222         case Mmcnone:
223                 type = "no-disc";
224                 break;
225         default:
226                 type = "**GOK**";               /* traditional */
227                 break;
228         }
229         rw = "";
230         if (drive->mmctype != Mmcnone && drive->dvdtype == nil)
231                 if (drive->erasable == Yes)
232                         rw = drive->mmctype == Mmcbd? "re": "rw";
233                 else if (drive->recordable == Yes)
234                         rw = "r";
235                 else
236                         rw = "rom";
237         return smprint("%s%s", type, rw);
238 }
239
240 int
241 fillstat(ulong qid, Dir *d)
242 {
243         char *ty;
244         Track *t;
245         static char buf[32];
246
247         nulldir(d);
248         d->type = L'M';
249         d->dev = 1;
250         d->length = 0;
251         ty = disctype(drive);
252         strncpy(buf, ty, sizeof buf);
253         free(ty);
254         d->uid = d->gid = buf;
255         d->muid = "";
256         d->qid = (Qid){qid, drive->nchange, 0};
257         d->atime = time(0);
258         d->mtime = drive->changetime;
259
260         switch(qid){
261         case Qdir:
262                 d->name = "/";
263                 d->qid.type = QTDIR;
264                 d->mode = DMDIR|0777;
265                 break;
266
267         case Qctl:
268                 d->name = "ctl";
269                 d->mode = 0666;
270                 break;
271
272         case Qwa:
273                 if(drive->writeok == No ||
274                     drive->mmctype != Mmcnone &&
275                     drive->mmctype != Mmccd)
276                         return 0;
277                 d->name = "wa";
278                 d->qid.type = QTDIR;
279                 d->mode = DMDIR|0777;
280                 break;
281
282         case Qwd:
283                 if(drive->writeok == No)
284                         return 0;
285                 d->name = "wd";
286                 d->qid.type = QTDIR;
287                 d->mode = DMDIR|0777;
288                 break;
289
290         default:
291                 if(qid-Qtrack >= drive->ntrack)
292                         return 0;
293                 t = &drive->track[qid-Qtrack];
294                 if(strcmp(t->name, "") == 0)
295                         return 0;
296                 d->name = t->name;
297                 d->mode = t->mode;
298                 d->length = t->size;
299                 break;
300         }
301         return 1;
302 }
303
304 static ulong 
305 cddb_sum(int n)
306 {
307         int ret;
308         ret = 0;
309         while(n > 0) {
310                 ret += n%10;
311                 n /= 10;
312         }
313         return ret;
314 }
315
316 static ulong
317 diskid(Drive *d)
318 {
319         int i, n;
320         ulong tmp;
321         Msf *ms, *me;
322
323         n = 0;
324         for(i=0; i < d->ntrack; i++)
325                 n += cddb_sum(d->track[i].mbeg.m*60+d->track[i].mbeg.s);
326
327         ms = &d->track[0].mbeg;
328         me = &d->track[d->ntrack].mbeg;
329         tmp = (me->m*60+me->s) - (ms->m*60+ms->s);
330
331         /*
332          * the spec says n%0xFF rather than n&0xFF.  it's unclear which is
333          * correct.  most CDs are in the database under both entries.
334          */
335         return ((n % 0xFF) << 24 | (tmp << 8) | d->ntrack);
336 }
337
338 static void
339 readctl(Req *r)
340 {
341         int i, isaudio;
342         ulong nwa;
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                         nwa = getnwa(drive);
380                         p = seprint(p, e, " next writable sector ");
381                         if (nwa == ~0ul)
382                                 p = seprint(p, e, "none; disc full");
383                         else
384                                 p = seprint(p, e, "%lud", nwa);
385                 }
386                 seprint(p, e, "\n");
387         }
388         readstr(r, s);
389 }
390
391 static void
392 fsread(Req *r)
393 {
394         int j, n, m;
395         uchar *p, *ep;
396         Dir d;
397         Fid *fid;
398         Otrack *o;
399         vlong offset;
400         void *buf;
401         long count;
402         Aux *a;
403
404         fid = r->fid;
405         offset = r->ifcall.offset;
406         buf = r->ofcall.data;
407         count = r->ifcall.count;
408
409         switch((ulong)fid->qid.path) {
410         case Qdir:
411                 checktoc(drive);
412                 p = buf;
413                 ep = p+count;
414                 m = Qtrack+drive->ntrack;
415                 a = fid->aux;
416                 if(offset == 0)
417                         a->doff = 1;    /* skip root */
418
419                 for(j=a->doff; j<m; j++) {
420                         if(fillstat(j, &d)) {
421                                 if((n = convD2M(&d, p, ep-p)) <= BIT16SZ)
422                                         break;
423                                 p += n;
424                         }
425                 }
426                 a->doff = j;
427
428                 r->ofcall.count = p - (uchar*)buf;
429                 break;
430         case Qwa:
431         case Qwd:
432                 r->ofcall.count = 0;
433                 break;
434         case Qctl:
435                 readctl(r);
436                 break;
437         default:
438                 /* a disk track; we can only call read for whole blocks */
439                 o = ((Aux*)fid->aux)->o;
440                 if((count = o->drive->read(o, buf, count, offset)) < 0) {
441                         respond(r, geterrstr());
442                         return;
443                 }
444                 r->ofcall.count = count;
445                 break;
446         }
447         respond(r, nil);
448 }
449
450 static char Ebadmsg[] = "bad cdfs control message";
451
452 static char*
453 writectl(void *v, long count)
454 {
455         char buf[256];
456         char *f[10], *p;
457         int i, nf, n, r, w, what;
458
459         if(count >= sizeof(buf))
460                 count = sizeof(buf)-1;
461         memmove(buf, v, count);
462         buf[count] = '\0';
463
464         nf = tokenize(buf, f, nelem(f));
465         if(nf == 0)
466                 return Ebadmsg;
467
468         if(strcmp(f[0], "speed") == 0){
469                 what = 0;
470                 r = w = -1;
471                 if(nf == 1)
472                         return Ebadmsg;
473                 for(i=1; i<nf; i++){
474                         if(strcmp(f[i], "read") == 0 || strcmp(f[i], "write") == 0){
475                                 if(what!=0 && what!='?')
476                                         return Ebadmsg;
477                                 what = f[i][0];
478                         }else{
479                                 if (strcmp(f[i], "best") == 0)
480                                         n = (1<<16) - 1;
481                                 else {
482                                         n = strtol(f[i], &p, 0);
483                                         if(*p != '\0' || n <= 0)
484                                                 return Ebadmsg;
485                                 }
486                                 switch(what){
487                                 case 0:
488                                         if(r >= 0 || w >= 0)
489                                                 return Ebadmsg;
490                                         r = w = n;
491                                         break;
492                                 case 'r':
493                                         if(r >= 0)
494                                                 return Ebadmsg;
495                                         r = n;
496                                         break;
497                                 case 'w':
498                                         if(w >= 0)
499                                                 return Ebadmsg;
500                                         w = n;
501                                         break;
502                                 default:
503                                         return Ebadmsg;
504                                 }
505                                 what = '?';
506                         }
507                 }
508                 if(what != '?')
509                         return Ebadmsg;
510                 return drive->setspeed(drive, r, w);
511         }
512         return drive->ctl(drive, nf, f);
513 }
514
515 static void
516 fswrite(Req *r)
517 {
518         Otrack *o;
519         Fid *fid;
520
521         fid = r->fid;
522         r->ofcall.count = r->ifcall.count;
523         if(fid->qid.path == Qctl) {
524                 respond(r, writectl(r->ifcall.data, r->ifcall.count));
525                 return;
526         }
527
528         if((o = ((Aux*)fid->aux)->o) == nil || o->omode != OWRITE) {
529                 respond(r, "permission denied");
530                 return;
531         }
532
533         if(o->drive->write(o, r->ifcall.data, r->ifcall.count) < 0)
534                 respond(r, geterrstr());
535         else
536                 respond(r, nil);
537 }
538
539 static void
540 fsstat(Req *r)
541 {
542         fillstat((ulong)r->fid->qid.path, &r->d);
543         r->d.name = estrdup9p(r->d.name);
544         r->d.uid = estrdup9p(r->d.uid);
545         r->d.gid = estrdup9p(r->d.gid);
546         r->d.muid = estrdup9p(r->d.muid);
547         respond(r, nil);
548 }
549
550 static void
551 fsopen(Req *r)
552 {
553         int omode;
554         Fid *fid;
555         Otrack *o;
556
557         fid = r->fid;
558         omode = r->ifcall.mode;
559         checktoc(drive);
560         r->ofcall.qid = (Qid){fid->qid.path, drive->nchange, fid->qid.vers};
561
562         switch((ulong)fid->qid.path){
563         case Qdir:
564         case Qwa:
565         case Qwd:
566                 if(omode != OREAD) {
567                         respond(r, "permission denied");
568                         return;
569                 }
570                 break;
571         case Qctl:
572                 if(omode & ~(OTRUNC|OREAD|OWRITE|ORDWR)) {
573                         respond(r, "permission denied");
574                         return;
575                 }
576                 break;
577         default:
578                 if(fid->qid.path >= Qtrack+drive->ntrack) {
579                         respond(r, "file no longer exists");
580                         return;
581                 }
582
583                 /*
584                  * allow the open with OWRITE or ORDWR if the
585                  * drive and disc are both capable?
586                  */
587                 if(omode != OREAD ||
588                     (o = drive->openrd(drive, fid->qid.path-Qtrack)) == nil) {
589                         respond(r, "permission denied");
590                         return;
591                 }
592
593                 o->nref = 1;
594                 ((Aux*)fid->aux)->o = o;
595                 break;
596         }
597         respond(r, nil);
598 }
599
600 static void
601 fsdestroyfid(Fid *fid)
602 {
603         Aux *aux;
604         Otrack *o;
605
606         aux = fid->aux;
607         if(aux == nil)
608                 return;
609         o = aux->o;
610         if(o && --o->nref == 0) {
611                 bterm(o->buf);
612                 drive->close(o);
613                 checktoc(drive);
614         }
615 }
616
617 static void
618 checktoc(Drive *drive)
619 {
620         int i;
621         Track *t;
622
623         drive->gettoc(drive);
624         if(drive->nameok)
625                 return;
626
627         for(i=0; i<drive->ntrack; i++) {
628                 t = &drive->track[i];
629                 if(t->size == 0)        /* being created */
630                         t->mode = 0;
631                 else
632                         t->mode = 0444;
633                 sprint(t->name, "?%.3d", i);
634                 switch(t->type){
635                 case TypeNone:
636                         t->name[0] = 'u';
637 //                      t->mode = 0;
638                         break;
639                 case TypeData:
640                         t->name[0] = 'd';
641                         break;
642                 case TypeAudio:
643                         t->name[0] = 'a';
644                         break;
645                 case TypeBlank:
646                         t->name[0] = '\0';
647                         break;
648                 default:
649                         print("unknown track type %d\n", t->type);
650                         break;
651                 }
652         }
653
654         drive->nameok = 1;
655 }
656
657 long
658 bufread(Otrack *t, void *v, long n, vlong off)
659 {
660         return bread(t->buf, v, n, off);
661 }
662
663 long
664 bufwrite(Otrack *t, void *v, long n)
665 {
666         return bwrite(t->buf, v, n);
667 }
668
669 Srv fs = {
670 .attach=        fsattach,
671 .destroyfid=    fsdestroyfid,
672 .clone=         fsclone,
673 .walk1=         fswalk1,
674 .open=          fsopen,
675 .read=          fsread,
676 .write=         fswrite,
677 .create=        fscreate,
678 .remove=        fsremove,
679 .stat=          fsstat,
680 };
681
682 void
683 usage(void)
684 {
685         fprint(2, "usage: cdfs [-Dv] [-d /dev/sdC0] [-m mtpt]\n");
686         exits("usage");
687 }
688
689 void
690 main(int argc, char **argv)
691 {
692         Scsi *s;
693         int fd;
694         char *dev, *mtpt;
695
696         dev = "/dev/sdD0";
697         mtpt = "/mnt/cd";
698
699         ARGBEGIN{
700         case 'D':
701                 chatty9p++;
702                 break;
703         case 'd':
704                 dev = EARGF(usage());
705                 break;
706         case 'm':
707                 mtpt = EARGF(usage());
708                 break;
709         case 'v':
710                 if((fd = create("/tmp/cdfs.log", OWRITE, 0666)) >= 0) {
711                         dup(fd, 2);
712                         dup(fd, 1);
713                         if(fd != 1 && fd != 2)
714                                 close(fd);
715                         vflag++;
716                         scsiverbose = 2; /* verbose but no Readtoc errs */
717                 }
718                 break;
719         default:
720                 usage();
721         }ARGEND
722
723         if(dev == nil || mtpt == nil || argc > 0)
724                 usage();
725
726         werrstr("");
727         if((s = openscsi(dev)) == nil)
728                 sysfatal("openscsi '%s': %r", dev);
729         if((drive = mmcprobe(s)) == nil)
730                 sysfatal("mmcprobe '%s': %r", dev);
731         checktoc(drive);
732
733         postmountsrv(&fs, nil, mtpt, MREPL|MCREATE);
734         exits(nil);
735 }