blob: 127784d5ffa30fa601389f3a2aa429f09be5ac44 [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
120 try:
121 data = zip.read("META/imagesizes.txt")
122 for line in data.split("\n"):
123 if not line: continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700124 name, value = line.split(" ", 1)
125 if not value: continue
Doug Zongker37974732010-09-16 17:44:38 -0700126 if name == "blocksize":
127 d[name] = value
128 else:
129 d[name + "_size"] = value
130 except KeyError:
131 pass
132
133 def makeint(key):
134 if key in d:
135 d[key] = int(d[key], 0)
136
137 makeint("recovery_api_version")
138 makeint("blocksize")
139 makeint("system_size")
140 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700141 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700142 makeint("recovery_size")
143 makeint("boot_size")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700144
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700145 d["fstab"] = LoadRecoveryFSTab(zip)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700146 d["build.prop"] = LoadBuildProp(zip)
147 return d
148
149def LoadBuildProp(zip):
150 try:
151 data = zip.read("SYSTEM/build.prop")
152 except KeyError:
153 print "Warning: could not find SYSTEM/build.prop in %s" % zip
154 data = ""
155
156 d = {}
157 for line in data.split("\n"):
158 line = line.strip()
159 if not line or line.startswith("#"): continue
160 name, value = line.split("=", 1)
161 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700162 return d
163
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700164def LoadRecoveryFSTab(zip):
165 class Partition(object):
166 pass
167
168 try:
169 data = zip.read("RECOVERY/RAMDISK/etc/recovery.fstab")
170 except KeyError:
Jeff Davidson033fbe22011-10-26 18:08:09 -0700171 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab in %s." % zip
172 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700173
174 d = {}
175 for line in data.split("\n"):
176 line = line.strip()
177 if not line or line.startswith("#"): continue
178 pieces = line.split()
179 if not (3 <= len(pieces) <= 4):
180 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
181
182 p = Partition()
183 p.mount_point = pieces[0]
184 p.fs_type = pieces[1]
185 p.device = pieces[2]
Doug Zongker086cbb02011-02-17 15:54:20 -0800186 p.length = 0
187 options = None
188 if len(pieces) >= 4:
189 if pieces[3].startswith("/"):
190 p.device2 = pieces[3]
191 if len(pieces) >= 5:
192 options = pieces[4]
193 else:
194 p.device2 = None
195 options = pieces[3]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700196 else:
197 p.device2 = None
198
Doug Zongker086cbb02011-02-17 15:54:20 -0800199 if options:
200 options = options.split(",")
201 for i in options:
202 if i.startswith("length="):
203 p.length = int(i[7:])
204 else:
205 print "%s: unknown option \"%s\"" % (p.mount_point, i)
206
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700207 d[p.mount_point] = p
208 return d
209
210
Doug Zongker37974732010-09-16 17:44:38 -0700211def DumpInfoDict(d):
212 for k, v in sorted(d.items()):
213 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700214
Doug Zongkerd5131602012-08-02 14:46:42 -0700215def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700216 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700217 'sourcedir'), and turn them into a boot image. Return the image
218 data, or None if sourcedir does not appear to contains files for
219 building the requested image."""
220
221 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
222 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
223 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700224
Doug Zongkerd5131602012-08-02 14:46:42 -0700225 if info_dict is None:
226 info_dict = OPTIONS.info_dict
227
Doug Zongkereef39442009-04-02 12:14:19 -0700228 ramdisk_img = tempfile.NamedTemporaryFile()
229 img = tempfile.NamedTemporaryFile()
230
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700231 if os.access(fs_config_file, os.F_OK):
232 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
233 else:
234 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
235 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700236 p2 = Run(["minigzip"],
237 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700238
239 p2.wait()
240 p1.wait()
241 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
Doug Zongker32da27a2009-05-29 09:35:56 -0700242 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
Doug Zongkereef39442009-04-02 12:14:19 -0700243
Doug Zongker38a649f2009-06-17 09:07:09 -0700244 cmd = ["mkbootimg", "--kernel", os.path.join(sourcedir, "kernel")]
245
Doug Zongker171f1cd2009-06-15 22:36:37 -0700246 fn = os.path.join(sourcedir, "cmdline")
247 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700248 cmd.append("--cmdline")
249 cmd.append(open(fn).read().rstrip("\n"))
250
251 fn = os.path.join(sourcedir, "base")
252 if os.access(fn, os.F_OK):
253 cmd.append("--base")
254 cmd.append(open(fn).read().rstrip("\n"))
255
Ying Wang4de6b5b2010-08-25 14:29:34 -0700256 fn = os.path.join(sourcedir, "pagesize")
257 if os.access(fn, os.F_OK):
258 cmd.append("--pagesize")
259 cmd.append(open(fn).read().rstrip("\n"))
260
Doug Zongkerd5131602012-08-02 14:46:42 -0700261 args = info_dict.get("mkbootimg_args", None)
262 if args and args.strip():
263 cmd.extend(args.split())
264
Doug Zongker38a649f2009-06-17 09:07:09 -0700265 cmd.extend(["--ramdisk", ramdisk_img.name,
266 "--output", img.name])
267
268 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700269 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700270 assert p.returncode == 0, "mkbootimg of %s image failed" % (
271 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700272
273 img.seek(os.SEEK_SET, 0)
274 data = img.read()
275
276 ramdisk_img.close()
277 img.close()
278
279 return data
280
281
Doug Zongkerd5131602012-08-02 14:46:42 -0700282def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
283 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800284 """Return a File object (with name 'name') with the desired bootable
285 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
286 'prebuilt_name', otherwise construct it from the source files in
287 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700288
Doug Zongker55d93282011-01-25 17:03:34 -0800289 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
290 if os.path.exists(prebuilt_path):
291 print "using prebuilt %s..." % (prebuilt_name,)
292 return File.FromLocalFile(name, prebuilt_path)
293 else:
294 print "building image from target_files %s..." % (tree_subdir,)
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700295 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
296 return File(name, BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
Doug Zongkerd5131602012-08-02 14:46:42 -0700297 os.path.join(unpack_dir, fs_config),
298 info_dict))
Doug Zongker55d93282011-01-25 17:03:34 -0800299
Doug Zongkereef39442009-04-02 12:14:19 -0700300
Doug Zongker75f17362009-12-08 13:46:44 -0800301def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800302 """Unzip the given archive into a temporary directory and return the name.
303
304 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
305 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
306
307 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
308 main file), open for reading.
309 """
Doug Zongkereef39442009-04-02 12:14:19 -0700310
311 tmp = tempfile.mkdtemp(prefix="targetfiles-")
312 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800313
314 def unzip_to_dir(filename, dirname):
315 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
316 if pattern is not None:
317 cmd.append(pattern)
318 p = Run(cmd, stdout=subprocess.PIPE)
319 p.communicate()
320 if p.returncode != 0:
321 raise ExternalError("failed to unzip input target-files \"%s\"" %
322 (filename,))
323
324 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
325 if m:
326 unzip_to_dir(m.group(1), tmp)
327 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
328 filename = m.group(1)
329 else:
330 unzip_to_dir(filename, tmp)
331
332 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700333
334
335def GetKeyPasswords(keylist):
336 """Given a list of keys, prompt the user to enter passwords for
337 those which require them. Return a {key: password} dict. password
338 will be None if the key has no password."""
339
Doug Zongker8ce7c252009-05-22 13:34:54 -0700340 no_passwords = []
341 need_passwords = []
Doug Zongkereef39442009-04-02 12:14:19 -0700342 devnull = open("/dev/null", "w+b")
343 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800344 # We don't need a password for things that aren't really keys.
345 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700346 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700347 continue
348
Doug Zongker602a84e2009-06-18 08:35:12 -0700349 p = Run(["openssl", "pkcs8", "-in", k+".pk8",
350 "-inform", "DER", "-nocrypt"],
351 stdin=devnull.fileno(),
352 stdout=devnull.fileno(),
353 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700354 p.communicate()
355 if p.returncode == 0:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700356 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700357 else:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700358 need_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700359 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700360
361 key_passwords = PasswordManager().GetPasswords(need_passwords)
362 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700363 return key_passwords
364
365
Doug Zongker951495f2009-08-14 12:44:19 -0700366def SignFile(input_name, output_name, key, password, align=None,
367 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700368 """Sign the input_name zip/jar/apk, producing output_name. Use the
369 given key and password (the latter may be None if the key does not
370 have a password.
371
372 If align is an integer > 1, zipalign is run to align stored files in
373 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700374
375 If whole_file is true, use the "-w" option to SignApk to embed a
376 signature that covers the whole file in the archive comment of the
377 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700378 """
Doug Zongker951495f2009-08-14 12:44:19 -0700379
Doug Zongkereef39442009-04-02 12:14:19 -0700380 if align == 0 or align == 1:
381 align = None
382
383 if align:
384 temp = tempfile.NamedTemporaryFile()
385 sign_name = temp.name
386 else:
387 sign_name = output_name
388
Doug Zongkerca855e92011-02-23 09:25:01 -0800389 cmd = ["java", "-Xmx2048m", "-jar",
Doug Zongker951495f2009-08-14 12:44:19 -0700390 os.path.join(OPTIONS.search_path, "framework", "signapk.jar")]
391 if whole_file:
392 cmd.append("-w")
393 cmd.extend([key + ".x509.pem", key + ".pk8",
394 input_name, sign_name])
395
396 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700397 if password is not None:
398 password += "\n"
399 p.communicate(password)
400 if p.returncode != 0:
401 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
402
403 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700404 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700405 p.communicate()
406 if p.returncode != 0:
407 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
408 temp.close()
409
410
Doug Zongker37974732010-09-16 17:44:38 -0700411def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700412 """Check the data string passed against the max size limit, if
413 any, for the given target. Raise exception if the data is too big.
414 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700415
Doug Zongker1684d9c2010-09-17 07:44:38 -0700416 if target.endswith(".img"): target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700417 mount_point = "/" + target
418
419 if info_dict["fstab"]:
420 if mount_point == "/userdata": mount_point = "/data"
421 p = info_dict["fstab"][mount_point]
422 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800423 device = p.device
424 if "/" in device:
425 device = device[device.rfind("/")+1:]
426 limit = info_dict.get(device + "_size", None)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700427 if not fs_type or not limit: return
Doug Zongkereef39442009-04-02 12:14:19 -0700428
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700429 if fs_type == "yaffs2":
430 # image size should be increased by 1/64th to account for the
431 # spare area (64 bytes per 2k page)
432 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800433 size = len(data)
434 pct = float(size) * 100.0 / limit
435 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
436 if pct >= 99.0:
437 raise ExternalError(msg)
438 elif pct >= 95.0:
439 print
440 print " WARNING: ", msg
441 print
442 elif OPTIONS.verbose:
443 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700444
445
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800446def ReadApkCerts(tf_zip):
447 """Given a target_files ZipFile, parse the META/apkcerts.txt file
448 and return a {package: cert} dict."""
449 certmap = {}
450 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
451 line = line.strip()
452 if not line: continue
453 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
454 r'private_key="(.*)"$', line)
455 if m:
456 name, cert, privkey = m.groups()
457 if cert in SPECIAL_CERT_STRINGS and not privkey:
458 certmap[name] = cert
459 elif (cert.endswith(".x509.pem") and
460 privkey.endswith(".pk8") and
461 cert[:-9] == privkey[:-4]):
462 certmap[name] = cert[:-9]
463 else:
464 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
465 return certmap
466
467
Doug Zongkereef39442009-04-02 12:14:19 -0700468COMMON_DOCSTRING = """
469 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700470 Prepend <dir>/bin to the list of places to search for binaries
471 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700472
Doug Zongker05d3dea2009-06-22 11:32:31 -0700473 -s (--device_specific) <file>
474 Path to the python module containing device-specific
475 releasetools code.
476
Doug Zongker8bec09e2009-11-30 15:37:14 -0800477 -x (--extra) <key=value>
478 Add a key/value pair to the 'extras' dict, which device-specific
479 extension code may look at.
480
Doug Zongkereef39442009-04-02 12:14:19 -0700481 -v (--verbose)
482 Show command lines being executed.
483
484 -h (--help)
485 Display this usage message and exit.
486"""
487
488def Usage(docstring):
489 print docstring.rstrip("\n")
490 print COMMON_DOCSTRING
491
492
493def ParseOptions(argv,
494 docstring,
495 extra_opts="", extra_long_opts=(),
496 extra_option_handler=None):
497 """Parse the options in argv and return any arguments that aren't
498 flags. docstring is the calling module's docstring, to be displayed
499 for errors and -h. extra_opts and extra_long_opts are for flags
500 defined by the caller, which are processed by passing them to
501 extra_option_handler."""
502
503 try:
504 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800505 argv, "hvp:s:x:" + extra_opts,
506 ["help", "verbose", "path=", "device_specific=", "extra="] +
Doug Zongker05d3dea2009-06-22 11:32:31 -0700507 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700508 except getopt.GetoptError, err:
509 Usage(docstring)
510 print "**", str(err), "**"
511 sys.exit(2)
512
513 path_specified = False
514
515 for o, a in opts:
516 if o in ("-h", "--help"):
517 Usage(docstring)
518 sys.exit()
519 elif o in ("-v", "--verbose"):
520 OPTIONS.verbose = True
521 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700522 OPTIONS.search_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700523 elif o in ("-s", "--device_specific"):
524 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800525 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800526 key, value = a.split("=", 1)
527 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700528 else:
529 if extra_option_handler is None or not extra_option_handler(o, a):
530 assert False, "unknown option \"%s\"" % (o,)
531
Doug Zongker602a84e2009-06-18 08:35:12 -0700532 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
533 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700534
535 return args
536
537
538def Cleanup():
539 for i in OPTIONS.tempfiles:
540 if os.path.isdir(i):
541 shutil.rmtree(i)
542 else:
543 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700544
545
546class PasswordManager(object):
547 def __init__(self):
548 self.editor = os.getenv("EDITOR", None)
549 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
550
551 def GetPasswords(self, items):
552 """Get passwords corresponding to each string in 'items',
553 returning a dict. (The dict may have keys in addition to the
554 values in 'items'.)
555
556 Uses the passwords in $ANDROID_PW_FILE if available, letting the
557 user edit that file to add more needed passwords. If no editor is
558 available, or $ANDROID_PW_FILE isn't define, prompts the user
559 interactively in the ordinary way.
560 """
561
562 current = self.ReadFile()
563
564 first = True
565 while True:
566 missing = []
567 for i in items:
568 if i not in current or not current[i]:
569 missing.append(i)
570 # Are all the passwords already in the file?
571 if not missing: return current
572
573 for i in missing:
574 current[i] = ""
575
576 if not first:
577 print "key file %s still missing some passwords." % (self.pwfile,)
578 answer = raw_input("try to edit again? [y]> ").strip()
579 if answer and answer[0] not in 'yY':
580 raise RuntimeError("key passwords unavailable")
581 first = False
582
583 current = self.UpdateAndReadFile(current)
584
585 def PromptResult(self, current):
586 """Prompt the user to enter a value (password) for each key in
587 'current' whose value is fales. Returns a new dict with all the
588 values.
589 """
590 result = {}
591 for k, v in sorted(current.iteritems()):
592 if v:
593 result[k] = v
594 else:
595 while True:
596 result[k] = getpass.getpass("Enter password for %s key> "
597 % (k,)).strip()
598 if result[k]: break
599 return result
600
601 def UpdateAndReadFile(self, current):
602 if not self.editor or not self.pwfile:
603 return self.PromptResult(current)
604
605 f = open(self.pwfile, "w")
606 os.chmod(self.pwfile, 0600)
607 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
608 f.write("# (Additional spaces are harmless.)\n\n")
609
610 first_line = None
611 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
612 sorted.sort()
613 for i, (_, k, v) in enumerate(sorted):
614 f.write("[[[ %s ]]] %s\n" % (v, k))
615 if not v and first_line is None:
616 # position cursor on first line with no password.
617 first_line = i + 4
618 f.close()
619
620 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
621 _, _ = p.communicate()
622
623 return self.ReadFile()
624
625 def ReadFile(self):
626 result = {}
627 if self.pwfile is None: return result
628 try:
629 f = open(self.pwfile, "r")
630 for line in f:
631 line = line.strip()
632 if not line or line[0] == '#': continue
633 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
634 if not m:
635 print "failed to parse password file: ", line
636 else:
637 result[m.group(2)] = m.group(1)
638 f.close()
639 except IOError, e:
640 if e.errno != errno.ENOENT:
641 print "error reading password file: ", str(e)
642 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700643
644
645def ZipWriteStr(zip, filename, data, perms=0644):
646 # use a fixed timestamp so the output is repeatable.
647 zinfo = zipfile.ZipInfo(filename=filename,
648 date_time=(2009, 1, 1, 0, 0, 0))
649 zinfo.compress_type = zip.compression
650 zinfo.external_attr = perms << 16
651 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700652
653
654class DeviceSpecificParams(object):
655 module = None
656 def __init__(self, **kwargs):
657 """Keyword arguments to the constructor become attributes of this
658 object, which is passed to all functions in the device-specific
659 module."""
660 for k, v in kwargs.iteritems():
661 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800662 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700663
664 if self.module is None:
665 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700666 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700667 try:
668 if os.path.isdir(path):
669 info = imp.find_module("releasetools", [path])
670 else:
671 d, f = os.path.split(path)
672 b, x = os.path.splitext(f)
673 if x == ".py":
674 f = b
675 info = imp.find_module(f, [d])
676 self.module = imp.load_module("device_specific", *info)
677 except ImportError:
678 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700679
680 def _DoCall(self, function_name, *args, **kwargs):
681 """Call the named function in the device-specific module, passing
682 the given args and kwargs. The first argument to the call will be
683 the DeviceSpecific object itself. If there is no module, or the
684 module does not define the function, return the value of the
685 'default' kwarg (which itself defaults to None)."""
686 if self.module is None or not hasattr(self.module, function_name):
687 return kwargs.get("default", None)
688 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
689
690 def FullOTA_Assertions(self):
691 """Called after emitting the block of assertions at the top of a
692 full OTA package. Implementations can add whatever additional
693 assertions they like."""
694 return self._DoCall("FullOTA_Assertions")
695
Doug Zongkere5ff5902012-01-17 10:55:37 -0800696 def FullOTA_InstallBegin(self):
697 """Called at the start of full OTA installation."""
698 return self._DoCall("FullOTA_InstallBegin")
699
Doug Zongker05d3dea2009-06-22 11:32:31 -0700700 def FullOTA_InstallEnd(self):
701 """Called at the end of full OTA installation; typically this is
702 used to install the image for the device's baseband processor."""
703 return self._DoCall("FullOTA_InstallEnd")
704
705 def IncrementalOTA_Assertions(self):
706 """Called after emitting the block of assertions at the top of an
707 incremental OTA package. Implementations can add whatever
708 additional assertions they like."""
709 return self._DoCall("IncrementalOTA_Assertions")
710
Doug Zongkere5ff5902012-01-17 10:55:37 -0800711 def IncrementalOTA_VerifyBegin(self):
712 """Called at the start of the verification phase of incremental
713 OTA installation; additional checks can be placed here to abort
714 the script before any changes are made."""
715 return self._DoCall("IncrementalOTA_VerifyBegin")
716
Doug Zongker05d3dea2009-06-22 11:32:31 -0700717 def IncrementalOTA_VerifyEnd(self):
718 """Called at the end of the verification phase of incremental OTA
719 installation; additional checks can be placed here to abort the
720 script before any changes are made."""
721 return self._DoCall("IncrementalOTA_VerifyEnd")
722
Doug Zongkere5ff5902012-01-17 10:55:37 -0800723 def IncrementalOTA_InstallBegin(self):
724 """Called at the start of incremental OTA installation (after
725 verification is complete)."""
726 return self._DoCall("IncrementalOTA_InstallBegin")
727
Doug Zongker05d3dea2009-06-22 11:32:31 -0700728 def IncrementalOTA_InstallEnd(self):
729 """Called at the end of incremental OTA installation; typically
730 this is used to install the image for the device's baseband
731 processor."""
732 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700733
734class File(object):
735 def __init__(self, name, data):
736 self.name = name
737 self.data = data
738 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -0800739 self.sha1 = sha1(data).hexdigest()
740
741 @classmethod
742 def FromLocalFile(cls, name, diskname):
743 f = open(diskname, "rb")
744 data = f.read()
745 f.close()
746 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700747
748 def WriteToTemp(self):
749 t = tempfile.NamedTemporaryFile()
750 t.write(self.data)
751 t.flush()
752 return t
753
754 def AddToZip(self, z):
755 ZipWriteStr(z, self.name, self.data)
756
757DIFF_PROGRAM_BY_EXT = {
758 ".gz" : "imgdiff",
759 ".zip" : ["imgdiff", "-z"],
760 ".jar" : ["imgdiff", "-z"],
761 ".apk" : ["imgdiff", "-z"],
762 ".img" : "imgdiff",
763 }
764
765class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -0700766 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700767 self.tf = tf
768 self.sf = sf
769 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -0700770 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700771
772 def ComputePatch(self):
773 """Compute the patch (as a string of data) needed to turn sf into
774 tf. Returns the same tuple as GetPatch()."""
775
776 tf = self.tf
777 sf = self.sf
778
Doug Zongker24cd2802012-08-14 16:36:15 -0700779 if self.diff_program:
780 diff_program = self.diff_program
781 else:
782 ext = os.path.splitext(tf.name)[1]
783 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700784
785 ttemp = tf.WriteToTemp()
786 stemp = sf.WriteToTemp()
787
788 ext = os.path.splitext(tf.name)[1]
789
790 try:
791 ptemp = tempfile.NamedTemporaryFile()
792 if isinstance(diff_program, list):
793 cmd = copy.copy(diff_program)
794 else:
795 cmd = [diff_program]
796 cmd.append(stemp.name)
797 cmd.append(ttemp.name)
798 cmd.append(ptemp.name)
799 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
800 _, err = p.communicate()
801 if err or p.returncode != 0:
802 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
803 return None
804 diff = ptemp.read()
805 finally:
806 ptemp.close()
807 stemp.close()
808 ttemp.close()
809
810 self.patch = diff
811 return self.tf, self.sf, self.patch
812
813
814 def GetPatch(self):
815 """Return a tuple (target_file, source_file, patch_data).
816 patch_data may be None if ComputePatch hasn't been called, or if
817 computing the patch failed."""
818 return self.tf, self.sf, self.patch
819
820
821def ComputeDifferences(diffs):
822 """Call ComputePatch on all the Difference objects in 'diffs'."""
823 print len(diffs), "diffs to compute"
824
825 # Do the largest files first, to try and reduce the long-pole effect.
826 by_size = [(i.tf.size, i) for i in diffs]
827 by_size.sort(reverse=True)
828 by_size = [i[1] for i in by_size]
829
830 lock = threading.Lock()
831 diff_iter = iter(by_size) # accessed under lock
832
833 def worker():
834 try:
835 lock.acquire()
836 for d in diff_iter:
837 lock.release()
838 start = time.time()
839 d.ComputePatch()
840 dur = time.time() - start
841 lock.acquire()
842
843 tf, sf, patch = d.GetPatch()
844 if sf.name == tf.name:
845 name = tf.name
846 else:
847 name = "%s (%s)" % (tf.name, sf.name)
848 if patch is None:
849 print "patching failed! %s" % (name,)
850 else:
851 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
852 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
853 lock.release()
854 except Exception, e:
855 print e
856 raise
857
858 # start worker threads; wait for them all to finish.
859 threads = [threading.Thread(target=worker)
860 for i in range(OPTIONS.worker_threads)]
861 for th in threads:
862 th.start()
863 while threads:
864 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -0700865
866
867# map recovery.fstab's fs_types to mount/format "partition types"
868PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
869 "ext4": "EMMC", "emmc": "EMMC" }
870
871def GetTypeAndDevice(mount_point, info):
872 fstab = info["fstab"]
873 if fstab:
874 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
875 else:
Ying Wanga73b6562011-03-03 21:52:08 -0800876 return None