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