ref: fa9f4c5c6852ce467644f610e77504664cb86af4
dir: /appl/cmd/memfs.b/
implement MemFS;
include "sys.m";
sys: Sys;
OTRUNC, ORCLOSE, OREAD, OWRITE: import Sys;
include "styx.m";
styx: Styx;
Tmsg, Rmsg: import styx;
include "styxlib.m";
styxlib: Styxlib;
Styxserver: import styxlib;
include "draw.m";
include "arg.m";
MemFS: module {
init: fn(ctxt: ref Draw->Context, args: list of string);
};
blksz : con 512;
Efull : con "filesystem full";
Memfile : adt {
name : string;
owner : string;
qid : Sys->Qid;
perm : int;
atime : int;
mtime : int;
nopen : int;
data : array of array of byte; # allocated in blks, no holes
length : int;
parent : cyclic ref Memfile; # Dir entry linkage
kids : cyclic ref Memfile;
prev : cyclic ref Memfile;
next : cyclic ref Memfile;
hashnext : cyclic ref Memfile; # Qid hash linkage
};
Qidhash : adt {
buckets : array of ref Memfile;
nextqid : int;
new : fn () : ref Qidhash;
add : fn (h : self ref Qidhash, mf : ref Memfile);
remove : fn (h : self ref Qidhash, mf : ref Memfile);
lookup : fn (h : self ref Qidhash, qid : Sys->Qid) : ref Memfile;
};
timefd: ref Sys->FD;
init(nil: ref Draw->Context, argv: list of string)
{
sys = load Sys Sys->PATH;
styx = checkload(load Styx Styx->PATH, Styx->PATH);
styxlib = checkload(load Styxlib Styxlib->PATH, Styxlib->PATH);
arg := checkload(load Arg Arg->PATH, Arg->PATH);
amode := Sys->MREPL;
maxsz := 16r7fffffff;
srv := 0;
mntpt := "/tmp";
arg->init(argv);
arg->setusage("memfs [-s] [-rab] [-m size] [mountpoint]");
while((opt := arg->opt()) != 0) {
case opt{
's' =>
srv = 1;
'r' =>
amode = Sys->MREPL;
'a' =>
amode = Sys->MAFTER;
'b' =>
amode = Sys->MBEFORE;
'm' =>
maxsz = int arg->earg();
* =>
arg->usage();
}
}
argv = arg->argv();
arg = nil;
if (argv != nil)
mntpt = hd argv;
srvfd: ref Sys->FD;
mntfd: ref Sys->FD;
if (srv)
srvfd = sys->fildes(0);
else {
p := array [2] of ref Sys->FD;
if (sys->pipe(p) == -1)
error(sys->sprint("cannot create pipe: %r"));
mntfd = p[0];
srvfd = p[1];
}
styx->init();
styxlib->init(styx);
timefd = sys->open("/dev/time", sys->OREAD);
(tc, styxsrv) := Styxserver.new(srvfd);
if (srv)
memfs(maxsz, tc, styxsrv, nil);
else {
sync := chan of int;
spawn memfs(maxsz, tc, styxsrv, sync);
<-sync;
if (sys->mount(mntfd, nil, mntpt, amode | Sys->MCREATE, nil) == -1)
error(sys->sprint("failed to mount onto %s: %r", mntpt));
}
}
checkload[T](x: T, p: string): T
{
if(x == nil)
error(sys->sprint("cannot load %s: %r", p));
return x;
}
stderr(): ref Sys->FD
{
return sys->fildes(2);
}
error(e: string)
{
sys->fprint(stderr(), "memfs: %s\n", e);
raise "fail:error";
}
freeblks: int;
memfs(maxsz : int, tc : chan of ref Tmsg, srv : ref Styxserver, sync: chan of int)
{
sys->pctl(Sys->NEWNS, nil);
if (sync != nil)
sync <-= 1;
freeblks = (maxsz / blksz);
qhash := Qidhash.new();
# init root
root := newmf(qhash, nil, "memfs", srv.uname, 8r755 | Sys->DMDIR);
root.parent = root;
while((tmsg := <-tc) != nil) {
# sys->print("%s\n", tmsg.text());
Msg:
pick tm := tmsg {
Readerror =>
break;
Version =>
srv.devversion(tm);
Auth =>
srv.devauth(tm);
Flush =>
srv.reply(ref Rmsg.Flush(tm.tag));
Walk =>
(err, c, mf) := fidtomf(srv, qhash, tm.fid);
if (err != "") {
srv.reply(ref Rmsg.Error(tm.tag, err));
continue;
}
nc: ref styxlib->Chan;
if (tm.newfid != tm.fid) {
nc = srv.clone(c, tm.newfid);
if (nc == nil) {
srv.reply(ref Rmsg.Error(tm.tag, "fid in use"));
continue;
}
c = nc;
}
qids: array of Sys->Qid;
if (len tm.names > 0) {
oqid := c.qid;
opath := c.path;
qids = array[len tm.names] of Sys->Qid;
wmf := mf;
for (i := 0; i < len tm.names; i++) {
wmf = dirlookup(wmf, tm.names[i]);
if (wmf == nil) {
if (nc == nil) {
c.qid = oqid;
c.path = opath;
} else
srv.chanfree(nc);
if (i == 0)
srv.reply(ref Rmsg.Error(tm.tag, Styxlib->Enotfound));
else
srv.reply(ref Rmsg.Walk(tm.tag, qids[0:i]));
break Msg;
}
c.qid = wmf.qid;
qids[i] = wmf.qid;
}
}
srv.reply(ref Rmsg.Walk(tm.tag, qids));
Open =>
(err, c, mf) := fidtomf(srv, qhash, tm.fid);
if (err == "" && c.open)
err = Styxlib->Eopen;
if (err == "" && !modeok(tm.mode, mf.perm, c.uname, mf.owner))
err = Styxlib->Eperm;
if (err == "" && (mf.perm & Sys->DMDIR) && (tm.mode & (OTRUNC|OWRITE|ORCLOSE)))
err = Styxlib->Eperm;
if (err == "" && (tm.mode & ORCLOSE)) {
p := mf.parent;
if (p == nil || !modeok(OWRITE, p.perm, c.uname, p.owner))
err = Styxlib->Eperm;
}
if (err != "") {
srv.reply(ref Rmsg.Error(tm.tag, err));
continue;
}
c.open = 1;
c.mode = tm.mode;
c.qid.vers = mf.qid.vers;
mf.nopen++;
if ((tm.mode & OTRUNC) && !(mf.perm & Sys->DMAPPEND)) {
# OTRUNC cannot be set for a directory
# always at least one blk so don't need to check fs limit
freeblks += (len mf.data);
mf.data = nil;
freeblks--;
mf.data = array[1] of {* => array [blksz] of byte};
mf.length = 0;
mf.mtime = now();
}
srv.reply(ref Rmsg.Open(tm.tag, mf.qid, Styx->MAXFDATA));
Create =>
(err, c, parent) := fidtomf(srv, qhash, tm.fid);
if (err == "" && c.open)
err = Styxlib->Eopen;
if (err == "" && !(parent.qid.qtype & Sys->QTDIR))
err = Styxlib->Enotdir;
if (err == "" && !modeok(OWRITE, parent.perm, c.uname, parent.owner))
err = Styxlib->Eperm;
if (err == "" && (tm.perm & Sys->DMDIR) && (tm.mode & (OTRUNC|OWRITE|ORCLOSE)))
err = Styxlib->Eperm;
if (err == "" && dirlookup(parent, tm.name) != nil)
err = Styxlib->Eexists;
if (err != "") {
srv.reply(ref Rmsg.Error(tm.tag, err));
continue;
}
isdir := tm.perm & Sys->DMDIR;
if (!isdir && freeblks <= 0) {
srv.reply(ref Rmsg.Error(tm.tag, Efull));
continue;
}
# modify perms as per Styx specification...
perm : int;
if (isdir)
perm = (tm.perm&~8r777) | (parent.perm&tm.perm&8r777);
else
perm = (tm.perm&(~8r777|8r111)) | (parent.perm&tm.perm& 8r666);
nmf := newmf(qhash, parent, tm.name, c.uname, perm);
if (!isdir) {
freeblks--;
nmf.data = array[1] of {* => array [blksz] of byte};
}
# link in the new MemFile
nmf.next = parent.kids;
if (parent.kids != nil)
parent.kids.prev = nmf;
parent.kids = nmf;
c.open = 1;
c.mode = tm.mode;
c.qid = nmf.qid;
nmf.nopen = 1;
srv.reply(ref Rmsg.Create(tm.tag, nmf.qid, Styx->MAXFDATA));
Read =>
(err, c, mf) := fidtomf(srv, qhash, tm.fid);
if (err == "" && !c.open)
err = Styxlib->Ebadfid;
if (err != "") {
srv.reply(ref Rmsg.Error(tm.tag, err));
continue;
}
data: array of byte = nil;
if (mf.perm & Sys->DMDIR)
data = dirdata(mf, int tm.offset, tm.count);
else
data = filedata(mf, int tm.offset, tm.count);
mf.atime = now();
srv.reply(ref Rmsg.Read(tm.tag, data));
Write =>
(err, c, mf) := fidtomf(srv, qhash, tm.fid);
if (c != nil && !c.open)
err = Styxlib->Ebadfid;
if (err == nil && (mf.perm & Sys->DMDIR))
err = Styxlib->Eperm;
if (err == nil)
err = writefile(mf, int tm.offset, tm.data);
if (err != nil) {
srv.reply(ref Rmsg.Error(tm.tag, err));
continue;
}
srv.reply(ref Rmsg.Write(tm.tag, len tm.data));
Clunk =>
(err, c, mf) := fidtomf(srv, qhash, tm.fid);
if (c != nil)
srv.chanfree(c);
if (err != nil) {
srv.reply(ref Rmsg.Error(tm.tag, err));
continue;
}
if (c.open) {
if (c.mode & ORCLOSE)
unlink(mf);
mf.nopen--;
freeblks += delfile(qhash, mf);
}
srv.reply(ref Rmsg.Clunk(tm.tag));
Stat =>
(err, nil, mf) := fidtomf(srv, qhash, tm.fid);
if (err != nil) {
srv.reply(ref Rmsg.Error(tm.tag, err));
continue;
}
srv.reply(ref Rmsg.Stat(tm.tag, fileinfo(mf)));
Remove =>
(err, c, mf) := fidtomf(srv, qhash, tm.fid);
if (err != nil) {
srv.reply(ref Rmsg.Error(tm.tag, err));
continue;
}
srv.chanfree(c);
parent := mf.parent;
if (!modeok(OWRITE, parent.perm, c.uname, parent.owner))
err = Styxlib->Eperm;
if (err == "" && (mf.perm & Sys->DMDIR) && mf.kids != nil)
err = "directory not empty";
if (err == "" && mf == root)
err = "root directory";
if (err != nil) {
srv.reply(ref Rmsg.Error(tm.tag, err));
continue;
}
unlink(mf);
if (c.open)
mf.nopen--;
freeblks += delfile(qhash, mf);
srv.reply(ref Rmsg.Remove(tm.tag));
Wstat =>
(err, c, mf) := fidtomf(srv, qhash, tm.fid);
stat := tm.stat;
if (err == nil && stat.name != mf.name) {
parent := mf.parent;
if (!modeok(OWRITE, parent.perm, c.uname, parent.owner))
err = Styxlib->Eperm;
else if (dirlookup(parent, stat.name) != nil)
err = Styxlib->Eexists;
}
if (err == nil && (stat.mode != mf.perm || stat.mtime != mf.mtime)) {
if (c.uname != mf.owner)
err = Styxlib->Eperm;
}
if (err != nil) {
srv.reply(ref Rmsg.Error(tm.tag, err));
continue;
}
isdir := mf.perm & Sys->DMDIR;
if(stat.name != nil)
mf.name = stat.name;
if(stat.mode != ~0)
mf.perm = stat.mode | isdir;
if(stat.mtime != ~0)
mf.mtime = stat.mtime;
if(stat.uid != nil)
mf.owner = stat.uid;
t := now();
mf.atime = t;
mf.parent.mtime = t;
# not supporting group id at the moment
srv.reply(ref Rmsg.Wstat(tm.tag));
Attach =>
c := srv.newchan(tm.fid);
if (c == nil) {
srv.reply(ref Rmsg.Error(tm.tag, Styxlib->Einuse));
continue;
}
c.uname = tm.uname;
c.qid = root.qid;
srv.reply(ref Rmsg.Attach(tm.tag, c.qid));
}
}
}
writefile(mf: ref Memfile, offset: int, data: array of byte): string
{
if(mf.perm & Sys->DMAPPEND)
offset = mf.length;
startblk := offset/blksz;
nblks := ((len data + offset) - (startblk * blksz))/blksz;
lastblk := startblk + nblks;
need := lastblk + 1 - len mf.data;
if (need > 0) {
if (need > freeblks)
return Efull;
mf.data = (array [lastblk+1] of array of byte)[:] = mf.data;
freeblks -= need;
}
mf.length = max(mf.length, offset + len data);
# handle (possibly incomplete first block) separately
offset %= blksz;
end := min(blksz-offset, len data);
if (mf.data[startblk] == nil)
mf.data[startblk] = array [blksz] of byte;
mf.data[startblk++][offset:] = data[:end];
ix := blksz - offset;
while (ix < len data) {
if (mf.data[startblk] == nil)
mf.data[startblk] = array [blksz] of byte;
end = min(ix+blksz,len data);
mf.data[startblk++][:] = data[ix:end];
ix += blksz;
}
mf.mtime = now();
return nil;
}
filedata(mf: ref Memfile, offset, n: int): array of byte
{
if (offset +n > mf.length)
n = mf.length - offset;
if (n == 0)
return nil;
data := array [n] of byte;
startblk := offset/blksz;
offset %= blksz;
rn := min(blksz - offset, n);
data[:] = mf.data[startblk++][offset:offset+rn];
ix := blksz - offset;
while (ix < n) {
rn = blksz;
if (ix+rn > n)
rn = n - ix;
data[ix:] = mf.data[startblk++][:rn];
ix += blksz;
}
return data;
}
QHSIZE: con 256;
QHMASK: con QHSIZE-1;
Qidhash.new() : ref Qidhash
{
qh := ref Qidhash;
qh.buckets = array [QHSIZE] of ref Memfile;
qh.nextqid = 0;
return qh;
}
Qidhash.add(h : self ref Qidhash, mf : ref Memfile)
{
path := h.nextqid++;
mf.qid = Sys->Qid(big path, 0, Sys->QTFILE);
bix := path & QHMASK;
mf.hashnext = h.buckets[bix];
h.buckets[bix] = mf;
}
Qidhash.remove(h : self ref Qidhash, mf : ref Memfile)
{
bix := int mf.qid.path & QHMASK;
prev : ref Memfile;
for (cur := h.buckets[bix]; cur != nil; cur = cur.hashnext) {
if (cur == mf)
break;
prev = cur;
}
if (cur != nil) {
if (prev != nil)
prev.hashnext = cur.hashnext;
else
h.buckets[bix] = cur.hashnext;
cur.hashnext = nil;
}
}
Qidhash.lookup(h : self ref Qidhash, qid : Sys->Qid) : ref Memfile
{
bix := int qid.path & QHMASK;
for (mf := h.buckets[bix]; mf != nil; mf = mf.hashnext)
if (mf.qid.path == qid.path)
break;
return mf;
}
newmf(qh : ref Qidhash, parent : ref Memfile, name, owner : string, perm : int) : ref Memfile
{
# qid gets set by Qidhash.add()
t := now();
mf := ref Memfile (name, owner, Sys->Qid(big 0,0,Sys->QTFILE), perm, t, t, 0, nil, 0, parent, nil, nil, nil, nil);
qh.add(mf);
if(perm & Sys->DMDIR)
mf.qid.qtype = Sys->QTDIR;
return mf;
}
fidtomf(srv : ref Styxserver, qh : ref Qidhash, fid : int) : (string, ref Styxlib->Chan, ref Memfile)
{
c := srv.fidtochan(fid);
if (c == nil)
return (Styxlib->Ebadfid, nil, nil);
mf := qh.lookup(c.qid);
if (mf == nil)
return (Styxlib->Enotfound, c, nil);
return (nil, c, mf);
}
unlink(mf : ref Memfile)
{
parent := mf.parent;
if (parent == nil)
return;
if (mf.next != nil)
mf.next.prev = mf.prev;
if (mf.prev != nil)
mf.prev.next = mf.next;
else
mf.parent.kids = mf.next;
mf.parent = nil;
mf.prev = nil;
mf.next = nil;
}
delfile(qh : ref Qidhash, mf : ref Memfile) : int
{
if (mf.nopen <= 0 && mf.parent == nil && mf.kids == nil
&& mf.prev == nil && mf.next == nil) {
qh.remove(mf);
nblks := len mf.data;
mf.data = nil;
return nblks;
}
return 0;
}
dirlookup(dir : ref Memfile, name : string) : ref Memfile
{
if (name == ".")
return dir;
if (name == "..")
return dir.parent;
for (mf := dir.kids; mf != nil; mf = mf.next) {
if (mf.name == name)
break;
}
return mf;
}
access := array[] of {8r400, 8r200, 8r600, 8r100};
modeok(mode, perm : int, user, owner : string) : int
{
if(mode >= (OTRUNC|ORCLOSE|OREAD|OWRITE))
return 0;
# not handling groups!
if (user != owner)
perm <<= 6;
if ((mode & OTRUNC) && !(perm & 8r200))
return 0;
a := access[mode &3];
if ((a & perm) != a)
return 0;
return 1;
}
dirdata(dir : ref Memfile, start, n : int) : array of byte
{
data := array[Styx->MAXFDATA] of byte;
for (k := dir.kids; start > 0 && k != nil; k = k.next) {
a := styx->packdir(fileinfo(k));
start -= len a;
}
r := 0;
for (; r < n && k != nil; k = k.next) {
a := styx->packdir(fileinfo(k));
if(r+len a > n)
break;
data[r:] = a;
r += len a;
}
return data[0:r];
}
fileinfo(f : ref Memfile) : Sys->Dir
{
dir := sys->zerodir;
dir.name = f.name;
dir.uid = f.owner;
dir.gid = "memfs";
dir.qid = f.qid;
dir.mode = f.perm;
dir.atime = f.atime;
dir.mtime = f.mtime;
dir.length = big f.length;
dir.dtype = 0;
dir.dev = 0;
return dir;
}
min(a, b : int) : int
{
if (a < b)
return a;
return b;
}
max(a, b : int) : int
{
if (a > b)
return a;
return b;
}
now(): int
{
if (timefd == nil)
return 0;
buf := array[128] of byte;
sys->seek(timefd, big 0, 0);
n := sys->read(timefd, buf, len buf);
if(n < 0)
return 0;
t := (big string buf[0:n]) / big 1000000;
return int t;
}