plan9front/sys/src/games/wadfs.c

865 lines
14 KiB
C

#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);
}