code: 9ferno

ref: fe52f7e86eea5e5f1b2066e1e33cb90471999769
dir: /appl/demo/odbc/odbcmnt.b/

View raw version
implement Odbcmnt;

#
# Copyright © 2003 Vita Nuova Holdings Limited.  All rights reserved.
#

include "sys.m";
	sys: Sys;
	stderr: ref Sys->FD;
include "draw.m";
include "arg.m";
include "string.m";
	str: String;
include "daytime.m";
	daytime: Daytime;
include "convcs.m";
	convcs : Convcs;
include "styx.m";
	styx: Styx;
	Tmsg, Rmsg: import styx;
include "styxservers.m";
	styxservers: Styxservers;
	Styxserver, Navigator: import styxservers;
	nametree: Nametree;
	Tree: import nametree;

Column: adt {
	name: string;
	ctype: string;
	size: int;
};

Qroot: con iota;
WINCHARSET := "windows-1252";		# BUG: odbc.c should do the conversion!

Odbcmnt: module
{
	init:	fn(ctxt: ref Draw->Context, argv: list of string);
};

init(nil: ref Draw->Context, argv: list of string)
{
	sys = load Sys Sys->PATH;
	stderr = sys->fildes(2);

	arg := load Arg Arg->PATH;
	if(arg == nil)
		notloaded(Arg->PATH);
	daytime = load Daytime Daytime->PATH;
	if (daytime == nil)
		notloaded(Daytime->PATH);
	str = load String String->PATH;
	if(str == nil)
		notloaded(String->PATH);
	convcs = load Convcs Convcs->PATH;
	if(convcs == nil)
		notloaded(Convcs->PATH);
	cserr := convcs->init(nil);
	if (cserr != nil)
		err("convcs init failed " + cserr);
	styx = load Styx Styx->PATH;
	if(styx == nil)
		notloaded(Styx->PATH);
	styx->init();
	styxservers = load Styxservers Styxservers->PATH;
	if(styxservers == nil)
		notloaded(Styxservers->PATH);
	styxservers->init(styx);
	nametree = load Nametree Nametree->PATH;
	if(nametree == nil)
		notloaded(Nametree->PATH);
	nametree->init();
	addr := "127.0.0.1";
	arg->init(argv);
	stype := "ODBC";
	while((o := arg->opt()) != 0)
		case o {
		'a' =>
			addr = arg->earg();
		*   =>
			usage();
		}

	argv = arg->argv();
	arg = nil;
	sys->pctl(Sys->FORKNS | sys->NEWPGRP, nil);
	dbdir := do_mount(netmkaddr(addr, "tcp", "6700"));
	(cfd, cdir) := do_clone(dbdir);
	sources := find_sources(cdir);
	sys->print("Found %d sources\n", len sources);
	spawn serveloop(dbdir, sources, sys->fildes(0));
}

netmkaddr(addr, net, svc: string): string
{
	if(net == nil)
		net = "net";
	(n, l) := sys->tokenize(addr, "!");
	if(n <= 1){
		if(svc== nil)
			return sys->sprint("%s!%s", net, addr);
		return sys->sprint("%s!%s!%s", net, addr, svc);
	}
	if(svc == nil || n > 2)
		return addr;
	return sys->sprint("%s!%s", addr, svc);
}

split1(s, delim: string): (string, string)
{
	(l, r) := str->splitl(s, delim);
	return (l, str->drop(r, delim)); 
}

notloaded(s: string)
{
	err(sys->sprint("failed to load %s: %r", s));
}

usage()
{
	sys->fprint(stderr, "Usage: odbcmnt [ -a address ]\n");
	raise "fail:usage";
}

do_mount(addr: string): string
{
	(ok, c) := sys->dial(addr, nil);
	remdir := "/n/remote";
	if (ok < 0)
		err(sys->sprint("failed to dial odbc server on %s: %r", addr));
	if (sys->mount(c.dfd, nil, remdir, 0, nil) < 0)
		err(sys->sprint("failed to mount odbc server on %s: %r", addr));
	dbdir := remdir + "/db";
	return dbdir;
}


do_clone(dbdir: string): (ref Sys->FD, string)
{
	newfile := dbdir + "/new";
	cfd := sys->open(newfile, Sys->OREAD);
	if (cfd == nil)
		err(sys->sprint("failed to open  %s: %r", newfile));
	cname := read_fd(cfd);
	if (cname == nil)
		err("failed to find clone directory name");
	return(cfd, dbdir + "/" + cname);
}

dir(name: string, perm: int, length: int, qid: int): Sys->Dir
{
	uid := read_file("/dev/user");
	d := sys->zerodir;
	d.name = name;
	d.uid = uid;
	d.gid = uid;
	d.qid.path = big qid;
	if (perm & Sys->DMDIR)
		d.qid.qtype = Sys->QTDIR;
	else {
		d.qid.qtype = Sys->QTFILE;
		d.length = big length;
	}
	d.mode = perm;
	d.atime = d.mtime = daytime->now();
	return d;
}

newconv(dbdir, source: string): (ref Sys->FD, ref Sys->FD, ref Sys->FD, ref Sys->FD, string)
{
	err := "";
	(clonefd, cdir) := do_clone(dbdir);
	ctlf := cdir + "/ctl";
	ctlfd := sys->open(ctlf, Sys->ORDWR);
	if (ctlfd == nil)
		err = sys->sprint("Failed to open %s: %r", ctlf);
	cmdf := cdir + "/cmd";
	cmdfd := sys->open(cmdf, Sys->ORDWR);
	if (cmdfd == nil)
		err = sys->sprint("Failed to open %s: %r", cmdf);
	dataf := cdir + "/data";
	datafd := sys->open(dataf, Sys->ORDWR);
	if (datafd == nil)
		err = sys->sprint("Failed to open %s: %r", dataf);
	if (write_fd(ctlfd, "connect " + source) < 0)
		err = sys->sprint("failed to connect to %s: %r", source);
	return (clonefd, ctlfd, cmdfd, datafd, err);
}

SRCDIR: con 1;
SQL: con 2;
TABLE: con 3;
TABLEDIR: con 4;
COLUMN: con 5;

gettype(fid: big): int
{
	return int fid & 7;
}

SrcFD: adt {
	clonefd, ctlfd, cmdfd, datafd: ref sys->FD;
};

serveloop(dbdir: string, sources: list of string, confd: ref sys->FD)
{
	srcqid := 0;
	sqlqid := 0;
	tableqid := 0;
	colqid := 0;
	tabledirqid := 0;
	(bs, cserr) := convcs->getbtos(WINCHARSET);
	if (bs == nil)
		err("getbtos error: " + cserr);
	(tree, treeop) := nametree->start();
	tree.create(big Qroot, dir(".",8r555 | sys->DMDIR,0,Qroot));
	contents: list of string;
	srcfds := array[len sources] of SrcFD;
	i := 0;
	for (sl := sources; sl!=nil; sl=tl sl) {
		(srcname, srcdriver) := split1(hd sl, ":");
		# Don't do anything with 'srvdriver' - could make a driver 
		#    file to read - but does anyone care about it?
		(clonefd, ctlfd, cmdfd, datafd, e) := newconv(dbdir, srcname);
		if (e != nil)
			sys->fprint(sys->fildes(2), "Odbcmnt: %s\n",e);
		else {
			srcfds[i] = (clonefd, ctlfd, cmdfd, datafd);
			sys->print("%s\n",srcname);
			Qsrc := SRCDIR + (srcqid++<<3);
			tree.create(big Qroot, dir(srcname,8r555 | sys->DMDIR,0,Qsrc));
			Qtabledir := TABLEDIR + (tabledirqid++<<3);
			tree.create(big Qsrc, dir("tables",8r555 | sys->DMDIR,0,Qtabledir));
			Qsql := SQL + (sqlqid++<<3);
			tree.create(big Qsrc, dir("sql",8r666,0, Qsql));
			
			tables := find_tables(srcfds[i].cmdfd, srcfds[i].datafd);
			if (tables == nil)
				err(sys->sprint("failed to find tables: %r"));
			if (write_fd(srcfds[i].ctlfd, "headings") < 0)
				err(sys->sprint("failed to write to ctl file: %r"));
			sys->print("\tBuilding tree...");
			for (tlist:=tables; tlist!=nil; tlist=tl tlist) {
				table := hd tlist;
				Qtable := TABLE + (tableqid++<<3);
				tree.create(big Qtabledir, dir(table,8r555 | sys->DMDIR,0,Qtable));
				columns := find_columns(srcfds[i].cmdfd, srcfds[i].datafd, table);
				for (clist:=columns; clist!=nil; clist=tl clist) {
					column := hd clist;
					Qcol := COLUMN + (colqid<<3);
					tree.create(big Qtable, dir(column.name,8r555,0,Qcol));
					data := sys->sprint("%s %d\n", column.ctype, column.size);
					contents = data :: contents;
					colqid++;
				}
			}
			sys->print("done\n");
		}
		i++;
	}
	colcontent := array[colqid] of string;
	for (i = colqid - 1; i >= 0; i--) {
		colcontent[i] = hd contents;
		contents = tl contents;
	}
	(tchan, srv) := Styxserver.new(confd, Navigator.new(treeop), big Qroot);
	sys->pctl(Sys->FORKNS|Sys->FORKFD, nil);
	gm: ref Tmsg;
	buf := array[Sys->ATOMICIO] of byte;
	serverloop: for (;;) {
		gm = <-tchan;
		if (gm == nil)
			break serverloop;
		pick m := gm {
		Readerror =>
			sys->fprint(sys->fildes(2), "odbcmnt: fatal read error: %s\n", m.error);
			break serverloop;
		Read =>
			c := srv.getfid(m.fid);
			if(c.qtype & Sys->QTDIR){
				srv.read(m);	# does readdir
				break;
			}
			case gettype(c.path) {
				SQL =>
					srcno := int c.path >> 3;
					sys->seek(srcfds[srcno].datafd, m.offset, Sys->SEEKSTART);
					n := sys->read(srcfds[srcno].datafd, buf, len buf);
					if (n >= 0) {
						(state, s, err) := bs->btos(nil, buf[:n], -1);
						r := ref Rmsg.Read(gm.tag, array of byte s);
						srv.reply(r);
					} else
						srv.reply(ref Rmsg.Error(gm.tag, sys->sprint("%r")));
					break;
			COLUMN =>
				srv.reply(styxservers->readstr(m, colcontent[int c.path>>3]));
				* =>
					srv.default(gm);
			}
		Write =>
			c := srv.getfid(m.fid);
			case gettype(c.path) {
				SQL =>
					srcno := int c.path >> 3;
					n := sys->write(srcfds[srcno].cmdfd, m.data, len m.data);
					if (n == len m.data)
						srv.reply(ref Rmsg.Write(m.tag, n));
					else
						srv.reply(ref Rmsg.Error(gm.tag, sys->sprint("%r")));
					break;
				* =>
					srv.default(gm);
			}

		* =>
			srv.default(gm);
		}
	}
	tree.quit();
}

find_tables(cmdfd, datafd: ref Sys->FD): list of string
{
	tlist: list of string;
	if (write_fd(cmdfd, "tables") < 0)
		err(sys->sprint("failed to write to cmd file: %r"));
	while((rec := read_fd(datafd)) != nil) {
		fields := atokenize(rec, "|");
		if (len fields < 4)
			err("bad table name");
		tname := fields[2];
		tlist = tname :: tlist;
	}
	return tlist;
}


find_columns(cmdfd, datafd: ref Sys->FD, table: string): list of Column
{
	clist: list of Column;
	if (write_fd(cmdfd, "columns " + table) < 0)
		err(sys->sprint("failed to write to cmd file: %r"));
	while((rec := read_fd(datafd)) != nil) {
		fields := atokenize(rec, "|");
		if (len fields < 3)
			err("bad column name");
		cname :=fields[3];
		ctype := "";
		if (len fields > 5)
			ctype = fields[5];
		csize := 0;
		if (len fields > 6)
			csize = int fields[6];
		clist = (fields[3], ctype, csize) :: clist;
	}
	return clist;
}

atokenize(s: string, delim: string): array of string
{
	if (s == nil)
		return nil;
	dl := len delim;
	r: list of string;
	l: string;
	for (;;) {
		(l, s) = str->splitstrl(s, delim);
		r = l :: r;
		if (s == nil || s == delim)
			break;
		s = s[dl:];
	}
	a := array[len r] of string;
	for (i:=len r-1; i>=0; i--) {
		a[i] = hd r;
		r = tl r;
	}
	return a;
}

find_sources(cdir: string): list of string
{
	sfile := cdir+"/sources";
	fd := sys->open(sfile, Sys->OREAD);
	if (fd == nil)
		err(sys->sprint("failed to open  %s: %r", sfile));
	s := read_fd(fd);
	(n, lines) := sys->tokenize(s, "\n");
	return lines;
}

err(s: string)
{
	sys->fprint(stderr, "odbcgw: %s\n", s);
	raise "fail:error";
}

read_fd(fd: ref Sys->FD): string
{
	MAX : con Sys->ATOMICIO;
	buf := array[MAX] of byte;
#	sys->seek(fd, big 0, Sys->SEEKSTART);
	size := sys->read(fd, buf, MAX);
	if (size <= 0) {
#		if (size < 0)
#			sys->fprint(stderr, "read_fd error: %r\n");
		return nil;
	}
	return string buf[0:size];
}

read_file(f: string): string
{
	fd := sys->open(f, Sys->OREAD);
	if (fd == nil)
		return nil;
	return read_fd(fd);
}

write_fd(fd: ref Sys->FD, s: string): int
{
	a := array of byte s;
	if (sys->write(fd, a, len a) != len a)
		return -1;
	return 0;
}