code: purgatorio

ref: 00c219c7d9c2b9f60c2db0e1ba7289b2301209a7
dir: /appl/demo/camera/camera.b/

View raw version
implement Camera;

include "sys.m";
	sys : Sys;
include "daytime.m";
	daytime: Daytime;
include "styx.m";
	styx: Styx;
	Rmsg, Tmsg: import styx;
include "styxservers.m";
	styxservers: Styxservers;
	Fid, Navigator, Navop: import styxservers;
	Styxserver, Eexists, Eperm, Ebadfid, Enotdir, Enotfound, Ebadarg: import styxservers;
	nametree: Nametree;
	Tree: import nametree;
include "string.m";
	str : String;
include "draw.m";
include "arg.m";

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

cdp_get_product_info: 			con 16r01;
cdp_get_image_specifications:	con 16r02;
cdp_get_camera_status:			con 16r03;
cdp_set_product_info:			con 16r05;
cdp_get_camera_capabilities:		con 16r10;
cdp_get_camera_state:			con 16r11;
cdp_set_camera_state:			con 16r12;
cdp_get_camera_defaults:		con 16r13;
cdp_set_camera_defaults:		con 16r14;
cdp_restore_camera_states:		con 16r15;
cdp_get_scene_analysis:			con 16r18;
cdp_get_power_mode:			con 16r19;
cdp_set_power_mode:			con 16r1a;
cdp_get_s1_mode:				con 16r1d;
cdp_set_s1_mode:				con 16r1e;
cdp_start_capture:				con 16r30;
cdp_get_file_list:				con 16r40;
cdp_get_new_file_list:			con 16r41;
cdp_get_file_data:				con 16r42;
cdp_erase_file:					con 16r43;
cdp_get_storage_status:			con 16r44;
cdp_set_file_data:				con 16r47;
cdp_get_file_tag:				con 16r48;
cdp_set_user_file_tag:			con 16r49;
cdp_get_clock:					con 16r70;
cdp_set_clock:					con 16r71;
cdp_get_error:					con 16r78;
cdp_get_interface_timeout:		con 16r90;
cdp_set_interface_timeout:		con 16r91;

cdp_header_len:				con 12;

T_DIR: con 0;
T_CTL: con 1;
T_ABILITIES: con 2;
T_TIME: con 3;
T_JPGDIR: con 4;
T_JPG: con 5;
T_STORAGE: con 6;
T_POWER: con 7;
T_THUMB: con 8;
T_THUMBDIR: con 9;
T_STATE: con 10;
T_INTERFACE: con 11;

MAXFILESIZE : con 5000000;
TIMEOUT : con 4000;

nextjpgqid, nexttmbqid, dirqid, Qctl, Qabl, Qstore: int;
Qstate, Qtime, Qjpgdir, Qpwr, Qthumbdir, Qinterface : int;

error_table := array [] of {
	"No Error",
	"Unimplemented",
	"Unsupported Version",
	"Application Timeout",
	"Internal Error",
	"Parameter Error",
	"File System Null",
	"File Not Found",
	"Data Section Not Found",
	"Invalid File Type",
	"Unknown Drive",
	"Drive Not Mounted",
	"System Busy",
	"Battery Low",
};

bintro := array [] of {
	byte 16ra5,
	byte 16r5a,
	byte 16r00,
	byte 16rc8,
	byte 16r00,
	byte 16r02,
	byte 16rc9,
};

bak := array [] of {
	byte 16r5a,	# 2 byte header
	byte 16ra5,
	byte 16r55,	# I/F Type
	byte 16r00,	# Comm Flag
	byte 16r00,
	byte 16r00,
	byte 16r00,
	byte 16r00,
	byte 16r00,
	byte 16r00,
	byte 16r00,
	byte 16r00,
	byte 16r00,
};

pwl := array[] of {
	byte 0,
	byte 0,
};

pak := array [] of {
	byte 0,
	byte 0,
};

SERIAL, USB, IRDA: con (1<<iota);
BEACON, BEACONRESULT: con (1<<iota);

Camera_adt: adt {
	port_type: 	int;
	port_num:	int;
	command:	int;
	mode: 		int;
	fd:			ref Sys->FD;
	ctlfd:			ref Sys->FD;
	cdp:			array of byte;
	bufbytes:		int;
	baud:		int;
	dfs, hfs:		int;		# device and host frame sizes
	stat:			int;	# eia status file
};

statopt := array[] of {
	"status",
	"stat",
};

DL_QUANTA: con 20000;

TOUT: con -1729;

Partialtag: adt {
	offset, length, filesize: int;
};

Cfile: adt {
	driveno: int;
	pathname: array of byte;
	dosname: array of byte;
	filelength: int;
	filestatus: int;
	thumblength: int;
	thumbqid: int;
};

Fitem : adt {
	qid: Sys->Qid;
	cf: Cfile;
};

C: Camera_adt;

filelist: array of Fitem;
reslength: int;
currentstate := "";
wait : int;
usecache := 0;
connected : int;
recon := 0;
verbosity := 4;
interfacepath := "";
interfacepaths : array of string;
camname := "";
gpid : int;

init(nil : ref Draw->Context, argv : list of string)
{
	err: string;
	sys = load Sys Sys->PATH;
	gpid = sys->pctl(Sys->NEWPGRP, nil);

	str = load String String->PATH;
	daytime = load Daytime Daytime->PATH;

	styx = load Styx Styx->PATH;
	styx->init();
	styxservers = load Styxservers Styxservers->PATH;
	styxservers->init(styx);
	nametree = load Nametree Nametree->PATH;
	nametree->init();
	arg := load Arg Arg->PATH;

	filelist = array[200] of Fitem;
	C.port_num = 0;			# XXXXX from argv
	C.port_type = SERIAL;		# Serial only for now
	C.baud = 115200;
	C.dfs = C.hfs = 1023;
	C.cdp = array [DL_QUANTA] of byte;
	C.mode = BEACON;

	ex.pnum = -1;
	ex.offset = -1;
	cachelist = nil;

	nextjpgqid = getqid(1, T_JPG);
	nexttmbqid = getqid(1, T_THUMB);
	dirqid = getqid(1,T_JPGDIR);
	Qctl = getqid(Qroot,T_CTL);
	Qabl = getqid(Qroot,T_ABILITIES);
	Qstore = getqid(Qroot,T_STORAGE);
	Qtime = getqid(Qroot,T_TIME);
	Qstate = getqid(Qroot,T_STATE);
	Qpwr = getqid(Qroot,T_POWER);	
	Qjpgdir = getqid(Qroot,T_JPGDIR);
	Qthumbdir = getqid(Qroot,T_THUMBDIR);
	Qinterface = getqid(Qroot,T_INTERFACE);
	
	camname = "Camera";
	extrafilelist: list of string = nil;
	arg->init(argv);
	arg->setusage("camera [-b baud] [-F framesize] [-f extrafiles] [-p port] [-n name] [-v verbosity]");
	while ((opt := arg->opt()) != 0) {
		case opt {
		'n' =>
			camname = arg->earg();
		'v' =>
			verbosity = int arg->earg();
		'F' =>
			C.dfs = C.hfs = int arg->earg();
		'b' =>
			C.baud = int arg->earg();
		'p' =>
			C.port_num = int arg->earg();
		'c' =>
			usecache = 1;
		'f' =>
			extrafilelist = arg->earg() :: extrafilelist;
		* =>
			arg->usage();
		}
	}
	arg = nil;
	interfacepaths = array[len extrafilelist] of string;
	# sys->print("INTERFACEPATHS: %d\n", len extrafilelist);
	for (i := 0; i < len interfacepaths; i++) {
		interfacepaths[i] = hd extrafilelist;
		# sys->print("INTERFACEPATH %d: %s\n", i, hd extrafilelist);
		extrafilelist = tl extrafilelist;
	}
	
	print(sys->sprint("Trying to connect to eia%d...\n",C.port_num),2);
	case C.port_type {
		SERIAL =>
			# open port and return fd
			(C.fd, C.ctlfd, err) = serialport(C.port_num);
			if (C.fd == nil) {
				print("Could not open serial port\n",1);
				exit;
			}
		USB =>
			;
		IRDA =>
			;
		* =>
			;
	}
	if (connect() != 0) {;
		print("Connection failed\n",1);
		exit;
	}
	recon = 0;
	print("Connected!\n",2);
	set_interface_timeout();
	set_camera_properties();
	get_file_list();
	connected = 1;
	ignoreabls = nil;
	get_camera_capabilities();
	sync := chan of int;
	spawn serveloop(sys->fildes(0), sync);
	<-sync;
}

set_camera_properties()
{
	for (i := 0; i < len set_camera_props; i++)
		set_camera_state(set_camera_props[i].t0,set_camera_props[i].t1);
}

set_camera_props := array[] of {
	("mcap", 0),
	("acpd", 65535),
	("actc", 65535),
	("btpd", 65535),
	("bttc", 65535),
	("flty", 1246774599),
	("ssvl", 0),
};

argval(argv: list of string, arg: string): string
{
	if (arg == "") return "";
	if (arg[0] != '-') arg = "-" + arg;
	while (argv != nil) {
		if (hd argv == arg && tl argv != nil && (hd tl argv)[0] != '-')
			return tonext(tl argv);
		argv = tl argv;
	}
	return "";
}

tonext(los: list of string): string
{
	s := "";
	while (los != nil) {
		if ((hd los)[0] != '-') s += " " + hd los;
		else break;
		los = tl los;
	}
	if (s != "") s = s[1:];
	return s;
}

int2hex(i:int): int
{
	i2 := 0;
	s := string i;
	for (k := 0; k < len s; k++)
		i2 = (i2 * 16) + int s[k:k+1];
	return i2;
}

connect(): int
{	
	connected = 0;
	datain := chan of array of byte;
	pchan := chan of int;
	tick := chan of int;
	reset(C.ctlfd);

	spawn timer2(tick,TIMEOUT * 2);
	tpid := <-tick;

	spawn beacon_intro(datain, pchan, C.fd);
	pid := <- pchan;
	# beacon phase
	Beacon: for (;;) {
		alt {
			buf := <- datain =>
				# got some data
				case C.mode {
					BEACON =>
						if (beacon_ok(buf)) {
							print("Got beacon\n",3);
							beacon_ack(C);
							spawn beacon_result(datain, pchan, C.fd);
							pid = <-pchan;
							C.mode = BEACONRESULT;
							break;
						}
						else {
							print("resetting\n",3);
							reset(C.ctlfd);
						}
					BEACONRESULT =>
						kill(tpid);

						print("Checking beacon result\n",3);
						if (beacon_comp(buf, C) == 0) {
							return 0;
							break Beacon;
						}
						return -1;
				}
			<- tick =>
				kill(pid);
				return -1;		# failure
		}
	}
}

CTL, ABILITIES, DATA, JPG, PIC, TIME, CONV: con iota;
NAME, FSIZE, PHOTO, THUMB: con iota;

Qdir : con iota;

contains(s: string, test: string): int
{
	num :=0;
	if (len test > len s) return 0;
	for (i := 0; i < (1 + (len s) - (len test)); i++) {
		if (test == s[i:i+len test]) num++;
	}
	return num;
}

abilitiesfilter := array[] of {
	"Time Format",
	"Date Format",
	"File Type",
	"Video",
	"Media",
	"Sound",
	"Volume",
	"Reset Camera",
	"Slide",
	"Timelapse",
	"Burst",
	"Power",
	"Sleep",
};

ignoreabls : list of string;

defattr : list of (string, int);
defaultattr, currentattr: array of (string, int);

filterabls(pname, desc: string): int
{
	for (i := 0; i < len abilitiesfilter; i++) {
		if (contains(desc, abilitiesfilter[i])) {
			ignoreabls = pname :: ignoreabls;
			return 1;
		}
	}
	return 0;
}

mountit(dfd, mountfd: ref sys->FD, sync: chan of int)
{
	sys->pctl(sys->NEWNS | sys->NEWFD, 2 :: dfd.fd :: mountfd.fd :: nil);
	sync <-= 1;
	mountfd = sys->fildes(mountfd.fd);
	dfd = sys->fildes(dfd.fd);
	if (sys->mount(mountfd, nil, "/", sys->MREPL | sys->MCREATE, nil) == -1) {
		sys->fprint(sys->fildes(2), "cannot mount\n");
		spawn exporterror(dfd, sys->sprint("%r"));
	} else {
		sync = chan of int;
		spawn exportpath(sync, dfd);
		<-sync;
	}
}

exporterror(dfd: ref Sys->FD, error: string)
{
	tmsg := Tmsg.read(dfd, 0);
	if (tmsg == nil) {
		sys->fprint(sys->fildes(2), "exporterror() EOF\n");
		exit;
	}
	pick t := tmsg {
	Readerror =>
		sys->fprint(sys->fildes(2), "exporterror() Readerror\n");
	* =>
		reply: ref Rmsg = ref Rmsg.Error(tmsg.tag, error);
		data := reply.pack();
		sys->write(dfd, data, len data);
	}
}

exportpath(sync: chan of int, dfd: ref sys->FD)
{
	sync <-= 1;
	sys->export(dfd, "/", Sys->EXPWAIT);
}

Qroot : con int iota;

ss : ref Styxserver;
uid: string;

exitfid := -1;

getuid()
{
	buf := array [100] of byte;
	fd := sys->open("/dev/user", Sys->OREAD);
	uidlen := sys->read(fd, buf, len buf);
	uid = string buf[0: uidlen];
}

dir(name: string, perm: int, length: int, qid: int): Sys->Dir
{
	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;
}

User: adt {
	attachfid: int;
	attr: array of (string, int);
};

users : array of User;

getuser(fid: int): int
{
	for (i := 0; i < len users; i++)
		if (users[i].attachfid == fid)
			return i;
	return -1;
}

getattr(pname: string): int
{
	for (i := 0; i < len defaultattr; i++)
		if (defaultattr[i].t0 == pname)
			return i;
	return -1;
}

serveloop(fd : ref sys->FD, sync: chan of int)
{
	tchan: chan of ref Tmsg;
	srv: ref Styxserver;
	echan := chan of string;
	users = array[20] of { * => User (-1, nil) };
	sys->pctl(Sys->FORKNS, nil);
	sync <-= 1;
	print("serveloop\n",5);
	getuid();
	(tree, treeop) := nametree->start();
	tree.create(big Qroot, dir(".",8r555 | sys->DMDIR,0,Qroot));
	tree.create(big Qroot, dir("ctl",8r222,0,Qctl));
	tree.create(big Qroot, dir("abilities",8r444,0,Qabl));
	tree.create(big Qroot, dir("storage",8r444,0,Qstore));
	tree.create(big Qroot, dir("power",8r444,0,Qpwr));
	tree.create(big Qroot, dir("date",8r666,0,Qtime));
	tree.create(big Qroot, dir("state",8r666,0,Qstate));
	tree.create(big Qroot, dir("jpg",8r777 | sys->DMDIR,0,Qjpgdir));
	tree.create(big Qroot, dir("thumb",8r777 | sys->DMDIR,0,Qthumbdir));
	for (j := 0; j < len interfacepaths; j++) {
		(n, idir) := sys->stat(interfacepaths[j]);
		if (n != -1) {
			idir.qid.path = big Qinterface;
			# intdir := dir("",8r777,0,Qinterface);
			# intdir.name = idir.name;
			# intdir.length = idir.length;
			# intdir.atime = idir.atime;
			# intdir.mtime = idir.mtime;
			tree.create(big Qroot, idir);
			Qinterface += 1<<4;
		}
	}

	tmsgqueue := Tmsgqueue.new(50);

	(tchan, srv) = Styxserver.new(fd,Navigator.new(treeop), big Qroot);
	fd = nil;

	gm, lastgm: ref Tmsg;
	gm = nil;

	oldfiles = nil;
	updatetree(tree);

	print("serveloop loop\n",5);
	alivechan := chan of int;
	spawn keepalive(alivechan);
	alivepid := <-alivechan;
	retryit := 0;
	notries := 0;
	readfid := -1;
	serveloop: for (;;) {
		wait = daytime->now();
		if (notries > 5) retryit = 0;
		if (retryit) {
			gm = lastgm;
			notries++;
		}
		else {
			notries = 0;
			loop: for (;;) {
				gm = tmsgqueue.pop(readfid);
				if (gm != nil)
					break;
				alt {
				gm = <-tchan =>
					break loop;
				c := <-alivechan =>
					for (;;) {
						s := get_clock();
						wait = daytime->now();
						# print(sys->sprint("got alivechan: %s",s),1);
						if (recon) {
							killchan := chan of int;
							spawn noresponse(tchan,srv,killchan);
							reconnect(-1);
							killchan <-= 1;
						}
						else
							break;
					}
				}
			}
		}
		lastgm = gm;
		retryit = 0;
		if (gm == nil) {
			sys->print("exiting!\n");
			break serveloop;		# nil => EOF => last mount was unmounted
		}
		print(sys->sprint("Got new GM %s tag: %d\n", gm.text(), gm.tag),4);
		# print(sys->sprint("Got new GM %s tag: %d\n", gm.text(), gm.tag),2);

		if (!connected) {
			srv.reply(ref Rmsg.Error(gm.tag, "Could not connect to camera"));
			print("Error: not connected to camera\n",1);
		}
		else pick m := gm {
		Readerror =>
			print(sys->sprint( "camera: fatal read error: %s\n", m.error),1);
			break serveloop;
		Attach =>
			nu := getuser(-1);
			if (nu == -1) {
				srv.reply(ref Rmsg.Error(m.tag, "Camera in use"));
				break;
			}
			m.uname = string nu;
			srv.default(m);
			myattr := array[len currentattr] of (string, int);
			for (i := 0; i < len myattr; i++)
				myattr[i] = currentattr[i];
			users[nu] = User (m.fid, myattr);
			print("adding user "+string nu, 2);
		Clunk =>
			nu := getuser(m.fid);
			if (nu != -1) {
				users[nu] = User (-1, nil);
				print("removing user "+string nu, 2);
			}
			if (m.fid == readfid) {
				# sys->print("readfid clunk: %d\n",readfid);
				readfid = -1;
			}
			srv.default(gm);
		Remove =>
			print("Removing file\n",3);
			f := srv.getfid(m.fid);
			if (f == nil) {
				print("Remove: Invalid fid\n",1);
				srv.reply(ref Rmsg.Error(m.tag, Ebadfid));
				break;
			}
			ftype := gettype(int f.path);
			if (ftype != T_JPG) {
				srv.reply(ref Rmsg.Error(m.tag, "Cannot remove file"));
				break;
			}
			else {
				for (i := 0; i < reslength; i++) {
					if (f.path == filelist[i].qid.path) {
						print("removing filelist\n",5);
						if (erase_file(filelist[i].cf) != 0) {
							if (!recon) 
								srv.reply(ref Rmsg.Error(m.tag, "Cannot remove file"));
							break;
						}
					
						srv.delfid(f);
						if (get_file_list() != 0)
							srv.reply(ref Rmsg.Error(m.tag, "Cannot read files"));
						else {
							updatetree(tree);
							srv.reply(ref Rmsg.Remove(m.tag));
						}
						break;
					}
				}
			}
		Read =>
			print("got read request in serveloop\n",6);
			(f,e) := srv.canread(m);
			if(f == nil)
				break;
			if (f.qtype & Sys->QTDIR) {
				print("reading directory\n",5);
				srv.read(m);
				break;
			}
			data : array of byte;
			case gettype(int f.path) {
			T_INTERFACE =>
				(dir, intdata) := readinterface(int f.path, m.offset, m.count);
				if (dir != nil && m.offset == big 0) {
					dir.qid.path = f.path;
					tree.wstat(f.path, *dir);
				}
				srv.reply(ref Rmsg.Read(m.tag, intdata));
			T_POWER =>
				print("reading power mode...\n",3);
				data = array of byte get_power_mode();
				if (!recon) srv.reply(styxservers->readbytes(m, data));

			T_TIME =>
				print("reading clock...\n",3);
				data = array of byte get_clock();
				if (!recon)	
					srv.reply(styxservers->readbytes(m, data));

			T_ABILITIES =>
				data = array of byte get_camera_capabilities();
				if (!recon)
					srv.reply(styxservers->readbytes(m, data));

			T_JPG =>
				# sys->print("Read Jpg: user %d\n", int f.uname);
				if (readfid != -1 && readfid != m.fid) {
					tmsgqueue.push(m);
					# sys->print("in use!\n");
					# srv.reply(ref Rmsg.Error(m.tag, "Camera in use, please wait"));
					break;
				}
				readfid = m.fid;
				data = photoread2(f.path, m,tree,0);
				if (!recon)
					srv.reply(ref Rmsg.Read(m.tag, data));
	
			T_THUMB =>
				if (readfid != -1 && readfid != m.fid) {
					# srv.reply(ref Rmsg.Error(m.tag, "Camera in use, please wait"));
					tmsgqueue.push(m);
					break;
				}
				readfid = m.fid;
				# sys->print("Read Thumb: user %d\n", int f.uname);
				data = photoread2(f.path, m,tree,1);
				if (!recon)
					srv.reply(ref Rmsg.Read(m.tag, data));

			T_STATE =>
				if (currentstate == "") srv.reply(ref Rmsg.Error(m.tag, "No state requested"));
				else {
					data = array of byte get_camera_state(currentstate,int m.offset);
					if (!recon)
						srv.reply(ref Rmsg.Read(m.tag, data));
				}

			T_STORAGE =>
				data = array of byte get_storage_status();
				if (!recon) {
					if (len data == 0)
						srv.reply(ref Rmsg.Error(m.tag, "Could not read storage status"));
					else
						srv.reply(styxservers->readbytes(m, data));
				}
			* =>
				srv.reply(ref Rmsg.Error(m.tag, "Cannot read file"));
			}
			# if (readfid != -1)
			# 	sys->print("readfid set: %d\n",readfid);
		Write =>
			print("got write request in serveloop\n",6);

			(f,e) := srv.canwrite(m);
			if(f == nil) {
				print("cannot write to file\n",1);
				break;
			}
			wtype := gettype(int f.path);
			(n, s) := sys->tokenize(string m.data, " \t\n");
			if (wtype == T_TIME) {
				if (set_clock(string m.data) != 0)
					srv.reply(ref Rmsg.Error(m.tag, "Invalid date time format\n" + 
										"Usage: MM/DD/YY HH/MM/SS\n"));
				else srv.reply(ref Rmsg.Write(m.tag, len m.data));
				
			}
			else if (wtype == T_CTL) {
				err := "";
				case hd s {
				"refresh" =>
					# for (i := 0; i < reslength; i++) {
					#	tree.remove(filelist[i].qid.path);
					#	tree.remove(big filelist[i].cf.thumbqid);
					# }
					if (get_file_list() != 0)
						err = "Error: Could not read from camera";
					else 
						updatetree(tree);
						# for (i = 0; i < reslength; i++) 
						#	buildfilelist(tree, i);
				"snap" =>
					nu := int f.uname;
					print(sys->sprint("User %d taking photo\n",nu),2);
					for (i := 0; i < len currentattr; i++) {
						# sys->print("user: %s=%d current: %s=%d\n",
						# 	users[nu].attr[i].t0,users[nu].attr[i].t1,
						#	currentattr[i].t0,currentattr[i].t1);
						if (users[nu].attr[i].t1 != currentattr[i].t1) {
							set_camera_state(users[nu].attr[i].t0, users[nu].attr[i].t1);
							sys->sleep(100);
						}
					}
					e1 := capture();
					if (e1 == -1) {
						err = "Cannot communicate with camera";
						break;
					}
					if (e1 != 0) { 
						err = "Error: "+error_table[e1];
						break;
					}
					sys->sleep(4000);
					if (get_file_list() != 0) {
						err = "Error: Could not read from camera";
						break;
					}
					updatetree(tree);
				* =>
					if (n == 2) {	# assume that it is a (string, int) tuple
						na := getattr(hd s);
						if (na == -1)
							err = "Invalid command name '"+hd s+"'";
						else {
							e1 := set_camera_state(hd s, int hd tl s);
							if (e1 != nil)
								err = e;
							else
								users[int f.uname].attr[na].t1 = int hd tl s;
						}
					}
					
				}

				if (!recon) {
					if (err != "") {
						print(err+"\n",1);
						srv.reply(ref Rmsg.Error(m.tag, err));
					}
					else srv.reply(ref Rmsg.Write(m.tag, len m.data));
				}
			}
			else if (wtype == T_STATE) {
				if (s != nil)
					currentstate = hd s;
				srv.reply(ref Rmsg.Write(m.tag, len m.data));
			}
			else srv.reply(ref Rmsg.Error(m.tag, "Could not write to file"));
		Wstat =>
			print("Got Wstat command in serveloop\n",6);
			srv.reply(ref Rmsg.Error(m.tag, "Wstat failed"));
		* =>
			srv.default(gm);
		}
		if (recon) {
			retryit = 1;
			ok :=	reconnect(4);
			if (!ok) {
				srv.reply(ref Rmsg.Error(gm.tag, "Could not connect to camera"));
				killchan := chan of int;
				spawn noresponse(tchan,srv,killchan);
				reconnect(-1);
				killchan <-= 1;
				retryit = 0;
				sys->sleep(100);
			}
		}
	}
	tree.quit();
	kill(alivepid);
	killg(gpid);
}

Tmsgqueue: adt {
	start, end, length: int;
	a : array of ref Tmsg.Read;
	new: fn (n: int): ref Tmsgqueue;
	push: fn (t: self ref Tmsgqueue, t: ref Tmsg.Read): int;
	pop: fn (t: self ref Tmsgqueue, readfid: int): ref Tmsg.Read;
};

Tmsgqueue.new(n: int): ref Tmsgqueue
{
	t : Tmsgqueue;
	t.start = 0;
	t.end = 0;
	t.length = 0;
	t.a = array[n] of ref Tmsg.Read;
	return ref t;
}

Tmsgqueue.push(t: self ref Tmsgqueue,newt: ref Tmsg.Read): int
{
	if (t.length >= len t.a)
		return -1;
	t.a[t.end] = newt;
	t.end++;
	if (t.end >= len t.a)
		t.end = 0;
	t.length++;
	return 0;
}

Tmsgqueue.pop(t: self ref Tmsgqueue, readfid: int): ref Tmsg.Read
{
	if (t.length == 0)
		return nil;
	m := t.a[t.start];
	if (readfid != -1 && readfid != m.fid)
		return nil;
	t.start++;
	if (t.start >= len t.a)
		t.start = 0;
	t.length--;
	return m;
}

noresponse(tchan: chan of ref Tmsg, srv: ref Styxservers->Styxserver, killchan : chan of int)
{
	for (;;) alt {
		k := <- killchan =>
			return;
		gm := <- tchan =>
			print("noresponse: Returning Error\n",1);
			srv.reply(ref Rmsg.Error(gm.tag, "Could not connect to camera"));
			sys->sleep(100);
	}
}

photoread2(qid: big, m: ref Tmsg.Read, tree: ref Nametree->Tree, isthumb: int): array of byte
{
	photonum := -1;
	data : array of byte;
	# sys->print("photoread: qid: %d resl: %d\n",int qid,reslength);
	for (i := 0; i < reslength; i++) {
		# sys->print("%d: %s %d\n",i, sconv(filelist[i].cf.dosname),int filelist[i].qid.path);
		if (!isthumb && qid == filelist[i].qid.path) {
			photonum = i;
			break;
		}
		else if (isthumb && int qid == filelist[i].cf.thumbqid) {
			photonum = i;
			break;
		}
	}
	if (photonum >= reslength || photonum < 0) {
		print(sys->sprint( "error: photonum = %d (reslength = %d)\n", photonum,reslength),1);
		return nil;
	}
	offset := int m.offset;
	dosname := filelist[photonum].cf.dosname;
	filelen := filelist[photonum].cf.filelength;
	for (k := 0; k < 5; k++) {
		if (filelen == 0) {
			get_file_size(photonum);
			print(sys->sprint("\tFilelen: %d => ",filelen),5);
			filelen = filelist[photonum].cf.filelength;
			print(sys->sprint("%d\n",filelen),5);
			tree.wstat(qid,
					dir(str->tolower(sconv(filelist[photonum].cf.dosname)),
					8r444,
					filelen,
					int qid));
			sys->sleep(1000);
		}
		else break;
	}
	if (filelen == 0 && !isthumb) return nil; # doesn't matter if filesize is wrong for thumbnail
	if (isthumb) filelen = filelist[photonum].cf.thumblength;
	if (usecache && cachesize(dosname, isthumb) == filelen) {
#		print(sys->sprint("Is cached!\n");
		n := m.count;
		filesize := cachesize(dosname,isthumb);
		if (offset >= filesize) return nil;
		if (offset+m.count >= filesize) n = filesize - offset;
		data = array[n] of byte;
		fd := sys->open(cachename(dosname,isthumb), sys->OREAD);
		if (fd == nil) cachedel(dosname,isthumb);
		else {
			sys->seek(fd,m.offset,sys->SEEKSTART);
			sys->read(fd,data,len data);
			fd = nil;
			return data;
		}
	}
#	print(sys->sprint("Is NOT cached!\n");

	if (photonum == ex.pnum && offset == ex.offset && ex.isthumb == isthumb) 
		data = ex.data;
	else if (isthumb)
		data = getthumb(photonum, offset, m.count);
	else if (!isthumb)
		data = getpicture2(photonum, offset, m.count);
	if (len data > m.count) {
		ex.pnum = photonum;
		ex.offset = offset + m.count;
		ex.data = array[len data - m.count] of byte;
		ex.data[0:] = data[m.count:len data];
		ex.isthumb = isthumb;
		data = data[:m.count];
	}
	if (usecache) {
		fd : ref sys->FD;
		cname := cachename(dosname,isthumb);
	
		if (offset == 0)
			fd = sys->create(cname,sys->OWRITE,8r666);
		else {
			fd = sys->open(cname,sys->OWRITE);
			if (fd != nil)
				sys->seek(fd,big 0,sys->SEEKEND);
		}
		if (fd != nil) {
			i = sys->write(fd,data,len data);
			fd = nil;
		}
		(n, dir) := sys->stat(cname);
		if (n == 0) {
			cacheadd(dosname,isthumb,int dir.length);
		}
	}
	return data;
}

cachelist : list of (string, int, int);

cacheprint()
{
	tmp := cachelist;
	print("cache:\n",3);
	while (tmp != nil) {
		(dn,i1,i2) := hd tmp;
		print(sys->sprint("\t%s %d %d\n",dn,i1,i2),3);
		tmp = tl tmp;
	}
}

cacheclean()
{
	tmp : list of (string, int,int);
	tmp = nil;
	while (cachelist != nil) {
		(dosnm,it,fl) := hd cachelist;
		for (i := 0; i < reslength; i++) {
			filelen := filelist[i].cf.filelength;
			if (it) filelen = filelist[i].cf.thumblength;
			if (sconv(filelist[i].cf.dosname) == dosnm && filelen == fl) {
				tmp = (dosnm,it,fl) :: tmp;
				break;
			}
		}
		cachelist = tl cachelist;
	}
	cachelist = tmp;
}	

cacheadd(dosname1: array of byte, isthumb, filelen: int)
{
	dosname := sconv(dosname1);
	tmp : list of (string, int,int);
	tmp = nil;
	updated := 0;
	while (cachelist != nil) {
		(dosnm,it,fl) := hd cachelist;
		if (dosname == dosnm && it == isthumb) {
			updated = 1;
			tmp = (dosnm,it,filelen) :: tmp;
		}
		else
			tmp = (dosnm,it,fl) :: tmp;
		cachelist = tl cachelist;
	}
	if (updated == 0)
		tmp = (dosname,isthumb,filelen) :: tmp;
	cachelist = tmp;
}


cachedel(dosname1: array of byte, isthumb: int)
{
	dosname := sconv(dosname1);
	tmp : list of (string, int,int);
	tmp = nil;
	while (cachelist != nil) {
		(dosnm,it,filelen) := hd cachelist;
		if (dosname != dosnm || it != isthumb)
			tmp = (dosnm,it,filelen) :: tmp;
		cachelist = tl cachelist;
	}
	cachelist = tmp;
}

cachesize(dosname1: array of byte, isthumb: int): int
{
	dosname := sconv(dosname1);
	tmp := cachelist;
	while (tmp != nil) {
		(dosnm,it,filelen) := hd tmp;
		if (dosname == dosnm && isthumb == it) return filelen;
		tmp = tl tmp;
	}
	return -1;
}

cachename(dosname: array of byte, isthumb: int): string
{
	name := "/tmp/" + str->tolower(sconv(dosname));
	if (isthumb) name = jpg2bit(name);
	name[len name - 1] = '~';
	return name;
}

poll_and_wait(): int
{
	print("poll and wait\n",7);
	write_n(C.fd, pwl, len pwl);
#	sys->sleep(100);
	if (read_n_to(C.fd, pak, len pak,TIMEOUT) < 0) {
 		print("poll_and_wait: unexpected read failure, exiting...\n",1);
 		return -1;
	}
	return 0;
}

send_packet(): int
{
	# computing packet size
	to_write := C.bufbytes;

	# send the first packet
	pwl[0] = byte ((1<<5)|(1<<4)|(1<<3)|(1<<2)|(to_write>>8));
	pwl[1] = byte (to_write&16rff);

	if (poll_and_wait() != 0)
		return -1;
#	pak[1] == byte 2; ?
	pak[1] = byte 2;

	wrote_here := write_n(C.fd, C.cdp, to_write);
	if (wrote_here != to_write)
		return -1;
	return 0;
}

send_message(): int
{	
	v:= 0;
	rc := chan of int;
	tc := chan of int;

	spawn timer2(tc,6000);
	tpid := <- tc;
	spawn write_message(rc);
	rpid := <- rc;

	try := 0;
	alt {
		<- tc =>
			kill(rpid);
			print("error: write timeout\n",1);
			v = -2;
			break;
		v = <- rc =>
			kill(tpid);
			break;
	}
	return v;
}

write_message(rc: chan of int)
{	
	print("writing msg...\n",6);
	rc <- = sys->pctl(0, nil);	
	if (send_packet() != 0) {
		rc <-= -1;
		return;
	}
	pwl[0] = byte 0;
	pwl[1] = byte 0;
	wrote_here := write_n(C.fd, pwl, 2);
	if (wrote_here != 2) {
		rc <-= -1;
		return;
	}
	rc <-= 0;
	print("written\n",6);
}

extra: adt {
	pnum: int;
	offset: int;
	length: int;
	data: array of byte;
	isthumb: int;
};

ex : extra;

getthumb(photonum, offset, maxlength: int): array of byte
{
	if (offset != 0) return nil;
	print("getting thumbnail\n",3);
	thumbdata: array of byte;
	err, h, w, ttype: int;
	file := filelist[photonum].cf;
	filesize := 13020;
	if (offset > 0) {
		filesize = file.thumblength;
		if (offset >= filesize) return nil;
	}
	for(;;){
		print(sys->sprint("Filesize: %d offset: %d\n",filesize, offset),5);
		if (offset + maxlength > filesize)
			maxlength = filesize - offset;
		l := maxlength;
	
		C.command = cdp_get_file_data;
		C.bufbytes = build_cdp_header(C.cdp, 68);
		off := cdp_header_len;
		off = set_int(C.cdp[off:], file.driveno, off);
		off = set_fstring(C.cdp[off:], file.pathname, off);
		off = set_dosname(C.cdp[off:], file.dosname, off);
		off = set_int(C.cdp[off:], 1, off);
	
		off = set_int(C.cdp[off:], offset, off);
		off = set_int(C.cdp[off:], l, off);
		off = set_int(C.cdp[off:], filesize, off);
	
		print(sys->sprint( "getthumbdata %d %d %d\n", offset, maxlength, filesize),5);
		send_message();
#		sys->sleep(2000);
		if ((err = receive_message()) != 0) {
			print(sys->sprint("Error %d\n", err),1);
			return nil;
		}
		off = cdp_header_len;
		print(sys->sprint( "bufbytes  = %d\n", C.bufbytes),5);
		tmpoffset: int;
		(tmpoffset, off) = get_int(C.cdp[off:], off);
		(l, off) = get_int(C.cdp[off:], off);
		(filesize, off) = get_int(C.cdp[off:], off);
		print(sys->sprint( "getthumbdata returning %d %d %d\n", offset, l, filesize),5);
	
		if (offset == 0) {
			(filesize, off) = get_int(C.cdp[off:off+4], off);
			(h, off) = get_int(C.cdp[off:off+4], off);
			(w, off) = get_int(C.cdp[off:off+4], off);
			(ttype, off) = get_int(C.cdp[off:off+4], off);
			filelist[photonum].cf.thumblength = filesize;
			thumbdata = array[filesize] of byte;
			print(sys->sprint("Thumb (%d,%d) size: %d type: %d\n",w,h,filesize,ttype),5);
		}
		if (offset + l > filesize) l = filesize - offset;
		print(sys->sprint( "Making array of size: %d\n", l),5);
		thumbdata[offset:] = C.cdp[off:off+l];
		offset += l;
		if (offset >= filesize) break;
	}
	return thumb2bit(thumbdata,w,h);
}

getpicture2(photonum, offset, maxlength: int): array of byte
{
	file := filelist[photonum].cf;
	filesize := int file.filelength;
	print("getting image\n",3);
	print(sys->sprint("Filesize: %d offset: %d\n",filesize, offset),5);
	if (offset >= filesize) return nil;
	if (offset + maxlength > filesize)
		maxlength = filesize - offset;
	l := maxlength;
	C.command = cdp_get_file_data;
	C.bufbytes = build_cdp_header(C.cdp, 68);
	off := cdp_header_len;
	off = set_int(C.cdp[off:], file.driveno, off);
	off = set_fstring(C.cdp[off:], file.pathname, off);
	off = set_dosname(C.cdp[off:], file.dosname, off);
	off = set_int(C.cdp[off:], 0, off);

	off = set_int(C.cdp[off:], offset, off);
	off = set_int(C.cdp[off:], l, off);
	off = set_int(C.cdp[off:], filesize, off);

	print(sys->sprint( "getfiledata %d %d %d\n", offset, maxlength, filesize),5);
	send_message();
	if ((err := receive_message()) != 0) {
		print(sys->sprint("Error %d\n", err),1);
		return nil;
	}
	off = cdp_header_len;
	print(sys->sprint( "bufbytes  = %d\n", C.bufbytes),5);
	(offset, off) = get_int(C.cdp[off:], off);
	(l, off) = get_int(C.cdp[off:], off);
	(filesize, off) = get_int(C.cdp[off:], off);
	print(sys->sprint( "getfiledata returning %d %d %d\n", offset, maxlength, filesize),5);
	filedata := array[l] of byte;
	filedata[0:] = C.cdp[off:off+l];
	return filedata;
}

erase_file(file: Cfile): int
{
	C.command = cdp_erase_file;
	C.bufbytes = build_cdp_header(C.cdp, 52);
	
	off := cdp_header_len;
	off = set_int(C.cdp[off:], file.driveno, off);
	off = set_fstring(C.cdp[off:], file.pathname, off);
	off = set_dosname(C.cdp[off:], file.dosname, off);
	send_message();
#	sys->sleep(1000);
	if (receive_message() != 0)
		return -1;
	return 0;
}


set_power_mode(): int
{
	C.command = cdp_set_power_mode;
	C.bufbytes = build_cdp_header(C.cdp, 0);
	return (send_message());
}

get_storage_status(): string
{
	s := "";

	C.command = cdp_get_storage_status;
	C.bufbytes = build_cdp_header(C.cdp, 0);
	send_message();
#	sys->sleep(2000);
	if (receive_message() != 0) return "";
	off := cdp_header_len;
	taken, available, raw : int;
	(taken, off) = get_int(C.cdp[off:], off);
	(available, off) = get_int(C.cdp[off:], off);
	(raw, off) = get_int(C.cdp[off:], off);
	s += sys->sprint("Picture Memory\n\tused:\t%d\n\tfree:\t%d",taken,available);
	if (raw == -1)
		s += "\n";
	else
		s += sys->sprint(" (compressed)\n\t\t%d (raw)\n",raw);

	return s;
}

get_power_mode(): string
{
	mode: int;

	C.command = cdp_get_power_mode;
	C.bufbytes = build_cdp_header(C.cdp, 0);
	send_message();
#	sys->sleep(2000);
	if (receive_message() != 0) return "Could not read power mode";
	off := cdp_header_len;
	(mode, off) = get_int(C.cdp[off:], off);
	return sys->sprint("Power Mode = %d\n", mode);
}

set_clock_data(s:string): int
{
	err := 0;
	if (s == "") {
		tm := daytime->local(daytime->now());
		off := cdp_header_len;
		C.cdp[cdp_header_len+0] = byte 0;
		C.cdp[cdp_header_len+1] = byte int2hex(tm.mon+1);
		C.cdp[cdp_header_len+2] = byte int2hex(tm.mday);
		C.cdp[cdp_header_len+3] = byte int2hex(tm.year);
		C.cdp[cdp_header_len+4] = byte 0;
		C.cdp[cdp_header_len+5] = byte int2hex(tm.hour);
		C.cdp[cdp_header_len+6] = byte int2hex(tm.min);
		C.cdp[cdp_header_len+7] = byte int2hex(tm.sec);
	}
	else {
		(n,datetime) := sys->tokenize(s," ");
		if (n != 2) return 1;
		off := 0;
		for (i := 0; i < 2; i++) {
			(n2,data) := sys->tokenize(hd datetime, "./:");
			if (n2 != 3) return 1;
			off++;
			for (i2 := 0; i2 < 3; i2++) {
				C.cdp[cdp_header_len+off] = byte int2hex(int hd data);
				off++;
				data = tl data;
			}
			datetime = tl datetime;
		}
	}
	return 0;
}

set_clock(s:string): int
{
	C.command = cdp_set_clock;
	C.bufbytes = build_cdp_header(C.cdp, 8);
	if (set_clock_data(s)) return 1;
	send_message();
	if (receive_message() != 0) return 1;
	return 0;
}

addzeros(s: string): string
{
	s[len s] = ' ';
	rs := "";
	start := 0;
	isnum := 0;
	for (i := 0; i < len s; i++) {
		if (s[i] < '0' || s[i] > '9') {
			if (isnum && i - start < 2) rs[len rs] = '0';
			rs += s[start:i+1];
			start = i+1;
			isnum = 0;
		}
		else isnum = 1;
	}
	i = len rs - 1;
	while (i >= 0 && rs[i] == ' ') i--;
	return rs[:i+1];
}	

get_clock(): string
{
	C.command = cdp_get_clock;
	C.bufbytes = build_cdp_header(C.cdp, 0);
	send_message();
	if (receive_message() != 0)
		return "Could not read clock\n";
	s := sys->sprint("%x/%x/%x %x:%x:%x", int C.cdp[13],int C.cdp[14],
		int C.cdp[15], int C.cdp[17], int C.cdp[18], int C.cdp[19]);
	return "date is "+addzeros(s)+"\n";
}

get_file_list(): int
{
	getoldfiledata();
	print("getting file list\n",3);
	C.command = cdp_get_file_list;
	C.bufbytes = build_cdp_header(C.cdp, 56);
	setfiledata();
	send_message();
	if (receive_message() != 0)
		return -1;
	display_filelist();
	return 0;
}

setfiledata()
{
	off := cdp_header_len;
	off = set_int(C.cdp[off:], 1, off);						# ascending order
	off = set_int(C.cdp[off:], 1, off);						# drive a: internal RAM disk
	off = set_fstring(C.cdp[off:], array of byte "", off);		# set pathname to null
	off = set_dosname(C.cdp[off:], array of byte "", off);		# set Dos filename to null 
}

get_file_size(i: int): int
{
	C.command = cdp_get_file_list;
	C.bufbytes = build_cdp_header(C.cdp, 56);
	setfiledata2(i);
	send_message();
	if (receive_message() != 0) return -1;
	display_filelist();
	return 0;
}

setfiledata2(i: int)
{
	off := cdp_header_len;
	off = set_int(C.cdp[off:], 1, off);						# ascending order
	off = set_int(C.cdp[off:], 1, off);						# drive a: internal RAM disk
	off = set_fstring(C.cdp[off:], filelist[i].cf.pathname, off);	# set pathname
	off = set_dosname(C.cdp[off:], filelist[i].cf.dosname, off);	# set Dos filename
}

set_interface_timeout()
{
	print("Setting Interface timeout\n",3);
	C.command = cdp_set_interface_timeout;
	C.bufbytes = build_cdp_header(C.cdp, 8);
	off := cdp_header_len;
	off = set_int(C.cdp[off:], 100, off);
	off = set_int(C.cdp[off:], 5, off);
	send_message();
#	sys->sleep(1000);
	receive_message();
}

display_filelist(): string
{
	off, i: int;

	off = cdp_header_len;
	(reslength, off) = get_int(C.cdp[off:], off);
	s := sys->sprint("Number of entries: %d\n", reslength);
	for (i = 0; i < reslength; i++) {
		(filelist[i].cf.driveno, off) = get_int(C.cdp[off:], off);
		(filelist[i].cf.pathname, off) = get_fstring(C.cdp[off:], off);
		(filelist[i].cf.dosname, off) = get_dosname(C.cdp[off:], off);
		(filelist[i].cf.filelength, off) = get_int(C.cdp[off:], off);
		(filelist[i].cf.filestatus, off) = get_int(C.cdp[off:], off);
		if (filelist[i].cf.filelength < 0 || filelist[i].cf.filelength > MAXFILESIZE)
			filelist[i].cf.filelength = 0;
		s += sys->sprint("\t%d, %s, %s, %d\n", filelist[i].cf.driveno,
				string filelist[i].cf.pathname,
				string filelist[i].cf.dosname,
				filelist[i].cf.filelength);
	}
	print(s,5);
	if (usecache)
		cacheclean();
	return s;
}

get_camera_capabilities(): string
{
	print("Get capabilities\n",3);
	C.command = cdp_get_camera_capabilities;
	C.bufbytes = build_cdp_header(C.cdp, 0);
	send_message();
#	sys->sleep(500);
	if (receive_message() != -1)
		return capabilities();
	print("Error recieving abilities message\n",1);
	return "";
}

Capability: adt {
	pname: string;
	d: string;
	pick {
		List =>
			t: list of (string, int);
		Range =>
			min, max, default, current: int;
		}
};

caplist: list of ref Capability;

print_camera_capabilities(): string
{
	rs := "";
#	p : ref Capability;

	pick p := hd caplist{
	List =>
		rs += sys->sprint("Pname = %s ", p.pname);
	Range =>
		rs += sys->sprint("Pname = %s  min = %d  max = %d  default = %d ", p.pname, 
				p.min, p.max, p.default);
	}
#	p := tl p;
	return rs;
}

capabilities(): string
{
	off, i, ncaps, t: int;
	l, m, n: int;
	pname, desc: array of byte;
	s: array of byte;
	rs := "";
	off = cdp_header_len;
	(ncaps, off) = get_int(C.cdp[off:], off);
	if (ncaps > 200)
		return "error reading capabilities\n";
	rs += sys->sprint("i = %d\n", i);
	firsttime := 0;
	if (ignoreabls == nil)
		firsttime = 1;
	for (j := 0; j < ncaps; j++) {
		line := "";
		(pname, off) = get_pname(C.cdp[off:], off);
		line += sys->sprint("%s,  ", string pname);
		(t, off) = get_int(C.cdp[off:], off);
		(desc, off) = get_fstring(C.cdp[off:], off);
		line += sys->sprint("%s:  ", string desc);
		fact := "";
		case t {
			1 =>
				t: list of (string, int);

				(l, off) = get_int(C.cdp[off:], off);
				(m, off) = get_int(C.cdp[off:], off);
				line += sys->sprint("items: %d  factory: %d\n", l, m);

				for (k := 0; k < l; k++) {
					(s, off) = get_fstring(C.cdp[off:], off);
					(n, off) = get_int(C.cdp[off:], off);
					line += sys->sprint("		%s: %d\n", string s, n);
					if (m == n)
						fact = sconv(s);
					t = (sconv(s), n) :: t;
				}
				cl := ref Capability.List (sconv(pname), sconv(desc), t);
			2 =>
				(l, off) = get_int(C.cdp[off:], off);
				(m, off) = get_int(C.cdp[off:], off);
				(n, off) = get_int(C.cdp[off:], off);
				line += sys->sprint("min: %d   max: %d   factory:%d\n", l, m, n);
				fact = string n;
			3 =>
				(l, off) = get_int(C.cdp[off:], off);
				case l {
					7 =>
						(s, off) = get_dosname(C.cdp[off:], off);
					8 =>
						(s, off) = get_fstring(C.cdp[off:], off);
					* =>
						line += sys->sprint("Invalid type %d\n", l);
						break;
				}
				fact = string s;
				line += sys->sprint("%s\n", string s);
			4 to 8 =>
				break;
			9 =>
				break;
			* =>
				line += sys->sprint("Invalid type %d\n", t);
				break;
		}
		if (firsttime) {
			if (!filterabls(sconv(pname), string desc))
				defattr = (sconv(pname), int fact) :: defattr;
		}
		if (!isin(ignoreabls, string pname))
			rs += line;
	}
	if (firsttime) {
		defaultattr = array[len defattr] of (string, int);
		currentattr = array[len defattr] of (string, int);
		i = 0;
		for (;defattr != nil; defattr = tl defattr) {
			defaultattr[i] = hd defattr;
			currentattr[i++] = hd defattr;
		}
	}
	return rs;
}

isin(los: list of string, s: string): int
{
	for (;los !=nil; los = tl los)
		if (hd los == s)
			return 1;
	return 0;
}

set_capture_data(): int
{
	C.cdp[cdp_header_len+0] = byte 0;
	C.cdp[cdp_header_len+1] = byte 0;
	C.cdp[cdp_header_len+2] = byte 0;
	C.cdp[cdp_header_len+3] = byte 0;
	return 4;
}

get_camera_state(pname: string,offset: int): string
{
	if (offset != 0) return "";
	print(sys->sprint( "get_camera_state(%s)\n", pname),3);
	C.command = cdp_get_camera_state;
	off := cdp_header_len;
	if (pname == "")
		C.bufbytes = build_cdp_header(C.cdp, 0);
	else {
		if (len pname != 4)
			return "Invalid command name: "+pname+"\n";
		C.cdp[off+0] = byte pname[0];
		C.cdp[off+1] = byte pname[1];
		C.cdp[off+2] = byte pname[2];
		C.cdp[off+3] = byte pname[3];
		C.bufbytes = build_cdp_header(C.cdp, 4);
	}
	send_message();
	if (receive_message() != 0) return "Could not read state: "+pname+"\n";
	off = cdp_header_len;
	rlen: int;
	(rlen, off) = get_int(C.cdp[off:],off);
	s := "";
	rlen = 1;
	if (pname == "") {
		for (q := off; q < len C.cdp; q++) {
			s[0] = int C.cdp[q];
			if (s[0] > 0) print(sys->sprint("%s",s),5);
		}
		print("\n",5);
	}
	for (i := 0; i < rlen; i++) {
		name, data: array of byte;
		type1, tmp: int;
		(name,off) = get_pname(C.cdp[off:],off);
		(type1,off) = get_int(C.cdp[off:],off);
		print(sys->sprint( "%d: %s - %d\n", i,pname,type1),5);
		case type1 {
			1 to 5 =>
				(tmp,off) = get_int(C.cdp[off:],off);
				data = array of byte string tmp;
			6 =>
				(data,off) = get_pname(C.cdp[off:],off);
			7 =>
				(data,off) = get_dosname(C.cdp[off:],off);
			8 =>
				(data,off) = get_fstring(C.cdp[off:],off);
			* =>
				data = array of byte "!ERROR!";
		}
		# if (string data == "!ERROR!") return "";
#		if (rlen == 1)
#			s = string data;
#		else s += sys->sprint("%s: %s\n",string name, string data);
		s += sys->sprint("%s: %s\n",string name, string data);
	}
	return s;
}


set_camera_state(pname: string, val: int): string
{
	print(sys->sprint( "set_camera_state(%s, %d)\n", pname, val),3);
	if (len pname != 4)
		return "Command name must be 4 characters";
	off := cdp_header_len;
	C.cdp[off+0] = byte pname[0];
	C.cdp[off+1] = byte pname[1];
	C.cdp[off+2] = byte pname[2];
	C.cdp[off+3] = byte pname[3];
	off += 4;
	off = set_int(C.cdp[off:], val, off);

	C.command = cdp_set_camera_state;
	C.bufbytes = build_cdp_header(C.cdp, 8);
	send_message();
#	sys->sleep(1000);
	if ((e := receive_message()) == 0) {
		na := getattr(pname);
		if (na != -1)
			currentattr[na].t1 =  val;
		return nil;
	}
	else
		return error_table[e];
}

capture(): int
{
	C.command = cdp_get_camera_status;
	C.bufbytes = build_cdp_header(C.cdp, 0);
	send_message();
#	sys->sleep(1000);
	if (receive_message() != 0)
		return -1;

	d := set_capture_data();
	C.command = cdp_start_capture;
	C.bufbytes = build_cdp_header(C.cdp, d);
	send_message();
#	sys->sleep(3000);
	return receive_message();
}

dump_message()
{
	print(sys->sprint("	Message length = %d\n", C.bufbytes),5);
	print(sys->sprint("	CDP Length = %d\n", (int C.cdp[2]<<8)+(int C.cdp[3])),5);
	print(sys->sprint("	CDP Version = %d\n", int C.cdp[4]),5);
	print(sys->sprint("	CDP Command = %x\n", int ((C.cdp[8]<<8)|(C.cdp[9]))),5);
	print(sys->sprint("	CDP Result Code = %d\n", int ((C.cdp[10]<<8)|(C.cdp[11]))),5);
}

build_cdp_header(cdp: array of byte, x: int): int
{
	cdp[4] = byte 0;
	cdp[5] = byte 0;
	cdp[6] = byte 0;
	cdp[7] = byte 0;
	cdp[8] = byte ((C.command>>8)&16rff);
	cdp[9] = byte (C.command&16rff);
	cdp[10] = byte 0;
	cdp[11] = byte 0;

	l := 8 + x;
	cdp[0] = byte ((l>>24)&16rff);
	cdp[1] = byte ((l>>16)&16rff);
	cdp[2] = byte ((l>>8)&16rff);
	cdp[3] = byte (l&16rff);

	return 12+x;
}

poll_and_reply(nak: int): int
{
	print("poll and reply\n",7);
	if ((read_n_to(C.fd, pwl, len pwl,TIMEOUT) < 0) && nak) {
		pak[0] = byte 0;	
		pak[1] = byte 2;		# reject
		write_n(C.fd, pak, len pak);
		return 0;
	}
	pak[0] = byte 0;
	pak[1] = byte 1;
	write_n(C.fd, pak, len pak);

	return 1;
}

receive_packet(buf: array of byte): int
{
	print("receive_packet\n",6);
	if (!poll_and_reply(!0)) {
		print("Poll and reply failed\n",1);
		return -1;
	}

	l := int (((int pwl[0]&3)<<8)|(int pwl[1]));
	C.bufbytes += l;
	r := read_n_to(C.fd, buf, l,TIMEOUT);
	if (r != l) {
		print(sys->sprint( "could not read packet (read %d, expected %d)\n", r, l),1);
		return -1;
	}
	return 0;
}

receive_message(): int
{
	print("read_message\n",6);
	C.bufbytes = 0;
	if (receive_packet(C.cdp[0:]) != 0) {
		recon = 1;
		print("receive packet failed\n",1);
		return 3;
		# raise "error: receive packet failed";
	}
	dump_message();	
	rc := int C.cdp[9];
	if ((~rc&16rff) != (C.command&16rff)) {
		print("command & return are different\n",1);
		consume(C.fd);
		return 3;
		# raise "error: command and return command are not the same\n";
	}
	message_len := (int C.cdp[2]<<8)+(int C.cdp[3]);

	while (C.bufbytes < message_len) {
		if (receive_packet(C.cdp[C.bufbytes:]) != 0) {
			print("Packet is too short\n",1);
			recon = 1;
			return 3;
			# raise "error: receive packet2 failed";
		}
	}
#	sys->sleep(500);
	read_n_to(C.fd, pak, len pak, TIMEOUT);
	return  (int ((C.cdp[10]<<8)|(C.cdp[11])));  # result code
}

reset(fd: ref Sys->FD)
{
	sys->fprint(fd, "d1");
	sys->sleep(20);
	sys->fprint(fd, "d0");
	sys->fprint(fd, "b9600");
}

kill(pid: int)
{	
	pctl := sys->open("/prog/" + string pid + "/ctl", Sys->OWRITE);
	if (pctl != nil)
		sys->write(pctl, array of byte "kill", len "kill");
}

killg(pid: int)
{
	if ((fd := sys->open("/prog/" + string pid + "/ctl", Sys->OWRITE)) != nil)
		sys->fprint(fd, "killgrp");
}

#dump_buf(buf: array of byte, i: int)
#{
#	for (j := 0; j < i; j++)
#		sys->fprint(sys->fildes(2), "%x ", int buf[j]);
#	sys->fprint(sys->fildes(2), "\n");
#}

serialport(port : int) : (ref Sys->FD, ref Sys->FD, string)
{
	C.fd = nil;
	C.ctlfd = nil;
	C.mode = BEACON;

	serport := "/dev/eia" + string port;
	serctl := serport + "ctl";

	for (i := 0; i < len statopt; i++) {
		statfd := sys->open("/dev/eia"+string port+statopt[i],sys->OREAD);
		if (statfd != nil)
			C.stat = i;
		statfd = nil;
	}
	readstat();	

	fd := sys->open(serport, Sys->ORDWR);
	if (fd == nil)
		return (nil, nil, sys->sprint("cannot read %s: %r", serport));
	ctlfd := sys->open(serctl, Sys->OWRITE);
	if (ctlfd == nil)
		return (nil, nil, sys->sprint("cannot open %s: %r", serctl));

	config := array [] of {
		"b9600",
		"l8",
		"p0",
		"m0",
		"s1",
		"r1",
		"i1",
		"f",
	};

	for (i = 0; i < len config; i++) {
		if (sys->fprint(ctlfd,"%s", config[i]) < 0)
			print(sys->sprint("serial config (%s): %r\n", config[i]),3);
	}
	sys->sleep(100);
	consume(fd);
	sys->fprint(ctlfd, "d1");
	sys->sleep(40);
	sys->fprint(ctlfd, "d0");
	return (fd, ctlfd, nil);
}

consume(fd: ref sys->FD)
{
	if (fd != nil) {
		print("Consuming...\n",6);
		read_n_to(fd, array[1000] of byte, 1000, 1000);
	}
}

beacon_intro(data: chan of array of byte, pchan: chan of int, fd: ref Sys->FD)
{
	buf := array[64] of byte;
	cbuf: array of byte;
	pid := sys->pctl(0, nil);
#	print(sys->sprint("b_intro: starting %d\n",pid);
	pchan <-= pid;
	failed := array[len bintro] of { * => byte 0 };
	# discard characters until lead in character reached
	print(sys->sprint("\tWaiting for: %d...\n",int bintro[0]),3);
	do {
		n := read_n_to(fd, buf, 1, TIMEOUT);
		if (n == -1) {
			data <- = failed;
			return;
		}
		print(sys->sprint("\tGot: %d\n",int buf[0]),5);
	} while (buf[0] != bintro[0]);
	print("Getting beacon\n",3);
	# read the next 6 bytes of beacon
	i := read_n_to(fd, buf[1:], 6,TIMEOUT);
	for (k := 0; k < i; k++) 
		print(sys->sprint("\tRead %d: %d (wanted %d)\n",k+1, int buf[1+k], int bintro[1+k]),5);
	if (i != 6) {
		print("Error reading beacon\n",3);
		exit;
	}
	else {
		print("sending beacon\n",3);
		cbuf = buf[0:7];
		data <- = cbuf;	
	}

}

beacon_result(data: chan of array of byte, pchan: chan of int, fd: ref Sys->FD)
{
	buf := array[64] of byte;
	cbuf: array of byte;
	pid := sys->pctl(0, nil);
	pchan <-= pid;

	# read the next 10 bytes of beacon
	p := 0;
	intro := 1;
	for (;;) {
		i := read_n_to(fd, buf[p:], 1, TIMEOUT);
		if (intro) {
			if (buf[p] != bintro[p]) {
				intro = 0;
				buf[0] = buf[p];
				p = 1;
			}
			else {
				p++;
				if (p >= len bintro) p = 0;
			}
		}
		else p++;
		if (p == 10) break;
	}
			
	for (k := 0; k < p; k++) print(sys->sprint("\tRead %d: %d\n",k, int buf[k]),5);
	if (p != 10) {
		print("Error reading beacon result\n",3);
		exit;
	}
	else {
		print("reading beacon result\n",3);
		cbuf = buf[0:10];
		data <- = cbuf;	
	}
}

beacon_comp(buf: array of byte, C: Camera_adt): int
{
	speed: string;

	case int buf[0] {
		0 =>
			C.baud = (int buf[2]<<24)|(int buf[3]<<16)|(int buf[4]<<8)|(int buf[5]);
			C.dfs = (int buf[6]<<8)|(int buf[7]);
			C.hfs = (int buf[8]<<8)|(int buf[9]);
			# do baud rate change here
			sys->sleep(1000);

			case C.baud {
				115200 =>
					speed = "b115200";
				57600 =>
					speed = "b57600";
				38400 =>
					speed = "b38400";
				19200 =>
					speed = "b19200";
				* =>
					speed = "b9600";
			}
			print(sys->sprint("Connection Details:\n  Baud rate:\t%dbps\n",C.baud),3);
			print(sys->sprint("  Host frame size:\t%dbytes\n",C.hfs),3);
			print(sys->sprint("  Device frame size:\t%dbytes\n",C.dfs),3);
			if (sys->fprint(C.ctlfd,"%s", speed) < 0) {
				print(sys->sprint("Error setting baud rate %s\n", speed),3);
				return -1;
			}
		-1 =>
			print("Incompatible Data Rate\n",1);
			return -1;
		-2 =>
			print("Device does not support these modes\n",1);
			return -2;
		* =>
			print(sys->sprint("I'm here!? buf[0] = %d\n",int buf[0]),1);
			return -1;
	}
	return 0;
}

read_n(fd: ref Sys->FD, buf: array of byte, n: int, res: chan of int)
{
	pid := sys->pctl(0, nil);
#	print(sys->sprint("read_n: starting %d\n",pid);
	res <-= pid;
	print(sys->sprint( "read_n %d\n", n),7);
	nread := 0;
	while (nread < n) {
		i := sys->read(fd, buf[nread:], n-nread);
		sys->sleep(1);
		if (i <= 0) 
			break;
		nread += i;
	}
	res <-= nread;
#	print(sys->sprint("read_n: ending %d\n",pid);
}

read_n2(fd: ref Sys->FD, buf: array of byte, n: int): int
{
	print(sys->sprint( "read_n2 %d\n", n),7);
	nread := 0;
	while (nread < n) {
		i := sys->read(fd, buf[nread:], n-nread);
		sys->sleep(1);
		if (i <= 0) 
			break;
		nread += i;
	}
	return nread;
}

read_n_to(fd: ref Sys->FD, buf: array of byte, n,t : int): int
{	
	v:= 0;
	rc := chan of int;
	tc := chan of int;

	spawn timer2(tc,t);
	tpid := <- tc;
	spawn read_n(fd, buf, n, rc);
	rpid := <- rc;

	try := 0;
	alt {
		<- tc =>
			kill(rpid);
			print(sys->sprint( "error: read_n timeout\n"),1);
			recon = 1;
			return -1;
		v = <- rc =>
			kill(tpid);
			break;
	}
	return v;
}

write_n(fd: ref Sys->FD, buf: array of byte, n: int): int
{
	print(sys->sprint("write_n %d\n", n),7);
	nwrite := 0;
	while (nwrite < n) {
		i := sys->write(fd, buf[nwrite:], n-nwrite);
		sys->sleep(1);
		if (i <= 0) {
			print(sys->sprint("Error returned by write: %r\n"),1);
			readstat();
#			recon = 1;
			return nwrite;
		}
		nwrite += i;
	}
	print(sys->sprint("write_n returning %d\n", nwrite),7);
	return nwrite;	
}

readstat()
{
	consume(C.fd);
	print("Serial status: ",5);
	statfd := sys->open("/dev/eia"+string C.port_num+statopt[C.stat], sys->OREAD);
	buf := array[100] of byte;
	if (statfd != nil) {
		for (;;) {
			k := sys->read(statfd,buf,len buf);
			if (k > 0) print(string buf[:k],2);
			else break;
		}
		print("\n",2);
	}
	else print("cannot read serial status\n",1);
}

beacon_ack(C: Camera_adt)
{
	# set speed
	i := C.baud;
	bak[4] = byte ((i>>24)&16rff);
	bak[5] = byte ((i>>16)&16rff);
	bak[6] = byte ((i>>8)&16rff);
	bak[7] = byte (i&16rff);

	# set frame size to device
	i = C.dfs;
	bak[8] = byte ((i>>8)&16rff);
	bak[9] = byte (i&16rff);

	# set frame size to host
	i = C.hfs;
	bak[10] = byte ((i>>8)&16rff);
	bak[11] = byte (i&16rff);
	bak[12] = check_sum(bak, 12);

	if (write_n(C.fd, bak, len bak) != len bak) {
		print("Error writing beacon acknowledgement\n",3);
		exit;
	}
	print("beacon acknowledgement written\n",3);
}

# timer thread send tick <- = 0 to kill

timer2(tick: chan of int, delay: int)
{
	pid := sys->pctl(0, nil);
	tick <-= pid;
	sys->sleep(delay);
	tick <- = TOUT;
}

beacon_ok(buf: array of byte): int
{

	for (i := 0; i < len bintro; i++) {
		if (buf[i] != bintro[i]) {
			print(sys->sprint("Beacon failed on byte %d: %d (wanted %d)\n",i,int buf[i],int bintro[i]),3);
			return 0;
		}
	}
	print("Beacon passed\n",3);
	return 1;
}

check_sum(buf: array of byte, l: int): byte
{
	sum := 0;
 	for (i := 0; i < l; i++) 
		sum += int buf[i];
  	return byte (sum&16rff);
}


set_int(b: array of byte, i, off: int): int
{
	b[0] = byte (i>>24&16rff);
	b[1] = byte (i>>16&16rff);
	b[2] = byte (i>>8&16rff);
	b[3] = byte (i&16rff);

	return (off+4);
}

set_fstring(b: array of byte, s: array of byte, off: int): int
{
	for (i := 0; i < 32; i++)
		b[i] = byte 0;
	for (i = 0; i < len s; i++)
		b[i] = s[i];
	return (off+32);
}

set_dosname(b: array of byte, s: array of byte, off: int): int
{
	for (i := 0; i < 16; i++)
		b[i] = byte 0;
	for (i = 0; i < len s; i++)
		b[i] = s[i];
	return (off+16);
}

get_tag(b: array of byte, off: int): (int, Partialtag)
{
	tag: Partialtag;
	(off, tag.offset) = get_int(b, off);
	(off, tag.length) = get_int(b, off);
	(off, tag.filesize) = get_int(b, off);
	return (off, tag);
}

get_int(b: array of byte, off: int): (int, int)
{
	return (get_int2(b), off+4);
}

get_int2(b: array of byte): int
{
	i := (int b[0]<<24)|(int b[1]<<16)|(int b[2]<<8)|(int b[3]);
	return i;
}


get_pname(b: array of byte, off: int): (array of byte, int)
{
	return get_string(b, off, 4);
}

get_dosname(b: array of byte, off: int): (array of byte, int)
{
	return get_string(b, off, 16);
}

get_string(b: array of byte, off: int, l: int): (array of byte, int)
{
	s := array[l] of byte;
	s[0:] = b[0:l];
	return (s, off+l);
}

get_fstring(b: array of byte, off: int): (array of byte, int)
{
	return get_string(b, off, 32);
}

sconv(b: array of byte): string
{
	s := string b;
	i := len s-1;
	while (i >= 0 && s[i] == 0)
		i--;
	return s[0:i+1];
}

name2dos(s: string): array of byte
{
	return array of byte str->toupper(s);
}

getqid(i, ftype: int): int
{
	qid := (i<<4) + ftype;
	return qid;
}

gettype(qid: int): int
{
	ftype := qid & 15;
	return ftype;
}

cutdir(ab:array of byte): string
{
	s := sconv(ab);
	for (i := 0; i < len s-1; i++)
		if (s[i] == '/')
			return s[i+1:len s - 1];
	return "";
}

convert_thumb(w,h: int, data: array of byte): array of byte
{
	rgb := array[w * h * 3] of byte;
	index := 0;
	rgbi := 0;
	for (i := 0; i < (w * h) / 2; i++) {

		cb := real data[index];
		y := real data[index+1];
		cr := real data[index+2];

		rb := conv(y + (1.77200 * (cb - 128.0)));
		gb := conv(y - (0.34414 * (cb - 128.0)) - (0.71414 * (cr - 128.0)));
		bb := conv(y + (1.4020 * (cr - 128.0)));

		for (loop := 0; loop < 2; loop++) {
			rgb[rgbi++] = rb;
			rgb[rgbi++] = gb;
			rgb[rgbi++] = bb;
		}
		index += 4;
	}
	return rgb;
}

conv(a: real): byte
{
	r := int a;
	if (r < 0) r = -r;
	if (r > 255) r = 255;
	return byte r;
}

thumb2bit(buf: array of byte, w,h: int):  array of byte
{
	convbuf := convert_thumb(w,h,buf);
	# assume thumbs are small so we wont gain much by compressing them
	bitarray := array [60+len convbuf] of byte;
	# assume chans = RGB24
	bitarray[:] = array of byte sys->sprint("%11s %11d %11d %11d %11d ", "r8g8b8", 0, 0, w, h);
	bitarray[60:] = convbuf;
	return bitarray;
}

jpg2bit(s: string): string
{
	if (len s < 4) return s;
	if (s[len s - 4:] != ".jpg") return s;
	return s[:len s - 4]+".bit";
}

oldfiles : list of (string, int, int);

getoldfiledata()
{
	oldfiles = nil;
	for(i := 0; i < reslength; i++)
		oldfiles = (str->tolower(sconv(filelist[i].cf.dosname)),
				int filelist[i].qid.path,
				filelist[i].cf.thumbqid) :: oldfiles;
}

updatetree(tree: ref Nametree->Tree)
{
	for (i := 0; i < reslength; i++) {
		name := str->tolower(sconv(filelist[i].cf.dosname));
		found := 0;
		tmp : list of (string, int, int) = nil;
		for (; oldfiles != nil; oldfiles = tl oldfiles) {
			(oldname, oldqid, oldthumbqid) := hd oldfiles;
			# sys->print("'%s' == '%s'?\n",name,oldname);
			if (name == oldname) {
				found = 1;	
				filelist[i].qid = (big oldqid, 0, sys->QTFILE);
				filelist[i].cf.thumbqid = oldthumbqid;
			}
			else
				tmp = hd oldfiles :: tmp;
		}
		oldfiles = tmp;
		# sys->print("len oldfiles: %d\n",len oldfiles);
		if (found)
			updateintree(tree, name, i);
		else
			addtotree(tree, name, i);
		
	}
	for (; oldfiles != nil; oldfiles = tl oldfiles) {
		(oldname, oldqid, oldthumbqid) := hd oldfiles;
		# sys->print("remove from tree: %s\n",oldname);
		tree.remove(big oldqid);
		tree.remove(big oldthumbqid);
	}
}

updateintree(tree: ref Nametree->Tree, name: string, i: int)
{
	# sys->print("update tree: %s\n",name);
	tree.wstat(filelist[i].qid.path, 
			dir(name, 
				8r444,
				filelist[i].cf.filelength,
				int filelist[i].qid.path));
	tree.wstat(big filelist[i].cf.thumbqid,
			dir(jpg2bit(name),
				8r444,
				13020,
				filelist[i].cf.thumbqid));
}

addtotree(tree: ref Nametree->Tree, name: string, i: int)
{
	# sys->print("addtotree: %s\n",name);
	nextjpgqid += 1<<4;
	filelist[i].qid = (big nextjpgqid, 0, sys->QTFILE);
	parentqid := Qjpgdir;
	tree.create(big parentqid,
				dir(name,
				8r444,
				filelist[i].cf.filelength,
				nextjpgqid));

	nexttmbqid += 1<<4;
	filelist[i].cf.thumbqid = nexttmbqid;
	tree.create(big Qthumbdir,
			dir(jpg2bit(name),
			8r444,
			13020,
			nexttmbqid));
}

keepalive(alivechan: chan of int)
{	
	alivechan <-= sys->pctl(0,nil);
	for (;;) {
		sys->sleep(300000);
		now := daytime->now();
		print(sys->sprint("Alive: %d idle seconds\n",now-wait),6);
		if (now < wait)
			wait = now - 300;
		if (now - wait >= 300)
			alivechan <-= 1;
	}
}

reconnect(n: int): int
{
	attempt := 0;
	connected = 0;
	delay := 100;
	to5 := 0;
	for (;;) {
		print(sys->sprint( "Attempting to reconnect (attempt %d)\n",++attempt),2);
		sys->sleep(100);
		(C.fd, C.ctlfd, nil) = serialport(C.port_num);
		if (C.fd == nil || C.ctlfd == nil)
			print(sys->sprint("Could not open serial port\n"),3);
		else if (connect() == 0) {
			set_interface_timeout();
			connected = 1;
			print("Reconnected!\n",2);
			break;
		}
		if (n != -1 && attempt >= n)
			break;
		if (++to5 >= 5) {
			delay  *= 2;
			to5 = 0;
			if (delay > 600000)
				delay = 600000;
		}
		sys->sleep(delay);
	}
	recon = 0;
	return connected;
}

# 1: errors
# 2: connection
# 3: main procs

print(s: string, v: int)
{
	if (s != nil && s[len s - 1] == '\n')
		s = s[:len s - 1];
	if (v <= verbosity)
		sys->fprint(sys->fildes(2), "%s (%s)\n",s,camname);
}

readinterface(qid : int, offset: big, size: int): (ref sys->Dir, array of byte)
{
	i := qid >> 4;
	buf := array[size] of byte;
	fd := sys->open(interfacepaths[i], sys->OREAD);
	if (fd == nil)
		return (nil,nil);
	(n, dir) := sys->fstat(fd);
	if (offset >= dir.length)
		return (nil,nil);
	sys->seek(fd, offset, sys->SEEKSTART);
	i = sys->read(fd,buf,size);
	return (ref dir, buf[:i]);
}

readfile(f: string): string
{
	fd := sys->open(f, sys->OREAD);
	if(fd == nil)
		return nil;

	buf := array[8192] of byte;
	n := sys->read(fd, buf, len buf);
	if(n < 0)
		return nil;

	return string buf[0:n];	
}