summaryrefslogtreecommitdiff
path: root/appl/lib/palmdb.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/lib/palmdb.b')
-rw-r--r--appl/lib/palmdb.b576
1 files changed, 576 insertions, 0 deletions
diff --git a/appl/lib/palmdb.b b/appl/lib/palmdb.b
new file mode 100644
index 00000000..d734e529
--- /dev/null
+++ b/appl/lib/palmdb.b
@@ -0,0 +1,576 @@
+implement Palmdb;
+
+#
+# Copyright © 2001-2002 Vita Nuova Holdings Limited. All rights reserved.
+#
+# Based on ``Palm® File Format Specification'', Document Number 3008-004, 1 May 2001, by Palm Inc.
+# Doc compression based on description by Paul Lucas, 18 August 1998
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "palm.m";
+ palm: Palm;
+ DBInfo, Record, Resource, get2, get3, get4, put2, put3, put4, gets, puts: import palm;
+ filename, dbname: import palm;
+
+Entry: adt {
+ id: int; # resource: id; record: unique ID
+ offset: int;
+ size: int;
+ name: int; # resource entry only
+ attr: int; # record entry only
+};
+
+Ofile: adt {
+ fname: string;
+ f: ref Iobuf;
+ mode: int;
+ info: ref DBInfo;
+ appinfo: array of byte;
+ sortinfo: array of int;
+ uidseed: int;
+ entries: array of ref Entry;
+};
+
+files: array of ref Ofile;
+
+Dbhdrlen: con 72+6;
+Datahdrsize: con 4+1+3;
+Resourcehdrsize: con 4+2+4;
+
+# Exact value of "Jan 1, 1970 0:00:00 GMT" - "Jan 1, 1904 0:00:00 GMT"
+Epochdelta: con 2082844800;
+tzoff := 0;
+
+init(m: Palm): string
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ daytime = load Daytime Daytime->PATH;
+ if(bufio == nil || daytime == nil)
+ return "can't load required module";
+ palm = m;
+ tzoff = daytime->local(0).tzoff;
+ return nil;
+}
+
+Eshort: con "file format error: too small";
+
+DB.open(name: string, mode: int): (ref DB, string)
+{
+ if(mode != Sys->OREAD)
+ return (nil, "invalid mode");
+ fd := sys->open(name, mode);
+ if(fd == nil)
+ return (nil, sys->sprint("%r"));
+ (ok, d) := sys->fstat(fd);
+ if(ok < 0)
+ return (nil, sys->sprint("%r"));
+ length := int d.length;
+ if(length == 0)
+ return (nil, "empty file");
+ (pf, ofile, fx) := mkpfile(name, mode);
+
+ f := bufio->fopen(fd, mode); # automatically closed if open fails
+
+ p := array[Dbhdrlen] of byte;
+ if(f.read(p, Dbhdrlen) != Dbhdrlen)
+ return (nil, "invalid file header: too short");
+
+ ip := ofile.info;
+ ip.name = gets(p[0:32]);
+ ip.attr = get2(p[32:]);
+ ip.version = get2(p[34:]);
+ ip.ctime = pilot2epoch(get4(p[36:]));
+ ip.mtime = pilot2epoch(get4(p[40:]));
+ ip.btime = pilot2epoch(get4(p[44:]));
+ ip.modno = get4(p[48:]);
+ appinfo := get4(p[52:]);
+ sortinfo := get4(p[56:]);
+ if(appinfo < 0 || sortinfo < 0 || (appinfo|sortinfo)&1)
+ return (nil, "invalid header: bad offset");
+ ip.dtype = xs(get4(p[60:]));
+ ip.creator = xs(get4(p[64:]));
+ ofile.uidseed = ip.uidseed = get4(p[68:]);
+
+ if(get4(p[72:]) != 0)
+ return (nil, "chained headers not supported"); # Palm says to reject such files
+ nrec := get2(p[76:]);
+ if(nrec < 0)
+ return (nil, sys->sprint("invalid header: bad record count: %d", nrec));
+
+ esize := Datahdrsize;
+ if(ip.attr & Palm->Fresource)
+ esize = Resourcehdrsize;
+
+ dataoffset := length;
+ ofile.entries = array[nrec] of ref Entry;
+ if(nrec > 0){
+ laste: ref Entry;
+ buf := array[esize] of byte;
+ for(i := 0; i < nrec; i++){
+ if(f.read(buf, len buf) != len buf)
+ return (nil, Eshort);
+ e := ref Entry;
+ if(ip.attr & Palm->Fresource){
+ # resource entry: type[4], id[2], offset[4]
+ e.name = get4(buf);
+ e.id = get2(buf[4:]);
+ e.offset = get4(buf[6:]);
+ e.attr = 0;
+ }else{
+ # record entry: offset[4], attr[1], id[3]
+ e.offset = get4(buf);
+ e.attr = int buf[4];
+ e.id = get3(buf[5:]);
+ e.name = 0;
+ }
+ if(laste != nil)
+ laste.size = e.offset - laste.offset;
+ laste = e;
+ ofile.entries[i] = e;
+ }
+ if(laste != nil)
+ laste.size = length - laste.offset;
+ dataoffset = ofile.entries[0].offset;
+ }else{
+ if(f.read(p, 2) != 2)
+ return (nil, Eshort); # discard placeholder bytes
+ }
+
+ n := 0;
+ if(appinfo > 0){
+ n = appinfo - int f.offset();
+ while(--n >= 0)
+ f.getb();
+ if(sortinfo)
+ n = sortinfo - appinfo;
+ else
+ n = dataoffset - appinfo;
+ ofile.appinfo = array[n] of byte;
+ if(f.read(ofile.appinfo, n) != n)
+ return (nil, Eshort);
+ }
+ if(sortinfo > 0){
+ n = sortinfo - int f.offset();
+ while(--n >= 0)
+ f.getb();
+ n = (dataoffset-sortinfo)/2;
+ ofile.sortinfo = array[n] of int;
+ tmp := array[2*n] of byte;
+ if(f.read(tmp, len tmp) != len tmp)
+ return (nil, Eshort);
+ for(i := 0; i < n; i++)
+ ofile.sortinfo[i] = get2(tmp[2*i:]);
+ }
+ ofile.f = f; # safe to save open file reference
+ files[fx] = ofile;
+ return (pf, nil);
+}
+
+DB.close(db: self ref DB): string
+{
+ ofile := files[db.x];
+ if(ofile.f != nil){
+ ofile.f.close();
+ ofile.f = nil;
+ }
+ files[db.x] = nil;
+ return nil;
+}
+
+DB.stat(db: self ref DB): ref DBInfo
+{
+ return ref *files[db.x].info;
+}
+
+DB.create(name: string, mode: int, perm: int, info: ref DBInfo): (ref DB, string)
+{
+ return (nil, "DB.create not implemented");
+}
+
+DB.wstat(db: self ref DB, ip: ref DBInfo, flags: int)
+{
+ raise "DB.wstat not implemented";
+}
+
+#DB.wstat(db: self ref DB, ip: ref DBInfo): string
+#{
+# ofile := files[db.x];
+# if(ofile.mode != Sys->OWRITE)
+# return "not open for writing";
+# if((ip.attr & Palm->Fresource) != (ofile.info.attr & Palm->Fresource))
+# return "cannot change file type";
+# # copy only a subset
+# ofile.info.name = ip.name;
+# ofile.info.attr = ip.attr;
+# ofile.info.version = ip.version;
+# ofile.info.ctime = ip.ctime;
+# ofile.info.mtime = ip.mtime;
+# ofile.info.btime = ip.btime;
+# ofile.info.modno = ip.modno;
+# ofile.info.dtype = ip.dtype;
+# ofile.info.creator = ip.creator;
+# return nil;
+#}
+
+DB.rdappinfo(db: self ref DB): (array of byte, string)
+{
+ return (files[db.x].appinfo, nil);
+}
+
+DB.wrappinfo(db: self ref DB, data: array of byte): string
+{
+ ofile := files[db.x];
+ if(ofile.mode != Sys->OWRITE)
+ return "not open for writing";
+ ofile.appinfo = array[len data] of byte;
+ ofile.appinfo[0:] = data;
+ return nil;
+}
+
+DB.rdsortinfo(db: self ref DB): (array of int, string)
+{
+ return (files[db.x].sortinfo, nil);
+}
+
+DB.wrsortinfo(db: self ref DB, sort: array of int): string
+{
+ ofile := files[db.x];
+ if(ofile.mode != Sys->OWRITE)
+ return "not open for writing";
+ ofile.sortinfo = array[len sort] of int;
+ ofile.sortinfo[0:] = sort;
+ return nil;
+}
+
+DB.readidlist(db: self ref DB, nil: int): array of int
+{
+ ent := files[db.x].entries;
+ a := array[len ent] of int;
+ for(i := 0; i < len a; i++)
+ a[i] = ent[i].id;
+ return a;
+}
+
+DB.nentries(db: self ref DB): int
+{
+ return len files[db.x].entries;
+}
+
+DB.resetsyncflags(db: self ref DB): string
+{
+ raise "DB.resetsyncflags not implemented";
+}
+
+DB.records(db: self ref DB): ref PDB
+{
+ if(db == nil || db.attr & Palm->Fresource)
+ return nil;
+ return ref PDB(db);
+}
+
+DB.resources(db: self ref DB): ref PRC
+{
+ if(db == nil || (db.attr & Palm->Fresource) == 0)
+ return nil;
+ return ref PRC(db);
+}
+
+PDB.read(pdb: self ref PDB, i: int): ref Record
+{
+ ofile := files[pdb.db.x];
+ if(i < 0 || i >= len ofile.entries){
+ if(i == len ofile.entries)
+ return nil; # treat as end-of-file
+ #return "index out of range";
+ return nil;
+ }
+ e := ofile.entries[i];
+ nb := e.size;
+ r := ref Record(e.id, e.attr & 16rF0, e.attr & 16r0F, array[nb] of byte);
+ ofile.f.seek(big e.offset, 0);
+ if(ofile.f.read(r.data, nb) != nb)
+ return nil;
+ return r;
+}
+
+PDB.readid(pdb: self ref PDB, id: int): (ref Record, int)
+{
+ ofile := files[pdb.db.x];
+ ent := ofile.entries;
+ for(i := 0; i < len ent; i++)
+ if((e := ent[i]).id == id){
+ nb := e.size;
+ r := ref Record(e.id, e.attr & 16rF0, e.attr & 16r0F, array[e.size] of byte);
+ ofile.f.seek(big e.offset, 0);
+ if(ofile.f.read(r.data, nb) != nb)
+ return (nil, -1);
+ return (r, id);
+ }
+ sys->werrstr("ID not found");
+ return (nil, -1);
+}
+
+PDB.resetnext(db: self ref PDB): int
+{
+ raise "PDB.resetnext not implemented";
+}
+
+PDB.readnextmod(db: self ref PDB): (ref Record, int)
+{
+ raise "PDB.readnextmod not implemented";
+}
+
+PDB.write(db: self ref PDB, r: ref Record): string
+{
+ return "PDB.write not implemented";
+}
+
+PDB.truncate(db: self ref PDB): string
+{
+ return "PDB.truncate not implemented";
+}
+
+PDB.delete(db: self ref PDB, id: int): string
+{
+ return "PDB.delete not implemented";
+}
+
+PDB.deletecat(db: self ref PDB, cat: int): string
+{
+ return "PDB.deletecat not implemented";
+}
+
+PDB.purge(db: self ref PDB): string
+{
+ return "PDB.purge not implemented";
+}
+
+PDB.movecat(db: self ref PDB, old: int, new: int): string
+{
+ return "PDB.movecat not implemented";
+}
+
+PRC.read(db: self ref PRC, index: int): ref Resource
+{
+ return nil;
+}
+
+PRC.readtype(db: self ref PRC, name: int, id: int): (ref Resource, int)
+{
+ return (nil, -1);
+}
+
+PRC.write(db: self ref PRC, r: ref Resource): string
+{
+ return "PRC.write not implemented";
+}
+
+PRC.truncate(db: self ref PRC): string
+{
+ return "PRC.truncate not implemented";
+}
+
+PRC.delete(db: self ref PRC, name: int, id: int): string
+{
+ return "PRC.delete not implemented";
+}
+
+#
+# internal function to extend entry list if necessary, and return a
+# pointer to the next available slot
+#
+entryensure(db: ref DB, i: int): ref Entry
+{
+ ofile := files[db.x];
+ if(i < len ofile.entries)
+ return ofile.entries[i];
+ e := ref Entry(0, -1, 0, 0, 0);
+ n := len ofile.entries;
+ if(n == 0)
+ n = 64;
+ else
+ n = (i+63) & ~63;
+ a := array[n] of ref Entry;
+ a[0:] = ofile.entries;
+ a[i] = e;
+ ofile.entries = a;
+ return e;
+}
+
+writefilehdr(db: ref DB, mode: int, perm: int): string
+{
+ ofile := files[db.x];
+ if(len ofile.entries >= 64*1024)
+ return "too many records for Palm file"; # is there a way to extend it?
+
+ if((f := bufio->create(ofile.fname, mode, perm)) == nil)
+ return sys->sprint("%r");
+
+ ip := ofile.info;
+
+ esize := Datahdrsize;
+ if(ip.attr & Palm->Fresource)
+ esize = Resourcehdrsize;
+ offset := Dbhdrlen + esize*len ofile.entries + 2;
+ offset += 2; # placeholder bytes or gap bytes
+ appinfo := 0;
+ if(len ofile.appinfo > 0){
+ appinfo = offset;
+ offset += len ofile.appinfo;
+ }
+ sortinfo := 0;
+ if(len ofile.sortinfo > 0){
+ sortinfo = offset;
+ offset += 2*len ofile.sortinfo; # 2-byte entries
+ }
+ p := array[Dbhdrlen] of byte; # bigger than any entry as well
+ puts(p[0:32], ip.name);
+ put2(p[32:], ip.attr);
+ put2(p[34:], ip.version);
+ put4(p[36:], epoch2pilot(ip.ctime));
+ put4(p[40:], epoch2pilot(ip.mtime));
+ put4(p[44:], epoch2pilot(ip.btime));
+ put4(p[48:], ip.modno);
+ put4(p[52:], appinfo);
+ put4(p[56:], sortinfo);
+ put4(p[60:], sx(ip.dtype));
+ put4(p[64:], sx(ip.creator));
+ put4(p[68:], ofile.uidseed);
+ put4(p[72:], 0); # next record list ID
+ put2(p[76:], len ofile.entries);
+
+ if(f.write(p, Dbhdrlen) != Dbhdrlen)
+ return ewrite(f);
+ if(len ofile.entries > 0){
+ for(i := 0; i < len ofile.entries; i++) {
+ e := ofile.entries[i];
+ e.offset = offset;
+ if(ip.attr & Palm->Fresource) {
+ put4(p, e.name);
+ put2(p[4:], e.id);
+ put4(p[6:], e.offset);
+ } else {
+ put4(p, e.offset);
+ p[4] = byte e.attr;
+ put3(p[5:], e.id);
+ }
+ if(f.write(p, esize) != esize)
+ return ewrite(f);
+ offset += e.size;
+ }
+ }
+
+ f.putb(byte 0); # placeholder bytes (figure 1.4) or gap bytes (p. 15)
+ f.putb(byte 0);
+
+ if(appinfo != 0){
+ if(f.write(ofile.appinfo, len ofile.appinfo) != len ofile.appinfo)
+ return ewrite(f);
+ }
+
+ if(sortinfo != 0){
+ tmp := array[2*len ofile.sortinfo] of byte;
+ for(i := 0; i < len ofile.sortinfo; i++)
+ put2(tmp[2*i:], ofile.sortinfo[i]);
+ if(f.write(tmp, len tmp) != len tmp)
+ return ewrite(f);
+ }
+
+ if(f.flush() != 0)
+ return ewrite(f);
+
+ return nil;
+}
+
+ewrite(f: ref Iobuf): string
+{
+ e := sys->sprint("write error: %r");
+ f.close();
+ return e;
+}
+
+xs(i: int): string
+{
+ if(i == 0)
+ return "";
+ if(i & int 16r80808080)
+ return sys->sprint("%8.8ux", i);
+ return sys->sprint("%c%c%c%c", (i>>24)&16rFF, (i>>16)&16rFF, (i>>8)&16rFF, i&16rFF);
+}
+
+sx(s: string): int
+{
+ n := 0;
+ for(i := 0; i < 4; i++){
+ c := 0;
+ if(i < len s)
+ c = s[i] & 16rFF;
+ n = (n<<8) | c;
+ }
+ return n;
+}
+
+mkpfile(name: string, mode: int): (ref DB, ref Ofile, int)
+{
+ ofile := ref Ofile(name, nil, mode, DBInfo.new(name, 0, nil, 0, nil),
+ array[0] of byte, array[0] of int, 0, nil);
+ for(x := 0; x < len files; x++)
+ if(files[x] == nil)
+ return (ref DB(x, mode, 0), ofile, x);
+ a := array[x] of ref Ofile;
+ a[0:] = files;
+ files = a;
+ return (ref DB(x, mode, 0), ofile, x);
+}
+
+#
+# because PalmOS treats all times as local times, and doesn't associate
+# them with time zones, we'll convert using local time on Plan 9 and Inferno
+#
+
+pilot2epoch(t: int): int
+{
+ if(t == 0)
+ return 0; # we'll assume it's not set
+ return t - Epochdelta + tzoff;
+}
+
+epoch2pilot(t: int): int
+{
+ if(t == 0)
+ return t;
+ return t - tzoff + Epochdelta;
+}
+
+#
+# map Palm name to string, assuming iso-8859-1,
+# but remap space and /
+#
+latin1(a: array of byte, remap: int): string
+{
+ s := "";
+ for(i := 0; i < len a; i++){
+ c := int a[i];
+ if(c == 0)
+ break;
+ if(remap){
+ if(c == ' ')
+ c = 16r00A0; # unpaddable space
+ else if(c == '/')
+ c = 16r2215; # division /
+ }
+ s[len s] = c;
+ }
+ return s;
+}