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.
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.
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.
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
23
zlib = archive.DummyZlib()
25
if sys.version[0] == '1':
32
def split(s, delim, count):
33
return s.split(delim, count)
36
"""A class encapsulating the table of contents of a CArchive.
38
When written to disk, it is easily read from C."""
39
ENTRYSTRUCT = '!iiiibc' #(structlen, dpos, dlen, ulen, flag, typcd) followed by name
43
def frombinary(self, s):
44
"""Decode the binary string into an in memory list.
46
S is a binary string."""
47
entrylen = struct.calcsize(self.ENTRYSTRUCT)
50
(slen, dpos, dlen, ulen, flag, typcd) = struct.unpack(self.ENTRYSTRUCT,
52
nmlen = slen - entrylen
54
(nm,) = struct.unpack(`nmlen`+'s', s[p:p+nmlen])
57
# self.data.append((dpos, dlen, ulen, flag, typcd, nm[:-1]))
59
# nm may have up to 15 bytes of padding
62
self.data.append((dpos, dlen, ulen, flag, typcd, nm))
64
self.data.append((dpos, dlen, ulen, flag, typcd, nm[:pos]))
69
"""Return self as a binary string."""
71
entrylen = struct.calcsize(self.ENTRYSTRUCT)
73
for (dpos, dlen, ulen, flag, typcd, nm) in self.data:
74
nmlen = len(nm) + 1 # add 1 for a '\0'
76
# rslt.append(struct.pack(self.ENTRYSTRUCT+`nmlen`+'s',
77
# nmlen+entrylen, dpos, dlen, ulen, flag, typcd, nm+'\0'))
79
# align to 16 byte boundary so xplatform C can read
80
toclen = nmlen + entrylen
84
padlen = 16 - (toclen % 16)
86
nmlen = nmlen + padlen
87
rslt.append(struct.pack(self.ENTRYSTRUCT+`nmlen`+'s',
88
nmlen+entrylen, dpos, dlen, ulen, flag, typcd, nm+pad))
91
return string.join(rslt, '')
93
def add(self, dpos, dlen, ulen, flag, typcd, nm):
94
"""Add an entry to the table of contents.
96
DPOS is data position.
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))
105
"""return the toc entry (tuple) at index NDX"""
106
return self.data[ndx]
108
def __getitem__(self, ndx):
109
return self.data[ndx]
111
def find(self, name):
112
"""Return the index of the toc entry with name NAME.
114
Return -1 for failure."""
115
for i in range(len(self.data)):
116
if self.data[i][-1] == name:
120
class CArchive(archive.Archive):
121
"""An Archive subclass that an hold arbitrary data.
123
Easily handled from C or from Python."""
124
MAGIC = 'MEI\014\013\012\013\016'
127
TRLSTRUCT = '!8siiii'
130
def __init__(self, path=None, start=0, len=0):
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). """
137
archive.Archive.__init__(self, path, start)
139
def checkmagic(self):
140
"""Verify that self is a valid CArchive.
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
145
self.lib.seek(self.start+self.len, 0)
148
filelen = self.lib.tell()
150
self.lib.seek(self.start+self.len-self.TRLLEN, 0)
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
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
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)
171
def extract(self, name):
172
"""Get the contents of an entry.
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)
184
(dpos, dlen, ulen, flag, typcd, nm) = self.toc.get(ndx)
185
self.lib.seek(self.pkgstart+dpos)
186
rslt = self.lib.read(dlen)
191
# Note: keep this in sync with bootloader's code
192
rslt = AES.new(key, AES.MODE_CFB, "\0"*AES.block_size).decrypt(rslt[32:])
193
if flag == 1 or flag == 2:
194
rslt = zlib.decompress(rslt)
200
"""Return the names of the entries"""
202
for (dpos, dlen, ulen, flag, typcd, nm) in self.toc:
206
def add(self, entry):
207
"""Add an ENTRY to the CArchive.
210
entry[0] is name (under which it will be saved).
211
entry[1] is fullpathname of the file.
212
entry[2] is a flag for it's storage format (0==uncompressed,
214
entry[3] is the entry's type code.
216
If the type code is 'o':
217
entry[0] is the runtime option
218
eg: v (meaning verbose imports)
219
u (menaing unbuffered)
220
W arg (warning option arg)
221
s (meaning do site.py processing."""
222
(nm, pathnm, flag, typcd) = entry[:4]
223
# version 5 - allow type 'o' = runtime option
229
# If it's a source code file, add \0 terminator as it will be
230
# executed as-is by the bootloader.
231
s = open(pathnm, 'r').read()
232
s = s.replace("\r\n", "\n")
235
s = open(pathnm, 'rb').read()
237
print "Cannot find ('%s', '%s', %s, '%s')" % (nm, pathnm, flag, typcd)
240
assert flag in range(3)
241
if flag == 1 or flag == 2:
242
s = zlib.compress(s, self.LEVEL)
246
key = Crypt.gen_random_key(32)
247
# Note: keep this in sync with bootloader's code
248
s = key + AES.new(key, AES.MODE_CFB, "\0"*AES.block_size).encrypt(s)
250
where = self.lib.tell()
252
if find(pathnm, '.__init__.py') > -1:
254
self.toc.add(where, dlen, ulen, flag, typcd, nm)
257
def save_toc(self, tocpos):
258
"""Save the table of contents to disk."""
260
tocstr = self.toc.tobinary()
261
self.toclen = len(tocstr)
262
self.lib.write(tocstr)
264
def save_trailer(self, tocpos):
265
"""Save the trailer to disk.
267
CArchives can be opened from the end - the trailer points
268
back to the start. """
269
totallen = tocpos + self.toclen + self.TRLLEN
270
if hasattr(sys, "version_info"):
271
pyvers = sys.version_info[0]*10 + sys.version_info[1]
273
toks = split(sys.version, '.', 2)
274
pyvers = int(toks[0])*10 + int(toks[1])
275
trl = struct.pack(self.TRLSTRUCT, self.MAGIC, totallen,
276
tocpos, self.toclen, pyvers)
279
def openEmbedded(self, name):
280
"""Open a CArchive of name NAME embedded within this CArchive."""
281
ndx = self.toc.find(name)
283
raise KeyError, "Member '%s' not found in %s" % (name, self.path)
284
(dpos, dlen, ulen, flag, typcd, nm) = self.toc.get(ndx)
286
raise ValueError, "Cannot open compressed archive %s in place"
287
return CArchive(self.path, self.pkgstart+dpos, dlen)