Plan 9 from Bell Labs’s /usr/web/sources/contrib/maht/inferno/appl/cmd/mdbfs1.b

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


# Mdbfs was written my Matt Heath matt@proweb.co.uk in July 2003
# It was heavily adapted from 
# /appl/cmd/dbfs.b

# which contains this :

# Copyright � 1999 Vita Nuova Limited.  All rights reserved.
# Revisions copyright � 2002 Vita Nuova Holdings Limited.  All rights reserved.

# I'm not sure what I'm supposed to put here. The Licence says that if I supply a binary I agree to supply the source
# and any derivatative works (i.e. this one) carry the same burden
# /me shrugs and carries on

# on with the show :
# Mdbfs is an extension to dbfs that takes it into the second dimension with both rows & columns
# it mounts the data read only whereas dbfs was read/write.
# Based on my tabfs the idea was that it would consume less memory but with the primary key hashtable 
# it actually uses more !
# mind you the trade off is time, it took six hours to run out of 256Mb of memory with the last one 
# at least this one has the grace to do it in 30 seconds

# Usage: mdbfs [-a|-b] [-D] [-1] [-p n] [-P] file mountpoint

# -a|-b	- after / before
# -D	- print each Tmsg/Rmsg 
# -1    - use the first line of the file as the column names (otherwise they are numbered)
# -p n  - use column number n as the primary key and therefore the name of the directory for each row (otherwise numbered)
#         if this column contains duplicates they will still be created but with distinct Qids but the Hashtable will get screwed
#         so it's best not to do that, I've not played with that so be it on your own head
# -P	- print every cell of the database in qid order, one per line, data is bracketed thus : >%s<
#           I was using it for debugging but left it in
# file	- The file is split into records on each non-empty line using string->unquoted. The number of columns is calculated
#	  from the first line. If a row contains less they are still included, any extra are discarded

implement Mdbfs;

include "sys.m";
	sys: Sys;
	Qid: import Sys;

include "draw.m";

include "arg.m";

include "styx.m";
	styx: Styx;
	Tmsg, Rmsg: import styx;

include "string.m";
	str: String;

include "styxservers.m";
	styxservers: Styxservers;
	Fid, Styxserver, Navigator, Navop: import styxservers;
	Enotfound, Eperm, Ebadarg: import styxservers;

include "bufio.m";
	bufio: Bufio;
	Iobuf: import bufio;

include "hash.m";
	hash : Hash;
	HashTable, HashVal : import hash;

Record: adt {
	id:		int;		# file number in directory
	dirty:	int;		# modified but not written
	vers:		int;		# version
	data:		string;

	print:	fn(r: self ref Record);
	qid:	fn (r: self ref Record): Sys->Qid;
};

Database: adt {
	name:	string;
	file:	ref Iobuf;
	records:	array of ref Record;
	dirty:	int;
	vers:		int;
	row_count :	int;
	first_row_contains_column_names : int;
	primary_key_column_index : int;

	column_names_string : string;
	column_names_hash : ref HashTable;
	column_names_array : array of string;
	column_count : int;

	pk_hash : ref HashTable;

	getrec: fn(db: self ref Database): (string, array of string, string);
	findrec:	fn(db: self ref Database, row, column: int): ref Record;
	set_column_names: fn(db: self ref Database, names : string);
	set_column_names_to_numbers: fn(db: self ref Database, num : int);
	print: fn(db : self ref Database);
};

Mdbfs: module
{
	init: fn(nil: ref Draw->Context, nil: list of string);
};

Qroot, Qrow, Qcolumn: con iota;

stderr: ref Sys->FD;
database: ref Database;
user: string;
Eremoved: con "file removed";
progname : con "2dbfs";

rootfiles := array[] of {"/", "data", "src_filename", "column_names"};

usage()
{
	sys->fprint(stderr, "Usage: %s [-a|-b] [-D] [-1] [-p n] [-P] file mountpoint\n", progname);
	raise "fail:usage";
}

nomod(s: string)
{
	sys->fprint(stderr, "%s: can't load %s: %r\n", progname, s);
	raise "fail:load";
}

nomem(s : string)
{
	sys->fprint(stderr, "%s : can't allocate memory - %s", progname, s);
	raise "fail:memory";
}

init(nil: ref Draw->Context, args: list of string)
{
	sys = load Sys Sys->PATH;
	sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil);

	stderr = sys->fildes(2);

	styx = load Styx Styx->PATH;
	if(styx == nil)
		nomod(Styx->PATH);
	styx->init();
	styxservers = load Styxservers Styxservers->PATH;
	if(styxservers == nil)
		nomod(Styxservers->PATH);
	styxservers->init(styx);

	bufio = load Bufio Bufio->PATH;
	if(bufio == nil)
		nomod(Bufio->PATH);

	str = load String String->PATH;
	if(str == nil)
		nomod(String->PATH);

	hash = load Hash Hash->PATH;
	if(hash == nil)
		nomod(Hash->PATH);

	arg := load Arg Arg->PATH;
	if(arg == nil)
		nomod(Arg->PATH);
	arg->init(args);
	flags := Sys->MREPL;
	copt := 0;
	first_row_contains_column_names := 0;
	primary_key_column_index := -1;
	print_db := 0;

	while((o := arg->opt()) != 0)
		case o {
		'a' =>	flags = Sys->MAFTER;
		'b' =>	flags = Sys->MBEFORE;
		'D' =>	styxservers->traceset(1);
		'P' =>	print_db = 1;
		'1' =>	first_row_contains_column_names = 1;
		'p' =>	primary_key_column_index = int arg->arg();
		* =>		usage();
		}
	args = arg->argv();
	arg = nil;

	if(len args != 2)
		usage();

	file := hd args;
	args = tl args;
	mountpt := hd args;

	df := bufio->open(file, Sys->OREAD);

	if(df == nil){
		sys->fprint(stderr, "%s: can't open %s: %r\n", progname, file);
		raise "fail:open";
	}
	(db, err) := dbread(ref Database(file, df, nil, 0, 0, 0, first_row_contains_column_names, primary_key_column_index, nil, nil, nil, 0, nil));
	if(db == nil){
		sys->fprint(stderr, "%s: can't read %s: %s\n", progname, file, err);
		raise "fail:dbread";
	}
	db.file = nil;

	database = db;

	sys->pctl(Sys->FORKFD, nil);

	user = rf("/dev/user");
	if(user == nil)
		user = "inferno";

	fds := array[2] of ref Sys->FD;
	if(sys->pipe(fds) < 0){
		sys->fprint(stderr, "%s: can't create pipe: %r\n", progname);
		raise "fail:pipe";
	}

	navops := chan of ref Navop;
	spawn navigator(navops);

	(tchan, srv) := Styxserver.new(fds[0], Navigator.new(navops), big Qroot);
	fds[0] = nil;

	pidc := chan of int;
	spawn serveloop(tchan, srv, pidc, navops);
	<-pidc;

	if (print_db)
		db.print();

	if(sys->mount(fds[1], nil, mountpt, flags, nil) < 0) {
		sys->fprint(stderr, "%s: mount failed: %r\n", progname);
		raise "fail:mount";
	}
}

rf(f: string): string
{
	fd := sys->open(f, Sys->OREAD);
	if(fd == nil)
		return nil;
	b := array[Sys->NAMEMAX] of byte;
	n := sys->read(fd, b, len b);
	if(n < 0)
		return nil;
	return string b[0:n];
}

dbread(db: ref Database): (ref Database, string)
{
	db.file.seek(big 0, Sys->SEEKSTART);
	rl: list of array of string;

	(data, r, err) := db.getrec();

	if(err != nil)
		return (nil, err);	
	if(db.first_row_contains_column_names == 1) {
		db.set_column_names(data);
		(nil, r, err) = db.getrec();
		if(err != nil)
			return (nil, err);	
	} else 
		db.set_column_names_to_numbers(len r);

	rl = r :: rl;
	db.row_count = 1;

	if(db.column_count  < 1) 
		return (nil, "need at least one column name");
	
	for(;;){
		(nil, r, err) = db.getrec();
		if(err != nil)
			return (nil, err);	
		if(r == nil)
			break;
		rl = r :: rl;
		db.row_count++;
	}

	record_count := db.row_count * db.column_count;
	db.records = array[record_count] of ref Record;
	db.pk_hash = hash->new(record_count);

	row_num := db.row_count -1;
	pk_value : string;

	for(; rl != nil; rl = tl rl) {
		r = hd rl;

		if(db.primary_key_column_index < 0)
			pk_value = sys->sprint("%d", row_num);
		else
			if (len r < db.column_count)
				pk_value = sys->sprint("!%d!", row_num);
			else
				pk_value = r[db.primary_key_column_index];

		db.pk_hash.insert(pk_value, HashVal(row_num--, 0.0, nil));

		for (column_count := db.column_count - 1; column_count  >= 0; column_count--) {
			rec :=  ref Record(--record_count, 0, 0, r[column_count]);
			db.records[record_count] = rec;
		}
	}

	return (db, nil);
}



Database.getrec(db: self ref Database): (string, array of string, string)
{
	data := db.file.gets('\n');
	if(data == nil)
		return (nil, nil, nil);		# BUG: distinguish i/o error from EOF? - nah 

	(data, nil) = str->splitl(data, "\n");

	if(data == "")
		return db.getrec(); # skip blank lines

	return (data, list_to_array(str->unquoted(data)), nil);
}


Database.findrec(db: self ref Database, row, column: int): ref Record
{
	a := db.records[row].data[column];
	return nil;
}

Database.set_column_names(db: self ref Database, names : string)
{
	uq := str->unquoted(names);
	db.column_names_hash  = list_to_hash(uq);
	db.column_names_array  = list_to_array(uq);
	db.column_names_string = names;
	db.column_count = len db.column_names_array;
}

Database.set_column_names_to_numbers(db: self ref Database, num : int)
{
	namestring := sys->sprint("%d", 0);
	for(i:=1; i < num; i++)
		namestring += sys->sprint(" %d", i);
	db.set_column_names(namestring);
}

Database.print(db: self ref Database) 
{
	record_count := len db.records;

	for( i:=0; i < record_count; i++)
		db.records[i].print();
}

Record.qid(r: self ref Record): Sys->Qid
{
	return Sys->Qid(QPATH(r.id, Qcolumn), r.vers, Sys->QTFILE);
}

Record.print(r: self ref Record)
{
	sys->fprint(stderr, "id: %6d	qid: %16.16bx	data: >%s<\n", r.id, r.qid().path, r.data);
}

serveloop(tchan: chan of ref Tmsg, srv: ref Styxserver, pidc: chan of int, navops: chan of ref Navop)
{
	pidc <-= sys->pctl(Sys->FORKNS|Sys->NEWFD, 1::2::srv.fd.fd::nil);
Serve:
	while((gm := <-tchan) != nil){
		pick m := gm {
		Readerror =>
			sys->fprint(stderr, "%s: fatal read error: %s\n", progname, m.error);
			break Serve;

		Read =>
			(c, err) := srv.canread(m);
			if(c == nil){
				srv.reply(ref Rmsg.Error(m.tag, err));
				break;
			}
			if(c.qtype & Sys->QTDIR){

				srv.read(m);	# does readdir
				break;
			}

			case TYPE(c.path) {
			Qroot =>
				case FILENO(c.path) {
					2 => srv.reply(styxservers->readstr(m, database.name));
					3 => srv.reply(styxservers->readstr(m, database.column_names_string));
					* => srv.default(gm);
				}
			Qcolumn =>
				rec_index := FILENO(c.path);

				if (rec_index < len database.records)
					srv.reply(styxservers->readstr(m, database.records[rec_index].data));
				else
					srv.default(gm);
			* => 
				srv.default(gm);
			}

		Write =>
		# nothing writable atm.
			(c, merr) := srv.canwrite(m);
			if(c == nil){
				srv.reply(ref Rmsg.Error(m.tag, merr));
				break;
			}
			case TYPE(c.path) {
			Qroot =>
				case FILENO(c.path) {
				#	2 => srv.reply(styxservers->readstr(m, database.name));
				#	3 => srv.reply(styxservers->readstr(m, database.column_names_string));
				* => 
					srv.default(gm);
				}
			}

		Clunk =>
			srv.clunk(m);
		* =>
			srv.default(gm);
		}	
	}
	navops <-= nil;		# shut down navigator
}

dir(qid: Sys->Qid, name: string, length: big, uid: string, perm: int): ref Sys->Dir
{
	d := ref sys->zerodir;
	d.qid = qid;
	if(qid.qtype & Sys->QTDIR)
		perm |= Sys->DMDIR;
	d.mode = perm;
	d.name = name;
	d.uid = uid;
	d.gid = uid;
	d.length = length;
	return d;
}

dirgen(p: big): (ref Sys->Dir, string)
{
	file_index := FILENO(p) ;
	case TYPE(p) {
	Qroot =>
		case file_index {
		0 => 
			qid := Qid(QPATH(file_index, Qroot), database.vers, Sys->QTDIR);
			return (dir(qid, "/", big 0, user, 8r700), nil);
		1 =>
			qid := Qid(QPATH(file_index, Qroot), database.vers, Sys->QTDIR);
			return (dir(qid, "data", big 0, user, 8r555), nil);
		* =>
			if (file_index < len rootfiles) {
				qid := Qid(QPATH(file_index, Qroot), database.vers, Sys->QTFILE);
				return (dir(qid, rootfiles[file_index], big 0, user, 8r666), nil);
			}
		}
	Qrow =>
		pk_value : string;

		if (database.primary_key_column_index < 0) {
			pk_value = sys->sprint("%d", file_index);
		} else {
			cell := file_index * database.column_count + database.primary_key_column_index;

			if (cell < len database.records)
				pk_value = database.records[cell].data;
		}

		if (pk_value != nil) {
			qid := Qid(QPATH(file_index, Qrow), database.vers, Sys->QTDIR);
			return (dir(qid, pk_value, big 0, user, 8r555), nil);
		}

	Qcolumn =>
		column_number := file_index % database.column_count;

		if (file_index < len database.records) 
			return (dir(database.records[file_index].qid(), database.column_names_array[column_number], big 0, user, 8r444), nil);
	}

	return (nil, Enotfound);
}

navigator(navops: chan of ref Navop)
{
	while((m := <-navops) != nil){
		pick n := m {
		Stat =>
			n.reply <-= dirgen(n.path);
		Walk =>
			case (TYPE(n.path)) {
			Qroot => 
				case FILENO(n.path) {
				0 =>
					case n.name {
						"data" => n.reply <-= dirgen(QPATH(1, Qroot));
						"src_filename" => n.reply <-= dirgen(QPATH(2, Qroot));
						"column_names" => n.reply <-= dirgen(QPATH(3, Qroot));
						* => 	n.reply <-= (nil, Enotfound);
					}
				1 =>
					pk := database.pk_hash.find(n.name);

					if(pk == nil)  {
						n.reply <-= (nil, Enotfound);
						continue;
					}

					n.path = QPATH(pk.i, Qrow);
					n.reply <-= dirgen(n.path);
				* =>
					n.reply <-= (nil, "not a directory");
				}
			Qrow =>
				row_index := FILENO(n.path);
				column_hashval := database.column_names_hash.find(n.name);
				if(column_hashval == nil) {
					n.reply <-= (nil, Enotfound);
					continue;
				}
				n.path = QPATH(column_hashval.i + database.column_count * row_index, Qcolumn);
				n.reply <-= dirgen(n.path);
			* => 
				n.reply <-= (nil, "not a directory");
			}

		Readdir =>
			case (TYPE(n.path)) {
			Qroot =>
				case FILENO(n.path) {
				0 =>
					for(i := n.offset; --n.count >= 0 && i < len rootfiles; i++)
						n.reply <-= dirgen(QPATH(i,Qroot));	
				1 =>
					for(i := n.offset; --n.count >= 0 && i < database.row_count ; i++) 
						n.reply <-= dirgen(QPATH(i,Qrow));
				}
				n.reply <-= (nil, nil);
			Qrow =>
				i_start := FILENO(n.path) * database.column_count;

				for(i := n.offset; --n.count >= 0 && i < database.column_count ; i++) 
					n.reply <-= dirgen(QPATH(i_start++, Qcolumn));

				n.reply <-= (nil, nil);
			* =>
				n.reply <-= (nil, "not a directory");
			}
		}
	}
}

QPATH(index, qtype: int): big
{
	bigi := big index;
	bigi = bigi << 32;

	return bigi | big qtype;
}

TYPE(path: big): int
{
	return int path;
}

FILENO(path: big) : int
{
	return int (path >> 32) ;
}

list_to_array(lizt : list of string) : array of string 
{
	list_length := len lizt;
	hooray := array[list_length] of string;
	for(i := 0;  i < list_length ; i++) {
		hooray[i] = hd lizt;
		lizt = tl lizt;
	}

	return hooray;
}

list_to_hash(lizt : list of string) : ref HashTable 
{
	list_length := len lizt;

	hsh := hash->new(list_length);

	j := 0;

	for(i := list_length - 1;  i > -1 ; i--) {
		v := HashVal(j++, 0.0, nil);
		hsh.insert(hd lizt, v);
		lizt = tl lizt;
	}

	return hsh;
}

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.