blob: 263ae115d585ebb0df78ad0b2ddb62bb407fae21 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Doug Zongkerea5d7a92010-09-12 15:26:16 -070015import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070016import errno
Doug Zongkereef39442009-04-02 12:14:19 -070017import getopt
18import getpass
Doug Zongker05d3dea2009-06-22 11:32:31 -070019import imp
Doug Zongkereef39442009-04-02 12:14:19 -070020import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080021import platform
Doug Zongkereef39442009-04-02 12:14:19 -070022import re
23import shutil
24import subprocess
25import sys
26import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070027import threading
28import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070029import zipfile
Doug Zongkereef39442009-04-02 12:14:19 -070030
Doug Zongker55d93282011-01-25 17:03:34 -080031try:
davidcad0bb92011-03-15 14:21:38 +000032 from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080033except ImportError:
davidcad0bb92011-03-15 14:21:38 +000034 from sha import sha as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080035
Doug Zongkereef39442009-04-02 12:14:19 -070036# missing in Python 2.4 and before
37if not hasattr(os, "SEEK_SET"):
38 os.SEEK_SET = 0
39
40class Options(object): pass
41OPTIONS = Options()
Doug Zongker602a84e2009-06-18 08:35:12 -070042OPTIONS.search_path = "out/host/linux-x86"
Doug Zongkereef39442009-04-02 12:14:19 -070043OPTIONS.verbose = False
44OPTIONS.tempfiles = []
Doug Zongker05d3dea2009-06-22 11:32:31 -070045OPTIONS.device_specific = None
Doug Zongker8bec09e2009-11-30 15:37:14 -080046OPTIONS.extras = {}
Doug Zongkerc77a9ad2010-09-16 11:28:43 -070047OPTIONS.info_dict = None
Doug Zongkereef39442009-04-02 12:14:19 -070048
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080049
50# Values for "certificate" in apkcerts that mean special things.
51SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
52
53
Doug Zongkereef39442009-04-02 12:14:19 -070054class ExternalError(RuntimeError): pass
55
56
57def Run(args, **kwargs):
58 """Create and return a subprocess.Popen object, printing the command
59 line on the terminal if -v was specified."""
60 if OPTIONS.verbose:
61 print " running: ", " ".join(args)
62 return subprocess.Popen(args, **kwargs)
63
64
Ying Wang7e6d4e42010-12-13 16:25:36 -080065def CloseInheritedPipes():
66 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
67 before doing other work."""
68 if platform.system() != "Darwin":
69 return
70 for d in range(3, 1025):
71 try:
72 stat = os.fstat(d)
73 if stat is not None:
74 pipebit = stat[0] & 0x1000
75 if pipebit != 0:
76 os.close(d)
77 except OSError:
78 pass
79
80
Doug Zongker37974732010-09-16 17:44:38 -070081def LoadInfoDict(zip):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070082 """Read and parse the META/misc_info.txt key/value pairs from the
83 input target files and return a dict."""
84
85 d = {}
86 try:
Doug Zongker37974732010-09-16 17:44:38 -070087 for line in zip.read("META/misc_info.txt").split("\n"):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070088 line = line.strip()
89 if not line or line.startswith("#"): continue
90 k, v = line.split("=", 1)
91 d[k] = v
Doug Zongker37974732010-09-16 17:44:38 -070092 except KeyError:
93 # ok if misc_info.txt doesn't exist
94 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -070095
Doug Zongker37974732010-09-16 17:44:38 -070096 # backwards compatibility: These values used to be in their own
97 # files. Look for them, in case we're processing an old
98 # target_files zip.
99
100 if "mkyaffs2_extra_flags" not in d:
101 try:
102 d["mkyaffs2_extra_flags"] = zip.read("META/mkyaffs2-extra-flags.txt").strip()
103 except KeyError:
104 # ok if flags don't exist
105 pass
106
107 if "recovery_api_version" not in d:
108 try:
109 d["recovery_api_version"] = zip.read("META/recovery-api-version.txt").strip()
110 except KeyError:
111 raise ValueError("can't find recovery API version in input target-files")
112
113 if "tool_extensions" not in d:
114 try:
115 d["tool_extensions"] = zip.read("META/tool-extensions.txt").strip()
116 except KeyError:
117 # ok if extensions don't exist
118 pass
119
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800120 if "fstab_version" not in d:
121 d["fstab_version"] = "1"
122
Doug Zongker37974732010-09-16 17:44:38 -0700123 try:
124 data = zip.read("META/imagesizes.txt")
125 for line in data.split("\n"):
126 if not line: continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700127 name, value = line.split(" ", 1)
128 if not value: continue
Doug Zongker37974732010-09-16 17:44:38 -0700129 if name == "blocksize":
130 d[name] = value
131 else:
132 d[name + "_size"] = value
133 except KeyError:
134 pass
135
136 def makeint(key):
137 if key in d:
138 d[key] = int(d[key], 0)
139
140 makeint("recovery_api_version")
141 makeint("blocksize")
142 makeint("system_size")
143 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700144 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700145 makeint("recovery_size")
146 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800147 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700148
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800149 d["fstab"] = LoadRecoveryFSTab(zip, d["fstab_version"])
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700150 d["build.prop"] = LoadBuildProp(zip)
151 return d
152
153def LoadBuildProp(zip):
154 try:
155 data = zip.read("SYSTEM/build.prop")
156 except KeyError:
157 print "Warning: could not find SYSTEM/build.prop in %s" % zip
158 data = ""
159
160 d = {}
161 for line in data.split("\n"):
162 line = line.strip()
163 if not line or line.startswith("#"): continue
164 name, value = line.split("=", 1)
165 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700166 return d
167
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800168def LoadRecoveryFSTab(zip, fstab_version):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700169 class Partition(object):
170 pass
171
172 try:
173 data = zip.read("RECOVERY/RAMDISK/etc/recovery.fstab")
174 except KeyError:
Jeff Davidson033fbe22011-10-26 18:08:09 -0700175 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab in %s." % zip
176 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700177
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800178 if fstab_version == 1:
179 d = {}
180 for line in data.split("\n"):
181 line = line.strip()
182 if not line or line.startswith("#"): continue
183 pieces = line.split()
184 if not (3 <= len(pieces) <= 4):
185 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700186
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800187 p = Partition()
188 p.mount_point = pieces[0]
189 p.fs_type = pieces[1]
190 p.device = pieces[2]
191 p.length = 0
192 options = None
193 if len(pieces) >= 4:
194 if pieces[3].startswith("/"):
195 p.device2 = pieces[3]
196 if len(pieces) >= 5:
197 options = pieces[4]
198 else:
199 p.device2 = None
200 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800201 else:
202 p.device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700203
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800204 if options:
205 options = options.split(",")
206 for i in options:
207 if i.startswith("length="):
208 p.length = int(i[7:])
209 else:
210 print "%s: unknown option \"%s\"" % (p.mount_point, i)
211
212 d[p.mount_point] = p
213
214 elif fstab_version == 2:
215 d = {}
216 for line in data.split("\n"):
217 line = line.strip()
218 if not line or line.startswith("#"): continue
219 pieces = line.split()
220 if len(pieces) != 5:
221 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
222
223 # Ignore entries that are managed by vold
224 options = pieces[4]
225 if "voldmanaged=" in options: continue
226
227 # It's a good line, parse it
228 p = Partition()
229 p.device = pieces[0]
230 p.mount_point = pieces[1]
231 p.fs_type = pieces[2]
232 p.device2 = None
233 p.length = 0
234
Doug Zongker086cbb02011-02-17 15:54:20 -0800235 options = options.split(",")
236 for i in options:
237 if i.startswith("length="):
238 p.length = int(i[7:])
239 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800240 # Ignore all unknown options in the unified fstab
241 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800242
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800243 d[p.mount_point] = p
244
245 else:
246 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
247
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700248 return d
249
250
Doug Zongker37974732010-09-16 17:44:38 -0700251def DumpInfoDict(d):
252 for k, v in sorted(d.items()):
253 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700254
Doug Zongkerd5131602012-08-02 14:46:42 -0700255def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700256 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700257 'sourcedir'), and turn them into a boot image. Return the image
258 data, or None if sourcedir does not appear to contains files for
259 building the requested image."""
260
261 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
262 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
263 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700264
Doug Zongkerd5131602012-08-02 14:46:42 -0700265 if info_dict is None:
266 info_dict = OPTIONS.info_dict
267
Doug Zongkereef39442009-04-02 12:14:19 -0700268 ramdisk_img = tempfile.NamedTemporaryFile()
269 img = tempfile.NamedTemporaryFile()
270
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700271 if os.access(fs_config_file, os.F_OK):
272 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
273 else:
274 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
275 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700276 p2 = Run(["minigzip"],
277 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700278
279 p2.wait()
280 p1.wait()
281 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
Doug Zongker32da27a2009-05-29 09:35:56 -0700282 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
Doug Zongkereef39442009-04-02 12:14:19 -0700283
Doug Zongker38a649f2009-06-17 09:07:09 -0700284 cmd = ["mkbootimg", "--kernel", os.path.join(sourcedir, "kernel")]
285
Doug Zongker171f1cd2009-06-15 22:36:37 -0700286 fn = os.path.join(sourcedir, "cmdline")
287 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700288 cmd.append("--cmdline")
289 cmd.append(open(fn).read().rstrip("\n"))
290
291 fn = os.path.join(sourcedir, "base")
292 if os.access(fn, os.F_OK):
293 cmd.append("--base")
294 cmd.append(open(fn).read().rstrip("\n"))
295
Ying Wang4de6b5b2010-08-25 14:29:34 -0700296 fn = os.path.join(sourcedir, "pagesize")
297 if os.access(fn, os.F_OK):
298 cmd.append("--pagesize")
299 cmd.append(open(fn).read().rstrip("\n"))
300
Doug Zongkerd5131602012-08-02 14:46:42 -0700301 args = info_dict.get("mkbootimg_args", None)
302 if args and args.strip():
303 cmd.extend(args.split())
304
Doug Zongker38a649f2009-06-17 09:07:09 -0700305 cmd.extend(["--ramdisk", ramdisk_img.name,
306 "--output", img.name])
307
308 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700309 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700310 assert p.returncode == 0, "mkbootimg of %s image failed" % (
311 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700312
313 img.seek(os.SEEK_SET, 0)
314 data = img.read()
315
316 ramdisk_img.close()
317 img.close()
318
319 return data
320
321
Doug Zongkerd5131602012-08-02 14:46:42 -0700322def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
323 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800324 """Return a File object (with name 'name') with the desired bootable
325 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
326 'prebuilt_name', otherwise construct it from the source files in
327 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700328
Doug Zongker55d93282011-01-25 17:03:34 -0800329 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
330 if os.path.exists(prebuilt_path):
331 print "using prebuilt %s..." % (prebuilt_name,)
332 return File.FromLocalFile(name, prebuilt_path)
333 else:
334 print "building image from target_files %s..." % (tree_subdir,)
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700335 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
336 return File(name, BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
Doug Zongkerd5131602012-08-02 14:46:42 -0700337 os.path.join(unpack_dir, fs_config),
338 info_dict))
Doug Zongker55d93282011-01-25 17:03:34 -0800339
Doug Zongkereef39442009-04-02 12:14:19 -0700340
Doug Zongker75f17362009-12-08 13:46:44 -0800341def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800342 """Unzip the given archive into a temporary directory and return the name.
343
344 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
345 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
346
347 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
348 main file), open for reading.
349 """
Doug Zongkereef39442009-04-02 12:14:19 -0700350
351 tmp = tempfile.mkdtemp(prefix="targetfiles-")
352 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800353
354 def unzip_to_dir(filename, dirname):
355 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
356 if pattern is not None:
357 cmd.append(pattern)
358 p = Run(cmd, stdout=subprocess.PIPE)
359 p.communicate()
360 if p.returncode != 0:
361 raise ExternalError("failed to unzip input target-files \"%s\"" %
362 (filename,))
363
364 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
365 if m:
366 unzip_to_dir(m.group(1), tmp)
367 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
368 filename = m.group(1)
369 else:
370 unzip_to_dir(filename, tmp)
371
372 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700373
374
375def GetKeyPasswords(keylist):
376 """Given a list of keys, prompt the user to enter passwords for
377 those which require them. Return a {key: password} dict. password
378 will be None if the key has no password."""
379
Doug Zongker8ce7c252009-05-22 13:34:54 -0700380 no_passwords = []
381 need_passwords = []
Doug Zongkereef39442009-04-02 12:14:19 -0700382 devnull = open("/dev/null", "w+b")
383 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800384 # We don't need a password for things that aren't really keys.
385 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700386 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700387 continue
388
Doug Zongker602a84e2009-06-18 08:35:12 -0700389 p = Run(["openssl", "pkcs8", "-in", k+".pk8",
390 "-inform", "DER", "-nocrypt"],
391 stdin=devnull.fileno(),
392 stdout=devnull.fileno(),
393 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700394 p.communicate()
395 if p.returncode == 0:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700396 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700397 else:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700398 need_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700399 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700400
401 key_passwords = PasswordManager().GetPasswords(need_passwords)
402 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700403 return key_passwords
404
405
Doug Zongker951495f2009-08-14 12:44:19 -0700406def SignFile(input_name, output_name, key, password, align=None,
407 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700408 """Sign the input_name zip/jar/apk, producing output_name. Use the
409 given key and password (the latter may be None if the key does not
410 have a password.
411
412 If align is an integer > 1, zipalign is run to align stored files in
413 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700414
415 If whole_file is true, use the "-w" option to SignApk to embed a
416 signature that covers the whole file in the archive comment of the
417 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700418 """
Doug Zongker951495f2009-08-14 12:44:19 -0700419
Doug Zongkereef39442009-04-02 12:14:19 -0700420 if align == 0 or align == 1:
421 align = None
422
423 if align:
424 temp = tempfile.NamedTemporaryFile()
425 sign_name = temp.name
426 else:
427 sign_name = output_name
428
Doug Zongkerca855e92011-02-23 09:25:01 -0800429 cmd = ["java", "-Xmx2048m", "-jar",
Doug Zongker951495f2009-08-14 12:44:19 -0700430 os.path.join(OPTIONS.search_path, "framework", "signapk.jar")]
431 if whole_file:
432 cmd.append("-w")
433 cmd.extend([key + ".x509.pem", key + ".pk8",
434 input_name, sign_name])
435
436 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700437 if password is not None:
438 password += "\n"
439 p.communicate(password)
440 if p.returncode != 0:
441 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
442
443 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700444 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700445 p.communicate()
446 if p.returncode != 0:
447 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
448 temp.close()
449
450
Doug Zongker37974732010-09-16 17:44:38 -0700451def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700452 """Check the data string passed against the max size limit, if
453 any, for the given target. Raise exception if the data is too big.
454 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700455
Doug Zongker1684d9c2010-09-17 07:44:38 -0700456 if target.endswith(".img"): target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700457 mount_point = "/" + target
458
459 if info_dict["fstab"]:
460 if mount_point == "/userdata": mount_point = "/data"
461 p = info_dict["fstab"][mount_point]
462 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800463 device = p.device
464 if "/" in device:
465 device = device[device.rfind("/")+1:]
466 limit = info_dict.get(device + "_size", None)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700467 if not fs_type or not limit: return
Doug Zongkereef39442009-04-02 12:14:19 -0700468
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700469 if fs_type == "yaffs2":
470 # image size should be increased by 1/64th to account for the
471 # spare area (64 bytes per 2k page)
472 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800473 size = len(data)
474 pct = float(size) * 100.0 / limit
475 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
476 if pct >= 99.0:
477 raise ExternalError(msg)
478 elif pct >= 95.0:
479 print
480 print " WARNING: ", msg
481 print
482 elif OPTIONS.verbose:
483 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700484
485
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800486def ReadApkCerts(tf_zip):
487 """Given a target_files ZipFile, parse the META/apkcerts.txt file
488 and return a {package: cert} dict."""
489 certmap = {}
490 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
491 line = line.strip()
492 if not line: continue
493 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
494 r'private_key="(.*)"$', line)
495 if m:
496 name, cert, privkey = m.groups()
497 if cert in SPECIAL_CERT_STRINGS and not privkey:
498 certmap[name] = cert
499 elif (cert.endswith(".x509.pem") and
500 privkey.endswith(".pk8") and
501 cert[:-9] == privkey[:-4]):
502 certmap[name] = cert[:-9]
503 else:
504 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
505 return certmap
506
507
Doug Zongkereef39442009-04-02 12:14:19 -0700508COMMON_DOCSTRING = """
509 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700510 Prepend <dir>/bin to the list of places to search for binaries
511 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700512
Doug Zongker05d3dea2009-06-22 11:32:31 -0700513 -s (--device_specific) <file>
514 Path to the python module containing device-specific
515 releasetools code.
516
Doug Zongker8bec09e2009-11-30 15:37:14 -0800517 -x (--extra) <key=value>
518 Add a key/value pair to the 'extras' dict, which device-specific
519 extension code may look at.
520
Doug Zongkereef39442009-04-02 12:14:19 -0700521 -v (--verbose)
522 Show command lines being executed.
523
524 -h (--help)
525 Display this usage message and exit.
526"""
527
528def Usage(docstring):
529 print docstring.rstrip("\n")
530 print COMMON_DOCSTRING
531
532
533def ParseOptions(argv,
534 docstring,
535 extra_opts="", extra_long_opts=(),
536 extra_option_handler=None):
537 """Parse the options in argv and return any arguments that aren't
538 flags. docstring is the calling module's docstring, to be displayed
539 for errors and -h. extra_opts and extra_long_opts are for flags
540 defined by the caller, which are processed by passing them to
541 extra_option_handler."""
542
543 try:
544 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800545 argv, "hvp:s:x:" + extra_opts,
546 ["help", "verbose", "path=", "device_specific=", "extra="] +
Doug Zongker05d3dea2009-06-22 11:32:31 -0700547 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700548 except getopt.GetoptError, err:
549 Usage(docstring)
550 print "**", str(err), "**"
551 sys.exit(2)
552
553 path_specified = False
554
555 for o, a in opts:
556 if o in ("-h", "--help"):
557 Usage(docstring)
558 sys.exit()
559 elif o in ("-v", "--verbose"):
560 OPTIONS.verbose = True
561 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700562 OPTIONS.search_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700563 elif o in ("-s", "--device_specific"):
564 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800565 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800566 key, value = a.split("=", 1)
567 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700568 else:
569 if extra_option_handler is None or not extra_option_handler(o, a):
570 assert False, "unknown option \"%s\"" % (o,)
571
Doug Zongker602a84e2009-06-18 08:35:12 -0700572 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
573 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700574
575 return args
576
577
578def Cleanup():
579 for i in OPTIONS.tempfiles:
580 if os.path.isdir(i):
581 shutil.rmtree(i)
582 else:
583 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700584
585
586class PasswordManager(object):
587 def __init__(self):
588 self.editor = os.getenv("EDITOR", None)
589 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
590
591 def GetPasswords(self, items):
592 """Get passwords corresponding to each string in 'items',
593 returning a dict. (The dict may have keys in addition to the
594 values in 'items'.)
595
596 Uses the passwords in $ANDROID_PW_FILE if available, letting the
597 user edit that file to add more needed passwords. If no editor is
598 available, or $ANDROID_PW_FILE isn't define, prompts the user
599 interactively in the ordinary way.
600 """
601
602 current = self.ReadFile()
603
604 first = True
605 while True:
606 missing = []
607 for i in items:
608 if i not in current or not current[i]:
609 missing.append(i)
610 # Are all the passwords already in the file?
611 if not missing: return current
612
613 for i in missing:
614 current[i] = ""
615
616 if not first:
617 print "key file %s still missing some passwords." % (self.pwfile,)
618 answer = raw_input("try to edit again? [y]> ").strip()
619 if answer and answer[0] not in 'yY':
620 raise RuntimeError("key passwords unavailable")
621 first = False
622
623 current = self.UpdateAndReadFile(current)
624
625 def PromptResult(self, current):
626 """Prompt the user to enter a value (password) for each key in
627 'current' whose value is fales. Returns a new dict with all the
628 values.
629 """
630 result = {}
631 for k, v in sorted(current.iteritems()):
632 if v:
633 result[k] = v
634 else:
635 while True:
636 result[k] = getpass.getpass("Enter password for %s key> "
637 % (k,)).strip()
638 if result[k]: break
639 return result
640
641 def UpdateAndReadFile(self, current):
642 if not self.editor or not self.pwfile:
643 return self.PromptResult(current)
644
645 f = open(self.pwfile, "w")
646 os.chmod(self.pwfile, 0600)
647 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
648 f.write("# (Additional spaces are harmless.)\n\n")
649
650 first_line = None
651 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
652 sorted.sort()
653 for i, (_, k, v) in enumerate(sorted):
654 f.write("[[[ %s ]]] %s\n" % (v, k))
655 if not v and first_line is None:
656 # position cursor on first line with no password.
657 first_line = i + 4
658 f.close()
659
660 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
661 _, _ = p.communicate()
662
663 return self.ReadFile()
664
665 def ReadFile(self):
666 result = {}
667 if self.pwfile is None: return result
668 try:
669 f = open(self.pwfile, "r")
670 for line in f:
671 line = line.strip()
672 if not line or line[0] == '#': continue
673 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
674 if not m:
675 print "failed to parse password file: ", line
676 else:
677 result[m.group(2)] = m.group(1)
678 f.close()
679 except IOError, e:
680 if e.errno != errno.ENOENT:
681 print "error reading password file: ", str(e)
682 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700683
684
685def ZipWriteStr(zip, filename, data, perms=0644):
686 # use a fixed timestamp so the output is repeatable.
687 zinfo = zipfile.ZipInfo(filename=filename,
688 date_time=(2009, 1, 1, 0, 0, 0))
689 zinfo.compress_type = zip.compression
690 zinfo.external_attr = perms << 16
691 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700692
693
694class DeviceSpecificParams(object):
695 module = None
696 def __init__(self, **kwargs):
697 """Keyword arguments to the constructor become attributes of this
698 object, which is passed to all functions in the device-specific
699 module."""
700 for k, v in kwargs.iteritems():
701 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800702 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700703
704 if self.module is None:
705 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700706 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700707 try:
708 if os.path.isdir(path):
709 info = imp.find_module("releasetools", [path])
710 else:
711 d, f = os.path.split(path)
712 b, x = os.path.splitext(f)
713 if x == ".py":
714 f = b
715 info = imp.find_module(f, [d])
716 self.module = imp.load_module("device_specific", *info)
717 except ImportError:
718 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700719
720 def _DoCall(self, function_name, *args, **kwargs):
721 """Call the named function in the device-specific module, passing
722 the given args and kwargs. The first argument to the call will be
723 the DeviceSpecific object itself. If there is no module, or the
724 module does not define the function, return the value of the
725 'default' kwarg (which itself defaults to None)."""
726 if self.module is None or not hasattr(self.module, function_name):
727 return kwargs.get("default", None)
728 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
729
730 def FullOTA_Assertions(self):
731 """Called after emitting the block of assertions at the top of a
732 full OTA package. Implementations can add whatever additional
733 assertions they like."""
734 return self._DoCall("FullOTA_Assertions")
735
Doug Zongkere5ff5902012-01-17 10:55:37 -0800736 def FullOTA_InstallBegin(self):
737 """Called at the start of full OTA installation."""
738 return self._DoCall("FullOTA_InstallBegin")
739
Doug Zongker05d3dea2009-06-22 11:32:31 -0700740 def FullOTA_InstallEnd(self):
741 """Called at the end of full OTA installation; typically this is
742 used to install the image for the device's baseband processor."""
743 return self._DoCall("FullOTA_InstallEnd")
744
745 def IncrementalOTA_Assertions(self):
746 """Called after emitting the block of assertions at the top of an
747 incremental OTA package. Implementations can add whatever
748 additional assertions they like."""
749 return self._DoCall("IncrementalOTA_Assertions")
750
Doug Zongkere5ff5902012-01-17 10:55:37 -0800751 def IncrementalOTA_VerifyBegin(self):
752 """Called at the start of the verification phase of incremental
753 OTA installation; additional checks can be placed here to abort
754 the script before any changes are made."""
755 return self._DoCall("IncrementalOTA_VerifyBegin")
756
Doug Zongker05d3dea2009-06-22 11:32:31 -0700757 def IncrementalOTA_VerifyEnd(self):
758 """Called at the end of the verification phase of incremental OTA
759 installation; additional checks can be placed here to abort the
760 script before any changes are made."""
761 return self._DoCall("IncrementalOTA_VerifyEnd")
762
Doug Zongkere5ff5902012-01-17 10:55:37 -0800763 def IncrementalOTA_InstallBegin(self):
764 """Called at the start of incremental OTA installation (after
765 verification is complete)."""
766 return self._DoCall("IncrementalOTA_InstallBegin")
767
Doug Zongker05d3dea2009-06-22 11:32:31 -0700768 def IncrementalOTA_InstallEnd(self):
769 """Called at the end of incremental OTA installation; typically
770 this is used to install the image for the device's baseband
771 processor."""
772 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700773
774class File(object):
775 def __init__(self, name, data):
776 self.name = name
777 self.data = data
778 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -0800779 self.sha1 = sha1(data).hexdigest()
780
781 @classmethod
782 def FromLocalFile(cls, name, diskname):
783 f = open(diskname, "rb")
784 data = f.read()
785 f.close()
786 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700787
788 def WriteToTemp(self):
789 t = tempfile.NamedTemporaryFile()
790 t.write(self.data)
791 t.flush()
792 return t
793
794 def AddToZip(self, z):
795 ZipWriteStr(z, self.name, self.data)
796
797DIFF_PROGRAM_BY_EXT = {
798 ".gz" : "imgdiff",
799 ".zip" : ["imgdiff", "-z"],
800 ".jar" : ["imgdiff", "-z"],
801 ".apk" : ["imgdiff", "-z"],
802 ".img" : "imgdiff",
803 }
804
805class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -0700806 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700807 self.tf = tf
808 self.sf = sf
809 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -0700810 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700811
812 def ComputePatch(self):
813 """Compute the patch (as a string of data) needed to turn sf into
814 tf. Returns the same tuple as GetPatch()."""
815
816 tf = self.tf
817 sf = self.sf
818
Doug Zongker24cd2802012-08-14 16:36:15 -0700819 if self.diff_program:
820 diff_program = self.diff_program
821 else:
822 ext = os.path.splitext(tf.name)[1]
823 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700824
825 ttemp = tf.WriteToTemp()
826 stemp = sf.WriteToTemp()
827
828 ext = os.path.splitext(tf.name)[1]
829
830 try:
831 ptemp = tempfile.NamedTemporaryFile()
832 if isinstance(diff_program, list):
833 cmd = copy.copy(diff_program)
834 else:
835 cmd = [diff_program]
836 cmd.append(stemp.name)
837 cmd.append(ttemp.name)
838 cmd.append(ptemp.name)
839 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
840 _, err = p.communicate()
841 if err or p.returncode != 0:
842 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
843 return None
844 diff = ptemp.read()
845 finally:
846 ptemp.close()
847 stemp.close()
848 ttemp.close()
849
850 self.patch = diff
851 return self.tf, self.sf, self.patch
852
853
854 def GetPatch(self):
855 """Return a tuple (target_file, source_file, patch_data).
856 patch_data may be None if ComputePatch hasn't been called, or if
857 computing the patch failed."""
858 return self.tf, self.sf, self.patch
859
860
861def ComputeDifferences(diffs):
862 """Call ComputePatch on all the Difference objects in 'diffs'."""
863 print len(diffs), "diffs to compute"
864
865 # Do the largest files first, to try and reduce the long-pole effect.
866 by_size = [(i.tf.size, i) for i in diffs]
867 by_size.sort(reverse=True)
868 by_size = [i[1] for i in by_size]
869
870 lock = threading.Lock()
871 diff_iter = iter(by_size) # accessed under lock
872
873 def worker():
874 try:
875 lock.acquire()
876 for d in diff_iter:
877 lock.release()
878 start = time.time()
879 d.ComputePatch()
880 dur = time.time() - start
881 lock.acquire()
882
883 tf, sf, patch = d.GetPatch()
884 if sf.name == tf.name:
885 name = tf.name
886 else:
887 name = "%s (%s)" % (tf.name, sf.name)
888 if patch is None:
889 print "patching failed! %s" % (name,)
890 else:
891 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
892 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
893 lock.release()
894 except Exception, e:
895 print e
896 raise
897
898 # start worker threads; wait for them all to finish.
899 threads = [threading.Thread(target=worker)
900 for i in range(OPTIONS.worker_threads)]
901 for th in threads:
902 th.start()
903 while threads:
904 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -0700905
906
907# map recovery.fstab's fs_types to mount/format "partition types"
908PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
909 "ext4": "EMMC", "emmc": "EMMC" }
910
911def GetTypeAndDevice(mount_point, info):
912 fstab = info["fstab"]
913 if fstab:
914 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
915 else:
Ying Wanga73b6562011-03-03 21:52:08 -0800916 return None