|
1 # Subclass of Archive that can be understood by a C program (see launch.c). |
|
2 # Copyright (C) 2005, Giovanni Bajo |
|
3 # Based on previous work under copyright (c) 1999, 2002 McMillan Enterprises, Inc. |
|
4 # |
|
5 # This program is free software; you can redistribute it and/or |
|
6 # modify it under the terms of the GNU General Public License |
|
7 # as published by the Free Software Foundation; either version 2 |
|
8 # of the License, or (at your option) any later version. |
|
9 # |
|
10 # This program is distributed in the hope that it will be useful, |
|
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
13 # GNU General Public License for more details. |
|
14 # |
|
15 # You should have received a copy of the GNU General Public License |
|
16 # along with this program; if not, write to the Free Software |
|
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA |
|
18 import archive |
|
19 import struct |
|
20 try: |
|
21 import zlib |
|
22 except ImportError: |
|
23 zlib = archive.DummyZlib() |
|
24 import sys |
|
25 if sys.version[0] == '1': |
|
26 import strop |
|
27 find = strop.find |
|
28 split = strop.split |
|
29 else: |
|
30 def find(s, sub): |
|
31 return s.find(sub) |
|
32 def split(s, delim, count): |
|
33 return s.split(delim, count) |
|
34 |
|
35 class CTOC: |
|
36 """A class encapsulating the table of contents of a CArchive. |
|
37 |
|
38 When written to disk, it is easily read from C.""" |
|
39 ENTRYSTRUCT = '!iiiibc' #(structlen, dpos, dlen, ulen, flag, typcd) followed by name |
|
40 def __init__(self): |
|
41 self.data = [] |
|
42 |
|
43 def frombinary(self, s): |
|
44 """Decode the binary string into an in memory list. |
|
45 |
|
46 S is a binary string.""" |
|
47 entrylen = struct.calcsize(self.ENTRYSTRUCT) |
|
48 p = 0 |
|
49 while p<len(s): |
|
50 (slen, dpos, dlen, ulen, flag, typcd) = struct.unpack(self.ENTRYSTRUCT, |
|
51 s[p:p+entrylen]) |
|
52 nmlen = slen - entrylen |
|
53 p = p + entrylen |
|
54 (nm,) = struct.unpack(`nmlen`+'s', s[p:p+nmlen]) |
|
55 p = p + nmlen |
|
56 # version 4 |
|
57 # self.data.append((dpos, dlen, ulen, flag, typcd, nm[:-1])) |
|
58 # version 5 |
|
59 # nm may have up to 15 bytes of padding |
|
60 pos = find(nm, '\0') |
|
61 if pos < 0: |
|
62 self.data.append((dpos, dlen, ulen, flag, typcd, nm)) |
|
63 else: |
|
64 self.data.append((dpos, dlen, ulen, flag, typcd, nm[:pos])) |
|
65 #end version 5 |
|
66 |
|
67 |
|
68 def tobinary(self): |
|
69 """Return self as a binary string.""" |
|
70 import string |
|
71 entrylen = struct.calcsize(self.ENTRYSTRUCT) |
|
72 rslt = [] |
|
73 for (dpos, dlen, ulen, flag, typcd, nm) in self.data: |
|
74 nmlen = len(nm) + 1 # add 1 for a '\0' |
|
75 # version 4 |
|
76 # rslt.append(struct.pack(self.ENTRYSTRUCT+`nmlen`+'s', |
|
77 # nmlen+entrylen, dpos, dlen, ulen, flag, typcd, nm+'\0')) |
|
78 # version 5 |
|
79 # align to 16 byte boundary so xplatform C can read |
|
80 toclen = nmlen + entrylen |
|
81 if toclen % 16 == 0: |
|
82 pad = '\0' |
|
83 else: |
|
84 padlen = 16 - (toclen % 16) |
|
85 pad = '\0'*padlen |
|
86 nmlen = nmlen + padlen |
|
87 rslt.append(struct.pack(self.ENTRYSTRUCT+`nmlen`+'s', |
|
88 nmlen+entrylen, dpos, dlen, ulen, flag, typcd, nm+pad)) |
|
89 # end version 5 |
|
90 |
|
91 return string.join(rslt, '') |
|
92 |
|
93 def add(self, dpos, dlen, ulen, flag, typcd, nm): |
|
94 """Add an entry to the table of contents. |
|
95 |
|
96 DPOS is data position. |
|
97 DLEN is data length. |
|
98 ULEN is the uncompressed data len. |
|
99 FLAG says if the data is compressed. |
|
100 TYPCD is the "type" of the entry (used by the C code) |
|
101 NM is the entry's name.""" |
|
102 self.data.append((dpos, dlen, ulen, flag, typcd, nm)) |
|
103 |
|
104 def get(self, ndx): |
|
105 """return the toc entry (tuple) at index NDX""" |
|
106 return self.data[ndx] |
|
107 |
|
108 def __getitem__(self, ndx): |
|
109 return self.data[ndx] |
|
110 |
|
111 def find(self, name): |
|
112 """Return the index of the toc entry with name NAME. |
|
113 |
|
114 Return -1 for failure.""" |
|
115 for i in range(len(self.data)): |
|
116 if self.data[i][-1] == name: |
|
117 return i |
|
118 return -1 |
|
119 |
|
120 class CArchive(archive.Archive): |
|
121 """An Archive subclass that an hold arbitrary data. |
|
122 |
|
123 Easily handled from C or from Python.""" |
|
124 MAGIC = 'MEI\014\013\012\013\016' |
|
125 HDRLEN = 0 |
|
126 TOCTMPLT = CTOC |
|
127 TRLSTRUCT = '!8siiii' |
|
128 TRLLEN = 24 |
|
129 LEVEL = 9 |
|
130 def __init__(self, path=None, start=0, len=0): |
|
131 """Constructor. |
|
132 |
|
133 PATH is path name of file (create an empty CArchive if path is None). |
|
134 START is the seekposition within PATH. |
|
135 LEN is the length of the CArchive (if 0, then read till EOF). """ |
|
136 self.len = len |
|
137 archive.Archive.__init__(self, path, start) |
|
138 |
|
139 def checkmagic(self): |
|
140 """Verify that self is a valid CArchive. |
|
141 |
|
142 Magic signature is at end of the archive.""" |
|
143 #magic is at EOF; if we're embedded, we need to figure where that is |
|
144 if self.len: |
|
145 self.lib.seek(self.start+self.len, 0) |
|
146 else: |
|
147 self.lib.seek(0, 2) |
|
148 filelen = self.lib.tell() |
|
149 if self.len: |
|
150 self.lib.seek(self.start+self.len-self.TRLLEN, 0) |
|
151 else: |
|
152 self.lib.seek(-self.TRLLEN, 2) |
|
153 (magic, totallen, tocpos, toclen, pyvers) = struct.unpack(self.TRLSTRUCT, |
|
154 self.lib.read(self.TRLLEN)) |
|
155 if magic != self.MAGIC: |
|
156 raise RuntimeError, "%s is not a valid %s archive file" \ |
|
157 % (self.path, self.__class__.__name__) |
|
158 self.pkgstart = filelen - totallen |
|
159 if self.len: |
|
160 if totallen != self.len or self.pkgstart != self.start: |
|
161 raise RuntimeError, "Problem with embedded archive in %s" % self.path |
|
162 self.tocpos, self.toclen = tocpos, toclen |
|
163 |
|
164 def loadtoc(self): |
|
165 """Load the table of contents into memory.""" |
|
166 self.toc = self.TOCTMPLT() |
|
167 self.lib.seek(self.pkgstart+self.tocpos) |
|
168 tocstr = self.lib.read(self.toclen) |
|
169 self.toc.frombinary(tocstr) |
|
170 |
|
171 def extract(self, name): |
|
172 """Get the contents of an entry. |
|
173 |
|
174 NAME is an entry name. |
|
175 Return the tuple (ispkg, contents). |
|
176 For non-Python resoures, ispkg is meaningless (and 0). |
|
177 Used by the import mechanism.""" |
|
178 if type(name) == type(''): |
|
179 ndx = self.toc.find(name) |
|
180 if ndx == -1: |
|
181 return None |
|
182 else: |
|
183 ndx = name |
|
184 (dpos, dlen, ulen, flag, typcd, nm) = self.toc.get(ndx) |
|
185 self.lib.seek(self.pkgstart+dpos) |
|
186 rslt = self.lib.read(dlen) |
|
187 if flag == 1: |
|
188 rslt = zlib.decompress(rslt) |
|
189 if typcd == 'M': |
|
190 return (1, rslt) |
|
191 return (0, rslt) |
|
192 |
|
193 def contents(self): |
|
194 """Return the names of the entries""" |
|
195 rslt = [] |
|
196 for (dpos, dlen, ulen, flag, typcd, nm) in self.toc: |
|
197 rslt.append(nm) |
|
198 return rslt |
|
199 |
|
200 def add(self, entry): |
|
201 """Add an ENTRY to the CArchive. |
|
202 |
|
203 ENTRY must have: |
|
204 entry[0] is name (under which it will be saved). |
|
205 entry[1] is fullpathname of the file. |
|
206 entry[2] is a flag for it's storage format (0==uncompressed, |
|
207 1==compressed) |
|
208 entry[3] is the entry's type code. |
|
209 Version 5: |
|
210 If the type code is 'o': |
|
211 entry[0] is the runtime option |
|
212 eg: v (meaning verbose imports) |
|
213 u (menaing unbuffered) |
|
214 W arg (warning option arg) |
|
215 s (meaning do site.py processing.""" |
|
216 (nm, pathnm, flag, typcd) = entry[:4] |
|
217 # version 5 - allow type 'o' = runtime option |
|
218 try: |
|
219 if typcd == 'o': |
|
220 s = '' |
|
221 flag = 0 |
|
222 elif typcd == 's': |
|
223 # If it's a source code file, add \0 terminator as it will be |
|
224 # executed as-is by the bootloader. |
|
225 s = open(pathnm, 'r').read() |
|
226 s = s + '\n\0' |
|
227 else: |
|
228 s = open(pathnm, 'rb').read() |
|
229 except IOError: |
|
230 print "Cannot find ('%s', '%s', %s, '%s')" % (nm, pathnm, flag, typcd) |
|
231 raise |
|
232 ulen = len(s) |
|
233 if flag == 1: |
|
234 s = zlib.compress(s, self.LEVEL) |
|
235 dlen = len(s) |
|
236 where = self.lib.tell() |
|
237 if typcd == 'm': |
|
238 if find(pathnm, '.__init__.py') > -1: |
|
239 typcd = 'M' |
|
240 self.toc.add(where, dlen, ulen, flag, typcd, nm) |
|
241 self.lib.write(s) |
|
242 |
|
243 def save_toc(self, tocpos): |
|
244 """Save the table of contents to disk.""" |
|
245 self.tocpos = tocpos |
|
246 tocstr = self.toc.tobinary() |
|
247 self.toclen = len(tocstr) |
|
248 self.lib.write(tocstr) |
|
249 |
|
250 def save_trailer(self, tocpos): |
|
251 """Save the trailer to disk. |
|
252 |
|
253 CArchives can be opened from the end - the trailer points |
|
254 back to the start. """ |
|
255 totallen = tocpos + self.toclen + self.TRLLEN |
|
256 if hasattr(sys, "version_info"): |
|
257 pyvers = sys.version_info[0]*10 + sys.version_info[1] |
|
258 else: |
|
259 toks = split(sys.version, '.', 2) |
|
260 pyvers = int(toks[0])*10 + int(toks[1]) |
|
261 trl = struct.pack(self.TRLSTRUCT, self.MAGIC, totallen, |
|
262 tocpos, self.toclen, pyvers) |
|
263 self.lib.write(trl) |
|
264 |
|
265 def openEmbedded(self, name): |
|
266 """Open a CArchive of name NAME embedded within this CArchive.""" |
|
267 ndx = self.toc.find(name) |
|
268 if ndx == -1: |
|
269 raise KeyError, "Member '%s' not found in %s" % (name, self.path) |
|
270 (dpos, dlen, ulen, flag, typcd, nm) = self.toc.get(ndx) |
|
271 if flag: |
|
272 raise ValueError, "Cannot open compressed archive %s in place" |
|
273 return CArchive(self.path, self.pkgstart+dpos, dlen) |