xref: /inferno-os/appl/lib/palmdb.b (revision 37da2899f40661e3e9631e497da8dc59b971cbd0)
1implement Palmdb;
2
3#
4# Copyright © 2001-2002 Vita Nuova Holdings Limited.  All rights reserved.
5#
6# Based on ``Palm® File Format Specification'', Document Number 3008-004, 1 May 2001, by Palm Inc.
7# Doc compression based on description by Paul Lucas, 18 August 1998
8#
9
10include "sys.m";
11	sys: Sys;
12
13include "daytime.m";
14	daytime: Daytime;
15
16include "bufio.m";
17	bufio: Bufio;
18	Iobuf: import bufio;
19
20include "palm.m";
21	palm: Palm;
22	DBInfo, Record, Resource, get2, get3, get4, put2, put3, put4, gets, puts: import palm;
23	filename, dbname: import palm;
24
25Entry: adt {
26	id:	int;	# resource: id; record: unique ID
27	offset:	int;
28	size:	int;
29	name:	int;	# resource entry only
30	attr:	int;	# record entry only
31};
32
33Ofile: adt {
34	fname:	string;
35	f:	ref Iobuf;
36	mode:	int;
37	info:	ref DBInfo;
38	appinfo:	array of byte;
39	sortinfo:	array of int;
40	uidseed:	int;
41	entries:	array of ref Entry;
42};
43
44files:	array of ref Ofile;
45
46Dbhdrlen: con 72+6;
47Datahdrsize: con 4+1+3;
48Resourcehdrsize: con 4+2+4;
49
50# Exact value of "Jan 1, 1970 0:00:00 GMT" - "Jan 1, 1904 0:00:00 GMT"
51Epochdelta: con 2082844800;
52tzoff := 0;
53
54init(m: Palm): string
55{
56	sys = load Sys Sys->PATH;
57	bufio = load Bufio Bufio->PATH;
58	daytime = load Daytime Daytime->PATH;
59	if(bufio == nil || daytime == nil)
60		return "can't load required module";
61	palm = m;
62	tzoff = daytime->local(0).tzoff;
63	return nil;
64}
65
66Eshort: con "file format error: too small";
67
68DB.open(name: string, mode: int): (ref DB, string)
69{
70	if(mode != Sys->OREAD)
71		return (nil, "invalid mode");
72	fd := sys->open(name, mode);
73	if(fd == nil)
74		return (nil, sys->sprint("%r"));
75	(ok, d) := sys->fstat(fd);
76	if(ok < 0)
77		return (nil, sys->sprint("%r"));
78	length := int d.length;
79	if(length == 0)
80		return (nil, "empty file");
81	(pf, ofile, fx) := mkpfile(name, mode);
82
83	f := bufio->fopen(fd, mode);	# automatically closed if open fails
84
85	p := array[Dbhdrlen] of byte;
86	if(f.read(p, Dbhdrlen) != Dbhdrlen)
87		return (nil, "invalid file header: too short");
88
89	ip := ofile.info;
90	ip.name = gets(p[0:32]);
91	ip.attr = get2(p[32:]);
92	ip.version = get2(p[34:]);
93	ip.ctime = pilot2epoch(get4(p[36:]));
94	ip.mtime = pilot2epoch(get4(p[40:]));
95	ip.btime = pilot2epoch(get4(p[44:]));
96	ip.modno = get4(p[48:]);
97	appinfo := get4(p[52:]);
98	sortinfo := get4(p[56:]);
99	if(appinfo < 0 || sortinfo < 0 || (appinfo|sortinfo)&1)
100		return (nil, "invalid header: bad offset");
101	ip.dtype = xs(get4(p[60:]));
102	ip.creator = xs(get4(p[64:]));
103	ofile.uidseed = ip.uidseed = get4(p[68:]);
104
105	if(get4(p[72:]) != 0)
106		return (nil, "chained headers not supported");	# Palm says to reject such files
107	nrec := get2(p[76:]);
108	if(nrec < 0)
109		return (nil, sys->sprint("invalid header: bad record count: %d", nrec));
110
111	esize := Datahdrsize;
112	if(ip.attr & Palm->Fresource)
113		esize = Resourcehdrsize;
114
115	dataoffset := length;
116	ofile.entries = array[nrec] of ref Entry;
117	if(nrec > 0){
118		laste: ref Entry;
119		buf := array[esize] of byte;
120		for(i := 0; i < nrec; i++){
121			if(f.read(buf, len buf) != len buf)
122				return (nil, Eshort);
123			e := ref Entry;
124			if(ip.attr & Palm->Fresource){
125				# resource entry: type[4], id[2], offset[4]
126				e.name = get4(buf);
127				e.id = get2(buf[4:]);
128				e.offset = get4(buf[6:]);
129				e.attr = 0;
130			}else{
131				# record entry: offset[4], attr[1], id[3]
132				e.offset = get4(buf);
133				e.attr = int buf[4];
134				e.id = get3(buf[5:]);
135				e.name = 0;
136			}
137			if(laste != nil)
138				laste.size = e.offset - laste.offset;
139			laste = e;
140			ofile.entries[i] = e;
141		}
142		if(laste != nil)
143			laste.size = length - laste.offset;
144		dataoffset = ofile.entries[0].offset;
145	}else{
146		if(f.read(p, 2) != 2)
147			return (nil, Eshort);	# discard placeholder bytes
148	}
149
150	n := 0;
151	if(appinfo > 0){
152		n = appinfo - int f.offset();
153		while(--n >= 0)
154			f.getb();
155		if(sortinfo)
156			n = sortinfo - appinfo;
157		else
158			n = dataoffset - appinfo;
159		ofile.appinfo = array[n] of byte;
160		if(f.read(ofile.appinfo, n) != n)
161			return (nil, Eshort);
162	}
163	if(sortinfo > 0){
164		n = sortinfo - int f.offset();
165		while(--n >= 0)
166			f.getb();
167		n = (dataoffset-sortinfo)/2;
168		ofile.sortinfo = array[n] of int;
169		tmp := array[2*n] of byte;
170		if(f.read(tmp, len tmp) != len tmp)
171			return (nil, Eshort);
172		for(i := 0; i < n; i++)
173			ofile.sortinfo[i] = get2(tmp[2*i:]);
174	}
175	ofile.f = f;	# safe to save open file reference
176	files[fx] = ofile;
177	return (pf, nil);
178}
179
180DB.close(db: self ref DB): string
181{
182	ofile := files[db.x];
183	if(ofile.f != nil){
184		ofile.f.close();
185		ofile.f = nil;
186	}
187	files[db.x] = nil;
188	return nil;
189}
190
191DB.stat(db: self ref DB): ref DBInfo
192{
193	return ref *files[db.x].info;
194}
195
196DB.create(name: string, mode: int, perm: int, info: ref DBInfo): (ref DB, string)
197{
198	return (nil, "DB.create not implemented");
199}
200
201DB.wstat(db: self ref DB, ip: ref DBInfo, flags: int)
202{
203	raise "DB.wstat not implemented";
204}
205
206#DB.wstat(db: self ref DB, ip: ref DBInfo): string
207#{
208#	ofile := files[db.x];
209#	if(ofile.mode != Sys->OWRITE)
210#		return "not open for writing";
211#	if((ip.attr & Palm->Fresource) != (ofile.info.attr & Palm->Fresource))
212#		return "cannot change file type";
213#	# copy only a subset
214#	ofile.info.name = ip.name;
215#	ofile.info.attr = ip.attr;
216#	ofile.info.version = ip.version;
217#	ofile.info.ctime = ip.ctime;
218#	ofile.info.mtime = ip.mtime;
219#	ofile.info.btime = ip.btime;
220#	ofile.info.modno = ip.modno;
221#	ofile.info.dtype = ip.dtype;
222#	ofile.info.creator = ip.creator;
223#	return nil;
224#}
225
226DB.rdappinfo(db: self ref DB): (array of byte, string)
227{
228	return (files[db.x].appinfo, nil);
229}
230
231DB.wrappinfo(db: self ref DB, data: array of byte): string
232{
233	ofile := files[db.x];
234	if(ofile.mode != Sys->OWRITE)
235		return "not open for writing";
236	ofile.appinfo = array[len data] of byte;
237	ofile.appinfo[0:] = data;
238	return nil;
239}
240
241DB.rdsortinfo(db: self ref DB): (array of int, string)
242{
243	return (files[db.x].sortinfo, nil);
244}
245
246DB.wrsortinfo(db: self ref DB, sort: array of int): string
247{
248	ofile := files[db.x];
249	if(ofile.mode != Sys->OWRITE)
250		return "not open for writing";
251	ofile.sortinfo = array[len sort] of int;
252	ofile.sortinfo[0:] = sort;
253	return nil;
254}
255
256DB.readidlist(db: self ref DB, nil: int): array of int
257{
258	ent := files[db.x].entries;
259	a := array[len ent] of int;
260	for(i := 0; i < len a; i++)
261		a[i] = ent[i].id;
262	return a;
263}
264
265DB.nentries(db: self ref DB): int
266{
267	return len files[db.x].entries;
268}
269
270DB.resetsyncflags(db: self ref DB): string
271{
272	raise "DB.resetsyncflags not implemented";
273}
274
275DB.records(db: self ref DB): ref PDB
276{
277	if(db == nil || db.attr & Palm->Fresource)
278		return nil;
279	return ref PDB(db);
280}
281
282DB.resources(db: self ref DB): ref PRC
283{
284	if(db == nil || (db.attr & Palm->Fresource) == 0)
285		return nil;
286	return ref PRC(db);
287}
288
289PDB.read(pdb: self ref PDB, i: int): ref Record
290{
291	ofile := files[pdb.db.x];
292	if(i < 0 || i >= len ofile.entries){
293		if(i == len ofile.entries)
294			return nil; # treat as end-of-file
295		#return "index out of range";
296		return nil;
297	}
298	e := ofile.entries[i];
299	nb := e.size;
300	r := ref Record(e.id, e.attr & 16rF0, e.attr & 16r0F, array[nb] of byte);
301	ofile.f.seek(big e.offset, 0);
302	if(ofile.f.read(r.data, nb) != nb)
303		return nil;
304	return r;
305}
306
307PDB.readid(pdb: self ref PDB, id: int): (ref Record, int)
308{
309	ofile := files[pdb.db.x];
310	ent := ofile.entries;
311	for(i := 0; i < len ent; i++)
312		if((e := ent[i]).id == id){
313			nb := e.size;
314			r := ref Record(e.id, e.attr & 16rF0, e.attr & 16r0F, array[e.size] of byte);
315			ofile.f.seek(big e.offset, 0);
316			if(ofile.f.read(r.data, nb) != nb)
317				return (nil, -1);
318			return (r, id);
319		}
320	sys->werrstr("ID not found");
321	return (nil, -1);
322}
323
324PDB.resetnext(db: self ref PDB): int
325{
326	raise "PDB.resetnext not implemented";
327}
328
329PDB.readnextmod(db: self ref PDB): (ref Record, int)
330{
331	raise "PDB.readnextmod not implemented";
332}
333
334PDB.write(db: self ref PDB, r: ref Record): string
335{
336	return "PDB.write not implemented";
337}
338
339PDB.truncate(db: self ref PDB): string
340{
341	return "PDB.truncate not implemented";
342}
343
344PDB.delete(db: self ref PDB, id: int): string
345{
346	return "PDB.delete not implemented";
347}
348
349PDB.deletecat(db: self ref PDB, cat: int): string
350{
351	return "PDB.deletecat not implemented";
352}
353
354PDB.purge(db: self ref PDB): string
355{
356	return "PDB.purge not implemented";
357}
358
359PDB.movecat(db: self ref PDB, old: int, new: int): string
360{
361	return "PDB.movecat not implemented";
362}
363
364PRC.read(db: self ref PRC, index: int): ref Resource
365{
366	return nil;
367}
368
369PRC.readtype(db: self ref PRC, name: int, id: int): (ref Resource, int)
370{
371	return (nil, -1);
372}
373
374PRC.write(db: self ref PRC, r: ref Resource): string
375{
376	return "PRC.write not implemented";
377}
378
379PRC.truncate(db: self ref PRC): string
380{
381	return "PRC.truncate not implemented";
382}
383
384PRC.delete(db: self ref PRC, name: int, id: int): string
385{
386	return "PRC.delete not implemented";
387}
388
389#
390# internal function to extend entry list if necessary, and return a
391# pointer to the next available slot
392#
393entryensure(db: ref DB, i: int): ref Entry
394{
395	ofile := files[db.x];
396	if(i < len ofile.entries)
397		return ofile.entries[i];
398	e := ref Entry(0, -1, 0, 0, 0);
399	n := len ofile.entries;
400	if(n == 0)
401		n = 64;
402	else
403		n = (i+63) & ~63;
404	a := array[n] of ref Entry;
405	a[0:] = ofile.entries;
406	a[i] = e;
407	ofile.entries = a;
408	return e;
409}
410
411writefilehdr(db: ref DB, mode: int, perm: int): string
412{
413	ofile := files[db.x];
414	if(len ofile.entries >= 64*1024)
415		return "too many records for Palm file";	# is there a way to extend it?
416
417	if((f := bufio->create(ofile.fname, mode, perm)) == nil)
418		return sys->sprint("%r");
419
420	ip := ofile.info;
421
422	esize := Datahdrsize;
423	if(ip.attr & Palm->Fresource)
424		esize = Resourcehdrsize;
425	offset := Dbhdrlen + esize*len ofile.entries + 2;
426	offset += 2;	# placeholder bytes or gap bytes
427	appinfo := 0;
428	if(len ofile.appinfo > 0){
429		appinfo = offset;
430		offset += len ofile.appinfo;
431	}
432	sortinfo := 0;
433	if(len ofile.sortinfo > 0){
434		sortinfo = offset;
435		offset += 2*len ofile.sortinfo;	# 2-byte entries
436	}
437	p := array[Dbhdrlen] of byte;	# bigger than any entry as well
438	puts(p[0:32], ip.name);
439	put2(p[32:], ip.attr);
440	put2(p[34:], ip.version);
441	put4(p[36:], epoch2pilot(ip.ctime));
442	put4(p[40:], epoch2pilot(ip.mtime));
443	put4(p[44:], epoch2pilot(ip.btime));
444	put4(p[48:], ip.modno);
445	put4(p[52:], appinfo);
446	put4(p[56:], sortinfo);
447	put4(p[60:], sx(ip.dtype));
448	put4(p[64:], sx(ip.creator));
449	put4(p[68:], ofile.uidseed);
450	put4(p[72:], 0);		# next record list ID
451	put2(p[76:], len ofile.entries);
452
453	if(f.write(p, Dbhdrlen) != Dbhdrlen)
454		return ewrite(f);
455	if(len ofile.entries > 0){
456		for(i := 0; i < len ofile.entries; i++) {
457			e := ofile.entries[i];
458			e.offset = offset;
459			if(ip.attr & Palm->Fresource) {
460				put4(p, e.name);
461				put2(p[4:], e.id);
462				put4(p[6:], e.offset);
463			} else {
464				put4(p, e.offset);
465				p[4] = byte e.attr;
466				put3(p[5:], e.id);
467			}
468			if(f.write(p, esize) != esize)
469				return ewrite(f);
470			offset += e.size;
471		}
472	}
473
474	f.putb(byte 0);	# placeholder bytes (figure 1.4) or gap bytes (p. 15)
475	f.putb(byte 0);
476
477	if(appinfo != 0){
478		if(f.write(ofile.appinfo, len ofile.appinfo) != len ofile.appinfo)
479			return ewrite(f);
480	}
481
482	if(sortinfo != 0){
483		tmp := array[2*len ofile.sortinfo] of byte;
484		for(i := 0; i < len ofile.sortinfo; i++)
485			put2(tmp[2*i:], ofile.sortinfo[i]);
486		if(f.write(tmp, len tmp) != len tmp)
487			return ewrite(f);
488	}
489
490	if(f.flush() != 0)
491		return ewrite(f);
492
493	return nil;
494}
495
496ewrite(f: ref Iobuf): string
497{
498	e := sys->sprint("write error: %r");
499	f.close();
500	return e;
501}
502
503xs(i: int): string
504{
505	if(i == 0)
506		return "";
507	if(i & int 16r80808080)
508		return sys->sprint("%8.8ux", i);
509	return sys->sprint("%c%c%c%c", (i>>24)&16rFF, (i>>16)&16rFF, (i>>8)&16rFF, i&16rFF);
510}
511
512sx(s: string): int
513{
514	n := 0;
515	for(i := 0; i < 4; i++){
516		c := 0;
517		if(i < len s)
518			c = s[i] & 16rFF;
519		n = (n<<8) | c;
520	}
521	return n;
522}
523
524mkpfile(name: string, mode: int): (ref DB, ref Ofile, int)
525{
526	ofile := ref Ofile(name, nil, mode, DBInfo.new(name, 0, nil, 0, nil),
527		array[0] of byte, array[0] of int, 0, nil);
528	for(x := 0; x < len files; x++)
529		if(files[x] == nil)
530			return (ref DB(x, mode, 0), ofile, x);
531	a := array[x] of ref Ofile;
532	a[0:] = files;
533	files = a;
534	return (ref DB(x, mode, 0), ofile, x);
535}
536
537#
538# because PalmOS treats all times as local times, and doesn't associate
539# them with time zones, we'll convert using local time on Plan 9 and Inferno
540#
541
542pilot2epoch(t: int): int
543{
544	if(t == 0)
545		return 0;	# we'll assume it's not set
546	return t - Epochdelta + tzoff;
547}
548
549epoch2pilot(t: int): int
550{
551	if(t == 0)
552		return t;
553	return t - tzoff + Epochdelta;
554}
555
556#
557# map Palm name to string, assuming iso-8859-1,
558# but remap space and /
559#
560latin1(a: array of byte, remap: int): string
561{
562	s := "";
563	for(i := 0; i < len a; i++){
564		c := int a[i];
565		if(c == 0)
566			break;
567		if(remap){
568			if(c == ' ')
569				c = 16r00A0;	# unpaddable space
570			else if(c == '/')
571				c = 16r2215;	# division /
572		}
573		s[len s] = c;
574	}
575	return s;
576}
577