blob: 5e4055a219874cb5aa20c88a948cf666f66bf6ad [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 Zongkerc19a8d52010-07-01 15:30:11 -0700146 return d
147
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700148def LoadRecoveryFSTab(zip):
149 class Partition(object):
150 pass
151
152 try:
153 data = zip.read("RECOVERY/RAMDISK/etc/recovery.fstab")
154 except KeyError:
Jeff Davidson033fbe22011-10-26 18:08:09 -0700155 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab in %s." % zip
156 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700157
158 d = {}
159 for line in data.split("\n"):
160 line = line.strip()
161 if not line or line.startswith("#"): continue
162 pieces = line.split()
163 if not (3 <= len(pieces) <= 4):
164 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
165
166 p = Partition()
167 p.mount_point = pieces[0]
168 p.fs_type = pieces[1]
169 p.device = pieces[2]
Doug Zongker086cbb02011-02-17 15:54:20 -0800170 p.length = 0
171 options = None
172 if len(pieces) >= 4:
173 if pieces[3].startswith("/"):
174 p.device2 = pieces[3]
175 if len(pieces) >= 5:
176 options = pieces[4]
177 else:
178 p.device2 = None
179 options = pieces[3]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700180 else:
181 p.device2 = None
182
Doug Zongker086cbb02011-02-17 15:54:20 -0800183 if options:
184 options = options.split(",")
185 for i in options:
186 if i.startswith("length="):
187 p.length = int(i[7:])
188 else:
189 print "%s: unknown option \"%s\"" % (p.mount_point, i)
190
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700191 d[p.mount_point] = p
192 return d
193
194
Doug Zongker37974732010-09-16 17:44:38 -0700195def DumpInfoDict(d):
196 for k, v in sorted(d.items()):
197 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700198
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700199def BuildBootableImage(sourcedir, fs_config_file):
Doug Zongkereef39442009-04-02 12:14:19 -0700200 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700201 'sourcedir'), and turn them into a boot image. Return the image
202 data, or None if sourcedir does not appear to contains files for
203 building the requested image."""
204
205 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
206 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
207 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700208
209 ramdisk_img = tempfile.NamedTemporaryFile()
210 img = tempfile.NamedTemporaryFile()
211
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700212 if os.access(fs_config_file, os.F_OK):
213 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
214 else:
215 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
216 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700217 p2 = Run(["minigzip"],
218 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700219
220 p2.wait()
221 p1.wait()
222 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
Doug Zongker32da27a2009-05-29 09:35:56 -0700223 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
Doug Zongkereef39442009-04-02 12:14:19 -0700224
Doug Zongker38a649f2009-06-17 09:07:09 -0700225 cmd = ["mkbootimg", "--kernel", os.path.join(sourcedir, "kernel")]
226
Doug Zongker171f1cd2009-06-15 22:36:37 -0700227 fn = os.path.join(sourcedir, "cmdline")
228 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700229 cmd.append("--cmdline")
230 cmd.append(open(fn).read().rstrip("\n"))
231
232 fn = os.path.join(sourcedir, "base")
233 if os.access(fn, os.F_OK):
234 cmd.append("--base")
235 cmd.append(open(fn).read().rstrip("\n"))
236
Ying Wang4de6b5b2010-08-25 14:29:34 -0700237 fn = os.path.join(sourcedir, "pagesize")
238 if os.access(fn, os.F_OK):
239 cmd.append("--pagesize")
240 cmd.append(open(fn).read().rstrip("\n"))
241
Doug Zongker38a649f2009-06-17 09:07:09 -0700242 cmd.extend(["--ramdisk", ramdisk_img.name,
243 "--output", img.name])
244
245 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700246 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700247 assert p.returncode == 0, "mkbootimg of %s image failed" % (
248 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700249
250 img.seek(os.SEEK_SET, 0)
251 data = img.read()
252
253 ramdisk_img.close()
254 img.close()
255
256 return data
257
258
Doug Zongker55d93282011-01-25 17:03:34 -0800259def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir):
260 """Return a File object (with name 'name') with the desired bootable
261 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
262 'prebuilt_name', otherwise construct it from the source files in
263 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700264
Doug Zongker55d93282011-01-25 17:03:34 -0800265 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
266 if os.path.exists(prebuilt_path):
267 print "using prebuilt %s..." % (prebuilt_name,)
268 return File.FromLocalFile(name, prebuilt_path)
269 else:
270 print "building image from target_files %s..." % (tree_subdir,)
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700271 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
272 return File(name, BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
273 os.path.join(unpack_dir, fs_config)))
Doug Zongker55d93282011-01-25 17:03:34 -0800274
Doug Zongkereef39442009-04-02 12:14:19 -0700275
Doug Zongker75f17362009-12-08 13:46:44 -0800276def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800277 """Unzip the given archive into a temporary directory and return the name.
278
279 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
280 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
281
282 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
283 main file), open for reading.
284 """
Doug Zongkereef39442009-04-02 12:14:19 -0700285
286 tmp = tempfile.mkdtemp(prefix="targetfiles-")
287 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800288
289 def unzip_to_dir(filename, dirname):
290 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
291 if pattern is not None:
292 cmd.append(pattern)
293 p = Run(cmd, stdout=subprocess.PIPE)
294 p.communicate()
295 if p.returncode != 0:
296 raise ExternalError("failed to unzip input target-files \"%s\"" %
297 (filename,))
298
299 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
300 if m:
301 unzip_to_dir(m.group(1), tmp)
302 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
303 filename = m.group(1)
304 else:
305 unzip_to_dir(filename, tmp)
306
307 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700308
309
310def GetKeyPasswords(keylist):
311 """Given a list of keys, prompt the user to enter passwords for
312 those which require them. Return a {key: password} dict. password
313 will be None if the key has no password."""
314
Doug Zongker8ce7c252009-05-22 13:34:54 -0700315 no_passwords = []
316 need_passwords = []
Doug Zongkereef39442009-04-02 12:14:19 -0700317 devnull = open("/dev/null", "w+b")
318 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800319 # We don't need a password for things that aren't really keys.
320 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700321 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700322 continue
323
Doug Zongker602a84e2009-06-18 08:35:12 -0700324 p = Run(["openssl", "pkcs8", "-in", k+".pk8",
325 "-inform", "DER", "-nocrypt"],
326 stdin=devnull.fileno(),
327 stdout=devnull.fileno(),
328 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700329 p.communicate()
330 if p.returncode == 0:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700331 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700332 else:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700333 need_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700334 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700335
336 key_passwords = PasswordManager().GetPasswords(need_passwords)
337 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700338 return key_passwords
339
340
Doug Zongker951495f2009-08-14 12:44:19 -0700341def SignFile(input_name, output_name, key, password, align=None,
342 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700343 """Sign the input_name zip/jar/apk, producing output_name. Use the
344 given key and password (the latter may be None if the key does not
345 have a password.
346
347 If align is an integer > 1, zipalign is run to align stored files in
348 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700349
350 If whole_file is true, use the "-w" option to SignApk to embed a
351 signature that covers the whole file in the archive comment of the
352 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700353 """
Doug Zongker951495f2009-08-14 12:44:19 -0700354
Doug Zongkereef39442009-04-02 12:14:19 -0700355 if align == 0 or align == 1:
356 align = None
357
358 if align:
359 temp = tempfile.NamedTemporaryFile()
360 sign_name = temp.name
361 else:
362 sign_name = output_name
363
Doug Zongkerca855e92011-02-23 09:25:01 -0800364 cmd = ["java", "-Xmx2048m", "-jar",
Doug Zongker951495f2009-08-14 12:44:19 -0700365 os.path.join(OPTIONS.search_path, "framework", "signapk.jar")]
366 if whole_file:
367 cmd.append("-w")
368 cmd.extend([key + ".x509.pem", key + ".pk8",
369 input_name, sign_name])
370
371 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700372 if password is not None:
373 password += "\n"
374 p.communicate(password)
375 if p.returncode != 0:
376 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
377
378 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700379 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700380 p.communicate()
381 if p.returncode != 0:
382 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
383 temp.close()
384
385
Doug Zongker37974732010-09-16 17:44:38 -0700386def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700387 """Check the data string passed against the max size limit, if
388 any, for the given target. Raise exception if the data is too big.
389 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700390
Doug Zongker1684d9c2010-09-17 07:44:38 -0700391 if target.endswith(".img"): target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700392 mount_point = "/" + target
393
394 if info_dict["fstab"]:
395 if mount_point == "/userdata": mount_point = "/data"
396 p = info_dict["fstab"][mount_point]
397 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800398 device = p.device
399 if "/" in device:
400 device = device[device.rfind("/")+1:]
401 limit = info_dict.get(device + "_size", None)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700402 if not fs_type or not limit: return
Doug Zongkereef39442009-04-02 12:14:19 -0700403
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700404 if fs_type == "yaffs2":
405 # image size should be increased by 1/64th to account for the
406 # spare area (64 bytes per 2k page)
407 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800408 size = len(data)
409 pct = float(size) * 100.0 / limit
410 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
411 if pct >= 99.0:
412 raise ExternalError(msg)
413 elif pct >= 95.0:
414 print
415 print " WARNING: ", msg
416 print
417 elif OPTIONS.verbose:
418 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700419
420
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800421def ReadApkCerts(tf_zip):
422 """Given a target_files ZipFile, parse the META/apkcerts.txt file
423 and return a {package: cert} dict."""
424 certmap = {}
425 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
426 line = line.strip()
427 if not line: continue
428 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
429 r'private_key="(.*)"$', line)
430 if m:
431 name, cert, privkey = m.groups()
432 if cert in SPECIAL_CERT_STRINGS and not privkey:
433 certmap[name] = cert
434 elif (cert.endswith(".x509.pem") and
435 privkey.endswith(".pk8") and
436 cert[:-9] == privkey[:-4]):
437 certmap[name] = cert[:-9]
438 else:
439 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
440 return certmap
441
442
Doug Zongkereef39442009-04-02 12:14:19 -0700443COMMON_DOCSTRING = """
444 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700445 Prepend <dir>/bin to the list of places to search for binaries
446 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700447
Doug Zongker05d3dea2009-06-22 11:32:31 -0700448 -s (--device_specific) <file>
449 Path to the python module containing device-specific
450 releasetools code.
451
Doug Zongker8bec09e2009-11-30 15:37:14 -0800452 -x (--extra) <key=value>
453 Add a key/value pair to the 'extras' dict, which device-specific
454 extension code may look at.
455
Doug Zongkereef39442009-04-02 12:14:19 -0700456 -v (--verbose)
457 Show command lines being executed.
458
459 -h (--help)
460 Display this usage message and exit.
461"""
462
463def Usage(docstring):
464 print docstring.rstrip("\n")
465 print COMMON_DOCSTRING
466
467
468def ParseOptions(argv,
469 docstring,
470 extra_opts="", extra_long_opts=(),
471 extra_option_handler=None):
472 """Parse the options in argv and return any arguments that aren't
473 flags. docstring is the calling module's docstring, to be displayed
474 for errors and -h. extra_opts and extra_long_opts are for flags
475 defined by the caller, which are processed by passing them to
476 extra_option_handler."""
477
478 try:
479 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800480 argv, "hvp:s:x:" + extra_opts,
481 ["help", "verbose", "path=", "device_specific=", "extra="] +
Doug Zongker05d3dea2009-06-22 11:32:31 -0700482 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700483 except getopt.GetoptError, err:
484 Usage(docstring)
485 print "**", str(err), "**"
486 sys.exit(2)
487
488 path_specified = False
489
490 for o, a in opts:
491 if o in ("-h", "--help"):
492 Usage(docstring)
493 sys.exit()
494 elif o in ("-v", "--verbose"):
495 OPTIONS.verbose = True
496 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700497 OPTIONS.search_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700498 elif o in ("-s", "--device_specific"):
499 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800500 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800501 key, value = a.split("=", 1)
502 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700503 else:
504 if extra_option_handler is None or not extra_option_handler(o, a):
505 assert False, "unknown option \"%s\"" % (o,)
506
Doug Zongker602a84e2009-06-18 08:35:12 -0700507 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
508 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700509
510 return args
511
512
513def Cleanup():
514 for i in OPTIONS.tempfiles:
515 if os.path.isdir(i):
516 shutil.rmtree(i)
517 else:
518 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700519
520
521class PasswordManager(object):
522 def __init__(self):
523 self.editor = os.getenv("EDITOR", None)
524 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
525
526 def GetPasswords(self, items):
527 """Get passwords corresponding to each string in 'items',
528 returning a dict. (The dict may have keys in addition to the
529 values in 'items'.)
530
531 Uses the passwords in $ANDROID_PW_FILE if available, letting the
532 user edit that file to add more needed passwords. If no editor is
533 available, or $ANDROID_PW_FILE isn't define, prompts the user
534 interactively in the ordinary way.
535 """
536
537 current = self.ReadFile()
538
539 first = True
540 while True:
541 missing = []
542 for i in items:
543 if i not in current or not current[i]:
544 missing.append(i)
545 # Are all the passwords already in the file?
546 if not missing: return current
547
548 for i in missing:
549 current[i] = ""
550
551 if not first:
552 print "key file %s still missing some passwords." % (self.pwfile,)
553 answer = raw_input("try to edit again? [y]> ").strip()
554 if answer and answer[0] not in 'yY':
555 raise RuntimeError("key passwords unavailable")
556 first = False
557
558 current = self.UpdateAndReadFile(current)
559
560 def PromptResult(self, current):
561 """Prompt the user to enter a value (password) for each key in
562 'current' whose value is fales. Returns a new dict with all the
563 values.
564 """
565 result = {}
566 for k, v in sorted(current.iteritems()):
567 if v:
568 result[k] = v
569 else:
570 while True:
571 result[k] = getpass.getpass("Enter password for %s key> "
572 % (k,)).strip()
573 if result[k]: break
574 return result
575
576 def UpdateAndReadFile(self, current):
577 if not self.editor or not self.pwfile:
578 return self.PromptResult(current)
579
580 f = open(self.pwfile, "w")
581 os.chmod(self.pwfile, 0600)
582 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
583 f.write("# (Additional spaces are harmless.)\n\n")
584
585 first_line = None
586 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
587 sorted.sort()
588 for i, (_, k, v) in enumerate(sorted):
589 f.write("[[[ %s ]]] %s\n" % (v, k))
590 if not v and first_line is None:
591 # position cursor on first line with no password.
592 first_line = i + 4
593 f.close()
594
595 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
596 _, _ = p.communicate()
597
598 return self.ReadFile()
599
600 def ReadFile(self):
601 result = {}
602 if self.pwfile is None: return result
603 try:
604 f = open(self.pwfile, "r")
605 for line in f:
606 line = line.strip()
607 if not line or line[0] == '#': continue
608 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
609 if not m:
610 print "failed to parse password file: ", line
611 else:
612 result[m.group(2)] = m.group(1)
613 f.close()
614 except IOError, e:
615 if e.errno != errno.ENOENT:
616 print "error reading password file: ", str(e)
617 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700618
619
620def ZipWriteStr(zip, filename, data, perms=0644):
621 # use a fixed timestamp so the output is repeatable.
622 zinfo = zipfile.ZipInfo(filename=filename,
623 date_time=(2009, 1, 1, 0, 0, 0))
624 zinfo.compress_type = zip.compression
625 zinfo.external_attr = perms << 16
626 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700627
628
629class DeviceSpecificParams(object):
630 module = None
631 def __init__(self, **kwargs):
632 """Keyword arguments to the constructor become attributes of this
633 object, which is passed to all functions in the device-specific
634 module."""
635 for k, v in kwargs.iteritems():
636 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800637 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700638
639 if self.module is None:
640 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700641 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700642 try:
643 if os.path.isdir(path):
644 info = imp.find_module("releasetools", [path])
645 else:
646 d, f = os.path.split(path)
647 b, x = os.path.splitext(f)
648 if x == ".py":
649 f = b
650 info = imp.find_module(f, [d])
651 self.module = imp.load_module("device_specific", *info)
652 except ImportError:
653 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700654
655 def _DoCall(self, function_name, *args, **kwargs):
656 """Call the named function in the device-specific module, passing
657 the given args and kwargs. The first argument to the call will be
658 the DeviceSpecific object itself. If there is no module, or the
659 module does not define the function, return the value of the
660 'default' kwarg (which itself defaults to None)."""
661 if self.module is None or not hasattr(self.module, function_name):
662 return kwargs.get("default", None)
663 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
664
665 def FullOTA_Assertions(self):
666 """Called after emitting the block of assertions at the top of a
667 full OTA package. Implementations can add whatever additional
668 assertions they like."""
669 return self._DoCall("FullOTA_Assertions")
670
Doug Zongkere5ff5902012-01-17 10:55:37 -0800671 def FullOTA_InstallBegin(self):
672 """Called at the start of full OTA installation."""
673 return self._DoCall("FullOTA_InstallBegin")
674
Doug Zongker05d3dea2009-06-22 11:32:31 -0700675 def FullOTA_InstallEnd(self):
676 """Called at the end of full OTA installation; typically this is
677 used to install the image for the device's baseband processor."""
678 return self._DoCall("FullOTA_InstallEnd")
679
680 def IncrementalOTA_Assertions(self):
681 """Called after emitting the block of assertions at the top of an
682 incremental OTA package. Implementations can add whatever
683 additional assertions they like."""
684 return self._DoCall("IncrementalOTA_Assertions")
685
Doug Zongkere5ff5902012-01-17 10:55:37 -0800686 def IncrementalOTA_VerifyBegin(self):
687 """Called at the start of the verification phase of incremental
688 OTA installation; additional checks can be placed here to abort
689 the script before any changes are made."""
690 return self._DoCall("IncrementalOTA_VerifyBegin")
691
Doug Zongker05d3dea2009-06-22 11:32:31 -0700692 def IncrementalOTA_VerifyEnd(self):
693 """Called at the end of the verification phase of incremental OTA
694 installation; additional checks can be placed here to abort the
695 script before any changes are made."""
696 return self._DoCall("IncrementalOTA_VerifyEnd")
697
Doug Zongkere5ff5902012-01-17 10:55:37 -0800698 def IncrementalOTA_InstallBegin(self):
699 """Called at the start of incremental OTA installation (after
700 verification is complete)."""
701 return self._DoCall("IncrementalOTA_InstallBegin")
702
Doug Zongker05d3dea2009-06-22 11:32:31 -0700703 def IncrementalOTA_InstallEnd(self):
704 """Called at the end of incremental OTA installation; typically
705 this is used to install the image for the device's baseband
706 processor."""
707 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700708
709class File(object):
710 def __init__(self, name, data):
711 self.name = name
712 self.data = data
713 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -0800714 self.sha1 = sha1(data).hexdigest()
715
716 @classmethod
717 def FromLocalFile(cls, name, diskname):
718 f = open(diskname, "rb")
719 data = f.read()
720 f.close()
721 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700722
723 def WriteToTemp(self):
724 t = tempfile.NamedTemporaryFile()
725 t.write(self.data)
726 t.flush()
727 return t
728
729 def AddToZip(self, z):
730 ZipWriteStr(z, self.name, self.data)
731
732DIFF_PROGRAM_BY_EXT = {
733 ".gz" : "imgdiff",
734 ".zip" : ["imgdiff", "-z"],
735 ".jar" : ["imgdiff", "-z"],
736 ".apk" : ["imgdiff", "-z"],
737 ".img" : "imgdiff",
738 }
739
740class Difference(object):
741 def __init__(self, tf, sf):
742 self.tf = tf
743 self.sf = sf
744 self.patch = None
745
746 def ComputePatch(self):
747 """Compute the patch (as a string of data) needed to turn sf into
748 tf. Returns the same tuple as GetPatch()."""
749
750 tf = self.tf
751 sf = self.sf
752
753 ext = os.path.splitext(tf.name)[1]
754 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
755
756 ttemp = tf.WriteToTemp()
757 stemp = sf.WriteToTemp()
758
759 ext = os.path.splitext(tf.name)[1]
760
761 try:
762 ptemp = tempfile.NamedTemporaryFile()
763 if isinstance(diff_program, list):
764 cmd = copy.copy(diff_program)
765 else:
766 cmd = [diff_program]
767 cmd.append(stemp.name)
768 cmd.append(ttemp.name)
769 cmd.append(ptemp.name)
770 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
771 _, err = p.communicate()
772 if err or p.returncode != 0:
773 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
774 return None
775 diff = ptemp.read()
776 finally:
777 ptemp.close()
778 stemp.close()
779 ttemp.close()
780
781 self.patch = diff
782 return self.tf, self.sf, self.patch
783
784
785 def GetPatch(self):
786 """Return a tuple (target_file, source_file, patch_data).
787 patch_data may be None if ComputePatch hasn't been called, or if
788 computing the patch failed."""
789 return self.tf, self.sf, self.patch
790
791
792def ComputeDifferences(diffs):
793 """Call ComputePatch on all the Difference objects in 'diffs'."""
794 print len(diffs), "diffs to compute"
795
796 # Do the largest files first, to try and reduce the long-pole effect.
797 by_size = [(i.tf.size, i) for i in diffs]
798 by_size.sort(reverse=True)
799 by_size = [i[1] for i in by_size]
800
801 lock = threading.Lock()
802 diff_iter = iter(by_size) # accessed under lock
803
804 def worker():
805 try:
806 lock.acquire()
807 for d in diff_iter:
808 lock.release()
809 start = time.time()
810 d.ComputePatch()
811 dur = time.time() - start
812 lock.acquire()
813
814 tf, sf, patch = d.GetPatch()
815 if sf.name == tf.name:
816 name = tf.name
817 else:
818 name = "%s (%s)" % (tf.name, sf.name)
819 if patch is None:
820 print "patching failed! %s" % (name,)
821 else:
822 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
823 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
824 lock.release()
825 except Exception, e:
826 print e
827 raise
828
829 # start worker threads; wait for them all to finish.
830 threads = [threading.Thread(target=worker)
831 for i in range(OPTIONS.worker_threads)]
832 for th in threads:
833 th.start()
834 while threads:
835 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -0700836
837
838# map recovery.fstab's fs_types to mount/format "partition types"
839PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
840 "ext4": "EMMC", "emmc": "EMMC" }
841
842def GetTypeAndDevice(mount_point, info):
843 fstab = info["fstab"]
844 if fstab:
845 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
846 else:
Ying Wanga73b6562011-03-03 21:52:08 -0800847 return None