diff options
author | qwx <devnull@localhost> | 2017-08-10 11:39:18 +0200 |
---|---|---|
committer | qwx <devnull@localhost> | 2017-08-10 11:39:18 +0200 |
commit | 16ef6e5596e08a79bb620e605892f68188a817f0 (patch) | |
tree | 1cf2df9cb0731ad8f7317df88069c1d1dc0cdf14 /sys/src/games/wadfs.c | |
parent | 4be612946fe849d56d24d4816127644bf1a6ab17 (diff) |
add games/wadfs
Diffstat (limited to 'sys/src/games/wadfs.c')
-rw-r--r-- | sys/src/games/wadfs.c | 861 |
1 files changed, 861 insertions, 0 deletions
diff --git a/sys/src/games/wadfs.c b/sys/src/games/wadfs.c new file mode 100644 index 000000000..56a618407 --- /dev/null +++ b/sys/src/games/wadfs.c @@ -0,0 +1,861 @@ +#include <u.h> +#include <libc.h> +#include <ctype.h> +#include <fcall.h> +#include <thread.h> +#include <9p.h> +#include <bio.h> + +enum{ + Nsig = 4, + Nhdr = Nsig+4+4, + Ndict = 4+4+8, + Nname = 8, + Nbuf = 8192, + Maxsz = 0x7fffffff - Nhdr +}; + +enum{ + LTnil, + LTreg, + LTmap, + LTmrk, + LTend +}; +typedef struct Lump Lump; +struct Lump{ + char name[Nname+1]; + u32int ofs; + uchar *buf; + ulong nbuf; + int type; + File *f; + Lump *l; + Lump *lp; +}; +Lump l1 = {.l = &l1, .lp = &l1}, *lumps = &l1; + +Biobuf *wad; +u32int nlmp; +File *ldir, *fsig, *fwad; +int rdonly, dirty; + +Srv fs; + +char *mapn[] = { + "things", "linedefs", "sidedefs", "vertexes", "segs", + "ssectors", "nodes", "sectors", "reject", "blockmap" +}; + +void +strupr(char *s, char *p) +{ + char c; + + do{ + c = *p++; + *s++ = toupper(c); + }while(c != 0); +} + +void +strlwr(char *s, char *p) +{ + char c; + + do{ + c = *p++; + *s++ = tolower(c); + }while(c != 0); +} + +void +link(Lump *l, Lump *lp, int len) +{ + l->lp = lp; + l->l = lp->l; + lp->l->lp = l; + lp->l = l; + nlmp++; + fwad->length += Ndict + len; +} + +void +unlink(Lump *l) +{ + if(l->l == nil) + return; + l->lp->l = l->l; + l->l->lp = l->lp; + l->l = nil; + nlmp--; + fwad->length -= Ndict + (l->f != nil ? l->f->length : 0); +} + +void +freelump(Lump *l) +{ + unlink(l); + free(l->buf); + free(l); +} + +void +readlump(Lump *l, uchar *p, long n) +{ + if(n <= 0) + return; + Bseek(wad, l->ofs, 0); + if(Bread(wad, p, n) != n) + fprint(2, "readlump: short read: %r\n"); +} + +void +loadlump(File *f, ulong n) +{ + Lump *l; + + l = f->aux; + if(f->length > n) + n = f->length; + l->buf = emalloc9p(n); + l->nbuf = n; + l->ofs = 0; + readlump(l, l->buf, f->length); +} + +Lump * +lastlump(Lump *lp) +{ + File *f, *dir; + + for(dir=lp->f, f=lp->l->f; lp->l!=lumps; lp=lp->l, f=lp->l->f) + if(f->parent != dir && f->parent->parent != dir) + break; + if(lp->type == LTend && lp->f->parent == dir) + lp = lp->lp; + return lp; +} + +int +nextmaplump(char *s) +{ + char **p; + + for(p=mapn; p<mapn+nelem(mapn); p++) + if(strcmp(s, *p) == 0) + return p-mapn; + return -1; +} + +Lump * +sortmap(Lump *lp, Lump *l) +{ + int ip, i; + + i = nextmaplump(l->f->name); + for(; lp->l != lumps; lp=lp->l){ + ip = nextmaplump(lp->l->f->name); + if(ip < 0 || ip > i) + break; + } + return lp; +} + +int +ismaplump(char *s) +{ + return nextmaplump(s) >= 0; +} + +int +ismapname(char *s) +{ + if(strncmp(s, "map", 3) == 0) + return isdigit(s[3]) && isdigit(s[4]); + return s[0] == 'e' && isdigit(s[1]) + && s[2] == 'm' && isdigit(s[3]); +} + +int +ismarkname(char *s, char *m) +{ + char *p; + + p = strstr(s, m); + if(p == nil || p[strlen(m)] != 0) + return 0; + if(p - s > 2) + return 0; + return 1; +} + +int +validname(char *s, File *dir, int *type, int isnew, int isdir) +{ + int n; + char c, *p; + Lump *lp; + + *type = LTnil; + n = strlen(s); + if(n < 1 || n > sizeof(lp->name)-1){ + werrstr("invalid lump name"); + return 0; + } + for(p=s+n-1; c=*p, p-->=s;) + if(!isprint(c) || isupper(c) || c == '/'){ + werrstr("invalid char %c in filename", c); + return 0; + } + if(isnew && !ismaplump(s)) + for(lp=lumps->l; lp!=lumps; lp=lp->l) + if(cistrcmp(s, lp->name) == 0){ + werrstr("duplicate non map lump"); + return 0; + } + *type = LTreg; + lp = dir->aux; + if(ismapname(s)){ + *type = LTmap; + if(isnew && !isdir){ + werrstr("map marker not a directory"); + return 0; + }else if(dir != fs.tree->root){ + werrstr("nested map directory"); + return 0; + } + return 1; + }else if(ismarkname(s, "_end")){ + *type = LTend; + if(dir == fs.tree->root || lp == nil || lp->type == LTmap){ + werrstr("orphaned end marker"); + return 0; + } + return 1; + }else if(ismarkname(s, "_start")){ + *type = LTmrk; + if(isnew){ + werrstr("not allowed"); + return 0; + } + goto mrkchk; + }else if(isnew && isdir){ + *type = LTmrk; + if(n > 2){ + werrstr("marker name too long"); + return 0; + } +mrkchk: + if(dir->parent != fs.tree->root){ + werrstr("start marker nested too deep"); + return 0; + }else if(lp != nil && lp->type == LTmap){ + werrstr("start marker within map directory"); + return 0; + } + return 1; + }else if(ismaplump(s) ^ (lp != nil && lp->type == LTmap)){ + werrstr("map lump outside of map directory"); + return 0; + } + return 1; +} + +int +endldir(Lump *lp, Lump *le) +{ + char *s, name[sizeof lp->name]; + Lump *l; + File *f; + + l = emalloc9p(sizeof *l); + strcpy(l->name, lp->name); + s = strrchr(l->name, '_'); + strcpy(s, "_END"); + strlwr(name, l->name); + fprint(2, "adding end marker %s\n", l->name); + if(!validname(name, lp->f, &l->type, 1, 0) || l->type != LTend) + goto err; + f = createfile(lp->f, name, nil, lp->f->mode & 0666, l); + if(f == nil) + goto err; + closefile(f); + l->f = f; + link(l, le, 0); + return 0; +err: + free(l); + return -1; +} + +void +accessfile(File *f, int mode) +{ + f->atime = time(nil); + if(mode & AWRITE){ + f->mtime = f->atime; + f->qid.vers++; + dirty = 1; + } +} + +void +fswstat(Req *r) +{ + int type; + char *e; + File *f, *fp; + Lump *lp; + + e = "permission denied"; + if(rdonly) + goto err; + if(r->d.mode != ~0 || r->d.gid[0] != 0) + goto err; + f = r->fid->file; + lp = f->aux; + if(r->d.length != ~0 && r->d.length != f->length){ + if(f == fsig || f->mode & DMDIR) + goto err; + if(!hasperm(f, r->fid->uid, AWRITE)) + goto err; + if(r->d.length < 0){ + e = "invalid file length"; + goto err; + } + if(fwad->length - f->length + r->d.length >= Maxsz){ + e = "lump size exceeds wad limit"; + goto err; + } + } + if(r->d.name[0] != 0 && strcmp(r->d.name, f->name) != 0){ + fp = f->parent; + if(fp == nil){ + e = "orphaned file"; + goto err; + } + if(!hasperm(fp, r->fid->uid, AWRITE)) + goto err; + if(!validname(r->d.name, fp, &type, 1, f->mode & DMDIR)){ + responderror(r); + return; + } + if(lp->type != type){ + e = "incompatible lump type"; + goto err; + } + incref(fp); + fp = walkfile(fp, r->d.name); + if(fp != nil){ + e = "file already exists"; + goto err; + } + } + + if(r->d.length != ~0 && r->d.length != f->length){ + if(lp->buf == nil) + loadlump(f, r->d.length); + fwad->length += r->d.length - f->length; + f->length = r->d.length; + } + if(r->d.name[0] != 0 && strcmp(r->d.name, f->name) != 0){ + free(f->name); + f->name = estrdup9p(r->d.name); + strupr(lp->name, f->name); + if(lp->type == LTmrk) + strcat(lp->name, "_START"); + } + accessfile(f, AWRITE); + if(r->d.mtime != ~0) + f->mtime = r->d.mtime; + respond(r, nil); + return; +err: + respond(r, e); +} + +void +fsremove(Req *r) +{ + File *f; + Lump *lp; + + f = r->fid->file; + lp = f->aux; + if(f == fsig || f == fwad){ + respond(r, "not allowed"); + return; + }else if(lp->l->f != nil && lp->l->f->parent == f){ + respond(r, "has children"); + return; + } + unlink(f->aux); + dirty = 1; + respond(r, nil); +} + +char * +writesig(uchar *buf, char *s, vlong n) +{ + if(n > Nsig+1 || strncmp(s, "IWAD", Nsig) != 0 && strncmp(s, "PWAD", Nsig) != 0) + return "invalid wad signature"; + memcpy(buf, s, Nsig); + dirty = 1; + return nil; +} + +void +fswrite(Req *r) +{ + vlong n, m, ofs, end; + File *f; + Lump *l; + + f = r->fid->file; + n = r->ifcall.count; + ofs = r->ifcall.offset; + if(f->mode & DMAPPEND) + ofs = f->length; + end = ofs + n; + l = f->aux; + if(f == fsig){ + respond(r, writesig(l->buf, r->ifcall.data, n)); + return; + } + if(l->buf == nil) + loadlump(f, end + Nbuf); + if(end > l->nbuf){ + m = l->nbuf + Nbuf > end ? l->nbuf + Nbuf : end; + if(fwad->length - l->nbuf + m >= Maxsz){ + respond(r, "lump size exceeds wad limit"); + return; + } + l->buf = erealloc9p(l->buf, m); + l->nbuf = m; + } + memcpy(l->buf + ofs, r->ifcall.data, n); + m = end - f->length; + if(m > 0){ + f->length += m; + fwad->length += m; + } + accessfile(f, AWRITE); + r->ofcall.count = n; + respond(r, nil); +} + +void +makewad(void) +{ + vlong n; + uchar *p; + u32int ofs; + Lump *l, *lp; + + l = fwad->aux; + free(l->buf); + l->buf = emalloc9p(fwad->length); + p = l->buf; + lp = fsig->aux; + memcpy(p, lp->buf, 4), p += 4; + PBIT32(p, nlmp), p += 8; + for(lp=lumps->l; lp!=lumps; p+=n, lp=lp->l){ + n = lp->f->length; + if(lp->buf != nil) + memcpy(p, lp->buf, n); + else + readlump(lp, p, n); + } + PBIT32(l->buf + 8, p - l->buf); + ofs = Nhdr; + for(lp=lumps->l; lp!=lumps; ofs+=n, lp=lp->l){ + n = lp->f->length; + PBIT32(p, ofs), p += 4; + PBIT32(p, n), p += 4; + memcpy(p, lp->name, 8), p += 8; + } + dirty = 0; +} + +void +fsread(Req *r) +{ + vlong n, ofs, end; + File *f; + Lump *l; + + f = r->fid->file; + l = f->aux; + ofs = r->ifcall.offset + l->ofs; + end = l->ofs + f->length; + n = r->ifcall.count; + if(ofs + n >= end) + n = end - ofs; + if(n <= 0){ + r->ofcall.count = 0; + respond(r, nil); + return; + } + if(f == fwad && dirty) + makewad(); + if(l->buf != nil) + memcpy(r->ofcall.data, l->buf+ofs, n); + else{ + Bseek(wad, ofs, 0); + n = Bread(wad, r->ofcall.data, n); + if(n < 0){ + responderror(r); + return; + } + } + accessfile(f, AREAD); + r->ofcall.count = n; + respond(r, nil); +} + +int +addlump(Lump *l, File *dir) +{ + Lump *lp; + + lp = lumps->lp; + if(dir != fs.tree->root){ + lp = dir->aux; + lp = lp->type == LTmap ? sortmap(lp, l) : lastlump(lp); + } + if(l->type == LTend && lp->l->type == LTend && lp->l->f->parent == dir){ + werrstr("an end marker already exists"); + return -1; + } + link(l, lp, 0); + if(l->type == LTmrk){ + strcat(l->name, "_START"); + if(endldir(l, l) < 0) + return -1; + }else if(l->type == LTreg){ + l->buf = emalloc9p(Nbuf); + l->nbuf = Nbuf; + } + dirty = 1; + return 0; +} + +Lump * +createlump(char *s, File *dir, int ismark) +{ + int type; + Lump *l; + + if(!validname(s, dir, &type, 1, ismark)) + return nil; + l = emalloc9p(sizeof *l); + l->type = type; + strupr(l->name, s); + return l; +} + +void +fscreate(Req *r) +{ + int p; + File *f; + Lump *l; + + f = r->fid->file; + p = r->ifcall.perm; + if(p & DMDIR) + p = p & ~0777 | p & f->mode & 0777; + else + p = p & ~0666 | p & f->mode & 0666; + l = createlump(r->ifcall.name, f, p & DMDIR); + if(l == nil) + goto err; + f = createfile(f, r->ifcall.name, r->fid->uid, p, l); + if(f == nil){ + free(l); + goto err; + } + l->f = f; + if(addlump(l, r->fid->file) < 0){ + removefile(f); + goto err; + } + r->fid->file = f; + r->ofcall.qid = f->qid; + respond(r, nil); + return; +err: + responderror(r); +} + +void +fsopen(Req *r) +{ + File *f; + + f = r->fid->file; + if((f->mode & DMAPPEND) == 0 && (r->ifcall.mode & OTRUNC) != 0 + && f != fsig){ + fwad->length -= f->length; + f->length = 0; + dirty = 1; + } + respond(r, nil); +} + +void +fsdestroyfile(File *f) +{ + freelump(f->aux); +} + +Srv fs = { + .open = fsopen, + .create = fscreate, + .read = fsread, + .write = fswrite, + .remove = fsremove, + .wstat = fswstat +}; + +int +get32(Biobuf *bf, u32int *v) +{ + int n; + uchar u[4]; + + n = Bread(bf, u, sizeof u); + if(n != sizeof u) + return -1; + *v = GBIT32(u); + return 0; +} + +File * +replacefile(File *dir, char *fname, int mode, Lump *l) +{ + File *f; + + incref(dir); + f = walkfile(dir, fname); + if(f == nil) + return nil; + if(removefile(f) < 0) + return nil; + f = createfile(dir, fname, nil, mode, l); + return f; +} + +void +addsigfile(char *sig) +{ + int n; + Lump *l; + File *f; + + n = strlen(sig) + 1; + l = emalloc9p(sizeof *l); + l->buf = (uchar *)estrdup9p(sig); + l->buf[n-1] = '\n'; + f = createfile(fs.tree->root, "SIG", nil, rdonly ? 0444 : 0666, l); + if(f == nil) + sysfatal("addsigfile: %r"); + else{ + fsig = f; + f->length = n; + } +} + +void +addwadfile(void) +{ + Lump *l; + File *f; + + l = emalloc9p(sizeof *l); + f = createfile(fs.tree->root, "WAD", nil, 0444, l); + if(f == nil) + sysfatal("addwadfile: %r"); + else{ + fwad = f; + f->length = Nhdr; + } + dirty++; + +} + +void +checkends(void) +{ + Lump *lp; + + if(ldir == fs.tree->root) + return; + lp = ldir->aux; + if(lp->type != LTmap && endldir(lp, lastlump(lp)) < 0) + fprint(2, "checkends: %r\n"); + ldir = ldir->parent; + checkends(); +} + +int +addfile(Lump *l, u32int *len, int mode) +{ + int err; + char fname[sizeof l->name], *s; + Lump *lp; + File *f; + + *len = 0; + if(get32(wad, &l->ofs) < 0 || get32(wad, len) < 0) + return -1; + if(Bread(wad, l->name, sizeof(l->name)-1) != sizeof(l->name)-1) + return -1; + strlwr(fname, l->name); + + lp = ldir->aux; + err = !validname(fname, ldir, &l->type, 0, 0); + switch(l->type){ + case LTmap: + closefile(ldir); + ldir = fs.tree->root; + if(err && lp != nil && lp->type != LTmap){ + fprint(2, "addfile %s ofs=%#ux len=%#ux: %r\n", l->name, l->ofs, *len); + if(endldir(lp, lastlump(lp)) < 0) + fprint(2, "endldir: %r\n"); + } + mode |= DMDIR|0111; + *len = 0; + break; + case LTmrk: + if(err){ + if(lp != nil && lp->type == LTmap){ + closefile(ldir); + ldir = fs.tree->root; + }else{ + fprint(2, "addfile %s ofs=%#ux len=%#ux: %r\n", l->name, l->ofs, *len); + if(endldir(lp, lastlump(lp)) < 0) + return -1; + ldir = ldir->parent; + } + } + s = strrchr(fname, '_'); + *s = 0; + mode |= DMDIR|0111; + *len = 0; + break; + case LTend: + if(err){ + ldir = ldir->parent; + return -1; + } + *len = 0; + break; + case LTreg: + if(err){ + if(ismaplump(fname)) + fprint(2, "addfile %s ofs=%#ux len=%#ux: %r\n", l->name, l->ofs, *len); + else + ldir = fs.tree->root; + } + break; + default: + return -1; + } + + f = createfile(ldir, fname, nil, mode, l); + if(f == nil){ + fprint(2, "createfile %s: %r\n", l->name); + if(mode & DMDIR) + return -1; + f = replacefile(ldir, fname, mode, l); + if(f == nil) + return -1; + } + if(mode & DMDIR) + ldir = f; + else if(l->type == LTend) + ldir = ldir->parent; + else + closefile(f); + f->length = *len; + l->f = f; + return 0; +} + +void +parsewad(void) +{ + int n, ne, mode; + u32int len; + Lump *l; + + mode = rdonly ? 0444 : 0666; + ldir = fs.tree->root; + for(n=0, ne=nlmp, nlmp=0; n<ne; n++){ + l = emalloc9p(sizeof *l); + if(addfile(l, &len, mode) < 0){ + fprint(2, "addfile %s ofs=%#ux len=%#ux: %r\n", l->name, l->ofs, len); + free(l); + continue; + } + link(l, lumps->lp, len); + } + checkends(); +} + +void +wadinfo(char *sig) +{ + int n; + u32int dictofs; + + n = Bread(wad, sig, Nsig); + if(n != Nsig) + sysfatal("readwad: short read: %r"); + sig[4] = 0; + if(strcmp(sig, "IWAD") != 0 && strcmp(sig, "PWAD") != 0) + sysfatal("invalid wad signature"); + if(get32(wad, &nlmp) < 0 || get32(wad, &dictofs) < 0) + sysfatal("wadinfo: %r"); + Bseek(wad, dictofs, 0); +} + +void +usage(void) +{ + fprint(2, "usage: %s [-Dr] [-m mtpt] [-S srvname] [wad]\n", argv0); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + int fl, p; + char *mtpt, *srvname, sig[Nsig+1] = "PWAD"; + + mtpt = "/mnt/wad"; + srvname = nil; + fl = MREPL|MCREATE; + p = DMDIR|0777; + ARGBEGIN{ + case 'D': chatty9p++; break; + case 'S': srvname = EARGF(usage()); break; + case 'm': mtpt = EARGF(usage()); break; + case 'r': rdonly++; p &= ~0222; fl &= ~MCREATE; break; + default: usage(); + }ARGEND + if(*argv != nil){ + wad = Bopen(*argv, OREAD); + if(wad == nil) + sysfatal("Bopen: %r"); + wadinfo(sig); + } + fs.tree = alloctree(nil, nil, p, fsdestroyfile); + addsigfile(sig); + addwadfile(); + parsewad(); + postmountsrv(&fs, srvname, mtpt, fl); + exits(nil); +} |