]> git.lizzy.rs Git - plan9front.git/blob - sys/src/games/wadfs.c
add games/wadfs
[plan9front.git] / sys / src / games / wadfs.c
1 #include <u.h>
2 #include <libc.h>
3 #include <ctype.h>
4 #include <fcall.h>
5 #include <thread.h>
6 #include <9p.h>
7 #include <bio.h>
8
9 enum{
10         Nsig = 4,
11         Nhdr = Nsig+4+4,
12         Ndict = 4+4+8,
13         Nname = 8,
14         Nbuf = 8192,
15         Maxsz = 0x7fffffff - Nhdr
16 };
17
18 enum{
19         LTnil,
20         LTreg,
21         LTmap,
22         LTmrk,
23         LTend
24 };
25 typedef struct Lump Lump;
26 struct Lump{
27         char name[Nname+1];
28         u32int ofs;
29         uchar *buf;
30         ulong nbuf;
31         int type;
32         File *f;
33         Lump *l;
34         Lump *lp;
35 };
36 Lump l1 = {.l = &l1, .lp = &l1}, *lumps = &l1;
37
38 Biobuf *wad;
39 u32int nlmp;
40 File *ldir, *fsig, *fwad;
41 int rdonly, dirty;
42
43 Srv fs;
44
45 char *mapn[] = {
46         "things", "linedefs", "sidedefs", "vertexes", "segs",
47         "ssectors", "nodes", "sectors", "reject", "blockmap"
48 };
49
50 void
51 strupr(char *s, char *p)
52 {
53         char c;
54
55         do{
56                 c = *p++;
57                 *s++ = toupper(c);
58         }while(c != 0);
59 }
60
61 void
62 strlwr(char *s, char *p)
63 {
64         char c;
65
66         do{
67                 c = *p++;
68                 *s++ = tolower(c);
69         }while(c != 0);
70 }
71
72 void
73 link(Lump *l, Lump *lp, int len)
74 {
75         l->lp = lp;
76         l->l = lp->l;
77         lp->l->lp = l;
78         lp->l = l;
79         nlmp++;
80         fwad->length += Ndict + len;
81 }
82
83 void
84 unlink(Lump *l)
85 {
86         if(l->l == nil)
87                 return;
88         l->lp->l = l->l;
89         l->l->lp = l->lp;
90         l->l = nil;
91         nlmp--;
92         fwad->length -= Ndict + (l->f != nil ? l->f->length : 0);
93 }
94
95 void
96 freelump(Lump *l)
97 {
98         unlink(l);
99         free(l->buf);
100         free(l);
101 }
102
103 void
104 readlump(Lump *l, uchar *p, long n)
105 {
106         if(n <= 0)
107                 return;
108         Bseek(wad, l->ofs, 0);
109         if(Bread(wad, p, n) != n)
110                 fprint(2, "readlump: short read: %r\n");
111 }
112
113 void
114 loadlump(File *f, ulong n)
115 {
116         Lump *l;
117
118         l = f->aux;
119         if(f->length > n)
120                 n = f->length;
121         l->buf = emalloc9p(n);
122         l->nbuf = n;
123         l->ofs = 0;
124         readlump(l, l->buf, f->length);
125 }
126
127 Lump *
128 lastlump(Lump *lp)
129 {
130         File *f, *dir;
131
132         for(dir=lp->f, f=lp->l->f; lp->l!=lumps; lp=lp->l, f=lp->l->f)
133                 if(f->parent != dir && f->parent->parent != dir)
134                         break;
135         if(lp->type == LTend && lp->f->parent == dir)
136                 lp = lp->lp;
137         return lp;
138 }
139
140 int
141 nextmaplump(char *s)
142 {
143         char **p;
144
145         for(p=mapn; p<mapn+nelem(mapn); p++)
146                 if(strcmp(s, *p) == 0)
147                         return p-mapn;
148         return -1;
149 }
150
151 Lump *
152 sortmap(Lump *lp, Lump *l)
153 {
154         int ip, i;
155
156         i = nextmaplump(l->f->name);
157         for(; lp->l != lumps; lp=lp->l){
158                 ip = nextmaplump(lp->l->f->name);
159                 if(ip < 0 || ip > i)
160                         break;
161         }
162         return lp;
163 }
164
165 int
166 ismaplump(char *s)
167 {
168         return nextmaplump(s) >= 0;
169 }
170
171 int
172 ismapname(char *s)
173 {
174         if(strncmp(s, "map", 3) == 0)
175                 return isdigit(s[3]) && isdigit(s[4]);
176         return s[0] == 'e' && isdigit(s[1])
177                 && s[2] == 'm' && isdigit(s[3]);
178 }
179
180 int
181 ismarkname(char *s, char *m)
182 {
183         char *p;
184
185         p = strstr(s, m);
186         if(p == nil || p[strlen(m)] != 0)
187                 return 0;
188         if(p - s > 2)
189                 return 0;
190         return 1;
191 }
192
193 int
194 validname(char *s, File *dir, int *type, int isnew, int isdir)
195 {
196         int n;
197         char c, *p;
198         Lump *lp;
199
200         *type = LTnil;
201         n = strlen(s);
202         if(n < 1 || n > sizeof(lp->name)-1){
203                 werrstr("invalid lump name");
204                 return 0;
205         }
206         for(p=s+n-1; c=*p, p-->=s;)
207                 if(!isprint(c) || isupper(c) || c == '/'){
208                         werrstr("invalid char %c in filename", c);
209                         return 0;
210                 }
211         if(isnew && !ismaplump(s))
212                 for(lp=lumps->l; lp!=lumps; lp=lp->l)
213                         if(cistrcmp(s, lp->name) == 0){
214                                 werrstr("duplicate non map lump");
215                                 return 0;
216                         }
217         *type = LTreg;
218         lp = dir->aux;
219         if(ismapname(s)){
220                 *type = LTmap;
221                 if(isnew && !isdir){
222                         werrstr("map marker not a directory");
223                         return 0;
224                 }else if(dir != fs.tree->root){
225                         werrstr("nested map directory");
226                         return 0;
227                 }
228                 return 1;
229         }else if(ismarkname(s, "_end")){
230                 *type = LTend;
231                 if(dir == fs.tree->root || lp == nil || lp->type == LTmap){
232                         werrstr("orphaned end marker");
233                         return 0;
234                 }
235                 return 1;
236         }else if(ismarkname(s, "_start")){
237                 *type = LTmrk;
238                 if(isnew){
239                         werrstr("not allowed");
240                         return 0;
241                 }
242                 goto mrkchk;
243         }else if(isnew && isdir){
244                 *type = LTmrk;
245                 if(n > 2){
246                         werrstr("marker name too long");
247                         return 0;
248                 }
249 mrkchk:
250                 if(dir->parent != fs.tree->root){
251                         werrstr("start marker nested too deep");
252                         return 0;
253                 }else if(lp != nil && lp->type == LTmap){
254                         werrstr("start marker within map directory");
255                         return 0;
256                 }
257                 return 1;
258         }else if(ismaplump(s) ^ (lp != nil && lp->type == LTmap)){
259                 werrstr("map lump outside of map directory");
260                 return 0;
261         }
262         return 1;
263 }
264
265 int
266 endldir(Lump *lp, Lump *le)
267 {
268         char *s, name[sizeof lp->name];
269         Lump *l;
270         File *f;
271
272         l = emalloc9p(sizeof *l);
273         strcpy(l->name, lp->name);
274         s = strrchr(l->name, '_');
275         strcpy(s, "_END");
276         strlwr(name, l->name);
277         fprint(2, "adding end marker %s\n", l->name);
278         if(!validname(name, lp->f, &l->type, 1, 0) || l->type != LTend)
279                 goto err;
280         f = createfile(lp->f, name, nil, lp->f->mode & 0666, l);
281         if(f == nil)
282                 goto err;
283         closefile(f);
284         l->f = f;
285         link(l, le, 0);
286         return 0;
287 err:
288         free(l);
289         return -1;
290 }
291
292 void
293 accessfile(File *f, int mode)
294 {
295         f->atime = time(nil);
296         if(mode & AWRITE){
297                 f->mtime = f->atime;
298                 f->qid.vers++;
299                 dirty = 1;
300         }
301 }
302
303 void
304 fswstat(Req *r)
305 {
306         int type;
307         char *e;
308         File *f, *fp;
309         Lump *lp;
310
311         e = "permission denied";
312         if(rdonly)
313                 goto err;
314         if(r->d.mode != ~0 || r->d.gid[0] != 0)
315                 goto err;
316         f = r->fid->file;
317         lp = f->aux;
318         if(r->d.length != ~0 && r->d.length != f->length){
319                 if(f == fsig || f->mode & DMDIR)
320                         goto err;
321                 if(!hasperm(f, r->fid->uid, AWRITE))
322                         goto err;
323                 if(r->d.length < 0){
324                         e = "invalid file length";
325                         goto err;
326                 }
327                 if(fwad->length - f->length + r->d.length >= Maxsz){
328                         e = "lump size exceeds wad limit";
329                         goto err;
330                 }
331         }
332         if(r->d.name[0] != 0 && strcmp(r->d.name, f->name) != 0){
333                 fp = f->parent;
334                 if(fp == nil){
335                         e = "orphaned file";
336                         goto err;
337                 }
338                 if(!hasperm(fp, r->fid->uid, AWRITE))
339                         goto err;
340                 if(!validname(r->d.name, fp, &type, 1, f->mode & DMDIR)){
341                         responderror(r);
342                         return;
343                 }
344                 if(lp->type != type){
345                         e = "incompatible lump type";
346                         goto err;
347                 }
348                 incref(fp);
349                 fp = walkfile(fp, r->d.name);
350                 if(fp != nil){
351                         e = "file already exists";
352                         goto err;
353                 }
354         }
355
356         if(r->d.length != ~0 && r->d.length != f->length){
357                 if(lp->buf == nil)
358                         loadlump(f, r->d.length);
359                 fwad->length += r->d.length - f->length;
360                 f->length = r->d.length;
361         }
362         if(r->d.name[0] != 0 && strcmp(r->d.name, f->name) != 0){
363                 free(f->name);
364                 f->name = estrdup9p(r->d.name);
365                 strupr(lp->name, f->name);
366                 if(lp->type == LTmrk)
367                         strcat(lp->name, "_START");
368         }
369         accessfile(f, AWRITE);
370         if(r->d.mtime != ~0)
371                 f->mtime = r->d.mtime;
372         respond(r, nil);
373         return;
374 err:
375         respond(r, e);
376 }
377
378 void
379 fsremove(Req *r)
380 {
381         File *f;
382         Lump *lp;
383
384         f = r->fid->file;
385         lp = f->aux;
386         if(f == fsig || f == fwad){
387                 respond(r, "not allowed");
388                 return;
389         }else if(lp->l->f != nil && lp->l->f->parent == f){
390                 respond(r, "has children");
391                 return;
392         }
393         unlink(f->aux);
394         dirty = 1;
395         respond(r, nil);
396 }
397
398 char *
399 writesig(uchar *buf, char *s, vlong n)
400 {
401         if(n > Nsig+1 || strncmp(s, "IWAD", Nsig) != 0 && strncmp(s, "PWAD", Nsig) != 0)
402                 return "invalid wad signature";
403         memcpy(buf, s, Nsig);
404         dirty = 1;
405         return nil;
406 }
407
408 void
409 fswrite(Req *r)
410 {
411         vlong n, m, ofs, end;
412         File *f;
413         Lump *l;
414
415         f = r->fid->file;
416         n = r->ifcall.count;
417         ofs = r->ifcall.offset;
418         if(f->mode & DMAPPEND)
419                 ofs = f->length;
420         end = ofs + n;
421         l = f->aux;
422         if(f == fsig){
423                 respond(r, writesig(l->buf, r->ifcall.data, n));
424                 return;
425         }
426         if(l->buf == nil)
427                 loadlump(f, end + Nbuf);
428         if(end > l->nbuf){
429                 m = l->nbuf + Nbuf > end ? l->nbuf + Nbuf : end;
430                 if(fwad->length - l->nbuf + m >= Maxsz){
431                         respond(r, "lump size exceeds wad limit");
432                         return;
433                 }
434                 l->buf = erealloc9p(l->buf, m);
435                 l->nbuf = m;
436         }
437         memcpy(l->buf + ofs, r->ifcall.data, n);
438         m = end - f->length;
439         if(m > 0){
440                 f->length += m;
441                 fwad->length += m;
442         }
443         accessfile(f, AWRITE);
444         r->ofcall.count = n;
445         respond(r, nil);
446 }
447
448 void
449 makewad(void)
450 {
451         vlong n;
452         uchar *p;
453         u32int ofs;
454         Lump *l, *lp;
455
456         l = fwad->aux;
457         free(l->buf);
458         l->buf = emalloc9p(fwad->length);
459         p = l->buf;
460         lp = fsig->aux;
461         memcpy(p, lp->buf, 4), p += 4;
462         PBIT32(p, nlmp), p += 8;
463         for(lp=lumps->l; lp!=lumps; p+=n, lp=lp->l){
464                 n = lp->f->length;
465                 if(lp->buf != nil)
466                         memcpy(p, lp->buf, n);
467                 else
468                         readlump(lp, p, n);
469         }
470         PBIT32(l->buf + 8, p - l->buf);
471         ofs = Nhdr;
472         for(lp=lumps->l; lp!=lumps; ofs+=n, lp=lp->l){
473                 n = lp->f->length;
474                 PBIT32(p, ofs), p += 4;
475                 PBIT32(p, n), p += 4;
476                 memcpy(p, lp->name, 8), p += 8;
477         }
478         dirty = 0;
479 }
480
481 void
482 fsread(Req *r)
483 {
484         vlong n, ofs, end;
485         File *f;
486         Lump *l;
487
488         f = r->fid->file;
489         l = f->aux;
490         ofs = r->ifcall.offset + l->ofs;
491         end = l->ofs + f->length;
492         n = r->ifcall.count;
493         if(ofs + n >= end)
494                 n = end - ofs;
495         if(n <= 0){
496                 r->ofcall.count = 0;
497                 respond(r, nil);
498                 return;
499         }
500         if(f == fwad && dirty)
501                 makewad();
502         if(l->buf != nil)
503                 memcpy(r->ofcall.data, l->buf+ofs, n);
504         else{
505                 Bseek(wad, ofs, 0);
506                 n = Bread(wad, r->ofcall.data, n);
507                 if(n < 0){
508                         responderror(r);
509                         return;
510                 }
511         }
512         accessfile(f, AREAD);
513         r->ofcall.count = n;
514         respond(r, nil);
515 }
516
517 int
518 addlump(Lump *l, File *dir)
519 {
520         Lump *lp;
521
522         lp = lumps->lp;
523         if(dir != fs.tree->root){
524                 lp = dir->aux;
525                 lp = lp->type == LTmap ? sortmap(lp, l) : lastlump(lp);
526         }
527         if(l->type == LTend && lp->l->type == LTend && lp->l->f->parent == dir){
528                 werrstr("an end marker already exists");
529                 return -1;
530         }
531         link(l, lp, 0);
532         if(l->type == LTmrk){
533                 strcat(l->name, "_START");
534                 if(endldir(l, l) < 0)
535                         return -1;
536         }else if(l->type == LTreg){
537                 l->buf = emalloc9p(Nbuf);
538                 l->nbuf = Nbuf;
539         }
540         dirty = 1;
541         return 0;
542 }
543
544 Lump *
545 createlump(char *s, File *dir, int ismark)
546 {
547         int type;
548         Lump *l;
549
550         if(!validname(s, dir, &type, 1, ismark))
551                 return nil;
552         l = emalloc9p(sizeof *l);
553         l->type = type;
554         strupr(l->name, s);
555         return l;
556 }
557
558 void
559 fscreate(Req *r)
560 {
561         int p;
562         File *f;
563         Lump *l;
564
565         f = r->fid->file;
566         p = r->ifcall.perm;
567         if(p & DMDIR)
568                 p = p & ~0777 | p & f->mode & 0777;
569         else
570                 p = p & ~0666 | p & f->mode & 0666;
571         l = createlump(r->ifcall.name, f, p & DMDIR);
572         if(l == nil)
573                 goto err;
574         f = createfile(f, r->ifcall.name, r->fid->uid, p, l);
575         if(f == nil){
576                 free(l);
577                 goto err;
578         }
579         l->f = f;
580         if(addlump(l, r->fid->file) < 0){
581                 removefile(f);
582                 goto err;
583         }
584         r->fid->file = f;
585         r->ofcall.qid = f->qid;
586         respond(r, nil);
587         return;
588 err:
589         responderror(r);
590 }
591
592 void
593 fsopen(Req *r)
594 {
595         File *f;
596
597         f = r->fid->file;
598         if((f->mode & DMAPPEND) == 0 && (r->ifcall.mode & OTRUNC) != 0
599         && f != fsig){
600                 fwad->length -= f->length;
601                 f->length = 0;
602                 dirty = 1;
603         }
604         respond(r, nil);
605 }
606
607 void
608 fsdestroyfile(File *f)
609 {
610         freelump(f->aux);
611 }
612
613 Srv fs = {
614         .open = fsopen,
615         .create = fscreate,
616         .read = fsread,
617         .write = fswrite,
618         .remove = fsremove,
619         .wstat = fswstat
620 };
621
622 int
623 get32(Biobuf *bf, u32int *v)
624 {
625         int n;
626         uchar u[4];
627
628         n = Bread(bf, u, sizeof u);
629         if(n != sizeof u)
630                 return -1;
631         *v = GBIT32(u);
632         return 0;
633 }
634
635 File *
636 replacefile(File *dir, char *fname, int mode, Lump *l)
637 {
638         File *f;
639
640         incref(dir);
641         f = walkfile(dir, fname);
642         if(f == nil)
643                 return nil;
644         if(removefile(f) < 0)
645                 return nil;
646         f = createfile(dir, fname, nil, mode, l);
647         return f;
648 }
649
650 void
651 addsigfile(char *sig)
652 {
653         int n;
654         Lump *l;
655         File *f;
656
657         n = strlen(sig) + 1;
658         l = emalloc9p(sizeof *l);
659         l->buf = (uchar *)estrdup9p(sig);
660         l->buf[n-1] = '\n';
661         f = createfile(fs.tree->root, "SIG", nil, rdonly ? 0444 : 0666, l);
662         if(f == nil)
663                 sysfatal("addsigfile: %r");
664         else{
665                 fsig = f;
666                 f->length = n;
667         }
668 }
669
670 void
671 addwadfile(void)
672 {
673         Lump *l;
674         File *f;
675
676         l = emalloc9p(sizeof *l);
677         f = createfile(fs.tree->root, "WAD", nil, 0444, l);
678         if(f == nil)
679                 sysfatal("addwadfile: %r");
680         else{
681                 fwad = f;
682                 f->length = Nhdr;
683         }
684         dirty++;
685
686 }
687
688 void
689 checkends(void)
690 {
691         Lump *lp;
692
693         if(ldir == fs.tree->root)
694                 return;
695         lp = ldir->aux;
696         if(lp->type != LTmap && endldir(lp, lastlump(lp)) < 0)
697                 fprint(2, "checkends: %r\n");
698         ldir = ldir->parent;
699         checkends();
700 }
701
702 int
703 addfile(Lump *l, u32int *len, int mode)
704 {
705         int err;
706         char fname[sizeof l->name], *s;
707         Lump *lp;
708         File *f;
709
710         *len = 0;
711         if(get32(wad, &l->ofs) < 0 || get32(wad, len) < 0)
712                 return -1;
713         if(Bread(wad, l->name, sizeof(l->name)-1) != sizeof(l->name)-1)
714                 return -1;
715         strlwr(fname, l->name);
716
717         lp = ldir->aux;
718         err = !validname(fname, ldir, &l->type, 0, 0);
719         switch(l->type){
720         case LTmap:
721                 closefile(ldir);
722                 ldir = fs.tree->root;
723                 if(err && lp != nil && lp->type != LTmap){
724                         fprint(2, "addfile %s ofs=%#ux len=%#ux: %r\n", l->name, l->ofs, *len);
725                         if(endldir(lp, lastlump(lp)) < 0)
726                                 fprint(2, "endldir: %r\n");
727                 }
728                 mode |= DMDIR|0111;
729                 *len = 0;
730                 break;
731         case LTmrk:
732                 if(err){
733                         if(lp != nil && lp->type == LTmap){
734                                 closefile(ldir);
735                                 ldir = fs.tree->root;
736                         }else{
737                                 fprint(2, "addfile %s ofs=%#ux len=%#ux: %r\n", l->name, l->ofs, *len);
738                                 if(endldir(lp, lastlump(lp)) < 0)
739                                         return -1;
740                                 ldir = ldir->parent;
741                         }
742                 }
743                 s = strrchr(fname, '_');
744                 *s = 0;
745                 mode |= DMDIR|0111;
746                 *len = 0;
747                 break;
748         case LTend:
749                 if(err){
750                         ldir = ldir->parent;
751                         return -1;
752                 }
753                 *len = 0;
754                 break;
755         case LTreg:
756                 if(err){
757                         if(ismaplump(fname))
758                                 fprint(2, "addfile %s ofs=%#ux len=%#ux: %r\n", l->name, l->ofs, *len);
759                         else
760                                 ldir = fs.tree->root;
761                 }
762                 break;
763         default:
764                 return -1;
765         }
766
767         f = createfile(ldir, fname, nil, mode, l);
768         if(f == nil){
769                 fprint(2, "createfile %s: %r\n", l->name);
770                 if(mode & DMDIR)
771                         return -1;
772                 f = replacefile(ldir, fname, mode, l);
773                 if(f == nil)
774                         return -1;
775         }
776         if(mode & DMDIR)
777                 ldir = f;
778         else if(l->type == LTend)
779                 ldir = ldir->parent;
780         else
781                 closefile(f);
782         f->length = *len;
783         l->f = f;
784         return 0;
785 }
786
787 void
788 parsewad(void)
789 {
790         int n, ne, mode;
791         u32int len;
792         Lump *l;
793
794         mode = rdonly ? 0444 : 0666;
795         ldir = fs.tree->root;
796         for(n=0, ne=nlmp, nlmp=0; n<ne; n++){
797                 l = emalloc9p(sizeof *l);
798                 if(addfile(l, &len, mode) < 0){
799                         fprint(2, "addfile %s ofs=%#ux len=%#ux: %r\n", l->name, l->ofs, len);
800                         free(l);
801                         continue;
802                 }
803                 link(l, lumps->lp, len);
804         }
805         checkends();
806 }
807
808 void
809 wadinfo(char *sig)
810 {
811         int n;
812         u32int dictofs;
813
814         n = Bread(wad, sig, Nsig);
815         if(n != Nsig)
816                 sysfatal("readwad: short read: %r");
817         sig[4] = 0;
818         if(strcmp(sig, "IWAD") != 0 && strcmp(sig, "PWAD") != 0)
819                 sysfatal("invalid wad signature");
820         if(get32(wad, &nlmp) < 0 || get32(wad, &dictofs) < 0)
821                 sysfatal("wadinfo: %r");
822         Bseek(wad, dictofs, 0);
823 }
824
825 void
826 usage(void)
827 {
828         fprint(2, "usage: %s [-Dr] [-m mtpt] [-S srvname] [wad]\n", argv0);
829         exits("usage");
830 }
831
832 void
833 main(int argc, char **argv)
834 {
835         int fl, p;
836         char *mtpt, *srvname, sig[Nsig+1] = "PWAD";
837
838         mtpt = "/mnt/wad";
839         srvname = nil;
840         fl = MREPL|MCREATE;
841         p = DMDIR|0777;
842         ARGBEGIN{
843         case 'D': chatty9p++; break;
844         case 'S': srvname = EARGF(usage()); break;
845         case 'm': mtpt = EARGF(usage()); break;
846         case 'r': rdonly++; p &= ~0222; fl &= ~MCREATE; break;
847         default: usage();
848         }ARGEND
849         if(*argv != nil){
850                 wad = Bopen(*argv, OREAD);
851                 if(wad == nil)
852                         sysfatal("Bopen: %r");
853                 wadinfo(sig);
854         }
855         fs.tree = alloctree(nil, nil, p, fsdestroyfile);
856         addsigfile(sig);
857         addwadfile();
858         parsewad();
859         postmountsrv(&fs, srvname, mtpt, fl);
860         exits(nil);
861 }