Plan 9 from Bell Labs’s /usr/web/sources/contrib/nemo/octopus/port/ofs/ofstree.b

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


implement Ofstree;
include "sys.m";
	sys: Sys;
	fprint, OREAD, open, pwrite, pread, remove, sprint, pctl, millisec, DMDIR, write, nulldir, tokenize, fildes,
	QTDIR, FD, read, create, OWRITE, ORDWR, Dir, Qid: import sys;
include "names.m";
	names: Names;
	cleanname: import names;
include "string.m";
	str: String;
	splitstrl: import str;
include "styx.m";
	styx: Styx;
	unpackdir: import styx;
include "error.m";
	err: Error;
	stderr, panic: import err;
include "readdir.m";
	readdir: Readdir;
include "ofstree.m";


# Cachedir can be used to keep a map of names/serverqids
# to store within the directory ("dir/.ofs") when using a disk cache.
# this is untested; not yet used.

# BUG: we create the files as they are seen by stop, but for
# many of them, we don't know at that time if the files are
# files or directories. So, this must be changed to
# create the files really when we need them to keep data
# on them. Directories as necessary to keep the files there.

Cachefile: adt {
	name: string;
	qid: Qid;
};

Cachedir: adt {
	fname: string;
	map: list of Cachefile;
	get:	fn(path: string): ref Cachedir;
	find:	fn(c: self ref Cachedir, name: string): ref Qid;
	add:	fn(c: self ref Cachedir, name: string, q: Qid);
	del:	fn(c: self ref Cachedir, name: string);
	put:	fn(c: self ref Cachedir);
};

Cachedir.find(c: self ref Cachedir, name: string): ref Qid
{
	for (l := c.map; l != nil; l = tl l)
		if ((hd l).name == name)
			return ref (hd l).qid;
	return nil;
}

Cachedir.add(c: self ref Cachedir, name: string, q: Qid)
{
	nmap : list of Cachefile;
	found := 0;
	for (l := c.map; l != nil; l = tl l)
		if ((hd l).name == name){
			found = 1;
			nmap = Cachefile(name, q) :: nmap;
		} else
			nmap = hd l :: nmap;
	if (!found)
		c.map = Cachefile(name, q) :: c.map;
}

Cachedir.del(c: self ref Cachedir, name: string)
{
	nmap : list of Cachefile;
	for (l := c.map; l != nil; l = tl l)
		if ((hd l).name != name)
			nmap = hd l :: nmap;
	c.map = nmap;
}

Cachedir.get(fname: string): ref Cachedir
{
	# BUG: should read only if qid changed

	fname += "/.ofs";
	fd := open(fname, OREAD);
	if (fd == nil)
		return ref Cachedir(fname, nil);
	buf := array[16*1024] of byte;
	tot := 0;
	for(;;){
		nr := read(fd, buf[tot:], len buf - tot);
		if (nr < 0)
			return ref Cachedir(fname, nil);
		if (nr == 0){
			buf = buf[0:tot];
			break;
		}
		if (tot > 64 * 1024 * 1024){
			fprint(stderr, "ofs: cachedir: file too large. fix me.\n");
			return nil;
		}
		if (tot == len buf){
			nbuf := array[2 * len buf] of byte;
			nbuf[0:] = buf;
			buf = nbuf;
		}
	}
	text := string buf;
	buf = nil;
	(n, items) := tokenize(text, " \n");
	nents := n/4;
	map : list of Cachefile;
	for (i := 0; i < nents; i++){
		name := hd items; items = tl items;
		path := big hd items; items = tl items;
		vers := int hd items; items = tl items;
		qt := int hd items; items = tl items;
		map = Cachefile(name, Qid(path, vers, qt)) :: map;
	}
	return ref Cachedir(fname, map);
}

Cachedir.put(c: self ref Cachedir)
{
	fd := create(c.fname, OWRITE, 8r664);
	if (fd == nil)
		return;
	for (l := c.map; l != nil; l = tl l){
		e := hd l;
		fprint(fd, "%s %bd %d %d\n", e.name, e.qid.path, e.qid.vers, e.qid.qtype);
	}
}

ncachedfiles := 0;

tab: array of ref Cfile;
fsdir: string;

init(msys: Sys, mstr: String, mstyx: Styx, merr: Error, n: Names, dir: string): string
{
	sys = msys;
	str = mstr;
	styx = mstyx;
	err = merr;
	names = n;
	readdir = load Readdir Readdir->PATH;
	if (readdir == nil)
		fsdir = nil;
	tab = array[211] of ref Cfile;	# use a prime number 
	if (dir != nil)
		fsdir = names->cleanname(dir);
	return nil;
}

hashfn(q: big, n: int): int
{
	h := int (q % big n);
	if (h < 0)
		h += n;
	return h;
}

Cfile.create(parent: ref Cfile, d: ref Sys->Dir): ref Cfile
{
	if (Cfile.find(d.qid.path) != nil){
		fprint(stderr, "fscreate: qid already exists: %bx\n", d.qid.path);
		return nil;
	}
	fh: ref Cfile;
	if (parent == nil)
		fh = ref Cfile(~0, ~0, nil, d, nil, 0, 0, 0, 0, nil, d.qid.path, Qid(big 0, 0, d.qid.qtype), 0, nil, nil, nil);
	else {
		if (parent.walk(d.name) != nil)
			return nil;
		fh = ref Cfile(~0, ~0, nil, d, nil, 0, 0, 0, 0, nil, parent.d.qid.path, Qid(big 0, 0, d.qid.qtype), 0, nil, nil, nil);
		fh.sibling = parent.child;
		parent.child = fh;
	}
	slot := hashfn(d.qid.path, len tab);
	fh.hash = tab[slot];
	tab[slot] = fh;
	ncachedfiles++;

	if (fsdir != nil && parent != nil){
		path := names->cleanname(fsdir + "/" + fh.getpath() );
		mode := 8r664;
		if (d.mode&DMDIR)
			mode = DMDIR|8r775;
		fd := sys->open(path, OREAD);
		if (fd == nil)
			fd = sys->create(path, OREAD, mode);
		if (debug)
			fprint(stderr, "cache: create %s\n", path);
		if (fd == nil)
			fprint(stderr, "cache: create %s: %r\n", path);
	}
	return fh;
}

Cfile.find(q: big): ref Cfile
{
	for (fh := tab[hashfn(q, len tab)]; fh != nil; fh = fh.hash)
		if (fh.d.qid.path == q)
			return fh;
	return nil;
}

Cfile.updatedirdata(f: self ref Cfile, data : array of byte)
{
	sons, l : list of  ref Dir;
	gonesons : list of big;
	tot := n:= 0;
	d : ref Dir;
	xd : Dir;
	fh: ref Cfile;

	if (len data == 0)
		return;
	f.dirreaded = 1;
	sons = nil;
	gonesons = nil;
	l = nil;
	d = nil;
	fh = nil;
	# 1. unpack.
	do {
		(n, xd) = unpackdir(data[tot:]);
		tot += n;
		if (n >= 0)
			sons = ref xd :: sons;
	} while (n > 0 && tot < len data);

	# 2. update changed ones; record gone ones.
	gonesons = nil;
	for(fh = f.child; fh != nil; fh = fh.sibling){
		# search by name; qids might be faked
		for (l = sons; l != nil && (hd l).name != fh.d.name ; l = tl l)
				;
		if (l != nil){
			q := fh.d.qid.path;	# we invent our own qids
			fh.d = ref *(hd l);
			fh.serverqid = fh.d.qid;
			fh.d.qid.path = q;
		} else
			# must keep files just created (they may be not yet reported by the server)
			if (!fh.created)
				gonesons = fh.d.qid.path :: gonesons;
	}

	# 3. remove gone ones
	while(gonesons != nil){
		if (debug)
			fprint(stderr, "cache: readdir: invalidate %bx \n", hd gonesons);
		if (fsdir != nil){
			sf := Cfile.find(hd gonesons);
			path := names->cleanname(fsdir + "/" + sf.getpath());
			if (path != fsdir)
				removefsdir(fsdir + "/" + sf.getpath());
		}
		removeqid(hd gonesons);
		gonesons = tl gonesons;
	}

	# 4. add new ones
	for(l = sons; l != nil; l = tl l){
		d = hd l;
		sq := d.qid.path;
		for (fh = f.child; fh != nil && fh.serverqid.path != sq; fh = fh.sibling)
			;
		if (fh == nil){
			d.qid.path = ++qseq;
			fh = Cfile.create(f, d);
			if (fh == nil){
				# this happens for files bound twice or more in the same dir
				# the file is already cached, ignore.
				--qseq;
			} else {
				fh.serverqid =  Qid(sq, d.qid.vers, d.qid.qtype);
				if (debug)
					fprint(stderr, "cache: new: %s\n", fh.text());
			}
		}
	}
}

Cfile.getpath(fh: self ref Cfile): string
{
	if (fh == nil)
		panic("fsgetpath: nil fh");
	if (fh.d == nil)
		panic("fsgetpath: nil d");
	s : string;
	if (fh.parentqid == fh.d.qid.path)
		return "/";
	for(;;) {
		if (fh.d == nil)
			panic("fsgetpath:null dir");
		if (s == nil){
			if (fh.oldname != nil)
				s = fh.oldname;
			else
				s = fh.d.name;
		} else if (fh.parentqid == fh.d.qid.path)
			return "/" + s;
		else {
			if (fh.oldname != nil)
				s = fh.oldname + "/" + s;
			else
				s = fh.d.name + "/" + s;
		}
		fh = Cfile.find(fh.parentqid);
		if (fh == nil)
			panic("fsgetpath:parent not in table");
	}
	return nil;
}

Cfile.walk(fh: self ref Cfile, name: string): ref Cfile
{
	if (name == "..")
		return Cfile.find(fh.parentqid);
	for (fh = fh.child; fh != nil; fh = fh.sibling)
		if (fh.d.name == name)
			return fh;
	return nil;
}

qseq := big 16r6b60000000000000;

Cfile.walkorcreate(fh: self ref Cfile, name: string, d: ref Dir): (ref Cfile, int)
{
	newf := 0;
	f := fh.walk(name);
	if (f == nil){
		newf = 1;
		if (d == nil){
			d = ref *fh.d;
			d.qid.path = ++qseq;
			d.qid.qtype = QTDIR;
			d.qid.vers = 0;
			d.name = name;
		} else {
			d.qid.path = ++qseq;	# we use our own qids.
			d.name = name;		# even when d.qid is ok.
			if (d.mode&DMDIR)
				d.qid.qtype |= QTDIR;
		}
		f = Cfile.create(fh, d);
		if (f == nil)
			panic(sprint("walkorcreate: create: %s at %s q %bx\n", name, fh.d.name, d.qid.path));
		else
			if (debug)
				fprint(stderr, "cache: added: %s\n", f.text());
	}
	if (f == nil)
		panic(sprint("walkorcreate: nil f for name %s", name));
	return (f, newf);
}

Cfile.children(f: self ref Cfile, cnt, off: int) : list of Sys->Dir
{
	fh := f.child;
	while(off > 0 && fh != nil){
		off--;
		fh = fh.sibling;
	}
	l : list of Sys->Dir;
	l = nil;
	while(cnt > 0 && fh != nil){
		cnt--;
		l = *fh.d :: l;
		fh = fh.sibling;
	}
	return l;
}

removeqid(q: big): string
{
	prev: ref Cfile;

	# remove from hash table
	slot := hashfn(q, len tab);
	for (fh := tab[slot]; fh != nil; fh = fh.hash) {
		if (fh.d.qid.path == q)
			break;
		prev = fh;
	}
	if (fh == nil)
		return "file not found";
	if (prev == nil)
		tab[slot] = fh.hash;
	else
		prev.hash = fh.hash;
	fh.hash = nil;

	# remove from parent's children
	parent := Cfile.find(fh.parentqid);
	if (parent != nil) {
		prev = nil;
		for (sfh := parent.child; sfh != nil; sfh = sfh.sibling) {
			if (sfh == fh)
				break;
			prev = sfh;
		}
		if (sfh == nil)
			panic("child not found in parent");
		if (prev == nil)
			parent.child = fh.sibling;
		else
			prev.sibling = fh.sibling;
	}
	fh.sibling = nil;

	# now remove any descendents
	sibling: ref Cfile;
	for (sfh := fh.child; sfh != nil; sfh = sibling) {
		sibling = sfh.sibling;
		sfh.parentqid = sfh.d.qid.path;		# make sure it doesn't disrupt things.
		removeqid(sfh.d.qid.path);
	}
	ncachedfiles--;
	return nil;
}

removefsdir(path: string)
{
	if (debug)
		fprint(stderr, "cache: remove %s\n", path);
	if (path[0:4] != "/tmp" && path[0:6] != "/cache")
		return;			# SAFETY FIRST
	(dirs, e) := readdir->init(path, Readdir->NONE);
	for (i := 0; i < e; i++)
		removefsdir(path + "/" + dirs[i].name);
	sys->remove(path);
}

Cfile.remove(f: self ref Cfile): string
{
	if (fsdir != nil){
		path := names->cleanname(fsdir + "/" + f.getpath());
		if (path != fsdir)
			removefsdir(fsdir + "/" + f.getpath());
	}
	return removeqid(f.d.qid.path);
}

applydir(d: ref Sys->Dir, onto: ref Sys->Dir): ref Sys->Dir
{
	if (d.name != nil)
		onto.name = d.name;
	if (d.uid != nil)
		onto.uid = d.uid;
	if (d.gid != nil)
		onto.gid = d.gid;
	if (d.muid != nil)
		onto.muid = d.muid;
	if (d.qid.vers != ~0)
		onto.qid.vers = d.qid.vers;
	if (d.qid.qtype != ~0)
		onto.qid.qtype = d.qid.qtype;
	if (d.qid.vers != ~0)
		onto.qid.vers = d.qid.vers;
	if (d.mode != ~0)
		onto.mode = d.mode;
	if (d.atime != ~0)
		onto.atime = d.atime;
	if (d.mtime != ~0)
		onto.mtime = d.mtime;
	if (d.length != ~big 0)
		onto.length = d.length;
	if (d.dtype != ~0)
		onto.dtype = d.dtype;
	if (d.dev != ~0)
		onto.dev = d.dev;
	return onto;
}

Cfile.wstat(fh : self ref Cfile, d: ref Sys->Dir): string
{
	q := fh.d.qid.path;
	# if renaming a file, check for duplicates
	if (d.name != nil && d.name != fh.d.name) {
		parent := Cfile.find(fh.parentqid);
		if (parent != nil && parent != fh && parent.walk(d.name) != nil)
			return "File already exists";
		fh.oldname = fh.d.name;
		parent.time = 0;	# invalidate
		fh.time = 0;		# invalidate
		if (fsdir != nil && d.uid != nil){
			nd := sys->nulldir;
			nd.name = d.name;
			sys->wstat(fsdir + "/" + fh.getpath(), nd);
		}
	}
	d = applydir(d, fh.d);
	if (fh.data != nil && d.length < big len fh.data)
	if ((d.qid.qtype&QTDIR) == 0)
		fh.data = fh.data[0:int d.length];
	if (fsdir != nil && d.uid != nil){
		# we update cache attributes only to truncate files that are
		# shorter in the fs. No other wstat is propatagated to the cache.
		# It caches just data.
		cfd := sys->nulldir;
		(e, xd) := sys->stat(fsdir + "/" + fh.getpath());
		if (e >= 0 && xd.length > d.length){
			cfd.length = d.length;
			cfd.mode = d.mode;
			if (cfd.mode & DMDIR)
				cfd.mode |= 8r775;
			else
				cfd.mode |= 8r660;
			if (debug)
				fprint(stderr, "cache: wstat %s mode %x\n", fsdir + "/" + fh.getpath(), cfd.mode);
			sys->wstat(fsdir + "/" + fh.getpath(), cfd);
		}
	}
	fh.d = d;
	fh.d.qid.path = q;		# ensure the qid can't be changed
	return nil;
}


Cfile.dump(f: self ref Cfile, t: int, pref: string)
{
	tabs := "\t\t\t\t\t\t\t\t\t\t";
	s := "";
	if (pref != nil){
		s = pref + sprint("(%d files)\n", ncachedfiles);
		pref = nil;
	}
	ts := tabs[0:t];
	s += ts + f.text();
	a:= array of byte s;
	write(stderr, a, len a);
	for (fh := f.child; fh != nil; fh = fh.sibling)
		fh.dump(t+1, nil);
}

Cfile.pwrite(fh: self ref Cfile, data: array of byte, off: big): int
{
	if (fsdir != nil){
		path := fsdir + "/" + fh.getpath();
		if (fh.fsfd == nil){
			if (debug)
				fprint(stderr, "cache: open %s\n", path);
			fh.fsfd = open(path, ORDWR);
			if (fh.fsfd == nil){
				# perhaps we created a dir.
				# replace it with a file, now that we know.
				remove(path);
				fh.fsfd = create(path, ORDWR, 8664);
			}
		}
		if (fh.fsfd != nil)
			return pwrite(fh.fsfd, data, len data, off);
		else
			fprint(stderr, "cache: %s: %r\n", path);
	}
	return -1;
}

Cfile.pread(fh : self ref Cfile, cnt: int, off: big): array of byte
{
	if (fsdir == nil)
		return nil;
	if (fh.fsfd == nil){
		path := fsdir + "/" + fh.getpath();
		fh.fsfd = open(path, ORDWR);
		if (debug)
			fprint(stderr, "cache: open %s\n", path);
	}
	if (fh.fsfd == nil)
		return nil;
	data := array[cnt] of byte;
	nr := pread(fh.fsfd, data, len data, off);
	if (nr <= 0)
		return nil;
	return data[0:nr];
}

Cfile.text(fh: self ref Cfile): string
{
	if (fh == nil)
		return "nil file";
	return sprint(" \"%s\" %s\tc%d s%d sq=%s %d bytes\n",
		fh.getpath(), dir2text(fh.d), fh.created, fh.dirtyd,
		qid2text(fh.serverqid), len fh.data);
}

dir2text(d: ref Sys->Dir): string
{
	return sys->sprint("[\"%s\"  %s 8r%uo %bd]",
		d.uid, qid2text(d.qid), d.mode, d.length);
}

qid2text(q: Sys->Qid): string
{
	return sys->sprint("%.2ubx:%.2ux:%.2ux", q.path, q.vers, q.qtype);
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.