xref: /inferno-os/appl/cmd/rawdbfs.b (revision ad4c862fd80d3ad38a6464a9ea169a78354a77fc)
1implement Dbfs;
2
3#
4# Copyright © 1999, 2002 Vita Nuova Limited.  All rights reserved.
5#
6
7# Enhanced to include record locking, index field generation and update notification
8
9# TO DO:
10#	make writing & reading more like real files; don't ignore offsets.
11#	open with OTRUNC should work.
12#	provide some way of compacting a dbfs file.
13
14include "sys.m";
15	sys: Sys;
16	Qid: import Sys;
17
18include "draw.m";
19
20include "arg.m";
21
22include "styx.m";
23	styx: Styx;
24	Rmsg, Tmsg: import styx;
25
26include "styxservers.m";
27	styxservers: Styxservers;
28	Styxserver, Fid, Navigator, Navop: import styxservers;
29	Enotfound, Eperm, Ebadfid, Ebadarg: import styxservers;
30
31include "string.m";
32	str: String;
33
34include "bufio.m";
35	bufio: Bufio;
36	Iobuf: import bufio;
37
38include "sh.m";
39	sh: Sh;
40
41Record: adt {
42	id:		int;			# file number in directory (if block is allocated)
43	offset:	int;			# start of data
44	count:	int;			# length of block (excluding header)
45	datalen:	int;			# length of data (-1 if block is free)
46	vers:		int;			# version
47
48	new:		fn(offset: int, length: int): ref Record;
49	qid:		fn(r: self ref Record): Sys->Qid;
50};
51
52# Record lock
53Lock: adt {
54	qpath: big;
55	fid:	int;
56};
57
58HEADLEN: con 10;
59MINSIZE: con 20;
60
61Database: adt {
62	file:		ref Iobuf;
63	records:	array of ref Record;
64	maxid:	int;
65	locking:	int;
66	locklist:	list of Lock;
67	indexing:	int;
68	stats:	int;
69	index:	int;
70	s_reads:	int;
71	s_writes:	int;
72	s_creates:	int;
73	s_removes:	int;
74	updcmd:	string;
75	vers:		int;
76
77	build:	fn(f: ref Iobuf, locking, indexing: int, stats: int, updcmd: string): (ref Database, string);
78	write:	fn(db: self ref Database, n: int, data: array of byte): int;
79	read:		fn(db: self ref Database, n: int): array of byte;
80	remove:	fn(db: self ref Database, n: int);
81	create:	fn(db: self ref Database, data: array of byte): ref Record;
82	updated:	fn(db: self ref Database);
83	lock:		fn(db: self ref Database, c: ref Styxservers->Fid): int;
84	unlock:	fn(db: self ref Database, c: ref Styxservers->Fid);
85	ownlock:	fn(db: self ref Database, c: ref Styxservers->Fid): int;
86};
87
88Dbfs: module
89{
90	init:	fn(ctxt: ref Draw->Context, nil: list of string);
91};
92
93Qdir, Qnew, Qdata, Qindex, Qstats: con iota;
94
95stderr: ref Sys->FD;
96database: ref Database;
97context: ref Draw->Context;
98user: string;
99Eremoved: con "file removed";
100Egreg: con "thermal problems";
101Elocked: con "open/create -- file is locked";
102
103usage()
104{
105	sys->fprint(stderr, "Usage: dbfs [-abcelrxD][-u cmd] file mountpoint\n");
106	raise "fail:usage";
107}
108
109nomod(s: string)
110{
111	sys->fprint(stderr, "dbfs: can't load %s: %r\n", s);
112	raise "fail:load";
113}
114
115init(ctxt: ref Draw->Context, args: list of string)
116{
117	sys = load Sys Sys->PATH;
118	stderr = sys->fildes(2);
119	context = ctxt;
120	sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil);
121	styx = load Styx Styx->PATH;
122	if(styx == nil)
123		nomod(Styx->PATH);
124	styx->init();
125	styxservers = load Styxservers Styxservers->PATH;
126	if(styxservers == nil)
127		nomod(Styxservers->PATH);
128	styxservers->init(styx);
129	str = load String String->PATH;
130	if(str == nil)
131		nomod(String->PATH);
132	bufio = load Bufio Bufio->PATH;
133	if(bufio == nil)
134		nomod(Bufio->PATH);
135
136	arg := load Arg Arg->PATH;
137	if(arg == nil)
138		nomod(Arg->PATH);
139	arg->init(args);
140	flags := Sys->MREPL;
141	copt := 0;
142	empty := 0;
143	locking := 0;
144	stats := 0;
145	indexing := 0;
146	updcmd := "";
147	while((o := arg->opt()) != 0)
148		case o {
149		'a' =>	flags = Sys->MAFTER;
150		'b' =>	flags = Sys->MBEFORE;
151		'r' =>		flags = Sys->MREPL;
152		'c' =>	copt = 1;
153		'e' =>	empty = 1;
154		'l' =>		locking = 1;
155		'u' =>	updcmd = arg->arg();
156				if(updcmd == nil)
157					usage();
158		'x' =>	indexing = 1;
159				stats = 1;
160		'D' =>	styxservers->traceset(1);
161		* =>		usage();
162		}
163	args = arg->argv();
164	arg = nil;
165
166	if(len args != 2)
167		usage();
168	if(copt)
169		flags |= Sys->MCREATE;
170	file := hd args;
171	args = tl args;
172	mountpt := hd args;
173
174	if(updcmd != nil){
175		sh = load Sh Sh->PATH;
176		if(sh == nil)
177			nomod(Sh->PATH);
178	}
179
180	df := bufio->open(file, Sys->ORDWR);
181	if(df == nil && empty){
182		(rc, nil) := sys->stat(file);
183		if(rc < 0)
184			df = bufio->create(file, Sys->ORDWR, 8r600);
185	}
186	if(df == nil){
187		sys->fprint(stderr, "dbfs: can't open %s: %r\n", file);
188		raise "fail:cannot open file";
189	}
190	(db, err) := Database.build(df, locking, indexing, stats, updcmd);
191	if(db == nil){
192		sys->fprint(stderr, "dbfs: can't read %s: %s\n", file, err);
193		raise "fail:cannot read db";
194	}
195	database = db;
196
197	sys->pctl(Sys->FORKFD, nil);
198
199	user = rf("/dev/user");
200	if(user == nil)
201		user = "inferno";
202
203	fds := array[2] of ref Sys->FD;
204	if(sys->pipe(fds) < 0){
205		sys->fprint(stderr, "dbfs: can't create pipe: %r\n");
206		raise "fail:pipe";
207	}
208
209	navops := chan of ref Navop;
210	spawn navigator(navops);
211
212	(tchan, srv) := Styxserver.new(fds[0], Navigator.new(navops), big Qdir);
213	fds[0] = nil;
214
215	pidc := chan of int;
216	spawn serveloop(tchan, srv, pidc, navops);
217	<-pidc;
218
219	if(sys->mount(fds[1], nil, mountpt, flags, nil) < 0) {
220		sys->fprint(stderr, "dbfs: mount failed: %r\n");
221		raise "fail:bad mount";
222	}
223}
224
225rf(f: string): string
226{
227	fd := sys->open(f, Sys->OREAD);
228	if(fd == nil)
229		return nil;
230	b := array[Sys->NAMEMAX] of byte;
231	n := sys->read(fd, b, len b);
232	if(n < 0)
233		return nil;
234	return string b[0:n];
235}
236
237serveloop(tchan: chan of ref Tmsg, srv: ref Styxserver, pidc: chan of int, navops: chan of ref Navop)
238{
239	pidc <-= sys->pctl(Sys->FORKNS|Sys->NEWFD, stderr.fd::1::2::database.file.fd.fd::srv.fd.fd::nil);
240#	stderr = sys->fildes(stderr.fd);
241	database.file.fd = sys->fildes(database.file.fd.fd);
242Serve:
243	while((gm := <-tchan) != nil){
244		pick m := gm {
245		Readerror =>
246			sys->fprint(stderr, "dbfs: fatal read error: %s\n", m.error);
247			break Serve;
248		Open =>
249			open(srv, m);
250		Read =>
251			(c, err) := srv.canread(m);
252			if(c == nil) {
253				srv.reply(ref Rmsg.Error(m.tag, err));
254				break;
255			}
256			if(c.qtype & Sys->QTDIR){
257				srv.read(m);
258				break;
259			}
260			case TYPE(c.path) {
261			Qindex =>
262				if(database.index < 0) {
263					srv.reply(ref Rmsg.Error(m.tag, Eperm));
264					break;
265				}
266				if (m.offset > big 0) {
267					srv.reply(ref Rmsg.Read(m.tag, nil));
268					break;
269				}
270				reply := array of byte string ++database.index;
271				if(m.count < len reply)
272					reply = reply[:m.count];
273				srv.reply(ref Rmsg.Read(m.tag, reply));
274			Qstats =>
275				if (m.offset > big 0) {
276					srv.reply(ref Rmsg.Read(m.tag, nil));
277					break;
278				}
279				reply := array of byte sys->sprint("%d %d %d %d", database.s_reads, database.s_writes,
280												database.s_creates, database.s_removes);
281				if(m.count < len reply) reply = reply[:m.count];
282				srv.reply(ref Rmsg.Read(m.tag, reply));
283			Qdata =>
284				recno := id2recno(FILENO(c.path));
285				if(recno == -1)
286					srv.reply(ref Rmsg.Error(m.tag, Eremoved));
287				else
288					srv.reply(styxservers->readbytes(m, database.read(recno)));
289			* =>
290				srv.reply(ref Rmsg.Error(m.tag, Egreg));
291			}
292		Write =>
293			(c, err) := srv.canwrite(m);
294			if(c == nil){
295				srv.reply(ref Rmsg.Error(m.tag, err));
296				break;
297			}
298			if(!database.ownlock(c)) {
299				# shouldn't happen: open checks
300				srv.reply(ref Rmsg.Error(m.tag, Elocked));
301				break;
302			}
303			case TYPE(c.path) {
304			Qindex =>
305				if(database.index >= 0) {
306					srv.reply(ref Rmsg.Error(m.tag, Eperm));
307					break;
308				}
309				database.index = int string m.data;
310				srv.reply(ref Rmsg.Write(m.tag, len m.data));
311			Qdata =>
312				recno := id2recno(FILENO(c.path));
313				if(recno == -1)
314					srv.reply(ref Rmsg.Error(m.tag, "phase error"));
315				else {
316					changed := 1;
317					if(database.updcmd != nil){
318						oldrec := database.read(recno);
319						changed = !eqbytes(m.data, oldrec);
320					}
321					if(changed && database.write(recno, m.data) == -1){
322						srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r")));
323						break;
324					}
325					if(changed)
326						database.updated();	# run the command before reply
327					srv.reply(ref Rmsg.Write(m.tag, len m.data));
328				}
329			* =>
330				srv.reply(ref Rmsg.Error(m.tag, Eperm));
331			}
332		Clunk =>
333			c := srv.getfid(m.fid);
334			if(c != nil)
335				database.unlock(c);
336			srv.clunk(m);
337		Remove =>
338			c := srv.getfid(m.fid);
339			database.unlock(c);
340			if(c == nil || c.qtype & Sys->QTDIR || TYPE(c.path) != Qdata){
341				# let it diagnose all the errors
342				srv.remove(m);
343				break;
344			}
345			recno := id2recno(FILENO(c.path));
346			if(recno == -1)
347				srv.reply(ref Rmsg.Error(m.tag, "phase error"));
348			else {
349				database.remove(recno);
350				database.updated();
351				srv.reply(ref Rmsg.Remove(m.tag));
352			}
353			srv.delfid(c);
354		* =>
355			srv.default(gm);
356		}
357	}
358	navops <-= nil;		# shut down navigator
359}
360
361eqbytes(a, b: array of byte): int
362{
363	if(len a != len b)
364		return 0;
365	for(i := 0; i < len a; i++)
366		if(a[i] != b[i])
367			return 0;
368	return 1;
369}
370
371id2recno(id: int): int
372{
373	recs := database.records;
374	for(i := 0; i < len recs; i++)
375		if(recs[i].datalen >= 0 && recs[i].id == id)
376			return i;
377	return -1;
378}
379
380open(srv: ref Styxserver, m: ref Tmsg.Open): ref Fid
381{
382	(c, mode, d, err) := srv.canopen(m);
383	if(c == nil){
384		srv.reply(ref Rmsg.Error(m.tag, err));
385		return nil;
386	}
387	if(TYPE(c.path) == Qnew){
388		# generate new file
389		if(c.uname != user){
390			srv.reply(ref Rmsg.Error(m.tag, Eperm));
391			return nil;
392		}
393		r := database.create(array[0] of byte);
394		if(r == nil) {
395			srv.reply(ref Rmsg.Error(m.tag, "create -- i/o error"));
396			return nil;
397		}
398		(d, nil) = dirgen(QPATH(r.id, Qdata));
399	}
400	if(m.mode & Sys->OTRUNC) {
401		# TO DO
402	}
403	c.open(mode, d.qid);
404	if(database.locking && TYPE(c.path) == Qdata && (m.mode & (Sys->OWRITE|Sys->ORDWR))) {
405		if(!database.lock(c)) {
406			srv.reply(ref Rmsg.Error(m.tag, Elocked));
407			return nil;
408		}
409	}
410	srv.reply(ref Rmsg.Open(m.tag, d.qid, srv.iounit()));
411	return c;
412}
413
414dirslot(n: int): int
415{
416	for(i := 0; i < len database.records; i++){
417		r := database.records[i];
418		if(r != nil && r.datalen >= 0){
419			if(n == 0)
420				return i;
421			n--;
422		}
423	}
424	return -1;
425}
426
427dir(qid: Sys->Qid, name: string, length: big, uid: string, perm: int): ref Sys->Dir
428{
429	d := ref sys->zerodir;
430	d.qid = qid;
431	if(qid.qtype & Sys->QTDIR)
432		perm |= Sys->DMDIR;
433	d.mode = perm;
434	d.name = name;
435	d.uid = uid;
436	d.gid = uid;
437	d.length = length;
438	return d;
439}
440
441dirgen(p: big): (ref Sys->Dir, string)
442{
443	case TYPE(p) {
444	Qdir =>
445		return (dir(Qid(QPATH(0, Qdir),database.vers,Sys->QTDIR), "/", big 0, user, 8r700), nil);
446	Qnew =>
447		return (dir(Qid(QPATH(0, Qnew),0,Sys->QTFILE), "new", big 0, user, 8r600), nil);
448	Qindex =>
449		return (dir(Qid(QPATH(0, Qindex),0,Sys->QTFILE), "index", big 0, user, 8r600), nil);
450	Qstats =>
451		return (dir(Qid(QPATH(0, Qstats),0,Sys->QTFILE), "stats", big 0, user, 8r400), nil);
452	* =>
453		n := id2recno(FILENO(p));
454		if(n < 0 || n >= len database.records)
455			return (nil, nil);
456		r := database.records[n];
457		if(r == nil || r.datalen < 0)
458			return (nil, Enotfound);
459		l := r.datalen;
460		if(l < 0)
461			l = 0;
462		return (dir(r.qid(), sys->sprint("%d", r.id), big l, user, 8r600), nil);
463	}
464}
465
466navigator(navops: chan of ref Navop)
467{
468	while((m := <-navops) != nil){
469		pick n := m {
470		Stat =>
471			n.reply <-= dirgen(n.path);
472		Walk =>
473			if(int n.path != Qdir){
474				n.reply <-= (nil, "not a directory");
475				break;
476			}
477			case n.name {
478			".." =>
479				;	# nop
480			"new" =>
481				n.path = QPATH(0, Qnew);
482			"stats" =>
483				if(!database.indexing){
484					n.reply <-= (nil, Enotfound);
485					continue;
486				}
487				n.path = QPATH(0, Qstats);
488			"index" =>
489				if(!database.indexing){
490					n.reply <-= (nil, Enotfound);
491					continue;
492				}
493				n.path = QPATH(0, Qindex);
494			* =>
495				if(len n.name < 1 || !(n.name[0]>='0' && n.name[0]<='9')){	# weak test for now
496					n.reply <-= (nil, Enotfound);
497					continue;
498				}
499				n.path = QPATH(int n.name, Qdata);
500			}
501			n.reply <-= dirgen(n.path);
502		Readdir =>
503			if(int m.path != Qdir){
504				n.reply <-= (nil, "not a directory");
505				break;
506			}
507			o := 1;	# Qnew;
508			stats := -1;
509			indexing := -1;
510			if(database.indexing)
511				indexing = o++;
512			if(database.stats)
513				stats = o++;
514		    Dread:
515			for(i := n.offset; --n.count >= 0; i++){
516				case i {
517				0 =>
518					n.reply <-= dirgen(QPATH(0,Qnew));
519				* =>
520					if(i == indexing)
521						n.reply <-= dirgen(QPATH(0, Qindex));
522					if(i == stats)
523						n.reply <-= dirgen(QPATH(0, Qstats));
524					j := dirslot(i-o);	# n² but fine if the file will be small
525					if(j < 0)
526						break Dread;
527					r := database.records[j];
528					n.reply <-= dirgen(QPATH(r.id,Qdata));
529				}
530			}
531			n.reply <-= (nil, nil);
532		}
533	}
534}
535
536QPATH(w, q: int): big
537{
538	return big ((w<<8)|q);
539}
540
541TYPE(path: big): int
542{
543	return int path & 16rFF;
544}
545
546FILENO(path: big): int
547{
548	return (int path >> 8) & 16rFFFFFF;
549}
550
551Database.build(f: ref Iobuf, locking, indexing, stats: int, updcmd: string): (ref Database, string)
552{
553	rl: list of ref Record;
554	offset := 0;
555	maxid := 0;
556	for(;;) {
557		d := array[HEADLEN] of byte;
558		n := f.read(d, HEADLEN);
559		if(n < HEADLEN)
560			break;
561		orig := s := string d;
562		if(len s != HEADLEN)
563			return (nil, "found bad header");
564		r := ref Record;
565		r.vers = 0;
566		(r.count, s) = str->toint(s, 10);
567		(r.datalen, s) = str->toint(s, 10);
568		if(s != "\n")
569			return (nil, sys->sprint("found bad header '%s'\n", orig));
570		r.offset = offset + HEADLEN;
571		offset += r.count + HEADLEN;
572		f.seek(big offset, Bufio->SEEKSTART);
573		r.id = maxid++;
574		rl = r :: rl;
575	}
576	db := ref Database(f, array[maxid] of ref Record, maxid, locking, nil, indexing, stats, -1, 0, 0, 0, 0, updcmd, 0);
577	for(i := len db.records - 1; i >= 0; i--) {
578		db.records[i] = hd rl;
579		rl = tl rl;
580	}
581	return (db, nil);
582}
583
584Database.write(db: self ref Database, recno: int, data: array of byte): int
585{
586	db.s_writes++;
587	r := db.records[recno];
588	r.vers++;
589	if(len data <= r.count) {
590		if(r.count - len data >= HEADLEN + MINSIZE)
591			splitrec(db, recno, len data);
592		writerec(db, recno, data);
593		db.file.flush();
594	} else {
595		freerec(db, recno);
596		n := allocrec(db, len data);
597		if(n == -1)
598			return -1;		# BUG: we lose the original data in this case.
599		db.records[n].id = r.id;
600		db.write(n, data);
601	}
602	return 0;
603}
604
605Database.create(db: self ref Database, data: array of byte): ref Record
606{
607	db.s_creates++;
608	db.vers++;
609	n := allocrec(db, len data);
610	if(n < 0)
611		return nil;
612	if(db.write(n, data) < 0){
613		freerec(db, n);
614		return nil;
615	}
616	r := db.records[n];
617	r.id = db.maxid++;
618	return r;
619}
620
621Database.read(db: self ref Database, recno: int): array of byte
622{
623	db.s_reads++;
624	r := db.records[recno];
625	if(r.datalen <= 0)
626		return nil;
627	db.file.seek(big r.offset, Bufio->SEEKSTART);
628	d := array[r.datalen] of byte;
629	n := db.file.read(d, r.datalen);
630	if(n != r.datalen) {
631		sys->fprint(stderr, "dbfs: only read %d bytes (expected %d)\n", n, r.datalen);
632		return nil;
633	}
634	return d;
635}
636
637Database.remove(db: self ref Database, recno: int)
638{
639	db.s_removes++;
640	db.vers++;
641	freerec(db, recno);
642	db.file.flush();
643}
644
645Database.updated(db: self ref Database)
646{
647	if(db.updcmd != nil)
648		sh->system(context, db.updcmd);
649}
650
651# Locking - try to lock a record
652
653Database.lock(db: self ref Database, c: ref Styxservers->Fid): int
654{
655	if(TYPE(c.path) != Qdata || !db.locking)
656		return 1;
657	for(ll := db.locklist; ll != nil; ll = tl ll) {
658		lock := hd ll;
659		if(lock.qpath == c.path)
660			return lock.fid == c.fid;
661	}
662	db.locklist = (c.path, c.fid) :: db.locklist;
663	return 1;
664}
665
666
667# Locking - unlock a record
668
669Database.unlock(db: self ref Database, c: ref Styxservers->Fid)
670{
671	if(TYPE(c.path) != Qdata || !db.locking)
672		return;
673	ll := db.locklist;
674	db.locklist = nil;
675	for(; ll != nil; ll = tl ll){
676		lock := hd ll;
677		if(lock.qpath == c.path && lock.fid == c.fid){
678			# not replaced on list
679		}else
680			db.locklist = hd ll :: db.locklist;
681	}
682}
683
684
685# Locking - check if Fid c has the lock on its record
686
687Database.ownlock(db: self ref Database, c: ref Styxservers->Fid): int
688{
689	if(TYPE(c.path) != Qdata || !db.locking)
690		return 1;
691	for(ll := db.locklist; ll != nil; ll = tl ll) {
692		lock := hd ll;
693		if(lock.qpath == c.path)
694			return lock.fid == c.fid;
695	}
696	return 0;
697}
698
699Record.new(offset: int, length: int): ref Record
700{
701	return ref Record(-1, offset, length, -1, 0);
702}
703
704Record.qid(r: self ref Record): Qid
705{
706	return Qid(QPATH(r.id,Qdata), r.vers, Sys->QTFILE);
707}
708
709freerec(db: ref Database, recno: int)
710{
711	nr := len db.records;
712	db.records[recno].datalen = -1;
713	for(i := recno; i >= 0; i--)
714		if(db.records[i].datalen != -1)
715			break;
716	f := i + 1;
717	nb := 0;
718	for(i = f; i < nr; i++) {
719		if(db.records[i].datalen != -1)
720			break;
721		nb += db.records[i].count + HEADLEN;
722	}
723	db.records[f].count = nb - HEADLEN;
724	writeheader(db.file, db.records[f]);
725	# could blank out freed entries here if we cared.
726	if(i < nr && f < i)
727		db.records[f+1:] = db.records[i:];
728	db.records = db.records[0:nr - (i - f - 1)];
729}
730
731splitrec(db: ref Database, recno: int, pos: int)
732{
733	a := array[len db.records + 1] of ref Record;
734	a[0:] = db.records[0:recno+1];
735	if(recno < len db.records - 1)
736		a[recno+2:] = db.records[recno+1:];
737	db.records = a;
738	r := a[recno];
739	a[recno+1] = Record.new(r.offset + pos + HEADLEN, r.count - HEADLEN - pos);
740	r.count = pos;
741	writeheader(db.file, a[recno+1]);
742}
743
744writerec(db: ref Database, recno: int, data: array of byte): int
745{
746	db.records[recno].datalen = len data;
747	if(writeheader(db.file, db.records[recno]) == -1)
748		return -1;
749	if(db.file.write(data, len data) == Bufio->ERROR)
750		return -1;
751	return 0;
752}
753
754writeheader(f: ref Iobuf, r: ref Record): int
755{
756	f.seek(big r.offset - big HEADLEN, Bufio->SEEKSTART);
757	if(f.puts(sys->sprint("%4d %4d\n", r.count, r.datalen)) == Bufio->ERROR) {
758		sys->fprint(stderr, "dbfs: error writing header (id %d, offset %d, count %d, datalen %d): %r\n",
759					r.id, r.offset, r.count, r.datalen);
760		return -1;
761	}
762	return 0;
763}
764
765# finds or creates a record of the requisite size; does not mark it as allocated.
766allocrec(db: ref Database, nb: int): int
767{
768	if(nb < MINSIZE)
769		nb = MINSIZE;
770	best := -1;
771	n := -1;
772	for(i := 0; i < len db.records; i++) {
773		r := db.records[i];
774		if(r.datalen == -1) {
775			avail := r.count - nb;
776			if(avail >= 0 && (n == -1 || avail < best)) {
777				best = avail;
778				n = i;
779			}
780		}
781	}
782	if(n != -1)
783		return n;
784	nr := len db.records;
785	a := array[nr + 1] of ref Record;
786	a[0:] = db.records[0:];
787	offset := 0;
788	if(nr > 0)
789		offset = a[nr-1].offset + a[nr-1].count;
790	db.file.seek(big offset, Bufio->SEEKSTART);
791	if(db.file.write(array[nb + HEADLEN] of {* => byte(0)}, nb + HEADLEN) == Bufio->ERROR
792			|| db.file.flush() == Bufio->ERROR) {
793		sys->fprint(stderr, "dbfs: write of new entry failed: %r\n");
794		return -1;
795	}
796	a[nr] = Record.new(offset + HEADLEN, nb);
797	db.records = a;
798	return nr;
799}
800
801now(fd: ref Sys->FD): int
802{
803	if(fd == nil)
804		return 0;
805	buf := array[128] of byte;
806	sys->seek(fd, big 0, 0);
807	n := sys->read(fd, buf, len buf);
808	if(n < 0)
809		return 0;
810	t := (big string buf[0:n]) / big 1000000;
811	return int t;
812}
813