implement Palmfile; ## 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 "palmfile.m"; 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(): 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"; tzoff = daytime->local(0).tzoff; return nil;} Eshort: con "file format error: too small"; Pfile.open(name: string, mode: int): (ref Pfile, string){ if(mode != Sys->OREAD) return (nil, "invalid mode"); fd := sys->open(name, mode); if(fd == nil) return (nil, sys->sprint("%r")); pf := mkpfile(name, mode); (ok, d) := sys->fstat(fd); if(ok < 0) return (nil, sys->sprint("%r")); length := int d.length; if(length == 0) return (nil, "empty file"); 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 := pf.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:]); ip.appinfo = get4(p[52:]); ip.sortinfo = get4(p[56:]); if(ip.appinfo < 0 || ip.sortinfo < 0 || (ip.appinfo|ip.sortinfo)&1) return (nil, "invalid header: bad offset"); ip.dtype = xs(get4(p[60:])); ip.creator = xs(get4(p[64:])); pf.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 & Fresource) esize = Resourcehdrsize; dataoffset := length; pf.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 & 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; pf.entries[i] = e; } if(laste != nil) laste.size = length - laste.offset; dataoffset = pf.entries[0].offset; }else{ if(f.read(p, 2) != 2) return (nil, Eshort); # discard placeholder bytes } n := 0; if(ip.appinfo > 0){ n = ip.appinfo - int f.offset(); while(--n >= 0) f.getb(); if(ip.sortinfo) n = ip.sortinfo - ip.appinfo; else n = dataoffset - ip.appinfo; pf.appinfo = array[n] of byte; if(f.read(pf.appinfo, n) != n) return (nil, Eshort); } if(ip.sortinfo > 0){ n = ip.sortinfo - int f.offset(); while(--n >= 0) f.getb(); n = (dataoffset-ip.sortinfo)/2; pf.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++) pf.sortinfo[i] = get2(tmp[2*i:]); } pf.f = f; # safe to save open file reference return (pf, nil);} Pfile.close(pf: self ref Pfile): int{ if(pf.f != nil){ pf.f.close(); pf.f = nil; } return 0;} Pfile.stat(pf: self ref Pfile): ref DBInfo{ return ref *pf.info;} Pfile.read(pf: self ref Pfile, i: int): (ref Record, string){ if(i < 0 || i >= len pf.entries){ if(i == len pf.entries) return (nil, nil); # treat as end-of-file return (nil, "index out of range"); } e := pf.entries[i]; r := ref Record; r.index = i; nb := e.size; r.data = array[nb] of byte; pf.f.seek(big e.offset, 0); if(pf.f.read(r.data, nb) != nb) return (nil, sys->sprint("%r")); r.cat = e.attr & 16r0F; r.attr = e.attr & 16rF0; r.id = e.id; r.name = e.name; return (r, nil);} #Pfile.create(name: string, info: ref DBInfo): ref Pfile#{#} #Pfile.wstat(pf: self ref Pfile, ip: ref DBInfo): string#{# if(pf.mode != Sys->OWRITE)# return "not open for writing";# if((ip.attr & Fresource) != (pf.info.attr & Fresource))# return "cannot change file type";# # copy only a subset# pf.info.name = ip.name;# pf.info.attr = ip.attr;# pf.info.version = ip.version;# pf.info.ctime = ip.ctime;# pf.info.mtime = ip.mtime;# pf.info.btime = ip.btime;# pf.info.modno = ip.modno;# pf.info.dtype = ip.dtype;# pf.info.creator = ip.creator;# return nil;#} #Pfile.setappinfo(pf: self ref Pfile, data: array of byte): string#{# if(pf.mode != Sys->OWRITE)# return "not open for writing";# pf.appinfo = array[len data] of byte;# pf.appinfo[0:] = data;#} #Pfile.setsortinfo(pf: self ref Pfile, sort: array of int): string#{# if(pf.mode != Sys->OWRITE)# return "not open for writing";# pf.sortinfo = array[len sort] of int;# pf.sortinfo[0:] = sort;#} ## internal function to extend entry list if necessary, and return a# pointer to the next available slot#entryensure(pf: ref Pfile, i: int): ref Entry{ if(i < len pf.entries) return pf.entries[i]; e := ref Entry(0, -1, 0, 0, 0); n := len pf.entries; if(n == 0) n = 64; else n = (i+63) & ~63; a := array[n] of ref Entry; a[0:] = pf.entries; a[i] = e; pf.entries = a; return e;} writefilehdr(pf: ref Pfile, mode: int, perm: int): string{ if(len pf.entries >= 64*1024) return "too many records for Palm file"; # is there a way to extend it? if((f := bufio->create(pf.fname, mode, perm)) == nil) return sys->sprint("%r"); ip := pf.info; esize := Datahdrsize; if(ip.attr & Fresource) esize = Resourcehdrsize; offset := Dbhdrlen + esize*len pf.entries + 2; offset += 2; # placeholder bytes or gap bytes ip.appinfo = 0; if(len pf.appinfo > 0){ ip.appinfo = offset; offset += len pf.appinfo; } ip.sortinfo = 0; if(len pf.sortinfo > 0){ ip.sortinfo = offset; offset += 2*len pf.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:], ip.appinfo); put4(p[56:], ip.sortinfo); put4(p[60:], sx(ip.dtype)); put4(p[64:], sx(ip.creator)); put4(p[68:], pf.uidseed); put4(p[72:], 0); # next record list ID put2(p[76:], len pf.entries); if(f.write(p, Dbhdrlen) != Dbhdrlen) return ewrite(f); if(len pf.entries > 0){ for(i := 0; i < len pf.entries; i++) { e := pf.entries[i]; e.offset = offset; if(ip.attr & 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(ip.appinfo != 0){ if(f.write(pf.appinfo, len pf.appinfo) != len pf.appinfo) return ewrite(f); } if(ip.sortinfo != 0){ tmp := array[2*len pf.sortinfo] of byte; for(i := 0; i < len pf.sortinfo; i++) put2(tmp[2*i:], pf.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;} Doc.open(file: ref Pfile): (ref Doc, string){ if(file.info.dtype != "TEXt" || file.info.creator != "REAd") return (nil, "not a Doc file: wrong type or creator"); (r, err) := file.read(0); if(r == nil){ if(err == nil) err = "no directory record"; return (nil, sys->sprint("not a valid Doc file: %s", err)); } a := r.data; if(len a < 16) return (nil, sys->sprint("not a valid Doc file: bad length: %d", len a)); maxrec := len file.entries-1; d := ref Doc; d.file = file; d.version = get2(a); if(d.version != 1 && d.version != 2) err = "unknown Docfile version"; # a[2:] is spare d.length = get4(a[4:]); d.nrec = get2(a[8:]); if(maxrec >= 0 && d.nrec > maxrec){ d.nrec = maxrec; err = "invalid record count"; } d.recsize = get2(a[10:]); d.position = get4(a[12:]); return (d, sys->sprint("unexpected Doc file format: %s", err));} Doc.iscompressed(d: self ref Doc): int{ return (d.version&7) == 2; # high-order bits are sometimes used, ignore them} Doc.read(doc: self ref Doc, index: int): (string, string){ (r, err) := doc.file.read(index+1); if(r == nil) return (nil, err); (s, serr) := doc.unpacktext(r.data); if(s == nil) return (nil, serr); return (s, nil);} Doc.unpacktext(doc: self ref Doc, a: array of byte): (string, string){ nb := len a; s: string; if(!doc.iscompressed()){ for(i := 0; i < nb; i++) s[len s] = int a[i]; # assumes Latin-1 return (s, nil); } o := 0; for(i := 0; i < nb;){ c := int a[i++]; if(c >= 9 && c <= 16r7F || c == 0) s[o++] = c; else if(c >= 1 && c <= 8){ if(i+c > nb) return (nil, "missing data in record"); while(--c >= 0) s[o++] = int a[i++]; }else if(c >= 16rC0 && c <= 16rFF){ s[o] = ' '; s[o+1] = c & 16r7F; o += 2; }else{ # c >= 0x80 && c <= 16rBF v := int a[i++]; m := ((c & 16r3F)<<5)|(v>>3); n := (v&7) + 3; if(m == 0 || m > o) return (nil, sys->sprint("data is corrupt: m=%d n=%d o=%d", m, n, o)); for(; --n >= 0; o++) s[o] = s[o-m]; } } return (s, nil);} Doc.textlength(doc: self ref Doc, a: array of byte): int{ nb := len a; if(!doc.iscompressed()) return nb; o := 0; for(i := 0; i < nb;){ c := int a[i++]; if(c >= 9 && c <= 16r7F || c == 0) o++; else if(c >= 1 && c <= 8){ if(i+c > nb) return -1; o += c; i += c; }else if(c >= 16rC0 && c <= 16rFF){ o += 2; }else{ # c >= 0x80 && c <= 16rBF v := int a[i++]; m := ((c & 16r3F)<<5)|(v>>3); n := (v&7) + 3; if(m == 0 || m > o) return -1; o += n; } } return o;} 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 Pfile{ pf := ref Pfile; pf.mode = mode; pf.fname = name; pf.appinfo = array[0] of byte; # making it non-nil saves having to check each access pf.sortinfo = array[0] of int; pf.uidseed = 0; pf.info = DBInfo.new(name, 0, nil, 0, nil); return pf;} DBInfo.new(name: string, attr: int, dtype: string, version: int, creator: string): ref DBInfo{ info := ref DBInfo; info.name = name; info.attr = attr; info.version = version; info.ctime = daytime->now(); info.mtime = daytime->now(); info.btime = 0; info.modno = 0; info.appinfo = 0; info.sortinfo = 0; info.dtype = dtype; info.creator = creator;Lines 1–500 of 703. Load more
implement Palmfile; # # 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 "palmfile.m"; 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(): 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"; tzoff = daytime->local(0).tzoff; return nil; } Eshort: con "file format error: too small"; Pfile.open(name: string, mode: int): (ref Pfile, string) { if(mode != Sys->OREAD) return (nil, "invalid mode"); fd := sys->open(name, mode); if(fd == nil) return (nil, sys->sprint("%r")); pf := mkpfile(name, mode); (ok, d) := sys->fstat(fd); if(ok < 0) return (nil, sys->sprint("%r")); length := int d.length; if(length == 0) return (nil, "empty file"); 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 := pf.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:]); ip.appinfo = get4(p[52:]); ip.sortinfo = get4(p[56:]); if(ip.appinfo < 0 || ip.sortinfo < 0 || (ip.appinfo|ip.sortinfo)&1) return (nil, "invalid header: bad offset"); ip.dtype = xs(get4(p[60:])); ip.creator = xs(get4(p[64:])); pf.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 & Fresource) esize = Resourcehdrsize; dataoffset := length; pf.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 & 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; pf.entries[i] = e; } if(laste != nil) laste.size = length - laste.offset; dataoffset = pf.entries[0].offset; }else{ if(f.read(p, 2) != 2) return (nil, Eshort); # discard placeholder bytes } n := 0; if(ip.appinfo > 0){ n = ip.appinfo - int f.offset(); while(--n >= 0) f.getb(); if(ip.sortinfo) n = ip.sortinfo - ip.appinfo; else n = dataoffset - ip.appinfo; pf.appinfo = array[n] of byte; if(f.read(pf.appinfo, n) != n) return (nil, Eshort); } if(ip.sortinfo > 0){ n = ip.sortinfo - int f.offset(); while(--n >= 0) f.getb(); n = (dataoffset-ip.sortinfo)/2; pf.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++) pf.sortinfo[i] = get2(tmp[2*i:]); } pf.f = f; # safe to save open file reference return (pf, nil); } Pfile.close(pf: self ref Pfile): int { if(pf.f != nil){ pf.f.close(); pf.f = nil; } return 0; } Pfile.stat(pf: self ref Pfile): ref DBInfo { return ref *pf.info; } Pfile.read(pf: self ref Pfile, i: int): (ref Record, string) { if(i < 0 || i >= len pf.entries){ if(i == len pf.entries) return (nil, nil); # treat as end-of-file return (nil, "index out of range"); } e := pf.entries[i]; r := ref Record; r.index = i; nb := e.size; r.data = array[nb] of byte; pf.f.seek(big e.offset, 0); if(pf.f.read(r.data, nb) != nb) return (nil, sys->sprint("%r")); r.cat = e.attr & 16r0F; r.attr = e.attr & 16rF0; r.id = e.id; r.name = e.name; return (r, nil); } #Pfile.create(name: string, info: ref DBInfo): ref Pfile #{ #} #Pfile.wstat(pf: self ref Pfile, ip: ref DBInfo): string #{ # if(pf.mode != Sys->OWRITE) # return "not open for writing"; # if((ip.attr & Fresource) != (pf.info.attr & Fresource)) # return "cannot change file type"; # # copy only a subset # pf.info.name = ip.name; # pf.info.attr = ip.attr; # pf.info.version = ip.version; # pf.info.ctime = ip.ctime; # pf.info.mtime = ip.mtime; # pf.info.btime = ip.btime; # pf.info.modno = ip.modno; # pf.info.dtype = ip.dtype; # pf.info.creator = ip.creator; # return nil; #} #Pfile.setappinfo(pf: self ref Pfile, data: array of byte): string #{ # if(pf.mode != Sys->OWRITE) # return "not open for writing"; # pf.appinfo = array[len data] of byte; # pf.appinfo[0:] = data; #} #Pfile.setsortinfo(pf: self ref Pfile, sort: array of int): string #{ # if(pf.mode != Sys->OWRITE) # return "not open for writing"; # pf.sortinfo = array[len sort] of int; # pf.sortinfo[0:] = sort; #} # # internal function to extend entry list if necessary, and return a # pointer to the next available slot # entryensure(pf: ref Pfile, i: int): ref Entry { if(i < len pf.entries) return pf.entries[i]; e := ref Entry(0, -1, 0, 0, 0); n := len pf.entries; if(n == 0) n = 64; else n = (i+63) & ~63; a := array[n] of ref Entry; a[0:] = pf.entries; a[i] = e; pf.entries = a; return e; } writefilehdr(pf: ref Pfile, mode: int, perm: int): string { if(len pf.entries >= 64*1024) return "too many records for Palm file"; # is there a way to extend it? if((f := bufio->create(pf.fname, mode, perm)) == nil) return sys->sprint("%r"); ip := pf.info; esize := Datahdrsize; if(ip.attr & Fresource) esize = Resourcehdrsize; offset := Dbhdrlen + esize*len pf.entries + 2; offset += 2; # placeholder bytes or gap bytes ip.appinfo = 0; if(len pf.appinfo > 0){ ip.appinfo = offset; offset += len pf.appinfo; } ip.sortinfo = 0; if(len pf.sortinfo > 0){ ip.sortinfo = offset; offset += 2*len pf.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:], ip.appinfo); put4(p[56:], ip.sortinfo); put4(p[60:], sx(ip.dtype)); put4(p[64:], sx(ip.creator)); put4(p[68:], pf.uidseed); put4(p[72:], 0); # next record list ID put2(p[76:], len pf.entries); if(f.write(p, Dbhdrlen) != Dbhdrlen) return ewrite(f); if(len pf.entries > 0){ for(i := 0; i < len pf.entries; i++) { e := pf.entries[i]; e.offset = offset; if(ip.attr & 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(ip.appinfo != 0){ if(f.write(pf.appinfo, len pf.appinfo) != len pf.appinfo) return ewrite(f); } if(ip.sortinfo != 0){ tmp := array[2*len pf.sortinfo] of byte; for(i := 0; i < len pf.sortinfo; i++) put2(tmp[2*i:], pf.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; } Doc.open(file: ref Pfile): (ref Doc, string) { if(file.info.dtype != "TEXt" || file.info.creator != "REAd") return (nil, "not a Doc file: wrong type or creator"); (r, err) := file.read(0); if(r == nil){ if(err == nil) err = "no directory record"; return (nil, sys->sprint("not a valid Doc file: %s", err)); } a := r.data; if(len a < 16) return (nil, sys->sprint("not a valid Doc file: bad length: %d", len a)); maxrec := len file.entries-1; d := ref Doc; d.file = file; d.version = get2(a); if(d.version != 1 && d.version != 2) err = "unknown Docfile version"; # a[2:] is spare d.length = get4(a[4:]); d.nrec = get2(a[8:]); if(maxrec >= 0 && d.nrec > maxrec){ d.nrec = maxrec; err = "invalid record count"; } d.recsize = get2(a[10:]); d.position = get4(a[12:]); return (d, sys->sprint("unexpected Doc file format: %s", err)); } Doc.iscompressed(d: self ref Doc): int { return (d.version&7) == 2; # high-order bits are sometimes used, ignore them } Doc.read(doc: self ref Doc, index: int): (string, string) { (r, err) := doc.file.read(index+1); if(r == nil) return (nil, err); (s, serr) := doc.unpacktext(r.data); if(s == nil) return (nil, serr); return (s, nil); } Doc.unpacktext(doc: self ref Doc, a: array of byte): (string, string) { nb := len a; s: string; if(!doc.iscompressed()){ for(i := 0; i < nb; i++) s[len s] = int a[i]; # assumes Latin-1 return (s, nil); } o := 0; for(i := 0; i < nb;){ c := int a[i++]; if(c >= 9 && c <= 16r7F || c == 0) s[o++] = c; else if(c >= 1 && c <= 8){ if(i+c > nb) return (nil, "missing data in record"); while(--c >= 0) s[o++] = int a[i++]; }else if(c >= 16rC0 && c <= 16rFF){ s[o] = ' '; s[o+1] = c & 16r7F; o += 2; }else{ # c >= 0x80 && c <= 16rBF v := int a[i++]; m := ((c & 16r3F)<<5)|(v>>3); n := (v&7) + 3; if(m == 0 || m > o) return (nil, sys->sprint("data is corrupt: m=%d n=%d o=%d", m, n, o)); for(; --n >= 0; o++) s[o] = s[o-m]; } } return (s, nil); } Doc.textlength(doc: self ref Doc, a: array of byte): int { nb := len a; if(!doc.iscompressed()) return nb; o := 0; for(i := 0; i < nb;){ c := int a[i++]; if(c >= 9 && c <= 16r7F || c == 0) o++; else if(c >= 1 && c <= 8){ if(i+c > nb) return -1; o += c; i += c; }else if(c >= 16rC0 && c <= 16rFF){ o += 2; }else{ # c >= 0x80 && c <= 16rBF v := int a[i++]; m := ((c & 16r3F)<<5)|(v>>3); n := (v&7) + 3; if(m == 0 || m > o) return -1; o += n; } } return o; } 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 Pfile { pf := ref Pfile; pf.mode = mode; pf.fname = name; pf.appinfo = array[0] of byte; # making it non-nil saves having to check each access pf.sortinfo = array[0] of int; pf.uidseed = 0; pf.info = DBInfo.new(name, 0, nil, 0, nil); return pf; } DBInfo.new(name: string, attr: int, dtype: string, version: int, creator: string): ref DBInfo { info := ref DBInfo; info.name = name; info.attr = attr; info.version = version; info.ctime = daytime->now(); info.mtime = daytime->now(); info.btime = 0; info.modno = 0; info.appinfo = 0; info.sortinfo = 0; info.dtype = dtype; info.creator = creator;