Charles.Forsyth20060303a828770d19 years agoHistory
703 lines · 14.9 KB · Brainfuck
RawDownload
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.